diff --git a/app/assets/javascripts/discourse/templates/topic.js.handlebars b/app/assets/javascripts/discourse/templates/topic.js.handlebars index abf40bbfae7..ff179c83fdc 100644 --- a/app/assets/javascripts/discourse/templates/topic.js.handlebars +++ b/app/assets/javascripts/discourse/templates/topic.js.handlebars @@ -101,7 +101,7 @@
-

{{{unbound view.browseMoreMessage}}}

+

{{{view.browseMoreMessage}}}

{{/if}} {{/if}} diff --git a/app/assets/javascripts/discourse/views/topic_view.js b/app/assets/javascripts/discourse/views/topic_view.js index 18dcf94fc8f..a4d2a405452 100644 --- a/app/assets/javascripts/discourse/views/topic_view.js +++ b/app/assets/javascripts/discourse/views/topic_view.js @@ -402,8 +402,8 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, { }); this.nonUrgentPositionUpdate({ - userActive: userActive, - currentPost: currentPost || this.getPost($(rows[info.bottom])).get('post_number') + userActive: userActive, + currentPost: currentPost || this.getPost($(rows[info.bottom])).get('post_number') }); offset = window.pageYOffset || $('html').scrollTop(); @@ -440,20 +440,41 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, { } }, + topicTrackingState: function(){ + return Discourse.TopicTrackingState.current(); + }.property(), + browseMoreMessage: (function() { var category, opts; opts = { latestLink: "" + (Em.String.i18n("topic.view_latest_topics")) + "" }; + if (category = this.get('controller.content.category')) { opts.catLink = Discourse.Utilities.categoryLink(category); - return Ember.String.i18n("topic.read_more_in_category", opts); } else { opts.catLink = "" + (Em.String.i18n("topic.browse_all_categories")) + ""; + } + + var tracking = this.get('topicTrackingState'); + + var unreadTopics = tracking.countUnread(); + var newTopics = tracking.countNew(); + + if (newTopics + unreadTopics > 0) { + if(category) { + return I18n.messageFormat("topic.read_more_in_category_MF", {"UNREAD": unreadTopics, "NEW": newTopics, catLink: opts.catLink}) + } else { + return I18n.messageFormat("topic.read_more_MF", {"UNREAD": unreadTopics, "NEW": newTopics, latestLink: opts.latestLink}) + } + } + else if (category) { + return Ember.String.i18n("topic.read_more_in_category", opts); + } else { return Ember.String.i18n("topic.read_more", opts); } - }).property() + }).property('topicTrackingState.messageCount') }); @@ -474,7 +495,7 @@ Discourse.TopicView.reopenClass({ expectedOffset = title.height() - header.find('.contents').height(); if (expectedOffset < 0) { - expectedOffset = 0; + expectedOffset = 0; } $('html, body').scrollTop(existing.offset().top - (header.outerHeight(true) + expectedOffset)); diff --git a/app/helpers/js_locale_helper.rb b/app/helpers/js_locale_helper.rb deleted file mode 100644 index 75552fe9717..00000000000 --- a/app/helpers/js_locale_helper.rb +++ /dev/null @@ -1,23 +0,0 @@ -module JsLocaleHelper - - def self.output_locale(locale) - - locale_str = locale.to_s - - translations = YAML::load(File.open("#{Rails.root}/config/locales/client.#{locale_str}.yml")) - - # We used to split the admin versus the client side, but it's much simpler to just - # include both for now due to the small size of the admin section. - # - # For now, let's leave it split out in the translation file in case we want to split - # it again later, so we'll merge the JSON ourselves. - admin_contents = translations[locale_str].delete('admin_js') - - translations[locale_str]['js'].merge!(admin_contents) if admin_contents.present? - - result = "I18n.translations = #{translations.to_json};\n" - result << "I18n.locale = '#{locale_str}'\n" - result - end - -end diff --git a/config/application.rb b/config/application.rb index a647f4f9147..5d24e86843b 100644 --- a/config/application.rb +++ b/config/application.rb @@ -19,6 +19,7 @@ module Discourse # -- all .rb files in that directory are automatically loaded. require 'discourse' + require 'js_locale_helper' # mocha hates us, active_support/testing/mochaing.rb line 2 is requiring the wrong # require, patched in source, on upgrade remove this diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 652add6a3ba..de7761ba415 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -505,6 +505,11 @@ en: toggle_information: "toggle topic details" read_more_in_category: "Want to read more? Browse other topics in {{catLink}} or {{latestLink}}." read_more: "Want to read more? {{catLink}} or {{latestLink}}." + + # keys ending with _MF use message format, see /spec/components/js_local_helper_spec.rb for samples + read_more_in_category_MF: "There {UNREAD, plural, one {is 1 unread} other {are # unread}} and {NEW, plural, one {1 new topic} other {# new topics}} remaining, or browse other topics in {catLink}" + read_more_MF: "There {UNREAD, plural, one {is 1 unread} other {are # unread}} and {NEW, plural, one {1 new topic} other {# new topics}} remaining, or {latestLink}" + browse_all_categories: Browse all categories view_latest_topics: view latest topics diff --git a/lib/javascripts/locale/af.js b/lib/javascripts/locale/af.js new file mode 100644 index 00000000000..b03849f2d3a --- /dev/null +++ b/lib/javascripts/locale/af.js @@ -0,0 +1,6 @@ +MessageFormat.locale.af = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/am.js b/lib/javascripts/locale/am.js new file mode 100644 index 00000000000..aa8af1d1a4f --- /dev/null +++ b/lib/javascripts/locale/am.js @@ -0,0 +1,6 @@ +MessageFormat.locale.am = function(n) { + if (n === 0 || n == 1) { + return 'one'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/ar.js b/lib/javascripts/locale/ar.js new file mode 100644 index 00000000000..d33d95ddba8 --- /dev/null +++ b/lib/javascripts/locale/ar.js @@ -0,0 +1,18 @@ +MessageFormat.locale.ar = function(n) { + if (n === 0) { + return 'zero'; + } + if (n == 1) { + return 'one'; + } + if (n == 2) { + return 'two'; + } + if ((n % 100) >= 3 && (n % 100) <= 10 && n == Math.floor(n)) { + return 'few'; + } + if ((n % 100) >= 11 && (n % 100) <= 99 && n == Math.floor(n)) { + return 'many'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/bg.js b/lib/javascripts/locale/bg.js new file mode 100644 index 00000000000..868baea07d5 --- /dev/null +++ b/lib/javascripts/locale/bg.js @@ -0,0 +1,6 @@ +MessageFormat.locale.bg = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/bn.js b/lib/javascripts/locale/bn.js new file mode 100644 index 00000000000..1641ff32d6a --- /dev/null +++ b/lib/javascripts/locale/bn.js @@ -0,0 +1,6 @@ +MessageFormat.locale.bn = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/br.js b/lib/javascripts/locale/br.js new file mode 100644 index 00000000000..2e0d43fee1d --- /dev/null +++ b/lib/javascripts/locale/br.js @@ -0,0 +1,18 @@ +MessageFormat.locale.br = function (n) { + if (n === 0) { + return 'zero'; + } + if (n == 1) { + return 'one'; + } + if (n == 2) { + return 'two'; + } + if (n == 3) { + return 'few'; + } + if (n == 6) { + return 'many'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/ca.js b/lib/javascripts/locale/ca.js new file mode 100644 index 00000000000..e2a685c674f --- /dev/null +++ b/lib/javascripts/locale/ca.js @@ -0,0 +1,6 @@ +MessageFormat.locale.ca = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/cs.js b/lib/javascripts/locale/cs.js new file mode 100644 index 00000000000..6a7f67ee230 --- /dev/null +++ b/lib/javascripts/locale/cs.js @@ -0,0 +1,9 @@ +MessageFormat.locale.cs = function (n) { + if (n == 1) { + return 'one'; + } + if (n == 2 || n == 3 || n == 4) { + return 'few'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/cy.js b/lib/javascripts/locale/cy.js new file mode 100644 index 00000000000..d98b1f49fa0 --- /dev/null +++ b/lib/javascripts/locale/cy.js @@ -0,0 +1,18 @@ +MessageFormat.locale.cy = function (n) { + if (n === 0) { + return 'zero'; + } + if (n == 1) { + return 'one'; + } + if (n == 2) { + return 'two'; + } + if (n == 3) { + return 'few'; + } + if (n == 6) { + return 'many'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/da.js b/lib/javascripts/locale/da.js new file mode 100644 index 00000000000..7ea5765b295 --- /dev/null +++ b/lib/javascripts/locale/da.js @@ -0,0 +1,6 @@ +MessageFormat.locale.da = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/de.js b/lib/javascripts/locale/de.js new file mode 100644 index 00000000000..edca71c5400 --- /dev/null +++ b/lib/javascripts/locale/de.js @@ -0,0 +1,6 @@ +MessageFormat.locale.de = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/el.js b/lib/javascripts/locale/el.js new file mode 100644 index 00000000000..8c5215a4442 --- /dev/null +++ b/lib/javascripts/locale/el.js @@ -0,0 +1,6 @@ +MessageFormat.locale.el = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/en.js b/lib/javascripts/locale/en.js new file mode 100644 index 00000000000..c2380b9bfe6 --- /dev/null +++ b/lib/javascripts/locale/en.js @@ -0,0 +1,6 @@ +MessageFormat.locale.en = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/es.js b/lib/javascripts/locale/es.js new file mode 100644 index 00000000000..4397d10b8cc --- /dev/null +++ b/lib/javascripts/locale/es.js @@ -0,0 +1,6 @@ +MessageFormat.locale.es = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/et.js b/lib/javascripts/locale/et.js new file mode 100644 index 00000000000..d4b7f5a3139 --- /dev/null +++ b/lib/javascripts/locale/et.js @@ -0,0 +1,6 @@ +MessageFormat.locale.et = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/eu.js b/lib/javascripts/locale/eu.js new file mode 100644 index 00000000000..6da55df13e7 --- /dev/null +++ b/lib/javascripts/locale/eu.js @@ -0,0 +1,6 @@ +MessageFormat.locale.eu = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/fa.js b/lib/javascripts/locale/fa.js new file mode 100644 index 00000000000..4280d1dabba --- /dev/null +++ b/lib/javascripts/locale/fa.js @@ -0,0 +1,3 @@ +MessageFormat.locale.fa = function ( n ) { + return "other"; +}; diff --git a/lib/javascripts/locale/fi.js b/lib/javascripts/locale/fi.js new file mode 100644 index 00000000000..3315a840453 --- /dev/null +++ b/lib/javascripts/locale/fi.js @@ -0,0 +1,6 @@ +MessageFormat.locale.fi = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/fil.js b/lib/javascripts/locale/fil.js new file mode 100644 index 00000000000..af882daf477 --- /dev/null +++ b/lib/javascripts/locale/fil.js @@ -0,0 +1,6 @@ +MessageFormat.locale.fil = function(n) { + if (n === 0 || n == 1) { + return 'one'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/fr.js b/lib/javascripts/locale/fr.js new file mode 100644 index 00000000000..e562c3f8cb2 --- /dev/null +++ b/lib/javascripts/locale/fr.js @@ -0,0 +1,6 @@ +MessageFormat.locale.fr = function (n) { + if (n >= 0 && n < 2) { + return 'one'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/ga.js b/lib/javascripts/locale/ga.js new file mode 100644 index 00000000000..c29aaadb66b --- /dev/null +++ b/lib/javascripts/locale/ga.js @@ -0,0 +1,9 @@ +MessageFormat.locale.ga = function (n) { + if (n == 1) { + return 'one'; + } + if (n == 2) { + return 'two'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/gl.js b/lib/javascripts/locale/gl.js new file mode 100644 index 00000000000..0d2a1b448c4 --- /dev/null +++ b/lib/javascripts/locale/gl.js @@ -0,0 +1,6 @@ +MessageFormat.locale.gl = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/gsw.js b/lib/javascripts/locale/gsw.js new file mode 100644 index 00000000000..9aae2bcc8b6 --- /dev/null +++ b/lib/javascripts/locale/gsw.js @@ -0,0 +1,6 @@ +MessageFormat.locale.gsw = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/gu.js b/lib/javascripts/locale/gu.js new file mode 100644 index 00000000000..70820dd9e0b --- /dev/null +++ b/lib/javascripts/locale/gu.js @@ -0,0 +1,6 @@ +MessageFormat.locale.gu = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/he.js b/lib/javascripts/locale/he.js new file mode 100644 index 00000000000..bf828a5a7b3 --- /dev/null +++ b/lib/javascripts/locale/he.js @@ -0,0 +1,6 @@ +MessageFormat.locale.he = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/hi.js b/lib/javascripts/locale/hi.js new file mode 100644 index 00000000000..68fac22a36d --- /dev/null +++ b/lib/javascripts/locale/hi.js @@ -0,0 +1,6 @@ +MessageFormat.locale.hi = function(n) { + if (n === 0 || n == 1) { + return 'one'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/hr.js b/lib/javascripts/locale/hr.js new file mode 100644 index 00000000000..668e28e2b18 --- /dev/null +++ b/lib/javascripts/locale/hr.js @@ -0,0 +1,14 @@ +MessageFormat.locale.hr = function (n) { + if ((n % 10) == 1 && (n % 100) != 11) { + return 'one'; + } + if ((n % 10) >= 2 && (n % 10) <= 4 && + ((n % 100) < 12 || (n % 100) > 14) && n == Math.floor(n)) { + return 'few'; + } + if ((n % 10) === 0 || ((n % 10) >= 5 && (n % 10) <= 9) || + ((n % 100) >= 11 && (n % 100) <= 14) && n == Math.floor(n)) { + return 'many'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/hu.js b/lib/javascripts/locale/hu.js new file mode 100644 index 00000000000..1fa3c21b26a --- /dev/null +++ b/lib/javascripts/locale/hu.js @@ -0,0 +1,3 @@ +MessageFormat.locale.hu = function(n) { + return 'other'; +}; diff --git a/lib/javascripts/locale/id.js b/lib/javascripts/locale/id.js new file mode 100644 index 00000000000..fb4b62bdee4 --- /dev/null +++ b/lib/javascripts/locale/id.js @@ -0,0 +1,3 @@ +MessageFormat.locale.id = function(n) { + return 'other'; +}; diff --git a/lib/javascripts/locale/in.js b/lib/javascripts/locale/in.js new file mode 100644 index 00000000000..95abe006a9e --- /dev/null +++ b/lib/javascripts/locale/in.js @@ -0,0 +1,3 @@ +MessageFormat.locale["in"] = function(n) { + return 'other'; +}; diff --git a/lib/javascripts/locale/is.js b/lib/javascripts/locale/is.js new file mode 100644 index 00000000000..48efd8f4636 --- /dev/null +++ b/lib/javascripts/locale/is.js @@ -0,0 +1,6 @@ +MessageFormat.locale.is = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/it.js b/lib/javascripts/locale/it.js new file mode 100644 index 00000000000..be964ccbe14 --- /dev/null +++ b/lib/javascripts/locale/it.js @@ -0,0 +1,6 @@ +MessageFormat.locale.it = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/iw.js b/lib/javascripts/locale/iw.js new file mode 100644 index 00000000000..a25fb2b170d --- /dev/null +++ b/lib/javascripts/locale/iw.js @@ -0,0 +1,6 @@ +MessageFormat.locale.iw = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/ja.js b/lib/javascripts/locale/ja.js new file mode 100644 index 00000000000..a02267fa0a7 --- /dev/null +++ b/lib/javascripts/locale/ja.js @@ -0,0 +1,3 @@ +MessageFormat.locale.ja = function ( n ) { + return "other"; +}; diff --git a/lib/javascripts/locale/kn.js b/lib/javascripts/locale/kn.js new file mode 100644 index 00000000000..44c782db72f --- /dev/null +++ b/lib/javascripts/locale/kn.js @@ -0,0 +1,3 @@ +MessageFormat.locale.kn = function ( n ) { + return "other"; +}; diff --git a/lib/javascripts/locale/ko.js b/lib/javascripts/locale/ko.js new file mode 100644 index 00000000000..899ffeae93c --- /dev/null +++ b/lib/javascripts/locale/ko.js @@ -0,0 +1,3 @@ +MessageFormat.locale.ko = function ( n ) { + return "other"; +}; diff --git a/lib/javascripts/locale/lag.js b/lib/javascripts/locale/lag.js new file mode 100644 index 00000000000..d4990b96c43 --- /dev/null +++ b/lib/javascripts/locale/lag.js @@ -0,0 +1,9 @@ +MessageFormat.locale.lag = function (n) { + if (n === 0) { + return 'zero'; + } + if (n > 0 && n < 2) { + return 'one'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/ln.js b/lib/javascripts/locale/ln.js new file mode 100644 index 00000000000..562e220b8cf --- /dev/null +++ b/lib/javascripts/locale/ln.js @@ -0,0 +1,6 @@ +MessageFormat.locale.ln = function(n) { + if (n === 0 || n == 1) { + return 'one'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/lt.js b/lib/javascripts/locale/lt.js new file mode 100644 index 00000000000..82878cfef9b --- /dev/null +++ b/lib/javascripts/locale/lt.js @@ -0,0 +1,10 @@ +MessageFormat.locale.lt = function (n) { + if ((n % 10) == 1 && ((n % 100) < 11 || (n % 100) > 19)) { + return 'one'; + } + if ((n % 10) >= 2 && (n % 10) <= 9 && + ((n % 100) < 11 || (n % 100) > 19) && n == Math.floor(n)) { + return 'few'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/lv.js b/lib/javascripts/locale/lv.js new file mode 100644 index 00000000000..75beb340148 --- /dev/null +++ b/lib/javascripts/locale/lv.js @@ -0,0 +1,9 @@ +MessageFormat.locale.lv = function (n) { + if (n === 0) { + return 'zero'; + } + if ((n % 10) == 1 && (n % 100) != 11) { + return 'one'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/mk.js b/lib/javascripts/locale/mk.js new file mode 100644 index 00000000000..c17aa2e3ad2 --- /dev/null +++ b/lib/javascripts/locale/mk.js @@ -0,0 +1,6 @@ +MessageFormat.locale.mk = function (n) { + if ((n % 10) == 1 && n != 11) { + return 'one'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/ml.js b/lib/javascripts/locale/ml.js new file mode 100644 index 00000000000..f400a5f10cc --- /dev/null +++ b/lib/javascripts/locale/ml.js @@ -0,0 +1,6 @@ +MessageFormat.locale.ml = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/mo.js b/lib/javascripts/locale/mo.js new file mode 100644 index 00000000000..16d84d98f3e --- /dev/null +++ b/lib/javascripts/locale/mo.js @@ -0,0 +1,10 @@ +MessageFormat.locale.mo = function (n) { + if (n == 1) { + return 'one'; + } + if (n === 0 || n != 1 && (n % 100) >= 1 && + (n % 100) <= 19 && n == Math.floor(n)) { + return 'few'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/mr.js b/lib/javascripts/locale/mr.js new file mode 100644 index 00000000000..da4d494ac19 --- /dev/null +++ b/lib/javascripts/locale/mr.js @@ -0,0 +1,6 @@ +MessageFormat.locale.mr = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/ms.js b/lib/javascripts/locale/ms.js new file mode 100644 index 00000000000..e635ae79aa4 --- /dev/null +++ b/lib/javascripts/locale/ms.js @@ -0,0 +1,3 @@ +MessageFormat.locale.ms = function ( n ) { + return "other"; +}; diff --git a/lib/javascripts/locale/mt.js b/lib/javascripts/locale/mt.js new file mode 100644 index 00000000000..6a071a7345d --- /dev/null +++ b/lib/javascripts/locale/mt.js @@ -0,0 +1,12 @@ +MessageFormat.locale.mt = function (n) { + if (n == 1) { + return 'one'; + } + if (n === 0 || ((n % 100) >= 2 && (n % 100) <= 4 && n == Math.floor(n))) { + return 'few'; + } + if ((n % 100) >= 11 && (n % 100) <= 19 && n == Math.floor(n)) { + return 'many'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/nl.js b/lib/javascripts/locale/nl.js new file mode 100644 index 00000000000..617e9071b04 --- /dev/null +++ b/lib/javascripts/locale/nl.js @@ -0,0 +1,6 @@ +MessageFormat.locale.nl = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/no.js b/lib/javascripts/locale/no.js new file mode 100644 index 00000000000..025d3489d9b --- /dev/null +++ b/lib/javascripts/locale/no.js @@ -0,0 +1,6 @@ +MessageFormat.locale.no = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/or.js b/lib/javascripts/locale/or.js new file mode 100644 index 00000000000..04240a1859e --- /dev/null +++ b/lib/javascripts/locale/or.js @@ -0,0 +1,6 @@ +MessageFormat.locale.or = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/pl.js b/lib/javascripts/locale/pl.js new file mode 100644 index 00000000000..a9020d0a5e0 --- /dev/null +++ b/lib/javascripts/locale/pl.js @@ -0,0 +1,15 @@ +MessageFormat.locale.pl = function (n) { + if (n == 1) { + return 'one'; + } + if ((n % 10) >= 2 && (n % 10) <= 4 && + ((n % 100) < 12 || (n % 100) > 14) && n == Math.floor(n)) { + return 'few'; + } + if ((n % 10) === 0 || n != 1 && (n % 10) == 1 || + ((n % 10) >= 5 && (n % 10) <= 9 || (n % 100) >= 12 && (n % 100) <= 14) && + n == Math.floor(n)) { + return 'many'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/pt.js b/lib/javascripts/locale/pt.js new file mode 100644 index 00000000000..a4b65eb906d --- /dev/null +++ b/lib/javascripts/locale/pt.js @@ -0,0 +1,6 @@ +MessageFormat.locale.pt = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/ro.js b/lib/javascripts/locale/ro.js new file mode 100644 index 00000000000..26453eadd6e --- /dev/null +++ b/lib/javascripts/locale/ro.js @@ -0,0 +1,10 @@ +MessageFormat.locale.ro = function (n) { + if (n == 1) { + return 'one'; + } + if (n === 0 || n != 1 && (n % 100) >= 1 && + (n % 100) <= 19 && n == Math.floor(n)) { + return 'few'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/ru.js b/lib/javascripts/locale/ru.js new file mode 100644 index 00000000000..c34bd63c8d1 --- /dev/null +++ b/lib/javascripts/locale/ru.js @@ -0,0 +1,14 @@ +MessageFormat.locale.ru = function (n) { + if ((n % 10) == 1 && (n % 100) != 11) { + return 'one'; + } + if ((n % 10) >= 2 && (n % 10) <= 4 && + ((n % 100) < 12 || (n % 100) > 14) && n == Math.floor(n)) { + return 'few'; + } + if ((n % 10) === 0 || ((n % 10) >= 5 && (n % 10) <= 9) || + ((n % 100) >= 11 && (n % 100) <= 14) && n == Math.floor(n)) { + return 'many'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/shi.js b/lib/javascripts/locale/shi.js new file mode 100644 index 00000000000..9e86dca14a6 --- /dev/null +++ b/lib/javascripts/locale/shi.js @@ -0,0 +1,9 @@ +MessageFormat.locale.shi = function(n) { + if (n >= 0 && n <= 1) { + return 'one'; + } + if (n >= 2 && n <= 10 && n == Math.floor(n)) { + return 'few'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/sk.js b/lib/javascripts/locale/sk.js new file mode 100644 index 00000000000..ef041ddb236 --- /dev/null +++ b/lib/javascripts/locale/sk.js @@ -0,0 +1,9 @@ +MessageFormat.locale.sk = function (n) { + if (n == 1) { + return 'one'; + } + if (n == 2 || n == 3 || n == 4) { + return 'few'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/sl.js b/lib/javascripts/locale/sl.js new file mode 100644 index 00000000000..7dd591b01e9 --- /dev/null +++ b/lib/javascripts/locale/sl.js @@ -0,0 +1,12 @@ +MessageFormat.locale.sl = function (n) { + if ((n % 100) == 1) { + return 'one'; + } + if ((n % 100) == 2) { + return 'two'; + } + if ((n % 100) == 3 || (n % 100) == 4) { + return 'few'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/sq.js b/lib/javascripts/locale/sq.js new file mode 100644 index 00000000000..1e683894f21 --- /dev/null +++ b/lib/javascripts/locale/sq.js @@ -0,0 +1,6 @@ +MessageFormat.locale.sq = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/sr.js b/lib/javascripts/locale/sr.js new file mode 100644 index 00000000000..c2e3d3df1c5 --- /dev/null +++ b/lib/javascripts/locale/sr.js @@ -0,0 +1,14 @@ +MessageFormat.locale.sr = function (n) { + if ((n % 10) == 1 && (n % 100) != 11) { + return 'one'; + } + if ((n % 10) >= 2 && (n % 10) <= 4 && + ((n % 100) < 12 || (n % 100) > 14) && n == Math.floor(n)) { + return 'few'; + } + if ((n % 10) === 0 || ((n % 10) >= 5 && (n % 10) <= 9) || + ((n % 100) >= 11 && (n % 100) <= 14) && n == Math.floor(n)) { + return 'many'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/sv.js b/lib/javascripts/locale/sv.js new file mode 100644 index 00000000000..e566a339d12 --- /dev/null +++ b/lib/javascripts/locale/sv.js @@ -0,0 +1,6 @@ +MessageFormat.locale.sv = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/sw.js b/lib/javascripts/locale/sw.js new file mode 100644 index 00000000000..7dd56c146fe --- /dev/null +++ b/lib/javascripts/locale/sw.js @@ -0,0 +1,6 @@ +MessageFormat.locale.sw = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/ta.js b/lib/javascripts/locale/ta.js new file mode 100644 index 00000000000..08a4ae01fdf --- /dev/null +++ b/lib/javascripts/locale/ta.js @@ -0,0 +1,6 @@ +MessageFormat.locale.ta = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/te.js b/lib/javascripts/locale/te.js new file mode 100644 index 00000000000..61ccb27f6c5 --- /dev/null +++ b/lib/javascripts/locale/te.js @@ -0,0 +1,6 @@ +MessageFormat.locale.te = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/th.js b/lib/javascripts/locale/th.js new file mode 100644 index 00000000000..9ef170824d8 --- /dev/null +++ b/lib/javascripts/locale/th.js @@ -0,0 +1,3 @@ +MessageFormat.locale.th = function ( n ) { + return "other"; +}; diff --git a/lib/javascripts/locale/tl.js b/lib/javascripts/locale/tl.js new file mode 100644 index 00000000000..bc68843df27 --- /dev/null +++ b/lib/javascripts/locale/tl.js @@ -0,0 +1,6 @@ +MessageFormat.locale.tl = function(n) { + if (n === 0 || n == 1) { + return 'one'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/tr.js b/lib/javascripts/locale/tr.js new file mode 100644 index 00000000000..438941ad9b4 --- /dev/null +++ b/lib/javascripts/locale/tr.js @@ -0,0 +1,3 @@ +MessageFormat.locale.tr = function(n) { + return 'other'; +}; diff --git a/lib/javascripts/locale/uk.js b/lib/javascripts/locale/uk.js new file mode 100644 index 00000000000..aad90c79729 --- /dev/null +++ b/lib/javascripts/locale/uk.js @@ -0,0 +1,14 @@ +MessageFormat.locale.uk = function (n) { + if ((n % 10) == 1 && (n % 100) != 11) { + return 'one'; + } + if ((n % 10) >= 2 && (n % 10) <= 4 && + ((n % 100) < 12 || (n % 100) > 14) && n == Math.floor(n)) { + return 'few'; + } + if ((n % 10) === 0 || ((n % 10) >= 5 && (n % 10) <= 9) || + ((n % 100) >= 11 && (n % 100) <= 14) && n == Math.floor(n)) { + return 'many'; + } + return 'other'; +}; diff --git a/lib/javascripts/locale/ur.js b/lib/javascripts/locale/ur.js new file mode 100644 index 00000000000..5a875c9e372 --- /dev/null +++ b/lib/javascripts/locale/ur.js @@ -0,0 +1,6 @@ +MessageFormat.locale.ur = function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; +}; diff --git a/lib/javascripts/locale/vi.js b/lib/javascripts/locale/vi.js new file mode 100644 index 00000000000..8a5b74698ca --- /dev/null +++ b/lib/javascripts/locale/vi.js @@ -0,0 +1,3 @@ +MessageFormat.locale.vi = function ( n ) { + return "other"; +}; diff --git a/lib/javascripts/locale/zh.js b/lib/javascripts/locale/zh.js new file mode 100644 index 00000000000..6ae270cf660 --- /dev/null +++ b/lib/javascripts/locale/zh.js @@ -0,0 +1,3 @@ +MessageFormat.locale.zh = function ( n ) { + return "other"; +}; diff --git a/lib/javascripts/messageformat.js b/lib/javascripts/messageformat.js new file mode 100644 index 00000000000..04f5be94a70 --- /dev/null +++ b/lib/javascripts/messageformat.js @@ -0,0 +1,1593 @@ +/** + * messageformat.js + * + * ICU PluralFormat + SelectFormat for JavaScript + * + * @author Alex Sexton - @SlexAxton + * @version 0.1.5 + * @license WTFPL + * @contributor_license Dojo CLA +*/ +(function ( root ) { + + // Create the contructor function + function MessageFormat ( locale, pluralFunc ) { + var fallbackLocale; + + if ( locale && pluralFunc ) { + MessageFormat.locale[ locale ] = pluralFunc; + } + + // Defaults + fallbackLocale = locale = locale || "en"; + pluralFunc = pluralFunc || MessageFormat.locale[ fallbackLocale = MessageFormat.Utils.getFallbackLocale( locale ) ]; + + if ( ! pluralFunc ) { + throw new Error( "Plural Function not found for locale: " + locale ); + } + + // Own Properties + this.pluralFunc = pluralFunc; + this.locale = locale; + this.fallbackLocale = fallbackLocale; + } + + // Set up the locales object. Add in english by default + MessageFormat.locale = { + "en" : function ( n ) { + if ( n === 1 ) { + return "one"; + } + return "other"; + } + }; + + // Build out our basic SafeString type + // more or less stolen from Handlebars by @wycats + MessageFormat.SafeString = function( string ) { + this.string = string; + }; + + MessageFormat.SafeString.prototype.toString = function () { + return this.string.toString(); + }; + + MessageFormat.Utils = { + numSub : function ( string, key, depth ) { + // make sure that it's not an escaped octothorpe + return string.replace( /^#|[^\\]#/g, function (m) { + var prefix = m && m.length === 2 ? m.charAt(0) : ''; + return prefix + '" + (function(){ var x = ' + + key+';\nif( isNaN(x) ){\nthrow new Error("MessageFormat: `"+lastkey_'+depth+'+"` isnt a number.");\n}\nreturn x;\n})() + "'; + }); + }, + escapeExpression : function (string) { + var escape = { + "\n": "\\n", + "\"": '\\"' + }, + badChars = /[\n"]/g, + possible = /[\n"]/, + escapeChar = function(chr) { + return escape[chr] || "&"; + }; + + // Don't escape SafeStrings, since they're already safe + if ( string instanceof MessageFormat.SafeString ) { + return string.toString(); + } + else if ( string === null || string === false ) { + return ""; + } + + if ( ! possible.test( string ) ) { + return string; + } + return string.replace( badChars, escapeChar ); + }, + getFallbackLocale: function( locale ) { + var tagSeparator = locale.indexOf("-") >= 0 ? "-" : "_"; + + // Lets just be friends, fallback through the language tags + while ( ! MessageFormat.locale.hasOwnProperty( locale ) ) { + locale = locale.substring(0, locale.lastIndexOf( tagSeparator )); + if (locale.length === 0) { + return null; + } + } + + return locale; + } + }; + + // This is generated and pulled in for browsers. + var mparser = (function(){ + /* + * Generated by PEG.js 0.7.0. + * + * http://pegjs.majda.cz/ + */ + + function quote(s) { + /* + * ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a + * string literal except for the closing quote character, backslash, + * carriage return, line separator, paragraph separator, and line feed. + * Any character may appear in the form of an escape sequence. + * + * For portability, we also escape escape all control and non-ASCII + * characters. Note that "\0" and "\v" escape sequences are not used + * because JSHint does not like the first and IE the second. + */ + return '"' + s + .replace(/\\/g, '\\\\') // backslash + .replace(/"/g, '\\"') // closing quote character + .replace(/\x08/g, '\\b') // backspace + .replace(/\t/g, '\\t') // horizontal tab + .replace(/\n/g, '\\n') // line feed + .replace(/\f/g, '\\f') // form feed + .replace(/\r/g, '\\r') // carriage return + .replace(/[\x00-\x07\x0B\x0E-\x1F\x80-\uFFFF]/g, escape) + + '"'; + } + + var result = { + /* + * Parses the input with a generated parser. If the parsing is successfull, + * returns a value explicitly or implicitly specified by the grammar from + * which the parser was generated (see |PEG.buildParser|). If the parsing is + * unsuccessful, throws |PEG.parser.SyntaxError| describing the error. + */ + parse: function(input, startRule) { + var parseFunctions = { + "start": parse_start, + "messageFormatPattern": parse_messageFormatPattern, + "messageFormatPatternRight": parse_messageFormatPatternRight, + "messageFormatElement": parse_messageFormatElement, + "elementFormat": parse_elementFormat, + "pluralStyle": parse_pluralStyle, + "selectStyle": parse_selectStyle, + "pluralFormatPattern": parse_pluralFormatPattern, + "offsetPattern": parse_offsetPattern, + "selectFormatPattern": parse_selectFormatPattern, + "pluralForms": parse_pluralForms, + "stringKey": parse_stringKey, + "string": parse_string, + "id": parse_id, + "chars": parse_chars, + "char": parse_char, + "digits": parse_digits, + "hexDigit": parse_hexDigit, + "_": parse__, + "whitespace": parse_whitespace + }; + + if (startRule !== undefined) { + if (parseFunctions[startRule] === undefined) { + throw new Error("Invalid rule name: " + quote(startRule) + "."); + } + } else { + startRule = "start"; + } + + var pos = 0; + var reportFailures = 0; + var rightmostFailuresPos = 0; + var rightmostFailuresExpected = []; + + function padLeft(input, padding, length) { + var result = input; + + var padLength = length - input.length; + for (var i = 0; i < padLength; i++) { + result = padding + result; + } + + return result; + } + + function escape(ch) { + var charCode = ch.charCodeAt(0); + var escapeChar; + var length; + + if (charCode <= 0xFF) { + escapeChar = 'x'; + length = 2; + } else { + escapeChar = 'u'; + length = 4; + } + + return '\\' + escapeChar + padLeft(charCode.toString(16).toUpperCase(), '0', length); + } + + function matchFailed(failure) { + if (pos < rightmostFailuresPos) { + return; + } + + if (pos > rightmostFailuresPos) { + rightmostFailuresPos = pos; + rightmostFailuresExpected = []; + } + + rightmostFailuresExpected.push(failure); + } + + function parse_start() { + var result0; + var pos0; + + pos0 = pos; + result0 = parse_messageFormatPattern(); + if (result0 !== null) { + result0 = (function(offset, messageFormatPattern) { return { type: "program", program: messageFormatPattern }; })(pos0, result0); + } + if (result0 === null) { + pos = pos0; + } + return result0; + } + + function parse_messageFormatPattern() { + var result0, result1, result2; + var pos0, pos1; + + pos0 = pos; + pos1 = pos; + result0 = parse_string(); + if (result0 !== null) { + result1 = []; + result2 = parse_messageFormatPatternRight(); + while (result2 !== null) { + result1.push(result2); + result2 = parse_messageFormatPatternRight(); + } + if (result1 !== null) { + result0 = [result0, result1]; + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, s1, inner) { + var st = []; + if ( s1 && s1.val ) { + st.push( s1 ); + } + for( var i in inner ){ + if ( inner.hasOwnProperty( i ) ) { + st.push( inner[ i ] ); + } + } + return { type: 'messageFormatPattern', statements: st }; + })(pos0, result0[0], result0[1]); + } + if (result0 === null) { + pos = pos0; + } + return result0; + } + + function parse_messageFormatPatternRight() { + var result0, result1, result2, result3, result4, result5; + var pos0, pos1; + + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 123) { + result0 = "{"; + pos++; + } else { + result0 = null; + if (reportFailures === 0) { + matchFailed("\"{\""); + } + } + if (result0 !== null) { + result1 = parse__(); + if (result1 !== null) { + result2 = parse_messageFormatElement(); + if (result2 !== null) { + result3 = parse__(); + if (result3 !== null) { + if (input.charCodeAt(pos) === 125) { + result4 = "}"; + pos++; + } else { + result4 = null; + if (reportFailures === 0) { + matchFailed("\"}\""); + } + } + if (result4 !== null) { + result5 = parse_string(); + if (result5 !== null) { + result0 = [result0, result1, result2, result3, result4, result5]; + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, mfe, s1) { + var res = []; + if ( mfe ) { + res.push(mfe); + } + if ( s1 && s1.val ) { + res.push( s1 ); + } + return { type: "messageFormatPatternRight", statements : res }; + })(pos0, result0[2], result0[5]); + } + if (result0 === null) { + pos = pos0; + } + return result0; + } + + function parse_messageFormatElement() { + var result0, result1, result2; + var pos0, pos1, pos2; + + pos0 = pos; + pos1 = pos; + result0 = parse_id(); + if (result0 !== null) { + pos2 = pos; + if (input.charCodeAt(pos) === 44) { + result1 = ","; + pos++; + } else { + result1 = null; + if (reportFailures === 0) { + matchFailed("\",\""); + } + } + if (result1 !== null) { + result2 = parse_elementFormat(); + if (result2 !== null) { + result1 = [result1, result2]; + } else { + result1 = null; + pos = pos2; + } + } else { + result1 = null; + pos = pos2; + } + result1 = result1 !== null ? result1 : ""; + if (result1 !== null) { + result0 = [result0, result1]; + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, argIdx, efmt) { + var res = { + type: "messageFormatElement", + argumentIndex: argIdx + }; + if ( efmt && efmt.length ) { + res.elementFormat = efmt[1]; + } + else { + res.output = true; + } + return res; + })(pos0, result0[0], result0[1]); + } + if (result0 === null) { + pos = pos0; + } + return result0; + } + + function parse_elementFormat() { + var result0, result1, result2, result3, result4, result5, result6; + var pos0, pos1; + + pos0 = pos; + pos1 = pos; + result0 = parse__(); + if (result0 !== null) { + if (input.substr(pos, 6) === "plural") { + result1 = "plural"; + pos += 6; + } else { + result1 = null; + if (reportFailures === 0) { + matchFailed("\"plural\""); + } + } + if (result1 !== null) { + result2 = parse__(); + if (result2 !== null) { + if (input.charCodeAt(pos) === 44) { + result3 = ","; + pos++; + } else { + result3 = null; + if (reportFailures === 0) { + matchFailed("\",\""); + } + } + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result5 = parse_pluralStyle(); + if (result5 !== null) { + result6 = parse__(); + if (result6 !== null) { + result0 = [result0, result1, result2, result3, result4, result5, result6]; + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, t, s) { + return { + type : "elementFormat", + key : t, + val : s.val + }; + })(pos0, result0[1], result0[5]); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + pos1 = pos; + result0 = parse__(); + if (result0 !== null) { + if (input.substr(pos, 6) === "select") { + result1 = "select"; + pos += 6; + } else { + result1 = null; + if (reportFailures === 0) { + matchFailed("\"select\""); + } + } + if (result1 !== null) { + result2 = parse__(); + if (result2 !== null) { + if (input.charCodeAt(pos) === 44) { + result3 = ","; + pos++; + } else { + result3 = null; + if (reportFailures === 0) { + matchFailed("\",\""); + } + } + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result5 = parse_selectStyle(); + if (result5 !== null) { + result6 = parse__(); + if (result6 !== null) { + result0 = [result0, result1, result2, result3, result4, result5, result6]; + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, t, s) { + return { + type : "elementFormat", + key : t, + val : s.val + }; + })(pos0, result0[1], result0[5]); + } + if (result0 === null) { + pos = pos0; + } + } + return result0; + } + + function parse_pluralStyle() { + var result0; + var pos0; + + pos0 = pos; + result0 = parse_pluralFormatPattern(); + if (result0 !== null) { + result0 = (function(offset, pfp) { + return { type: "pluralStyle", val: pfp }; + })(pos0, result0); + } + if (result0 === null) { + pos = pos0; + } + return result0; + } + + function parse_selectStyle() { + var result0; + var pos0; + + pos0 = pos; + result0 = parse_selectFormatPattern(); + if (result0 !== null) { + result0 = (function(offset, sfp) { + return { type: "selectStyle", val: sfp }; + })(pos0, result0); + } + if (result0 === null) { + pos = pos0; + } + return result0; + } + + function parse_pluralFormatPattern() { + var result0, result1, result2; + var pos0, pos1; + + pos0 = pos; + pos1 = pos; + result0 = parse_offsetPattern(); + result0 = result0 !== null ? result0 : ""; + if (result0 !== null) { + result1 = []; + result2 = parse_pluralForms(); + while (result2 !== null) { + result1.push(result2); + result2 = parse_pluralForms(); + } + if (result1 !== null) { + result0 = [result0, result1]; + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, op, pf) { + var res = { + type: "pluralFormatPattern", + pluralForms: pf + }; + if ( op ) { + res.offset = op; + } + else { + res.offset = 0; + } + return res; + })(pos0, result0[0], result0[1]); + } + if (result0 === null) { + pos = pos0; + } + return result0; + } + + function parse_offsetPattern() { + var result0, result1, result2, result3, result4, result5, result6; + var pos0, pos1; + + pos0 = pos; + pos1 = pos; + result0 = parse__(); + if (result0 !== null) { + if (input.substr(pos, 6) === "offset") { + result1 = "offset"; + pos += 6; + } else { + result1 = null; + if (reportFailures === 0) { + matchFailed("\"offset\""); + } + } + if (result1 !== null) { + result2 = parse__(); + if (result2 !== null) { + if (input.charCodeAt(pos) === 58) { + result3 = ":"; + pos++; + } else { + result3 = null; + if (reportFailures === 0) { + matchFailed("\":\""); + } + } + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result5 = parse_digits(); + if (result5 !== null) { + result6 = parse__(); + if (result6 !== null) { + result0 = [result0, result1, result2, result3, result4, result5, result6]; + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, d) { + return d; + })(pos0, result0[5]); + } + if (result0 === null) { + pos = pos0; + } + return result0; + } + + function parse_selectFormatPattern() { + var result0, result1; + var pos0; + + pos0 = pos; + result0 = []; + result1 = parse_pluralForms(); + while (result1 !== null) { + result0.push(result1); + result1 = parse_pluralForms(); + } + if (result0 !== null) { + result0 = (function(offset, pf) { + return { + type: "selectFormatPattern", + pluralForms: pf + }; + })(pos0, result0); + } + if (result0 === null) { + pos = pos0; + } + return result0; + } + + function parse_pluralForms() { + var result0, result1, result2, result3, result4, result5, result6, result7; + var pos0, pos1; + + pos0 = pos; + pos1 = pos; + result0 = parse__(); + if (result0 !== null) { + result1 = parse_stringKey(); + if (result1 !== null) { + result2 = parse__(); + if (result2 !== null) { + if (input.charCodeAt(pos) === 123) { + result3 = "{"; + pos++; + } else { + result3 = null; + if (reportFailures === 0) { + matchFailed("\"{\""); + } + } + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result5 = parse_messageFormatPattern(); + if (result5 !== null) { + result6 = parse__(); + if (result6 !== null) { + if (input.charCodeAt(pos) === 125) { + result7 = "}"; + pos++; + } else { + result7 = null; + if (reportFailures === 0) { + matchFailed("\"}\""); + } + } + if (result7 !== null) { + result0 = [result0, result1, result2, result3, result4, result5, result6, result7]; + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, k, mfp) { + return { + type: "pluralForms", + key: k, + val: mfp + }; + })(pos0, result0[1], result0[5]); + } + if (result0 === null) { + pos = pos0; + } + return result0; + } + + function parse_stringKey() { + var result0, result1; + var pos0, pos1; + + pos0 = pos; + result0 = parse_id(); + if (result0 !== null) { + result0 = (function(offset, i) { + return i; + })(pos0, result0); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 61) { + result0 = "="; + pos++; + } else { + result0 = null; + if (reportFailures === 0) { + matchFailed("\"=\""); + } + } + if (result0 !== null) { + result1 = parse_digits(); + if (result1 !== null) { + result0 = [result0, result1]; + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, d) { + return d; + })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; + } + } + return result0; + } + + function parse_string() { + var result0, result1, result2, result3, result4; + var pos0, pos1, pos2; + + pos0 = pos; + pos1 = pos; + result0 = parse__(); + if (result0 !== null) { + result1 = []; + pos2 = pos; + result2 = parse__(); + if (result2 !== null) { + result3 = parse_chars(); + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result2 = [result2, result3, result4]; + } else { + result2 = null; + pos = pos2; + } + } else { + result2 = null; + pos = pos2; + } + } else { + result2 = null; + pos = pos2; + } + while (result2 !== null) { + result1.push(result2); + pos2 = pos; + result2 = parse__(); + if (result2 !== null) { + result3 = parse_chars(); + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result2 = [result2, result3, result4]; + } else { + result2 = null; + pos = pos2; + } + } else { + result2 = null; + pos = pos2; + } + } else { + result2 = null; + pos = pos2; + } + } + if (result1 !== null) { + result0 = [result0, result1]; + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, ws, s) { + var tmp = []; + for( var i = 0; i < s.length; ++i ) { + for( var j = 0; j < s[ i ].length; ++j ) { + tmp.push(s[i][j]); + } + } + return { + type: "string", + val: ws + tmp.join('') + }; + })(pos0, result0[0], result0[1]); + } + if (result0 === null) { + pos = pos0; + } + return result0; + } + + function parse_id() { + var result0, result1, result2, result3; + var pos0, pos1; + + pos0 = pos; + pos1 = pos; + result0 = parse__(); + if (result0 !== null) { + if (/^[a-zA-Z$_]/.test(input.charAt(pos))) { + result1 = input.charAt(pos); + pos++; + } else { + result1 = null; + if (reportFailures === 0) { + matchFailed("[a-zA-Z$_]"); + } + } + if (result1 !== null) { + result2 = []; + if (/^[^ \t\n\r,.+={}]/.test(input.charAt(pos))) { + result3 = input.charAt(pos); + pos++; + } else { + result3 = null; + if (reportFailures === 0) { + matchFailed("[^ \\t\\n\\r,.+={}]"); + } + } + while (result3 !== null) { + result2.push(result3); + if (/^[^ \t\n\r,.+={}]/.test(input.charAt(pos))) { + result3 = input.charAt(pos); + pos++; + } else { + result3 = null; + if (reportFailures === 0) { + matchFailed("[^ \\t\\n\\r,.+={}]"); + } + } + } + if (result2 !== null) { + result3 = parse__(); + if (result3 !== null) { + result0 = [result0, result1, result2, result3]; + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, s1, s2) { + return s1 + (s2 ? s2.join('') : ''); + })(pos0, result0[1], result0[2]); + } + if (result0 === null) { + pos = pos0; + } + return result0; + } + + function parse_chars() { + var result0, result1; + var pos0; + + pos0 = pos; + result1 = parse_char(); + if (result1 !== null) { + result0 = []; + while (result1 !== null) { + result0.push(result1); + result1 = parse_char(); + } + } else { + result0 = null; + } + if (result0 !== null) { + result0 = (function(offset, chars) { return chars.join(''); })(pos0, result0); + } + if (result0 === null) { + pos = pos0; + } + return result0; + } + + function parse_char() { + var result0, result1, result2, result3, result4; + var pos0, pos1; + + pos0 = pos; + if (/^[^{}\\\0-\x1F \t\n\r]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); + pos++; + } else { + result0 = null; + if (reportFailures === 0) { + matchFailed("[^{}\\\\\\0-\\x1F \\t\\n\\r]"); + } + } + if (result0 !== null) { + result0 = (function(offset, x) { + return x; + })(pos0, result0); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + if (input.substr(pos, 2) === "\\#") { + result0 = "\\#"; + pos += 2; + } else { + result0 = null; + if (reportFailures === 0) { + matchFailed("\"\\\\#\""); + } + } + if (result0 !== null) { + result0 = (function(offset) { + return "\\#"; + })(pos0); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + if (input.substr(pos, 2) === "\\{") { + result0 = "\\{"; + pos += 2; + } else { + result0 = null; + if (reportFailures === 0) { + matchFailed("\"\\\\{\""); + } + } + if (result0 !== null) { + result0 = (function(offset) { + return "\u007B"; + })(pos0); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + if (input.substr(pos, 2) === "\\}") { + result0 = "\\}"; + pos += 2; + } else { + result0 = null; + if (reportFailures === 0) { + matchFailed("\"\\\\}\""); + } + } + if (result0 !== null) { + result0 = (function(offset) { + return "\u007D"; + })(pos0); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + pos1 = pos; + if (input.substr(pos, 2) === "\\u") { + result0 = "\\u"; + pos += 2; + } else { + result0 = null; + if (reportFailures === 0) { + matchFailed("\"\\\\u\""); + } + } + if (result0 !== null) { + result1 = parse_hexDigit(); + if (result1 !== null) { + result2 = parse_hexDigit(); + if (result2 !== null) { + result3 = parse_hexDigit(); + if (result3 !== null) { + result4 = parse_hexDigit(); + if (result4 !== null) { + result0 = [result0, result1, result2, result3, result4]; + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, h1, h2, h3, h4) { + return String.fromCharCode(parseInt("0x" + h1 + h2 + h3 + h4)); + })(pos0, result0[1], result0[2], result0[3], result0[4]); + } + if (result0 === null) { + pos = pos0; + } + } + } + } + } + return result0; + } + + function parse_digits() { + var result0, result1; + var pos0; + + pos0 = pos; + if (/^[0-9]/.test(input.charAt(pos))) { + result1 = input.charAt(pos); + pos++; + } else { + result1 = null; + if (reportFailures === 0) { + matchFailed("[0-9]"); + } + } + if (result1 !== null) { + result0 = []; + while (result1 !== null) { + result0.push(result1); + if (/^[0-9]/.test(input.charAt(pos))) { + result1 = input.charAt(pos); + pos++; + } else { + result1 = null; + if (reportFailures === 0) { + matchFailed("[0-9]"); + } + } + } + } else { + result0 = null; + } + if (result0 !== null) { + result0 = (function(offset, ds) { + return parseInt((ds.join('')), 10); + })(pos0, result0); + } + if (result0 === null) { + pos = pos0; + } + return result0; + } + + function parse_hexDigit() { + var result0; + + if (/^[0-9a-fA-F]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); + pos++; + } else { + result0 = null; + if (reportFailures === 0) { + matchFailed("[0-9a-fA-F]"); + } + } + return result0; + } + + function parse__() { + var result0, result1; + var pos0; + + reportFailures++; + pos0 = pos; + result0 = []; + result1 = parse_whitespace(); + while (result1 !== null) { + result0.push(result1); + result1 = parse_whitespace(); + } + if (result0 !== null) { + result0 = (function(offset, w) { return w.join(''); })(pos0, result0); + } + if (result0 === null) { + pos = pos0; + } + reportFailures--; + if (reportFailures === 0 && result0 === null) { + matchFailed("whitespace"); + } + return result0; + } + + function parse_whitespace() { + var result0; + + if (/^[ \t\n\r]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); + pos++; + } else { + result0 = null; + if (reportFailures === 0) { + matchFailed("[ \\t\\n\\r]"); + } + } + return result0; + } + + + function cleanupExpected(expected) { + expected.sort(); + + var lastExpected = null; + var cleanExpected = []; + for (var i = 0; i < expected.length; i++) { + if (expected[i] !== lastExpected) { + cleanExpected.push(expected[i]); + lastExpected = expected[i]; + } + } + return cleanExpected; + } + + function computeErrorPosition() { + /* + * The first idea was to use |String.split| to break the input up to the + * error position along newlines and derive the line and column from + * there. However IE's |split| implementation is so broken that it was + * enough to prevent it. + */ + + var line = 1; + var column = 1; + var seenCR = false; + + for (var i = 0; i < Math.max(pos, rightmostFailuresPos); i++) { + var ch = input.charAt(i); + if (ch === "\n") { + if (!seenCR) { line++; } + column = 1; + seenCR = false; + } else if (ch === "\r" || ch === "\u2028" || ch === "\u2029") { + line++; + column = 1; + seenCR = true; + } else { + column++; + seenCR = false; + } + } + + return { line: line, column: column }; + } + + + var result = parseFunctions[startRule](); + + /* + * The parser is now in one of the following three states: + * + * 1. The parser successfully parsed the whole input. + * + * - |result !== null| + * - |pos === input.length| + * - |rightmostFailuresExpected| may or may not contain something + * + * 2. The parser successfully parsed only a part of the input. + * + * - |result !== null| + * - |pos < input.length| + * - |rightmostFailuresExpected| may or may not contain something + * + * 3. The parser did not successfully parse any part of the input. + * + * - |result === null| + * - |pos === 0| + * - |rightmostFailuresExpected| contains at least one failure + * + * All code following this comment (including called functions) must + * handle these states. + */ + if (result === null || pos !== input.length) { + var offset = Math.max(pos, rightmostFailuresPos); + var found = offset < input.length ? input.charAt(offset) : null; + var errorPosition = computeErrorPosition(); + + throw new this.SyntaxError( + cleanupExpected(rightmostFailuresExpected), + found, + offset, + errorPosition.line, + errorPosition.column + ); + } + + return result; + }, + + /* Returns the parser source code. */ + toSource: function() { return this._source; } + }; + + /* Thrown when a parser encounters a syntax error. */ + + result.SyntaxError = function(expected, found, offset, line, column) { + function buildMessage(expected, found) { + var expectedHumanized, foundHumanized; + + switch (expected.length) { + case 0: + expectedHumanized = "end of input"; + break; + case 1: + expectedHumanized = expected[0]; + break; + default: + expectedHumanized = expected.slice(0, expected.length - 1).join(", ") + + " or " + + expected[expected.length - 1]; + } + + foundHumanized = found ? quote(found) : "end of input"; + + return "Expected " + expectedHumanized + " but " + foundHumanized + " found."; + } + + this.name = "SyntaxError"; + this.expected = expected; + this.found = found; + this.message = buildMessage(expected, found); + this.offset = offset; + this.line = line; + this.column = column; + }; + + result.SyntaxError.prototype = Error.prototype; + + return result; + })(); + + MessageFormat.prototype.parse = function () { + // Bind to itself so error handling works + return mparser.parse.apply( mparser, arguments ); + }; + + MessageFormat.prototype.precompile = function ( ast ) { + var self = this, + needOther = false, + fp = { + begin: 'function(d){\nvar r = "";\n', + end : "return r;\n}" + }; + + function interpMFP ( ast, data ) { + // Set some default data + data = data || {}; + var s = '', i, tmp, lastkeyname; + + switch ( ast.type ) { + case 'program': + return interpMFP( ast.program ); + case 'messageFormatPattern': + for ( i = 0; i < ast.statements.length; ++i ) { + s += interpMFP( ast.statements[i], data ); + } + return fp.begin + s + fp.end; + case 'messageFormatPatternRight': + for ( i = 0; i < ast.statements.length; ++i ) { + s += interpMFP( ast.statements[i], data ); + } + return s; + case 'messageFormatElement': + data.pf_count = data.pf_count || 0; + s += 'if(!d){\nthrow new Error("MessageFormat: No data passed to function.");\n}\n'; + if ( ast.output ) { + s += 'r += d["' + ast.argumentIndex + '"];\n'; + } + else { + lastkeyname = 'lastkey_'+(data.pf_count+1); + s += 'var '+lastkeyname+' = "'+ast.argumentIndex+'";\n'; + s += 'var k_'+(data.pf_count+1)+'=d['+lastkeyname+'];\n'; + s += interpMFP( ast.elementFormat, data ); + } + return s; + case 'elementFormat': + if ( ast.key === 'select' ) { + s += interpMFP( ast.val, data ); + s += 'r += (pf_' + + data.pf_count + + '[ k_' + (data.pf_count+1) + ' ] || pf_'+data.pf_count+'[ "other" ])( d );\n'; + } + else if ( ast.key === 'plural' ) { + s += interpMFP( ast.val, data ); + s += 'if ( pf_'+(data.pf_count)+'[ k_'+(data.pf_count+1)+' + "" ] ) {\n'; + s += 'r += pf_'+data.pf_count+'[ k_'+(data.pf_count+1)+' + "" ]( d ); \n'; + s += '}\nelse {\n'; + s += 'r += (pf_' + + data.pf_count + + '[ MessageFormat.locale["' + + self.fallbackLocale + + '"]( k_'+(data.pf_count+1)+' - off_'+(data.pf_count)+' ) ] || pf_'+data.pf_count+'[ "other" ] )( d );\n'; + s += '}\n'; + } + return s; + /* // Unreachable cases. + case 'pluralStyle': + case 'selectStyle':*/ + case 'pluralFormatPattern': + data.pf_count = data.pf_count || 0; + s += 'var off_'+data.pf_count+' = '+ast.offset+';\n'; + s += 'var pf_' + data.pf_count + ' = { \n'; + needOther = true; + // We're going to simultaneously check to make sure we hit the required 'other' option. + + for ( i = 0; i < ast.pluralForms.length; ++i ) { + if ( ast.pluralForms[ i ].key === 'other' ) { + needOther = false; + } + if ( tmp ) { + s += ',\n'; + } + else{ + tmp = 1; + } + s += '"' + ast.pluralForms[ i ].key + '" : ' + interpMFP( ast.pluralForms[ i ].val, + (function(){ var res = JSON.parse(JSON.stringify(data)); res.pf_count++; return res; })() ); + } + s += '\n};\n'; + if ( needOther ) { + throw new Error("No 'other' form found in pluralFormatPattern " + data.pf_count); + } + return s; + case 'selectFormatPattern': + + data.pf_count = data.pf_count || 0; + s += 'var off_'+data.pf_count+' = 0;\n'; + s += 'var pf_' + data.pf_count + ' = { \n'; + needOther = true; + + for ( i = 0; i < ast.pluralForms.length; ++i ) { + if ( ast.pluralForms[ i ].key === 'other' ) { + needOther = false; + } + if ( tmp ) { + s += ',\n'; + } + else{ + tmp = 1; + } + s += '"' + ast.pluralForms[ i ].key + '" : ' + interpMFP( ast.pluralForms[ i ].val, + (function(){ + var res = JSON.parse( JSON.stringify( data ) ); + res.pf_count++; + return res; + })() + ); + } + s += '\n};\n'; + if ( needOther ) { + throw new Error("No 'other' form found in selectFormatPattern " + data.pf_count); + } + return s; + /* // Unreachable + case 'pluralForms': + */ + case 'string': + return 'r += "' + MessageFormat.Utils.numSub( + MessageFormat.Utils.escapeExpression( ast.val ), + 'k_' + data.pf_count + ' - off_' + ( data.pf_count - 1 ), + data.pf_count + ) + '";\n'; + default: + throw new Error( 'Bad AST type: ' + ast.type ); + } + } + return interpMFP( ast ); + }; + + MessageFormat.prototype.compile = function ( message ) { + return (new Function( 'MessageFormat', + 'return ' + + this.precompile( + this.parse( message ) + ) + ))(MessageFormat); + }; + + + if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = MessageFormat; + } + exports.MessageFormat = MessageFormat; + } + else if (typeof define === 'function' && define.amd) { + define(function() { + return MessageFormat; + }); + } + else { + root['MessageFormat'] = MessageFormat; + } + +})( this ); diff --git a/lib/js_locale_helper.rb b/lib/js_locale_helper.rb new file mode 100644 index 00000000000..f31d354f305 --- /dev/null +++ b/lib/js_locale_helper.rb @@ -0,0 +1,74 @@ +module JsLocaleHelper + + def self.output_locale(locale, translations = nil) + + locale_str = locale.to_s + + translations ||= YAML::load(File.open("#{Rails.root}/config/locales/client.#{locale_str}.yml")) + + # We used to split the admin versus the client side, but it's much simpler to just + # include both for now due to the small size of the admin section. + # + # For now, let's leave it split out in the translation file in case we want to split + # it again later, so we'll merge the JSON ourselves. + admin_contents = translations[locale_str].delete('admin_js') + translations[locale_str]['js'].merge!(admin_contents) if admin_contents.present? + message_formats = strip_out_message_formats!(translations[locale_str]['js']) + + result = generate_message_format(message_formats, locale_str) + + result << "I18n.translations = #{translations.to_json};\n" + result << "I18n.locale = '#{locale_str}'\n" + result + end + + def self.generate_message_format(message_formats, locale_str) + formats = message_formats.map{|k,v| k.inspect << " : " << compile_message_format(locale_str ,v)}.join(" , ") + + result = "MessageFormat = {locale: {}};\n" + result << File.read(Rails.root + "lib/javascripts/locale/#{locale_str}.js") << "\n" + + result << "I18n.messageFormat = (function(formats){ + var f = formats; + return function(key, options) { + var fn = f[key]; + if(fn){ + try { + return fn(options); + } catch(err) { + return err.message; + } + } else { + return 'Missing Key: ' + key + } + return f[key](options); + }; + })({#{formats}});" + end + + def self.compile_message_format(locale, format) + ctx = V8::Context.new + ctx.load(Rails.root + 'lib/javascripts/messageformat.js') + ctx.eval("mf = new MessageFormat('#{locale}');") + ctx.eval("mf.precompile(mf.parse(#{format.inspect}))") + + rescue V8::Error => e + message = "Invalid Format: " << e.message + "function(){ return #{message.inspect};}" + end + + def self.strip_out_message_formats!(hash, prefix = "", rval = {}) + if Hash === hash + hash.each do |k,v| + if Hash === v + rval.merge!(strip_out_message_formats!(v, prefix + (prefix.length > 0 ? "." : "") << k, rval)) + elsif k.end_with?("_MF") + rval[prefix + (prefix.length > 0 ? "." : "") << k] = v + hash.delete(k) + end + end + end + rval + end + +end diff --git a/spec/components/js_locale_helper_spec.rb b/spec/components/js_locale_helper_spec.rb new file mode 100644 index 00000000000..ceab7ff6e21 --- /dev/null +++ b/spec/components/js_locale_helper_spec.rb @@ -0,0 +1,82 @@ +require 'spec_helper' +require_dependency 'js_locale_helper' + +describe JsLocaleHelper do + it 'should be able to generate translations' do + JsLocaleHelper.output_locale('en').length.should > 0 + end + + def setup_message_format(format) + @ctx = V8::Context.new + @ctx.eval('MessageFormat = {locale: {}};') + @ctx.load(Rails.root + 'lib/javascripts/locale/en.js') + compiled = JsLocaleHelper.compile_message_format('en', format) + @ctx.eval("var test = #{compiled}") + end + + def localize(opts) + @ctx.eval("test(#{opts.to_json})") + end + + it 'handles plurals' do + setup_message_format('{NUM_RESULTS, plural, + one {1 result} + other {# results} + }') + localize(NUM_RESULTS: 1).should == '1 result' + localize(NUM_RESULTS: 2).should == '2 results' + end + + it 'handles double plurals' do + setup_message_format('{NUM_RESULTS, plural, + one {1 result} + other {# results} + } and {NUM_APPLES, plural, + one {1 apple} + other {# apples} + }') + + + localize(NUM_RESULTS: 1, NUM_APPLES: 2).should == '1 result and 2 apples' + localize(NUM_RESULTS: 2, NUM_APPLES: 1).should == '2 results and 1 apple' + end + + it 'handles select' do + setup_message_format('{GENDER, select, male {He} female {She} other {They}} read a book') + localize(GENDER: 'male').should == 'He read a book' + localize(GENDER: 'female').should == 'She read a book' + localize(GENDER: 'none').should == 'They read a book' + end + + it 'can strip out message formats' do + hash = {"a" => "b", "c" => { "d" => {"f_MF" => "bob"} }} + JsLocaleHelper.strip_out_message_formats!(hash).should == {"c.d.f_MF" => "bob"} + hash["c"]["d"].should == {} + end + + it 'handles message format special keys' do + ctx = V8::Context.new + ctx.eval("I18n = {};") + ctx.eval(JsLocaleHelper.output_locale('en', + { + "en" => + { + "js" => { + "hello" => "world", + "test_MF" => "{HELLO} {COUNT, plural, one {1 duck} other {# ducks}}", + "error_MF" => "{{BLA}", + "simple_MF" => "{COUNT, plural, one {1} other {#}}" + } + } + })) + + ctx.eval('I18n.translations')["en"]["js"]["hello"].should == "world" + ctx.eval('I18n.translations')["en"]["js"]["test_MF"].should be_nil + + ctx.eval('I18n.messageFormat("test_MF", { HELLO: "hi", COUNT: 3 })').should == "hi 3 ducks" + ctx.eval('I18n.messageFormat("error_MF", { HELLO: "hi", COUNT: 3 })').should =~ /Invalid Format/ + ctx.eval('I18n.messageFormat("missing", {})').should =~ /missing/ + ctx.eval('I18n.messageFormat("simple_MF", {})').should =~ /COUNT/ # error + end + +end