From 6bd67b602aff0363bae23d3b8b0aa5bb4c51754a Mon Sep 17 00:00:00 2001 From: "Bradley J. Spaulding" Date: Tue, 28 May 2013 11:46:24 -0700 Subject: [PATCH 001/466] setup! uses has_asset_pipeline? instead of Rails.version --- lib/i18n-js.rb | 2 +- spec/i18n_spec.rb | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/i18n-js.rb b/lib/i18n-js.rb index e26f08b6..de31a269 100644 --- a/lib/i18n-js.rb +++ b/lib/i18n-js.rb @@ -104,7 +104,7 @@ def config? # Copy configuration and JavaScript library files to # config/i18n-js.yml and public/javascripts/i18n.js. def setup! - FileUtils.cp(File.dirname(__FILE__) + "/../vendor/assets/javascripts/i18n.js", javascript_file) unless Rails.version >= "3.1" + FileUtils.cp(File.dirname(__FILE__) + "/../vendor/assets/javascripts/i18n.js", javascript_file) unless has_asset_pipeline? FileUtils.cp(File.dirname(__FILE__) + "/../config/i18n-js.yml", config_file) unless config? end diff --git a/spec/i18n_spec.rb b/spec/i18n_spec.rb index 52d1388f..d874bd7e 100644 --- a/spec/i18n_spec.rb +++ b/spec/i18n_spec.rb @@ -40,12 +40,23 @@ File.read(SimplesIdeias::I18n.config_file).should == "ORIGINAL" end - it "copies JavaScript library" do - path = Rails.root.join("public/javascripts/i18n.js") + context "copying i18n.js" do + let(:path) { Rails.root.join("public/javascripts/i18n.js") } - File.should_not be_file(path) - SimplesIdeias::I18n.setup! - File.should be_file(path) + it "copies JavaScript library" do + File.should_not be_file(path) + SimplesIdeias::I18n.setup! + File.should be_file(path) + end + + it "should copy i18n.js when has_asset_pipeline? is false and Rails version >= 3.1" do + SimplesIdeias::I18n.stub(:has_asset_pipeline?).and_return(false) + Rails.stub(:version).and_return("3.1") + + File.should_not be_file(path) + SimplesIdeias::I18n.setup! + File.should be_file(path) + end end it "loads configuration file" do From 12fe8ec2133dc162087eef2b1639309a01cbb414 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 10 Apr 2014 15:38:52 +0800 Subject: [PATCH 002/466] * Move branch `rewrite` to `master` --- .gitignore | 5 +- .rspec | 1 - Gemfile | 2 +- Gemfile.lock | 73 +- README.md | 326 +++ README.rdoc | 320 --- Rakefile | 12 +- app/assets/javascripts/i18n.js | 684 +++++ app/assets/javascripts/i18n/filtered.js.erb | 2 + app/assets/javascripts/i18n/shims.js | 93 + app/assets/javascripts/i18n/translations.js | 3 + config/i18n-js.yml | 22 - i18n-js.gemspec | 12 +- lib/i18n-js.rb | 178 +- lib/i18n-js/engine.rb | 63 - lib/i18n-js/railtie.rb | 13 - lib/i18n-js/rake.rb | 16 - lib/i18n-js/version.rb | 10 - lib/i18n/js.rb | 157 ++ lib/i18n/js/engine.rb | 22 + lib/{i18n-js => i18n/js}/middleware.rb | 14 +- lib/i18n/js/version.rb | 10 + package.json | 11 + spec/{resources => fixtures}/custom_path.yml | 2 +- spec/{resources => fixtures}/default.yml | 2 +- spec/fixtures/js_file_per_locale.yml | 3 + spec/{resources => fixtures}/locales.yml | 2 +- spec/fixtures/multiple_conditions.yml | 5 + .../multiple_files.yml | 6 +- spec/{resources => fixtures}/no_config.yml | 0 spec/{resources => fixtures}/no_scope.yml | 2 +- spec/{resources => fixtures}/simple_scope.yml | 2 +- spec/i18n_js_spec.rb | 122 + spec/i18n_spec.js | 820 ------ spec/i18n_spec.rb | 216 -- spec/js/currency.spec.js | 60 + spec/js/current_locale.spec.js | 19 + spec/js/dates.spec.js | 218 ++ spec/js/defaults.spec.js | 23 + spec/js/interpolation.spec.js | 28 + spec/js/jasmine/MIT.LICENSE | 20 + spec/js/jasmine/jasmine-html.js | 190 ++ spec/js/jasmine/jasmine.css | 166 ++ spec/js/jasmine/jasmine.js | 2476 +++++++++++++++++ spec/js/jasmine/jasmine_favicon.png | Bin 0 -> 905 bytes spec/js/localization.spec.js | 41 + spec/js/numbers.spec.js | 142 + spec/js/placeholder.spec.js | 24 + spec/js/pluralization.spec.js | 105 + spec/js/prepare_options.spec.js | 41 + spec/js/specs.html | 46 + spec/js/translate.spec.js | 120 + spec/js/translations.js | 120 + spec/resources/js_file_per_locale.yml | 3 - spec/resources/multiple_conditions.yml | 6 - spec/spec_helper.rb | 50 +- vendor/assets/javascripts/i18n.js | 531 ---- .../javascripts/i18n/translations.js.erb | 9 - 58 files changed, 5379 insertions(+), 2290 deletions(-) delete mode 100644 .rspec create mode 100644 README.md delete mode 100644 README.rdoc create mode 100644 app/assets/javascripts/i18n.js create mode 100644 app/assets/javascripts/i18n/filtered.js.erb create mode 100644 app/assets/javascripts/i18n/shims.js create mode 100644 app/assets/javascripts/i18n/translations.js delete mode 100644 config/i18n-js.yml delete mode 100644 lib/i18n-js/engine.rb delete mode 100644 lib/i18n-js/railtie.rb delete mode 100644 lib/i18n-js/rake.rb delete mode 100644 lib/i18n-js/version.rb create mode 100644 lib/i18n/js.rb create mode 100644 lib/i18n/js/engine.rb rename lib/{i18n-js => i18n/js}/middleware.rb (85%) create mode 100644 lib/i18n/js/version.rb create mode 100644 package.json rename spec/{resources => fixtures}/custom_path.yml (69%) mode change 100644 => 100755 rename spec/{resources => fixtures}/default.yml (70%) mode change 100644 => 100755 create mode 100755 spec/fixtures/js_file_per_locale.yml rename spec/{resources => fixtures}/locales.yml (98%) mode change 100644 => 100755 create mode 100755 spec/fixtures/multiple_conditions.yml rename spec/{resources => fixtures}/multiple_files.yml (55%) mode change 100644 => 100755 rename spec/{resources => fixtures}/no_config.yml (100%) mode change 100644 => 100755 rename spec/{resources => fixtures}/no_scope.yml (69%) mode change 100644 => 100755 rename spec/{resources => fixtures}/simple_scope.yml (72%) mode change 100644 => 100755 create mode 100644 spec/i18n_js_spec.rb delete mode 100644 spec/i18n_spec.js delete mode 100644 spec/i18n_spec.rb create mode 100644 spec/js/currency.spec.js create mode 100644 spec/js/current_locale.spec.js create mode 100644 spec/js/dates.spec.js create mode 100644 spec/js/defaults.spec.js create mode 100644 spec/js/interpolation.spec.js create mode 100644 spec/js/jasmine/MIT.LICENSE create mode 100644 spec/js/jasmine/jasmine-html.js create mode 100644 spec/js/jasmine/jasmine.css create mode 100644 spec/js/jasmine/jasmine.js create mode 100644 spec/js/jasmine/jasmine_favicon.png create mode 100644 spec/js/localization.spec.js create mode 100644 spec/js/numbers.spec.js create mode 100644 spec/js/placeholder.spec.js create mode 100644 spec/js/pluralization.spec.js create mode 100644 spec/js/prepare_options.spec.js create mode 100644 spec/js/specs.html create mode 100644 spec/js/translate.spec.js create mode 100644 spec/js/translations.js delete mode 100644 spec/resources/js_file_per_locale.yml delete mode 100644 spec/resources/multiple_conditions.yml delete mode 100644 vendor/assets/javascripts/i18n.js delete mode 100644 vendor/assets/javascripts/i18n/translations.js.erb diff --git a/.gitignore b/.gitignore index 7fee3d29..632d413e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,2 @@ -doc -coverage pkg -spec/tmp -.DS_Store \ No newline at end of file +node_modules diff --git a/.rspec b/.rspec deleted file mode 100644 index b782d241..00000000 --- a/.rspec +++ /dev/null @@ -1 +0,0 @@ ---color --format documentation \ No newline at end of file diff --git a/Gemfile b/Gemfile index e45e65f8..3be9c3cd 100644 --- a/Gemfile +++ b/Gemfile @@ -1,2 +1,2 @@ -source :rubygems +source "https://rubygems.org" gemspec diff --git a/Gemfile.lock b/Gemfile.lock index 3e73dd39..5447642f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,51 +1,52 @@ PATH remote: . specs: - i18n-js (2.1.2) + i18n-js (3.0.0.rc5) i18n GEM - remote: http://rubygems.org/ + remote: https://rubygems.org/ specs: - activesupport (3.1.1) + activesupport (3.2.12) + i18n (~> 0.6) multi_json (~> 1.0) - coderay (0.9.8) - diff-lcs (1.1.3) - fakeweb (1.3.0) - i18n (0.6.0) - method_source (0.6.7) - ruby_parser (>= 2.3.1) - multi_json (1.0.3) - notifier (0.1.4) - pry (0.9.7.4) - coderay (~> 0.9.8) - method_source (~> 0.6.7) - ruby_parser (>= 2.3.1) - slop (~> 2.1.0) - rake (0.9.2.2) - rspec (2.7.0) - rspec-core (~> 2.7.0) - rspec-expectations (~> 2.7.0) - rspec-mocks (~> 2.7.0) - rspec-core (2.7.1) - rspec-expectations (2.7.0) - diff-lcs (~> 1.1.2) - rspec-mocks (2.7.0) - ruby_parser (2.3.1) - sexp_processor (~> 3.0) - sexp_processor (3.0.8) - slop (2.1.0) - spec-js (0.1.0.beta.3) - notifier + awesome_print (1.1.0) + coderay (1.0.9) + diff-lcs (1.2.1) + i18n (0.6.4) + method_source (0.8.1) + multi_json (1.6.1) + pry (0.9.12) + coderay (~> 1.0.5) + method_source (~> 0.8) + slop (~> 3.4) + pry-meta (0.0.5) + awesome_print + pry + pry-nav + pry-remote + pry-nav (0.2.3) + pry (~> 0.9.10) + pry-remote (0.1.7) + pry (~> 0.9) + slop (~> 3.0) + rake (10.0.3) + rspec (2.13.0) + rspec-core (~> 2.13.0) + rspec-expectations (~> 2.13.0) + rspec-mocks (~> 2.13.0) + rspec-core (2.13.0) + rspec-expectations (2.13.0) + diff-lcs (>= 1.1.3, < 2.0) + rspec-mocks (2.13.0) + slop (3.4.3) PLATFORMS ruby DEPENDENCIES - activesupport (>= 3.0.0) - fakeweb + activesupport i18n-js! - pry + pry-meta rake - rspec (~> 2.6) - spec-js (~> 0.1.0.beta.0) + rspec diff --git a/README.md b/README.md new file mode 100644 index 00000000..5cb035d4 --- /dev/null +++ b/README.md @@ -0,0 +1,326 @@ +# I18n.js + +It's a small library to provide the Rails I18n translations on the Javascript. + +Features: + +- Pluralization +- Date/Time localization +- Number localization +- Locale fallback +- Asset pipeline support +- Lots more! :) + +## Usage + +### Installation + +#### Rails app + +Add the gem to your Gemfile. + + source :rubygems + gem "rails", "3.2.3" + gem "i18n-js" + +If you're using the [asset pipeline](http://guides.rubyonrails.org/asset_pipeline.html), +then you must add the following line to your `app/assets/javascripts/application.js`. + + //= require i18n/translations + +If you're not using the asset pipeline, download the JavaScript file at + and load it on your page. +Also load the `translations.js` file. + + <%= javascript_include_tag "i18n", "translations" %> + +This `translations.js` file can be automatically generated by the `I18n::JS::Middleware`. +Just add it to your `config/application.rb` file. + + config.middleware.use I18n::JS::Middleware + +If you can't generate this file in production (Heroku anyone?), you can "precompile" +it by running the following command. Move the middleware line to your +`config/environments/development.rb` file and run the following command before +deploying. + + $ rails runner I18n::JS.export + +This will export all translation files, including the custom scopes you may have +defined on `config/i18n-js.yml`. + +#### Vanilla JavaScript + +Just add the `i18n.js` file to your page. You'll have to build the translations object +by hand or using your favorite programming language. More info below. + +### Setting up + +You **don't** need to set up a thing. The default settings will work just okay. But if you want to split translations into several files or specify specific contexts, you can follow the rest of this setting up section. + +Set your locale is easy as + + I18n.defaultLocale = "pt-BR"; + I18n.locale = "pt-BR"; + I18n.currentLocale(); + // pt-BR + +**NOTE:** Just make sure you apply your configuration **after i18n.js** is loaded. Otherwise, your settings will be ignored. + +In practice, you'll have something like the following in your `application.html.erb`: + + + +You can use translate your messages: + + I18n.t("some.scoped.translation"); + // or translate with explicite setting of locale + I18n.t("some.scoped.translation", {locale: "fr"}); + +You can also interpolate values: + + I18n.t("hello", {name: "John Doe"}); + +You can set default values for missing scopes: + + // simple translation + I18n.t("some.missing.scope", {defaultValue: "A default message"}); + + // with interpolation + I18n.t("noun", {defaultValue: "I'm a {{noun}}", noun: "Mac"}); + +Translation fallback can be enabled by enabling the `I18n.fallbacks` option: + + + +By default missing translations will first be looked for in less +specific versions of the requested locale and if that fails by taking +them from your `I18n.defaultLocale`. + + // if I18n.defaultLocale = "en" and translation doesn't exist + // for I18n.locale = "de-DE" this key will be taken from "de" locale scope + // or, if that also doesn't exist, from "en" locale scope + I18n.t("some.missing.scope"); + +Custom fallback rules can also be specified for a particular language. There +are three different ways of doing it so: + + I18n.locales.no = ["nb", "en"]; + I18n.locales.no = "nb"; + I18n.locales.no = function(locale){ return ["nb"]; }; + +Pluralization is possible as well and by default provides english rules: + + I18n.t("inbox.counting", {count: 10}); // You have 10 messages + +The sample above expects the following translation: + + en: + inbox: + counting: + one: You have 1 new message + other: You have {{count}} new messages + zero: You have no messages + +**NOTE:** Rais I18n recognizes the `zero` option. + +If you need special rules just define them for your language, for example Russian, just add a new pluralizer: + + I18n.pluralization["ru"] = function (count) { + return count % 10 == 1 && count % 100 != 11 ? "one" : [2, 3, 4].indexOf(count % 10) >= 0 && [12, 13, 14].indexOf(count % 100) < 0 ? "few" : count % 10 == 0 || [5, 6, 7, 8, 9].indexOf(count % 10) >= 0 || [11, 12, 13, 14].indexOf(count % 100) >= 0 ? "many" : "other"; + }; + +You can find all rules on . + +If you're using the same scope over and over again, you may use the `scope` option. + + var options = {scope: "activerecord.attributes.user"}; + + I18n.t("name", options); + I18n.t("email", options); + I18n.t("username", options); + +You can also provide an array as scope. + + // use the greetings.hello scope + I18n.t(["greetings", "hello"]); + +#### Number formatting + +Similar to Rails helpers, you have localized number and currency formatting. + + I18n.l("currency", 1990.99); + // $1,990.99 + + I18n.l("number", 1990.99); + // 1,990.99 + + I18n.l("percentage", 123.45); + // 123.450% + +To have more control over number formatting, you can use the +`I18n.toNumber`, `I18n.toPercentage`, `I18n.toCurrency` and `I18n.toHumanSize` +functions. + + I18n.toNumber(1000); // 1,000.000 + I18n.toCurrency(1000); // $1,000.00 + I18n.toPercentage(100); // 100.000% + +The `toNumber` and `toPercentage` functions accept the following options: + +- `precision`: defaults to `3` +- `separator`: defaults to `.` +- `delimiter`: defaults to `,` +- `strip_insignificant_zeros`: defaults to `false` + +See some number formatting examples: + + I18n.toNumber(1000, {precision: 0}); // 1,000 + I18n.toNumber(1000, {delimiter: ".", separator: ","}); // 1.000,000 + I18n.toNumber(1000, {delimiter: ".", precision: 0}); // 1.000 + +The `toCurrency` function accepts the following options: + +- `precision`: sets the level of precision +- `separator`: sets the separator between the units +- `delimiter`: sets the thousands delimiter +- `format`: sets the format of the output string +- `unit`: sets the denomination of the currency +- `strip_insignificant_zeros`: defaults to `false` + +You can provide only the options you want to override: + + I18n.toCurrency(1000, {precision: 0}); // $1,000 + +The `toHumanSize` function accepts the following options: + +- `precision`: defaults to `1` +- `separator`: defaults to `.` +- `delimiter`: defaults to `""` +- `strip_insignificant_zeros`: defaults to `false` +- `format`: defaults to `%n%u` + + + + I18n.toHumanSize(1234); // 1KB + I18n.toHumanSize(1234 * 1024); // 1MB + +#### Date formatting + + // accepted formats + I18n.l("date.formats.short", "2009-09-18"); // yyyy-mm-dd + I18n.l("time.formats.short", "2009-09-18 23:12:43"); // yyyy-mm-dd hh:mm:ss + I18n.l("time.formats.short", "2009-11-09T18:10:34"); // JSON format with local Timezone (part of ISO-8601) + I18n.l("time.formats.short", "2009-11-09T18:10:34Z"); // JSON format in UTC (part of ISO-8601) + I18n.l("date.formats.short", 1251862029000); // Epoch time + I18n.l("date.formats.short", "09/18/2009"); // mm/dd/yyyy + I18n.l("date.formats.short", (new Date())); // Date object + +If you prefer, you can use the `I18n.strftime` function to format dates. + + var date = new Date(); + I18n.strftime(date, "%d/%m/%Y"); + +The accepted formats are: + + %a - The abbreviated weekday name (Sun) + %A - The full weekday name (Sunday) + %b - The abbreviated month name (Jan) + %B - The full month name (January) + %c - The preferred local date and time representation + %d - Day of the month (01..31) + %-d - Day of the month (1..31) + %H - Hour of the day, 24-hour clock (00..23) + %-H - Hour of the day, 24-hour clock (0..23) + %I - Hour of the day, 12-hour clock (01..12) + %-I - Hour of the day, 12-hour clock (1..12) + %m - Month of the year (01..12) + %-m - Month of the year (1..12) + %M - Minute of the hour (00..59) + %-M - Minute of the hour (0..59) + %p - Meridian indicator (AM or PM) + %S - Second of the minute (00..60) + %-S - Second of the minute (0..60) + %w - Day of the week (Sunday is 0, 0..6) + %y - Year without a century (00..99) + %-y - Year without a century (0..99) + %Y - Year with century + %z - Timezone offset (+0545) + +Check out `spec/*.spec.js` files for more examples! + +## Using I18n.js with other languages (Python, PHP, ...) + +The JavaScript library is language agnostic; so you can use it with PHP, Python, [you favorite language here]. +The only requirement is that you need to set the `translations` attribute like following: + + I18n.translations = {}; + + I18n.translations["en"] = { + message: "Some special message for you" + } + + I18n.translations["pt-BR"] = { + message: "Uma mensagem especial para você" + } + +## Maintainer + +- Nando Vieira - + +## Contributing + +Once you've made your great commits: + +1. [Fork](http://help.github.com/forking/) I18n.js +2. Create a topic branch - `git checkout -b my_branch` +3. Push to your branch - `git push origin my_branch` +4. [Create an Issue](http://github.com/fnando/i18n-js/issues) with a link to your branch +5. That's it! + +Please respect the indentation rules and code style. +And use 2 spaces, not tabs. And don't touch the versioning thing. + +## Running tests + +You can run I18n tests using Node.js or your browser. + +To use Node.js, install the `jasmine-node` library: + + $ npm install jasmine-node + +Then execute the following command from the lib's root directory: + + $ npm test + +To run using your browser, just open the `spec/js/specs.html` file. + +You can run both Ruby and JavaScript specs with `rake spec`. + +## License + +(The MIT License) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.rdoc b/README.rdoc deleted file mode 100644 index fd6bfd3a..00000000 --- a/README.rdoc +++ /dev/null @@ -1,320 +0,0 @@ -= I18n for JavaScript - -It's a small library to provide the Rails I18n translations on the Javascript. - -== Usage - -=== Installation - - gem install i18n-js - -=== Setting up - -You don't need to set up a thing. The default settings will work just okay. But if you want to split translations into several files or specify specific contexts, you can follow the rest of this setting up section. - -==== Rails <= 3.0 - -Run rake i18n:js:setup to copy i18n.js to your javascript directory and i18n-js.yml to your config folder (if not already present). Then you're ready to go! - -==== Rails >= 3.1 - -Add the following lines to your application.js to make the javascripts and translations available to your app: - - //= require i18n - //= require i18n/translations - -If asset pipeline has been disabled for your Rails application, then you will need to run rake i18n:js:setup to copy i18n-js.yml to your config folder (if not already present). - -==== Exporting translations - -You can export the translations file by running rake i18n:js:export. -Translations will be automatically exported in development mode. - -==== Configuration - -Translation files can be customized. You can even get more files generated to different folders and with different translations to best suit your needs. - -Examples: - - translations: - - file: 'public/javascripts/path-to-your-messages-file.js' - only: '*.date.formats' - - file: 'public/javascripts/path-to-your-second-file.js' - only: ['*.activerecord', '*.admin.*.title'] - -If only is omitted all the translations will be saved. Also, make sure you add that initial *; it specifies that all languages will be exported. If you want to export only one language, you can do something like this: - - translations: - - file: 'public/javascripts/en.js' - only: 'en.*' - - file: 'public/javascripts/pt-BR.js' - only: 'pt-BR.*' - -Optionally, you can auto generate a translation file per available locale if you specify the %{locale} placeholder. - - translations: - - file: "public/javascripts/i18n/%{locale}.js" - only: '*' - - file: "public/javascripts/frontend/i18n/%{locale}.js" - only: ['frontend', 'users'] - -To find more examples on how to use the configuration file please refer to the tests. - -=== On the Javascript - -Set your locale is easy as - - I18n.defaultLocale = "pt-BR"; - I18n.locale = "pt-BR"; - I18n.currentLocale(); - // pt-BR - -In practice, you'll have something like the following in your application.html.erb: - - - -You can use translate your messages: - - I18n.t("some.scoped.translation"); - // or translate with explicite setting of locale - I18n.t("some.scoped.translation", {locale: "fr"}); - -You can also interpolate values: - - I18n.t("hello", {name: "John Doe"}); - -The sample above will assume that you have the following translations in your -config/locales/*.yml: - - en: - hello: "Hello {{name}}!" - -You can set default values for missing scopes: - - // simple translation - I18n.t("some.missing.scope", {defaultValue: "A default message"}); - - // with interpolation - I18n.t("noun", {defaultValue: "I'm a {{noun}}", noun: "Mac"}); - -Translation fallback can be enabled by adding to your application.html.erb: - - - -By default missing translations will first be looked for in less -specific versions of the requested locale and if that fails by taking -them from your I18n.defaultLocale. -Example: - // if I18n.defaultLocale = "en" and translation doesn't exist for I18n.locale = "de-DE" - I18n.t("some.missing.scope"); - // this key will be taken from "de" locale scope - // or, if that also doesn't exist, from "en" locale scope - -Custom fallback rules can also be specified for a particular language, -for example: - - I18n.fallbackRules.no = [ "nb", "en" ]; - -Pluralization is possible as well and by default provides english rules: - - I18n.t("inbox.counting", {count: 10}); // You have 10 messages - -The sample above expects the following translation: - - en: - inbox: - counting: - one: You have 1 new message - other: You have {{count}} new messages - zero: You have no messages - -NOTE: Rais I18n recognizes the +zero+ option. - -If you need special rules just define them for your language, for example for ru locale in application.js: - - I18n.pluralizationRules.ru = function (n) { - return n % 10 == 1 && n % 100 != 11 ? "one" : [2, 3, 4].indexOf(n % 10) >= 0 && [12, 13, 14].indexOf(n % 100) < 0 ? "few" : n % 10 == 0 || [5, 6, 7, 8, 9].indexOf(n % 10) >= 0 || [11, 12, 13, 14].indexOf(n % 100) >= 0 ? "many" : "other"; - } - -You can find all rules on http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html - -If you're using the same scope over and over again, you may use the +scope+ option. - - var options = {scope: "activerecord.attributes.user"}; - - I18n.t("name", options); - I18n.t("email", options); - I18n.t("username", options); - -You also provide an array as scope. - - // use the greetings.hello scope - I18n.t(["greetings", "hello"]); - -==== Number formatting - -Similar to Rails helpers, you have localized number and currency formatting. - - I18n.l("currency", 1990.99); - // $1,990.99 - - I18n.l("number", 1990.99); - // 1,990.99 - - I18n.l("percentage", 123.45); - // 123.450% - -To have more control over number formatting, you can use the I18n.toNumber, I18n.toPercentage, I18n.toCurrency and I18n.toHumanSize functions. - - I18n.toNumber(1000); // 1,000.000 - I18n.toCurrency(1000); // $1,000.00 - I18n.toPercentage(100); // 100.000% - -The +toNumber+ and +toPercentage+ functions accept the following options: - -* +precision+: defaults to 3 -* +separator+: defaults to . -* +delimiter+: defaults to , -* +strip_insignificant_zeros+: defaults to false - -See some number formatting examples: - - I18n.toNumber(1000, {precision: 0}); // 1,000 - I18n.toNumber(1000, {delimiter: ".", separator: ","}); // 1.000,000 - I18n.toNumber(1000, {delimiter: ".", precision: 0}); // 1.000 - -The +toCurrency+ function accepts the following options: - -* +precision+: sets the level of precision -* +separator+: sets the separator between the units -* +delimiter+: sets the thousands delimiter -* +format+: sets the format of the output string -* +unit+: sets the denomination of the currency -* +strip_insignificant_zeros+: defaults to false - -You can provide only the options you want to override: - - I18n.toCurrency(1000, {precision: 0}); // $1,000 - -The +toHumanSize+ function accepts the following options: - -* +precision+: defaults to 1 -* +separator+: defaults to . -* +delimiter+: defaults to "" -* +strip_insignificant_zeros+: defaults to false -* +format+: defaults to %n%u - - I18n.toHumanSize(1234); // 1KB - I18n.toHumanSize(1234 * 1024); // 1MB - -==== Date formatting - - // accepted formats - I18n.l("date.formats.short", "2009-09-18"); // yyyy-mm-dd - I18n.l("time.formats.short", "2009-09-18 23:12:43"); // yyyy-mm-dd hh:mm:ss - I18n.l("time.formats.short", "2009-11-09T18:10:34"); // JSON format with local Timezone (part of ISO-8601) - I18n.l("time.formats.short", "2009-11-09T18:10:34Z"); // JSON format in UTC (part of ISO-8601) - I18n.l("date.formats.short", 1251862029000); // Epoch time - I18n.l("date.formats.short", "09/18/2009"); // mm/dd/yyyy - I18n.l("date.formats.short", (new Date())); // Date object - -If you prefer, you can use the I18n.strftime function to format dates. - - var date = new Date(); - I18n.strftime(date, "%d/%m/%Y"); - -The accepted formats are: - - %a - The abbreviated weekday name (Sun) - %A - The full weekday name (Sunday) - %b - The abbreviated month name (Jan) - %B - The full month name (January) - %c - The preferred local date and time representation - %d - Day of the month (01..31) - %-d - Day of the month (1..31) - %H - Hour of the day, 24-hour clock (00..23) - %-H - Hour of the day, 24-hour clock (0..23) - %I - Hour of the day, 12-hour clock (01..12) - %-I - Hour of the day, 12-hour clock (1..12) - %m - Month of the year (01..12) - %-m - Month of the year (1..12) - %M - Minute of the hour (00..59) - %-M - Minute of the hour (0..59) - %p - Meridian indicator (AM or PM) - %S - Second of the minute (00..60) - %-S - Second of the minute (0..60) - %w - Day of the week (Sunday is 0, 0..6) - %y - Year without a century (00..99) - %-y - Year without a century (0..99) - %Y - Year with century - %z - Timezone offset (+0545) - -Check out vendor/plugins/i18n-js/spec/i18n_spec.js for more examples! - -== Using I18nJS with other languages (Python, PHP, ...) - -The JavaScript library is language agnostic; so you can use it with PHP, Python, [you favorite language here]. -The only requirement is that you need to set the +translations+ attribute like following: - - I18n.translations = {}; - - I18n.translations["en"] = { - message: "Some special message for you" - } - - I18n.translations["pt-BR"] = { - message: "Uma mensagem especial para você" - } - -== Maintainer - -* Nando Vieira - http://simplesideias.com.br -* Sébastien Grosjean - http://github.com/ZenCocoon - -== Contributing - -Once you've made your great commits: - -1. Fork[http://help.github.com/forking/] I18n-JS -2. Create a topic branch - git checkout -b my_branch -3. Push to your branch - git push origin my_branch -4. Create an Issue[http://github.com/fnando/i18n-js/issues] with a link to your branch -5. That's it! - -Please respect the indentation rules. And use 2 spaces, not tabs. - -=== Running tests - -First, install all dependencies. - - bundle install - -Then just run rake spec. - -== License - -(The MIT License) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -'Software'), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Rakefile b/Rakefile index 25843c3f..b3f0c912 100644 --- a/Rakefile +++ b/Rakefile @@ -1,13 +1,13 @@ require "bundler" Bundler::GemHelper.install_tasks -require "spec_js/rake_task" -SpecJs::RakeTask.new do |t| - t.env_js = false -end - require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:"spec:ruby") +desc "Run JavaScript specs" +task "spec:js" do + system "npm", "test" +end + desc "Run all specs" -task :spec => [:"spec:ruby", :"spec:js"] +task :spec => ["spec:ruby", "spec:js"] diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js new file mode 100644 index 00000000..24c50375 --- /dev/null +++ b/app/assets/javascripts/i18n.js @@ -0,0 +1,684 @@ +// I18n.js +// ======= +// +// This small library provides the Rails I18n API on the Javascript. +// You don't actually have to use Rails (or even Ruby) to use I18n.js. +// Just make sure you export all translations in an object like this: +// +// I18n.translations.en = { +// hello: "Hello World" +// }; +// +// See tests for specific formatting like numbers and dates. +// +;(function(I18n){ + "use strict"; + + // Just cache the Array#slice function. + var slice = Array.prototype.slice; + + // Apply number padding. + var padding = function(number) { + return ("0" + number.toString()).substr(-2); + }; + + // Set default days/months translations. + var DAYS_AND_MONTHS = { + day_names: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] + , abbr_day_names: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] + , month_names: [null, "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] + , abbr_month_names: [null, "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + }; + + // Set default number format. + var NUMBER_FORMAT = { + precision: 3 + , separator: "." + , delimiter: "," + , strip_insignificant_zeros: false + }; + + // Set default currency format. + var CURRENCY_FORMAT = { + unit: "$" + , precision: 2 + , format: "%u%n" + , delimiter: "," + , separator: "." + }; + + // Set default percentage format. + var PERCENTAGE_FORMAT = { + precision: 3 + , separator: "." + , delimiter: "" + }; + + // Set default size units. + var SIZE_UNITS = [null, "kb", "mb", "gb", "tb"]; + + // Set meridian. + var MERIDIAN = ["AM", "PM"]; + + I18n.reset = function() { + // Set default locale. This locale will be used when fallback is enabled and + // the translation doesn't exist in a particular locale. + this.defaultLocale = "en"; + + // Set the current locale to `en`. + this.locale = "en"; + + // Set the translation key separator. + this.defaultSeparator = "."; + + // Set the placeholder format. Accepts `{{placeholder}}` and `%{placeholder}`. + this.placeholder = /(?:\{\{|%\{)(.*?)(?:\}\}?)/gm; + + // Set if engine should fallback to the default locale when a translation + // is missing. + this.fallbacks = false; + + // Set the default translation object. + this.translations = {}; + }; + + // Return a list of all locales that must be tried before returning the + // missing translation message. By default, this will consider the inline option, + // current locale and fallback locale. + // + // I18n.locales.get("de-DE"); + // // ["de-DE", "de", "en"] + // + // You can define custom rules for any locale. Just make sure you return a array + // containing all locales. + // + // // Default the Wookie locale to English. + // I18n.locales["wk"] = function(locale) { + // return ["en"]; + // }; + // + I18n.locales = {}; + + // Retrieve locales based on inline locale, current locale or default to + // I18n's detection. + I18n.locales.get = function(locale) { + var result = this[locale] || this[I18n.locale] || this["default"]; + + if (typeof(result) === "function") { + result = result(locale); + } + + if (result instanceof Array === false) { + result = [result]; + } + + return result; + }; + + // The default locale list. + I18n.locales["default"] = function(locale) { + var locales = [] + , list = [] + , countryCode + , count + ; + + // Handle the inline locale option that can be provided to + // the `I18n.t` options. + if (locale) { + locales.push(locale); + } + + // Add the current locale to the list. + if (!locale && I18n.locale) { + locales.push(I18n.locale); + } + + // Add the default locale if fallback strategy is enabled. + if (I18n.fallbacks && I18n.defaultLocale) { + locales.push(I18n.defaultLocale); + } + + // Compute each locale with its country code. + // So this will return an array containing both + // `de-DE` and `de` locales. + locales.forEach(function(locale){ + countryCode = locale.split("-")[0]; + + if (!~list.indexOf(locale)) { + list.push(locale); + } + + if (I18n.fallbacks && countryCode && countryCode !== locale && !~list.indexOf(countryCode)) { + list.push(countryCode); + } + }); + + // No locales set? English it is. + if (!locales.length) { + locales.push("en"); + } + + return list; + }; + + // Hold pluralization rules. + I18n.pluralization = {}; + + // Return the pluralizer for a specific locale. + // If no specify locale is found, then I18n's default will be used. + I18n.pluralization.get = function(locale) { + return this[locale] || this[I18n.locale] || this["default"]; + }; + + // The default pluralizer rule. + // It detects the `zero`, `one`, and `other` scopes. + I18n.pluralization["default"] = function(count) { + switch (count) { + case 0: return ["zero", "other"]; + case 1: return ["one"]; + default: return ["other"]; + } + }; + + // Reset all default attributes. This is specially useful + // while running tests. + I18n.reset(); + + // Return current locale. If no locale has been set, then + // the current locale will be the default locale. + I18n.currentLocale = function() { + return this.locale || this.defaultLocale; + }; + + // Check if value is different than undefined and null; + I18n.isSet = function(value) { + return value !== undefined && value !== null; + }; + + // Find and process the translation using the provided scope and options. + // This is used internally by some functions and should not be used as an + // public API. + I18n.lookup = function(scope, options) { + options = this.prepareOptions(options); + + var locales = this.locales.get(options.locale) + , requestedLocale = locales[0] + , locale + , scopes + , translations + ; + + while (locales.length) { + locale = locales.shift(); + scopes = scope.split(this.defaultSeparator); + translations = this.translations[locale]; + + if (!translations) { + continue; + } + + while (scopes.length) { + translations = translations[scopes.shift()]; + + if (translations === undefined || translations === null) { + break; + } + } + + if (translations !== undefined && translations !== null) { + return translations; + } + } + + if (this.isSet(options.defaultValue)) { + return options.defaultValue; + } + }; + + // Merge serveral hash options, checking if value is set before + // overwriting any value. The precedence is from left to right. + // + // I18n.prepareOptions({name: "John Doe"}, {name: "Mary Doe", role: "user"}); + // #=> {name: "John Doe", role: "user"} + // + I18n.prepareOptions = function() { + var args = slice.call(arguments) + , options = {} + , subject + ; + + while (args.length) { + subject = args.shift(); + + if (typeof(subject) != "object") { + continue; + } + + for (var attr in subject) { + if (!subject.hasOwnProperty(attr)) { + continue; + } + + if (this.isSet(options[attr])) { + continue; + } + + options[attr] = subject[attr]; + } + } + + return options; + }; + + // Translate the given scope with the provided options. + I18n.translate = function(scope, options) { + options = this.prepareOptions(options); + var translation = this.lookup(scope, options); + + if (translation === undefined || translation === null) { + return this.missingTranslation(scope); + } + + if (typeof(translation) === "string") { + translation = this.interpolate(translation, options); + } else if (translation instanceof Object && this.isSet(options.count)) { + translation = this.pluralize(options.count, translation, options); + } + + return translation; + }; + + // This function interpolates the all variables in the given message. + I18n.interpolate = function(message, options) { + options = this.prepareOptions(options); + var matches = message.match(this.placeholder) + , placeholder + , value + , name + , regex + ; + + if (!matches) { + return message; + } + + while (matches.length) { + placeholder = matches.shift(); + name = placeholder.replace(this.placeholder, "$1"); + value = options[name]; + + if (!this.isSet(options[name])) { + value = "[missing " + placeholder + " value]"; + } + + regex = new RegExp(placeholder.replace(/\{/gm, "\\{").replace(/\}/gm, "\\}")); + message = message.replace(regex, value); + } + + return message; + }; + + // Pluralize the given scope using the `count` value. + // The pluralized translation may have other placeholders, + // which will be retrieved from `options`. + I18n.pluralize = function(count, scope, options) { + options = this.prepareOptions(options); + var translations, pluralizer, keys, key, message; + + if (scope instanceof Object) { + translations = scope; + } else { + translations = this.lookup(scope, options); + } + + if (!translations) { + return this.missingTranslation(scope); + } + + pluralizer = this.pluralization.get(options.locale); + keys = pluralizer(Math.abs(count)); + + while (keys.length) { + key = keys.shift(); + + if (this.isSet(translations[key])) { + message = translations[key]; + break; + } + } + + options.count = String(count); + return this.interpolate(message, options); + }; + + // Return a missing translation message for the given parameters. + I18n.missingTranslation = function(scope) { + var message = '[missing "'; + + message += this.currentLocale() + "."; + message += slice.call(arguments).join("."); + message += '" translation]'; + + return message; + }; + + // Format number using localization rules. + // The options will be retrieved from the `number.format` scope. + // If this isn't present, then the following options will be used: + // + // - `precision`: `3` + // - `separator`: `"."` + // - `delimiter`: `","` + // - `strip_insignificant_zeros`: `false` + // + // You can also override these options by providing the `options` argument. + // + I18n.toNumber = function(number, options) { + options = this.prepareOptions( + options + , this.lookup("number.format") + , NUMBER_FORMAT + ); + + var negative = number < 0 + , string = Math.abs(number).toFixed(options.precision).toString() + , parts = string.split(".") + , precision + , buffer = [] + , formattedNumber + ; + + number = parts[0]; + precision = parts[1]; + + while (number.length > 0) { + buffer.unshift(number.substr(Math.max(0, number.length - 3), 3)); + number = number.substr(0, number.length -3); + } + + formattedNumber = buffer.join(options.delimiter); + + if (options.strip_insignificant_zeros && precision) { + precision = precision.replace(/0+$/, ""); + } + + if (options.precision > 0 && precision) { + formattedNumber += options.separator + precision; + } + + if (negative) { + formattedNumber = "-" + formattedNumber; + } + + return formattedNumber; + }; + + // Format currency with localization rules. + // The options will be retrieved from the `number.currency.format` and + // `number.format` scopes, in that order. + // + // Any missing option will be retrieved from the `I18n.toNumber` defaults and + // the following options: + // + // - `unit`: `"$"` + // - `precision`: `2` + // - `format`: `"%u%n"` + // - `delimiter`: `","` + // - `separator`: `"."` + // + // You can also override these options by providing the `options` argument. + // + I18n.toCurrency = function(number, options) { + options = this.prepareOptions( + options + , this.lookup("number.currency.format") + , this.lookup("number.format") + , CURRENCY_FORMAT + ); + + number = this.toNumber(number, options); + number = options.format + .replace("%u", options.unit) + .replace("%n", number) + ; + + return number; + }; + + // Localize several values. + // You can provide the following scopes: `currency`, `number`, or `percentage`. + // If you provide a scope that matches the `/^(date|time)/` regular expression + // then the `value` will be converted by using the `I18n.toTime` function. + // + // It will default to the value's `toString` function. + // + I18n.localize = function(scope, value) { + switch (scope) { + case "currency": + return this.toCurrency(value); + case "number": + scope = this.lookup("number.format"); + return this.toNumber(value, scope); + case "percentage": + return this.toPercentage(value); + default: + if (scope.match(/^(date|time)/)) { + return this.toTime(scope, value); + } else { + return value.toString(); + } + } + }; + + // Parse a given `date` string into a JavaScript Date object. + // This function is time zone aware. + // + // The following string formats are recognized: + // + // yyyy-mm-dd + // yyyy-mm-dd[ T]hh:mm::ss + // yyyy-mm-dd[ T]hh:mm::ss + // yyyy-mm-dd[ T]hh:mm::ssZ + // yyyy-mm-dd[ T]hh:mm::ss+0000 + // + I18n.parseDate = function(date) { + var matches, convertedDate; + + // we have a date, so just return it. + if (typeof(date) == "object") { + return date; + }; + + matches = date.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2}))?(Z|\+0000)?/); + + if (matches) { + for (var i = 1; i <= 6; i++) { + matches[i] = parseInt(matches[i], 10) || 0; + } + + // month starts on 0 + matches[2] -= 1; + + if (matches[7]) { + convertedDate = new Date(Date.UTC(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6])); + } else { + convertedDate = new Date(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6]); + } + } else if (typeof(date) == "number") { + // UNIX timestamp + convertedDate = new Date(); + convertedDate.setTime(date); + } else if (date.match(/\d+ \d+:\d+:\d+ [+-]\d+ \d+/)) { + // a valid javascript format with timezone info + convertedDate = new Date(); + convertedDate.setTime(Date.parse(date)) + } else { + // an arbitrary javascript string + convertedDate = new Date(); + convertedDate.setTime(Date.parse(date)); + } + + return convertedDate; + }; + + // Formats time according to the directives in the given format string. + // The directives begins with a percent (%) character. Any text not listed as a + // directive will be passed through to the output string. + // + // The accepted formats are: + // + // %a - The abbreviated weekday name (Sun) + // %A - The full weekday name (Sunday) + // %b - The abbreviated month name (Jan) + // %B - The full month name (January) + // %c - The preferred local date and time representation + // %d - Day of the month (01..31) + // %-d - Day of the month (1..31) + // %H - Hour of the day, 24-hour clock (00..23) + // %-H - Hour of the day, 24-hour clock (0..23) + // %I - Hour of the day, 12-hour clock (01..12) + // %-I - Hour of the day, 12-hour clock (1..12) + // %m - Month of the year (01..12) + // %-m - Month of the year (1..12) + // %M - Minute of the hour (00..59) + // %-M - Minute of the hour (0..59) + // %p - Meridian indicator (AM or PM) + // %S - Second of the minute (00..60) + // %-S - Second of the minute (0..60) + // %w - Day of the week (Sunday is 0, 0..6) + // %y - Year without a century (00..99) + // %-y - Year without a century (0..99) + // %Y - Year with century + // %z - Timezone offset (+0545) + // + I18n.strftime = function(date, format) { + var options = this.lookup("date"); + + if (!options) { + options = DAYS_AND_MONTHS; + } + + if (!options.meridian) { + options.meridian = MERIDIAN; + } + + var weekDay = date.getDay() + , day = date.getDate() + , year = date.getFullYear() + , month = date.getMonth() + 1 + , hour = date.getHours() + , hour12 = hour + , meridian = hour > 11 ? 1 : 0 + , secs = date.getSeconds() + , mins = date.getMinutes() + , offset = date.getTimezoneOffset() + , absOffsetHours = Math.floor(Math.abs(offset / 60)) + , absOffsetMinutes = Math.abs(offset) - (absOffsetHours * 60) + , timezoneoffset = (offset > 0 ? "-" : "+") + (absOffsetHours.toString().length < 2 ? "0" + absOffsetHours : absOffsetHours) + (absOffsetMinutes.toString().length < 2 ? "0" + absOffsetMinutes : absOffsetMinutes) + ; + + if (hour12 > 12) { + hour12 = hour12 - 12; + } else if (hour12 === 0) { + hour12 = 12; + } + + format = format.replace("%a", options.abbr_day_names[weekDay]); + format = format.replace("%A", options.day_names[weekDay]); + format = format.replace("%b", options.abbr_month_names[month]); + format = format.replace("%B", options.month_names[month]); + format = format.replace("%d", padding(day)); + format = format.replace("%e", day); + format = format.replace("%-d", day); + format = format.replace("%H", padding(hour)); + format = format.replace("%-H", hour); + format = format.replace("%I", padding(hour12)); + format = format.replace("%-I", hour12); + format = format.replace("%m", padding(month)); + format = format.replace("%-m", month); + format = format.replace("%M", padding(mins)); + format = format.replace("%-M", mins); + format = format.replace("%p", options.meridian[meridian]); + format = format.replace("%S", padding(secs)); + format = format.replace("%-S", secs); + format = format.replace("%w", weekDay); + format = format.replace("%y", padding(year)); + format = format.replace("%-y", padding(year).replace(/^0+/, "")); + format = format.replace("%Y", year); + format = format.replace("%z", timezoneoffset); + + return format; + }; + + // Convert the given dateString into a formatted date. + I18n.toTime = function(scope, dateString) { + var date = this.parseDate(dateString) + , format = this.lookup(scope) + ; + + if (date.toString().match(/invalid/i)) { + return date.toString(); + } + + if (!format) { + return date.toString(); + } + + return this.strftime(date, format); + }; + + // Convert a number into a formatted percentage value. + I18n.toPercentage = function(number, options) { + options = this.prepareOptions( + options + , this.lookup("number.percentage.format") + , this.lookup("number.format") + , PERCENTAGE_FORMAT + ); + + number = this.toNumber(number, options); + return number + "%"; + }; + + // Convert a number into a readable size representation. + I18n.toHumanSize = function(number, options) { + var kb = 1024 + , size = number + , iterations = 0 + , unit + , precision + ; + + while (size >= kb && iterations < 4) { + size = size / kb; + iterations += 1; + } + + if (iterations === 0) { + unit = this.t("number.human.storage_units.units.byte", {count: size}); + precision = 0; + } else { + unit = this.t("number.human.storage_units.units." + SIZE_UNITS[iterations]); + precision = (size - Math.floor(size) === 0) ? 0 : 1; + } + + options = this.prepareOptions( + options + , {precision: precision, format: "%n%u", delimiter: ""} + ); + + number = this.toNumber(size, options); + number = options.format + .replace("%u", unit) + .replace("%n", number) + ; + + return number; + }; + + // Set aliases, so we can save some typing. + I18n.t = I18n.translate; + I18n.l = I18n.localize; + I18n.p = I18n.pluralize; +})(typeof(exports) === "undefined" ? (this.I18n = {}) : exports); diff --git a/app/assets/javascripts/i18n/filtered.js.erb b/app/assets/javascripts/i18n/filtered.js.erb new file mode 100644 index 00000000..f5082e57 --- /dev/null +++ b/app/assets/javascripts/i18n/filtered.js.erb @@ -0,0 +1,2 @@ +<%# encoding: utf-8 %> +;I18n.translations = <%= I18n::JS.filtered_translations.to_json %>; diff --git a/app/assets/javascripts/i18n/shims.js b/app/assets/javascripts/i18n/shims.js new file mode 100644 index 00000000..9ecd1ddb --- /dev/null +++ b/app/assets/javascripts/i18n/shims.js @@ -0,0 +1,93 @@ +// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf +if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { + "use strict"; + if (this == null) { + throw new TypeError(); + } + var t = Object(this); + var len = t.length >>> 0; + if (len === 0) { + return -1; + } + var n = 0; + if (arguments.length > 1) { + n = Number(arguments[1]); + if (n != n) { // shortcut for verifying if it's NaN + n = 0; + } else if (n != 0 && n != Infinity && n != -Infinity) { + n = (n > 0 || -1) * Math.floor(Math.abs(n)); + } + } + if (n >= len) { + return -1; + } + var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); + for (; k < len; k++) { + if (k in t && t[k] === searchElement) { + return k; + } + } + return -1; + } +} + +// Production steps of ECMA-262, Edition 5, 15.4.4.18 +// Reference: http://es5.github.com/#x15.4.4.18 +// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach +if ( !Array.prototype.forEach ) { + + Array.prototype.forEach = function forEach( callback, thisArg ) { + + var T, k; + + if ( this == null ) { + throw new TypeError( "this is null or not defined" ); + } + + // 1. Let O be the result of calling ToObject passing the |this| value as the argument. + var O = Object(this); + + // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length". + // 3. Let len be ToUint32(lenValue). + var len = O.length >>> 0; // Hack to convert O.length to a UInt32 + + // 4. If IsCallable(callback) is false, throw a TypeError exception. + // See: http://es5.github.com/#x9.11 + if ( {}.toString.call(callback) !== "[object Function]" ) { + throw new TypeError( callback + " is not a function" ); + } + + // 5. If thisArg was supplied, let T be thisArg; else let T be undefined. + if ( thisArg ) { + T = thisArg; + } + + // 6. Let k be 0 + k = 0; + + // 7. Repeat, while k < len + while( k < len ) { + + var kValue; + + // a. Let Pk be ToString(k). + // This is implicit for LHS operands of the in operator + // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk. + // This step can be combined with c + // c. If kPresent is true, then + if ( Object.prototype.hasOwnProperty.call(O, k) ) { + + // i. Let kValue be the result of calling the Get internal method of O with argument Pk. + kValue = O[ k ]; + + // ii. Call the Call internal method of callback with T as the this value and + // argument list containing kValue, k, and O. + callback.call( T, kValue, k, O ); + } + // d. Increase k by 1. + k++; + } + // 8. return undefined + }; +} diff --git a/app/assets/javascripts/i18n/translations.js b/app/assets/javascripts/i18n/translations.js new file mode 100644 index 00000000..b048a6e8 --- /dev/null +++ b/app/assets/javascripts/i18n/translations.js @@ -0,0 +1,3 @@ +//= require i18n/shims +//= require i18n +//= require i18n/filtered diff --git a/config/i18n-js.yml b/config/i18n-js.yml deleted file mode 100644 index a5ed6d89..00000000 --- a/config/i18n-js.yml +++ /dev/null @@ -1,22 +0,0 @@ -# Split context in several files. -# By default only one file with all translations is exported and -# no configuration is required. Your settings for asset pipeline -# are automatically recognized. -# -# If you want to split translations into several files or specify -# locale contexts that will be exported, just use this file to do -# so. -# -# If you're going to use the Rails 3.1 asset pipeline, change -# the following configuration to something like this: -# -# translations: -# - file: "app/assets/javascripts/i18n/translations.js" -# -# If you're running an old version, you can use something -# like this: -# -# translations: -# - file: "public/javascripts/translations.js" -# only: "*" -# \ No newline at end of file diff --git a/i18n-js.gemspec b/i18n-js.gemspec index 13e31281..3bb8fc89 100644 --- a/i18n-js.gemspec +++ b/i18n-js.gemspec @@ -1,10 +1,10 @@ # -*- encoding: utf-8 -*- $:.push File.expand_path("../lib", __FILE__) -require "i18n-js/version" +require "i18n/js/version" Gem::Specification.new do |s| s.name = "i18n-js" - s.version = SimplesIdeias::I18n::Version::STRING + s.version = I18n::JS::Version::STRING s.platform = Gem::Platform::RUBY s.authors = ["Nando Vieira"] s.email = ["fnando.vieira@gmail.com"] @@ -18,10 +18,8 @@ Gem::Specification.new do |s| s.require_paths = ["lib"] s.add_dependency "i18n" - s.add_development_dependency "fakeweb" - s.add_development_dependency "activesupport", ">= 3.0.0" - s.add_development_dependency "rspec", "~> 2.6" - s.add_development_dependency "spec-js", "~> 0.1.0.beta.0" + s.add_development_dependency "activesupport" + s.add_development_dependency "rspec" s.add_development_dependency "rake" - s.add_development_dependency "pry" + s.add_development_dependency "pry-meta" end diff --git a/lib/i18n-js.rb b/lib/i18n-js.rb index de31a269..255c25a7 100644 --- a/lib/i18n-js.rb +++ b/lib/i18n-js.rb @@ -1,177 +1 @@ -require "FileUtils" unless defined?(FileUtils) - -module SimplesIdeias - module I18n - extend self - - require "i18n-js/railtie" if Rails.version >= "3.0" - require "i18n-js/engine" if Rails.version >= "3.1" - require "i18n-js/middleware" - - # deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809 - MERGER = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &MERGER) : v2 } - - # Under rails 3.1.1 and higher, perform a check to ensure that the - # full environment will be available during asset compilation. - # This is required to ensure I18n is loaded. - def assert_usable_configuration! - @usable_configuration ||= Rails.version >= "3.1.1" && - Rails.configuration.assets.initialize_on_precompile || - raise("Cannot precompile i18n-js translations unless environment is initialized. Please set config.assets.initialize_on_precompile to true.") - end - - def has_asset_pipeline? - Rails.configuration.respond_to?(:assets) && Rails.configuration.assets.enabled - end - - def config_file - Rails.root.join("config/i18n-js.yml") - end - - def export_dir - if has_asset_pipeline? - "app/assets/javascripts/i18n" - else - "public/javascripts" - end - end - - def javascript_file - Rails.root.join(export_dir, "i18n.js") - end - - # Export translations to JavaScript, considering settings - # from configuration file - def export! - translation_segments.each do |filename, translations| - save(translations, filename) - end - end - - def segments_per_locale(pattern,scope) - ::I18n.available_locales.each_with_object({}) do |locale,segments| - result = scoped_translations("#{locale}.#{scope}") - unless result.empty? - segment_name = ::I18n.interpolate(pattern,{:locale => locale}) - segments[segment_name] = result - end - end - end - - def segment_for_scope(scope) - if scope == "*" - translations - else - scoped_translations(scope) - end - end - - def configured_segments - config[:translations].each_with_object({}) do |options,segments| - options.reverse_merge!(:only => "*") - if options[:file] =~ ::I18n::INTERPOLATION_PATTERN - segments.merge!(segments_per_locale(options[:file],options[:only])) - else - result = segment_for_scope(options[:only]) - segments[options[:file]] = result unless result.empty? - end - end - end - - def translation_segments - if config? && config[:translations] - configured_segments - else - {"#{export_dir}/translations.js" => translations} - end - end - - # Load configuration file for partial exporting and - # custom output directory - def config - if config? - (YAML.load_file(config_file) || {}).with_indifferent_access - else - {} - end - end - - # Check if configuration file exist - def config? - File.file? config_file - end - - # Copy configuration and JavaScript library files to - # config/i18n-js.yml and public/javascripts/i18n.js. - def setup! - FileUtils.cp(File.dirname(__FILE__) + "/../vendor/assets/javascripts/i18n.js", javascript_file) unless has_asset_pipeline? - FileUtils.cp(File.dirname(__FILE__) + "/../config/i18n-js.yml", config_file) unless config? - end - - # Retrieve an updated JavaScript library from Github. - def update! - require "open-uri" - contents = open("https://raw.github.com/fnando/i18n-js/master/vendor/assets/javascripts/i18n.js").read - File.open(javascript_file, "w+") {|f| f << contents} - end - - # Convert translations to JSON string and save file. - def save(translations, file) - file = Rails.root.join(file) - FileUtils.mkdir_p File.dirname(file) - - File.open(file, "w+") do |f| - f << %(var I18n = I18n || {};\n) - f << %(I18n.translations = ); - f << translations.to_json - f << %(;) - end - end - - def scoped_translations(scopes) # :nodoc: - result = {} - - [scopes].flatten.each do |scope| - deep_merge! result, filter(translations, scope) - end - - result - end - - # Filter translations according to the specified scope. - def filter(translations, scopes) - scopes = scopes.split(".") if scopes.is_a?(String) - scopes = scopes.clone - scope = scopes.shift - - if scope == "*" - results = {} - translations.each do |scope, translations| - tmp = scopes.empty? ? translations : filter(translations, scopes) - results[scope.to_sym] = tmp unless tmp.nil? - end - return results - elsif translations.has_key?(scope.to_sym) - return {scope.to_sym => scopes.empty? ? translations[scope.to_sym] : filter(translations[scope.to_sym], scopes)} - end - nil - end - - # Initialize and return translations - def translations - ::I18n.backend.instance_eval do - init_translations unless initialized? - translations - end - end - - def deep_merge(target, hash) # :nodoc: - target.merge(hash, &MERGER) - end - - def deep_merge!(target, hash) # :nodoc: - target.merge!(hash, &MERGER) - end - end -end - +require "i18n/js" \ No newline at end of file diff --git a/lib/i18n-js/engine.rb b/lib/i18n-js/engine.rb deleted file mode 100644 index 8dfaaf3d..00000000 --- a/lib/i18n-js/engine.rb +++ /dev/null @@ -1,63 +0,0 @@ -module SimplesIdeias - module I18n - class Engine < ::Rails::Engine - I18N_TRANSLATIONS_ASSET = "i18n/translations" - - initializer "i18n-js.asset_dependencies", :after => "sprockets.environment", - :before => "i18n-js.initialize" do - next unless SimplesIdeias::I18n.has_asset_pipeline? - - config = I18n.config_file - cache_file = I18n::Engine.load_path_hash_cache - - Rails.application.assets.register_preprocessor "application/javascript", :"i18n-js_dependencies" do |context, data| - if context.logical_path == I18N_TRANSLATIONS_ASSET - context.depend_on(config) if I18n.config? - # also set up dependencies on every locale file - ::I18n.load_path.each {|path| context.depend_on(path)} - - # Set up a dependency on the contents of the load path - # itself. In some situations it is possible to get here - # before the path hash cache file has been written; in - # this situation, write it now. - I18n::Engine.write_hash! unless File.exists?(cache_file) - context.depend_on(cache_file) - end - - data - end - end - - # rewrite path cache hash at startup and before each request in development - config.to_prepare do - next unless SimplesIdeias::I18n.has_asset_pipeline? - SimplesIdeias::I18n::Engine.write_hash_if_changed unless Rails.env.production? - end - - def self.load_path_hash_cache - @load_path_hash_cache ||= Rails.root.join("tmp/i18n-js.cache") - end - - def self.write_hash_if_changed - load_path_hash = ::I18n.load_path.hash - - if load_path_hash != cached_load_path_hash - self.cached_load_path_hash = load_path_hash - write_hash! - end - end - - def self.write_hash! - FileUtils.mkdir_p Rails.root.join("tmp") - - File.open(load_path_hash_cache, "w+") do |f| - f.write(cached_load_path_hash) - end - end - - class << self - attr_accessor :cached_load_path_hash - end - end - end -end diff --git a/lib/i18n-js/railtie.rb b/lib/i18n-js/railtie.rb deleted file mode 100644 index 3a6b75ca..00000000 --- a/lib/i18n-js/railtie.rb +++ /dev/null @@ -1,13 +0,0 @@ -module SimplesIdeias - module I18n - class Railtie < Rails::Railtie - rake_tasks do - require "i18n-js/rake" - end - - initializer "i18n-js.initialize" do |app| - app.config.middleware.use(Middleware) if Rails.env.development? && !SimplesIdeias::I18n.has_asset_pipeline? - end - end - end -end diff --git a/lib/i18n-js/rake.rb b/lib/i18n-js/rake.rb deleted file mode 100644 index 03b5d890..00000000 --- a/lib/i18n-js/rake.rb +++ /dev/null @@ -1,16 +0,0 @@ -namespace "i18n:js" do - desc "Copy i18n.js and configuration file" - task :setup => :environment do - SimplesIdeias::I18n.setup! - end - - desc "Export the messages files" - task :export => :environment do - SimplesIdeias::I18n.export! - end - - desc "Update the JavaScript library" - task :update => :environment do - SimplesIdeias::I18n.update! - end -end diff --git a/lib/i18n-js/version.rb b/lib/i18n-js/version.rb deleted file mode 100644 index ac0b095b..00000000 --- a/lib/i18n-js/version.rb +++ /dev/null @@ -1,10 +0,0 @@ -module SimplesIdeias - module I18n - module Version - MAJOR = 2 - MINOR = 1 - PATCH = 2 - STRING = "#{MAJOR}.#{MINOR}.#{PATCH}" - end - end -end diff --git a/lib/i18n/js.rb b/lib/i18n/js.rb new file mode 100644 index 00000000..d4ed5594 --- /dev/null +++ b/lib/i18n/js.rb @@ -0,0 +1,157 @@ +require "i18n" +require "FileUtils" unless defined?(FileUtils) + +module I18n + module JS + if defined?(Rails) + require "i18n/js/middleware" + require "i18n/js/engine" + end + + # deep_merge by Stefan Rusterholz, see . + MERGER = proc do |key, v1, v2| + Hash === v1 && Hash === v2 ? v1.merge(v2, &MERGER) : v2 + end + + # Detect if Rails app has asset pipeline support. + # + def self.has_asset_pipeline? + Rails.configuration.respond_to?(:assets) && Rails.configuration.assets.enabled + end + + # The configuration file. This defaults to the `config/i18n-js.yml` file. + # + def self.config_file + @config_file ||= "config/i18n-js.yml" + end + + # Export translations to JavaScript, considering settings + # from configuration file + def self.export + translation_segments.each do |filename, translations| + save(translations, filename) + end + end + + def self.segments_per_locale(pattern, scope) + I18n.available_locales.each_with_object({}) do |locale, segments| + result = scoped_translations("#{locale}.#{scope}") + next if result.empty? + + segment_name = ::I18n.interpolate(pattern,{:locale => locale}) + segments[segment_name] = result + end + end + + def self.segment_for_scope(scope) + if scope == "*" + translations + else + scoped_translations(scope) + end + end + + def self.configured_segments + config[:translations].each_with_object({}) do |options, segments| + options.reverse_merge!(:only => "*") + if options[:file] =~ ::I18n::INTERPOLATION_PATTERN + segments.merge!(segments_per_locale(options[:file], options[:only])) + else + result = segment_for_scope(options[:only]) + segments[options[:file]] = result unless result.empty? + end + end + end + + def self.export_dir + "public/javascripts" + end + + def self.filtered_translations + {}.tap do |result| + translation_segments.each do |filename, translations| + deep_merge!(result, translations) + end + end + end + + def self.translation_segments + if config? && config[:translations] + configured_segments + else + {"#{export_dir}/translations.js" => translations} + end + end + + # Load configuration file for partial exporting and + # custom output directory + def self.config + if config? + (YAML.load_file(config_file) || {}).with_indifferent_access + else + {} + end + end + + # Check if configuration file exist + def self.config? + File.file? config_file + end + + # Convert translations to JSON string and save file. + def self.save(translations, file) + FileUtils.mkdir_p File.dirname(file) + + File.open(file, "w+") do |f| + f << %(I18n.translations = ); + f << translations.to_json + f << %(;) + end + end + + def self.scoped_translations(scopes) # :nodoc: + result = {} + + [scopes].flatten.each do |scope| + deep_merge! result, filter(translations, scope) + end + + result + end + + # Filter translations according to the specified scope. + def self.filter(translations, scopes) + scopes = scopes.split(".") if scopes.is_a?(String) + scopes = scopes.clone + scope = scopes.shift + + if scope == "*" + results = {} + translations.each do |scope, translations| + tmp = scopes.empty? ? translations : filter(translations, scopes) + results[scope.to_sym] = tmp unless tmp.nil? + end + return results + elsif translations.has_key?(scope.to_sym) + return {scope.to_sym => scopes.empty? ? translations[scope.to_sym] : filter(translations[scope.to_sym], scopes)} + end + nil + end + + # Initialize and return translations + def self.translations + ::I18n.backend.instance_eval do + init_translations unless initialized? + translations + end + end + + def self.deep_merge(target, hash) # :nodoc: + target.merge(hash, &MERGER) + end + + def self.deep_merge!(target, hash) # :nodoc: + target.merge!(hash, &MERGER) + end + end +end diff --git a/lib/i18n/js/engine.rb b/lib/i18n/js/engine.rb new file mode 100644 index 00000000..ab346b38 --- /dev/null +++ b/lib/i18n/js/engine.rb @@ -0,0 +1,22 @@ +require "i18n/js" + +module I18n + module JS + class Engine < ::Rails::Engine + initializer :after => "sprockets.environment" do + ActiveSupport.on_load(:after_initialize, :yield => true) do + next unless JS.has_asset_pipeline? + next unless Rails.configuration.assets.compile + + registry = Sprockets.respond_to?("register_preprocessor") ? Sprockets : Rails.application.assets + + registry.register_preprocessor "application/javascript", :"i18n-js_dependencies" do |context, source| + next source unless context.logical_path == "i18n/filtered" + ::I18n.load_path.each {|path| context.depend_on(File.expand_path(path))} + source + end + end + end + end + end +end diff --git a/lib/i18n-js/middleware.rb b/lib/i18n/js/middleware.rb similarity index 85% rename from lib/i18n-js/middleware.rb rename to lib/i18n/js/middleware.rb index ff019247..e148b7b2 100644 --- a/lib/i18n-js/middleware.rb +++ b/lib/i18n/js/middleware.rb @@ -1,5 +1,5 @@ -module SimplesIdeias - module I18n +module I18n + module JS class Middleware def initialize(app) @app = app @@ -46,13 +46,13 @@ def verify_locale_files! new_cache[path] = changed_at end - unless valid_cache.all? - File.open(cache_path, "w+") do |file| - file << new_cache.to_yaml - end + return if valid_cache.all? - SimplesIdeias::I18n.export! + File.open(cache_path, "w+") do |file| + file << new_cache.to_yaml end + + ::I18n::JS.export end end end diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb new file mode 100644 index 00000000..6cf62df0 --- /dev/null +++ b/lib/i18n/js/version.rb @@ -0,0 +1,10 @@ +module I18n + module JS + module Version + MAJOR = 3 + MINOR = 0 + PATCH = 0 + STRING = "#{MAJOR}.#{MINOR}.#{PATCH}.rc5" + end + end +end diff --git a/package.json b/package.json new file mode 100644 index 00000000..8d5412f0 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "i18n-js", + "version": "0.0.0", + "devDependencies": { + "jasmine-node": "*" + }, + + "scripts": { + "test": "./node_modules/.bin/jasmine-node spec/js" + } +} diff --git a/spec/resources/custom_path.yml b/spec/fixtures/custom_path.yml old mode 100644 new mode 100755 similarity index 69% rename from spec/resources/custom_path.yml rename to spec/fixtures/custom_path.yml index dcf81a0d..73597cd0 --- a/spec/resources/custom_path.yml +++ b/spec/fixtures/custom_path.yml @@ -1,4 +1,4 @@ # Find more details about this configuration file at http://github.com/fnando/i18n-js translations: - - file: "public/javascripts/translations/all.js" + - file: "tmp/i18n-js/all.js" only: "*" diff --git a/spec/resources/default.yml b/spec/fixtures/default.yml old mode 100644 new mode 100755 similarity index 70% rename from spec/resources/default.yml rename to spec/fixtures/default.yml index 3e298f5b..f05086fd --- a/spec/resources/default.yml +++ b/spec/fixtures/default.yml @@ -1,4 +1,4 @@ # Find more details about this configuration file at http://github.com/fnando/i18n-js translations: - - file: "public/javascripts/translations.js" + - file: "tmp/i18n-js/translations.js" only: "*" diff --git a/spec/fixtures/js_file_per_locale.yml b/spec/fixtures/js_file_per_locale.yml new file mode 100755 index 00000000..315f98cc --- /dev/null +++ b/spec/fixtures/js_file_per_locale.yml @@ -0,0 +1,3 @@ +translations: + - file: "tmp/i18n-js/%{locale}.js" + only: '*' diff --git a/spec/resources/locales.yml b/spec/fixtures/locales.yml old mode 100644 new mode 100755 similarity index 98% rename from spec/resources/locales.yml rename to spec/fixtures/locales.yml index eac57647..71147c67 --- a/spec/resources/locales.yml +++ b/spec/fixtures/locales.yml @@ -73,4 +73,4 @@ fr: title: "Visualiser" note: "plus de détails" edit: - title: "Editer" \ No newline at end of file + title: "Editer" diff --git a/spec/fixtures/multiple_conditions.yml b/spec/fixtures/multiple_conditions.yml new file mode 100755 index 00000000..85c24d13 --- /dev/null +++ b/spec/fixtures/multiple_conditions.yml @@ -0,0 +1,5 @@ +translations: + - file: "tmp/i18n-js/bitsnpieces.js" + only: + - "*.date.formats" + - "*.number.currency" diff --git a/spec/resources/multiple_files.yml b/spec/fixtures/multiple_files.yml old mode 100644 new mode 100755 similarity index 55% rename from spec/resources/multiple_files.yml rename to spec/fixtures/multiple_files.yml index 121bcc0c..9a94fe02 --- a/spec/resources/multiple_files.yml +++ b/spec/fixtures/multiple_files.yml @@ -1,6 +1,6 @@ # Find more details about this configuration file at http://github.com/fnando/i18n-js translations: - - file: "public/javascripts/all.js" + - file: "tmp/i18n-js/all.js" + only: "*" + - file: "tmp/i18n-js/tudo.js" only: "*" - - file: "public/javascripts/tudo.js" - only: "*" \ No newline at end of file diff --git a/spec/resources/no_config.yml b/spec/fixtures/no_config.yml old mode 100644 new mode 100755 similarity index 100% rename from spec/resources/no_config.yml rename to spec/fixtures/no_config.yml diff --git a/spec/resources/no_scope.yml b/spec/fixtures/no_scope.yml old mode 100644 new mode 100755 similarity index 69% rename from spec/resources/no_scope.yml rename to spec/fixtures/no_scope.yml index c3a15225..911f960a --- a/spec/resources/no_scope.yml +++ b/spec/fixtures/no_scope.yml @@ -1,3 +1,3 @@ # Find more details about this configuration file at http://github.com/fnando/i18n-js translations: - - file: "public/javascripts/no_scope.js" + - file: "tmp/i18n-js/no_scope.js" diff --git a/spec/resources/simple_scope.yml b/spec/fixtures/simple_scope.yml old mode 100644 new mode 100755 similarity index 72% rename from spec/resources/simple_scope.yml rename to spec/fixtures/simple_scope.yml index 081a30c1..4169a2d9 --- a/spec/resources/simple_scope.yml +++ b/spec/fixtures/simple_scope.yml @@ -1,4 +1,4 @@ # Find more details about this configuration file at http://github.com/fnando/i18n-js translations: - - file: "public/javascripts/simple_scope.js" + - file: "tmp/i18n-js/simple_scope.js" only: "*.date.formats" diff --git a/spec/i18n_js_spec.rb b/spec/i18n_js_spec.rb new file mode 100644 index 00000000..661a894c --- /dev/null +++ b/spec/i18n_js_spec.rb @@ -0,0 +1,122 @@ +require "spec_helper" + +describe I18n::JS do + context "exporting" do + before do + I18n::JS.stub :export_dir => temp_path + end + + it "exports messages to default path when configuration file doesn't exist" do + I18n::JS.export + file_should_exist "translations.js" + end + + it "exports messages using custom output path" do + set_config "custom_path.yml" + I18n::JS.should_receive(:save).with(translations, "tmp/i18n-js/all.js") + I18n::JS.export + end + + it "sets default scope to * when not specified" do + set_config "no_scope.yml" + I18n::JS.should_receive(:save).with(translations, "tmp/i18n-js/no_scope.js") + I18n::JS.export + end + + it "exports to multiple files" do + set_config "multiple_files.yml" + I18n::JS.export + + file_should_exist "all.js" + file_should_exist "tudo.js" + end + + it "ignores an empty config file" do + set_config "no_config.yml" + I18n::JS.export + + file_should_exist "translations.js" + end + + it "exports to a JS file per available locale" do + set_config "js_file_per_locale.yml" + I18n::JS.export + + file_should_exist "en.js" + end + + it "exports with multiple conditions" do + set_config "multiple_conditions.yml" + I18n::JS.export + + file_should_exist "bitsnpieces.js" + end + end + + context "filters" do + it "filters translations using scope *.date.formats" do + result = I18n::JS.filter(translations, "*.date.formats") + result[:en][:date].keys.should eql([:formats]) + result[:fr][:date].keys.should eql([:formats]) + end + + it "filters translations using scope [*.date.formats, *.number.currency.format]" do + result = I18n::JS.scoped_translations(["*.date.formats", "*.number.currency.format"]) + result[:en].keys.collect(&:to_s).sort.should eql(%w[ date number ]) + result[:fr].keys.collect(&:to_s).sort.should eql(%w[ date number ]) + end + + it "filters translations using multi-star scope" do + result = I18n::JS.scoped_translations("*.*.formats") + + result[:en].keys.collect(&:to_s).sort.should eql(%w[ date time ]) + result[:fr].keys.collect(&:to_s).sort.should eql(%w[ date time ]) + + result[:en][:date].keys.should eql([:formats]) + result[:en][:time].keys.should eql([:formats]) + + result[:fr][:date].keys.should eql([:formats]) + result[:fr][:time].keys.should eql([:formats]) + end + + it "filters translations using alternated stars" do + result = I18n::JS.scoped_translations("*.admin.*.title") + + result[:en][:admin].keys.collect(&:to_s).sort.should eql(%w[ edit show ]) + result[:fr][:admin].keys.collect(&:to_s).sort.should eql(%w[ edit show ]) + + result[:en][:admin][:show][:title].should eql("Show") + result[:fr][:admin][:show][:title].should eql("Visualiser") + + result[:en][:admin][:edit][:title].should eql("Edit") + result[:fr][:admin][:edit][:title].should eql("Editer") + end + end + + context "general" do + it "sets export directory" do + I18n::JS.export_dir.should eql("public/javascripts") + end + + it "sets empty hash as configuration when no file is found" do + I18n::JS.config?.should be_false + I18n::JS.config.should eql({}) + end + end + + context "hash merging" do + it "performs a deep merge" do + target = {:a => {:b => 1}} + result = I18n::JS.deep_merge(target, {:a => {:c => 2}}) + + result[:a].should eql({:b => 1, :c => 2}) + end + + it "performs a banged deep merge" do + target = {:a => {:b => 1}} + I18n::JS.deep_merge!(target, {:a => {:c => 2}}) + + target[:a].should eql({:b => 1, :c => 2}) + end + end +end diff --git a/spec/i18n_spec.js b/spec/i18n_spec.js deleted file mode 100644 index d9a65bb8..00000000 --- a/spec/i18n_spec.js +++ /dev/null @@ -1,820 +0,0 @@ -load("vendor/assets/javascripts/i18n.js"); - -describe("I18n.js", function(){ - before(function() { - I18n.defaultLocale = "en"; - I18n.defaultSeparator = "."; - I18n.locale = null; - - I18n.translations = { - en: { - hello: "Hello World!", - greetings: { - stranger: "Hello stranger!", - name: "Hello {{name}}!" - }, - profile: { - details: "{{name}} is {{age}}-years old" - }, - inbox: { - one: "You have {{count}} message", - other: "You have {{count}} messages", - zero: "You have no messages" - }, - unread: { - one: "You have 1 new message ({{unread}} unread)", - other: "You have {{count}} new messages ({{unread}} unread)", - zero: "You have no new messages ({{unread}} unread)" - }, - number: { - human: { - storage_units: { - units: { - "byte": { - one: "Byte", - other: "Bytes" - }, - - "kb": "KB", - "mb": "MB", - "gb": "GB", - "tb": "TB" - } - } - } - } - }, - - "pt-BR": { - hello: "Olá Mundo!", - date: { - formats: { - "default": "%d/%m/%Y", - "short": "%d de %B", - "long": "%d de %B de %Y" - }, - day_names: ["Domingo", "Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sábado"], - abbr_day_names: ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"], - month_names: [null, "Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"], - abbr_month_names: [null, "Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"] - }, - number: { - percentage: { - format: { - delimiter: "", - separator: ",", - precision: 2 - } - } - }, - time: { - formats: { - "default": "%A, %d de %B de %Y, %H:%M h", - "short": "%d/%m, %H:%M h", - "long": "%A, %d de %B de %Y, %H:%M h" - }, - am: "AM", - pm: "PM" - } - }, - - "en-US": { - date: { - formats: { - "default": "%d/%m/%Y", - "short": "%d de %B", - "long": "%d de %B de %Y" - }, - day_names: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], - abbr_day_names: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], - month_names: [null, "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], - abbr_month_names: [null, "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"], - meridian: ["am", "pm"] - } - }, - - "de": { - hello: "Hallo Welt!" - }, - - "nb": { - hello: "Hei Verden!" - } - }; - }); - - specify("with default options", function(){ - expect(I18n.defaultLocale).toBeEqualTo("en"); - expect(I18n.locale).toBeEqualTo(null); - expect(I18n.currentLocale()).toBeEqualTo("en"); - }); - - specify("with custom locale", function(){ - I18n.locale = "pt-BR"; - expect(I18n.currentLocale()).toBeEqualTo("pt-BR"); - }); - - specify("aliases", function(){ - expect(I18n.t).toBe(I18n.translate); - expect(I18n.l).toBe(I18n.localize); - expect(I18n.p).toBe(I18n.pluralize); - }); - - specify("translation with single scope", function(){ - expect(I18n.t("hello")).toBeEqualTo("Hello World!"); - }); - - specify("translation as object", function(){ - expect(I18n.t("greetings")).toBeInstanceOf(Object); - }); - - specify("translation with invalid scope shall not block", function(){ - actual = I18n.t("invalid.scope.shall.not.block"); - expected = '[missing "en.invalid.scope.shall.not.block" translation]'; - expect(actual).toBeEqualTo(expected); - }); - - specify("translation for single scope on a custom locale", function(){ - I18n.locale = "pt-BR"; - expect(I18n.t("hello")).toBeEqualTo("Olá Mundo!"); - }); - - specify("translation for multiple scopes", function(){ - expect(I18n.t("greetings.stranger")).toBeEqualTo("Hello stranger!"); - }); - - specify("translation with default locale option", function(){ - expect(I18n.t("hello", {locale: "en"})).toBeEqualTo("Hello World!"); - expect(I18n.t("hello", {locale: "pt-BR"})).toBeEqualTo("Olá Mundo!"); - }); - - specify("translation should fall if locale is missing", function(){ - I18n.locale = "pt-BR"; - expect(I18n.t("greetings.stranger")).toBeEqualTo("[missing \"pt-BR.greetings.stranger\" translation]"); - }); - - specify("translation should handle fallback if I18n.fallbacks == true", function(){ - I18n.locale = "pt-BR"; - I18n.fallbacks = true; - expect(I18n.t("greetings.stranger")).toBeEqualTo("Hello stranger!"); - }); - - specify("translation should handle fallback from unknown locale", function(){ - I18n.locale = "fr"; - I18n.fallbacks = true; - expect(I18n.t("greetings.stranger")).toBeEqualTo("Hello stranger!"); - }); - - specify("translation should handle fallback to less specific locale", function(){ - I18n.locale = "de-DE"; - I18n.fallbacks = true; - expect(I18n.t("hello")).toBeEqualTo("Hallo Welt!"); - }); - - specify("translation should handle fallback via custom rules", function(){ - I18n.locale = "no"; - I18n.fallbacks = true; - I18n.fallbackRules.no = [ "nb" ]; - expect(I18n.t("hello")).toBeEqualTo("Hei Verden!"); - }); - - specify("single interpolation", function(){ - actual = I18n.t("greetings.name", {name: "John Doe"}); - expect(actual).toBeEqualTo("Hello John Doe!"); - }); - - specify("multiple interpolation", function(){ - actual = I18n.t("profile.details", {name: "John Doe", age: 27}); - expect(actual).toBeEqualTo("John Doe is 27-years old"); - }); - - specify("translation with count option", function(){ - expect(I18n.t("inbox", {count: 0})).toBeEqualTo("You have no messages"); - expect(I18n.t("inbox", {count: 1})).toBeEqualTo("You have 1 message"); - expect(I18n.t("inbox", {count: 5})).toBeEqualTo("You have 5 messages"); - }); - - specify("translation with count option and multiple placeholders", function(){ - actual = I18n.t("unread", {unread: 5, count: 1}); - expect(actual).toBeEqualTo("You have 1 new message (5 unread)"); - - actual = I18n.t("unread", {unread: 2, count: 10}); - expect(actual).toBeEqualTo("You have 10 new messages (2 unread)"); - - actual = I18n.t("unread", {unread: 5, count: 0}); - expect(actual).toBeEqualTo("You have no new messages (5 unread)"); - }); - - specify("missing translation with count option", function(){ - actual = I18n.t("invalid", {count: 1}); - expect(actual).toBeEqualTo('[missing "en.invalid" translation]'); - - I18n.translations.en.inbox.one = null; - actual = I18n.t("inbox", {count: 1}); - expect(actual).toBeEqualTo('[missing "en.inbox.one" translation]'); - }); - - specify("pluralization", function(){ - expect(I18n.p(0, "inbox")).toBeEqualTo("You have no messages"); - expect(I18n.p(1, "inbox")).toBeEqualTo("You have 1 message"); - expect(I18n.p(5, "inbox")).toBeEqualTo("You have 5 messages"); - }); - - specify("pluralize should return 'other' scope", function(){ - I18n.translations["en"]["inbox"]["zero"] = null; - expect(I18n.p(0, "inbox")).toBeEqualTo("You have 0 messages"); - }); - - specify("pluralize should return 'zero' scope", function(){ - I18n.translations["en"]["inbox"]["zero"] = "No messages (zero)"; - I18n.translations["en"]["inbox"]["none"] = "No messages (none)"; - - expect(I18n.p(0, "inbox")).toBeEqualTo("No messages (zero)"); - }); - - specify("pluralize should return 'none' scope", function(){ - I18n.translations["en"]["inbox"]["zero"] = null; - I18n.translations["en"]["inbox"]["none"] = "No messages (none)"; - - expect(I18n.p(0, "inbox")).toBeEqualTo("No messages (none)"); - }); - - specify("pluralize with negative values", function(){ - expect(I18n.p(-1, "inbox")).toBeEqualTo("You have -1 message"); - expect(I18n.p(-5, "inbox")).toBeEqualTo("You have -5 messages"); - }); - - specify("pluralize with missing scope", function(){ - expect(I18n.p(-1, "missing")).toBeEqualTo('[missing "en.missing" translation]'); - }); - - specify("pluralize with multiple placeholders", function(){ - actual = I18n.p(1, "unread", {unread: 5}); - expect(actual).toBeEqualTo("You have 1 new message (5 unread)"); - - actual = I18n.p(10, "unread", {unread: 2}); - expect(actual).toBeEqualTo("You have 10 new messages (2 unread)"); - - actual = I18n.p(0, "unread", {unread: 5}); - expect(actual).toBeEqualTo("You have no new messages (5 unread)"); - }); - - specify("pluralize should allow empty strings", function(){ - I18n.translations["en"]["inbox"]["zero"] = ""; - - expect(I18n.p(0, "inbox")).toBeEqualTo(""); - }); - - specify("numbers with default settings", function(){ - expect(I18n.toNumber(1)).toBeEqualTo("1.000"); - expect(I18n.toNumber(12)).toBeEqualTo("12.000"); - expect(I18n.toNumber(123)).toBeEqualTo("123.000"); - expect(I18n.toNumber(1234)).toBeEqualTo("1,234.000"); - expect(I18n.toNumber(12345)).toBeEqualTo("12,345.000"); - expect(I18n.toNumber(123456)).toBeEqualTo("123,456.000"); - expect(I18n.toNumber(1234567)).toBeEqualTo("1,234,567.000"); - expect(I18n.toNumber(12345678)).toBeEqualTo("12,345,678.000"); - expect(I18n.toNumber(123456789)).toBeEqualTo("123,456,789.000"); - }); - - specify("negative numbers with default settings", function(){ - expect(I18n.toNumber(-1)).toBeEqualTo("-1.000"); - expect(I18n.toNumber(-12)).toBeEqualTo("-12.000"); - expect(I18n.toNumber(-123)).toBeEqualTo("-123.000"); - expect(I18n.toNumber(-1234)).toBeEqualTo("-1,234.000"); - expect(I18n.toNumber(-12345)).toBeEqualTo("-12,345.000"); - expect(I18n.toNumber(-123456)).toBeEqualTo("-123,456.000"); - expect(I18n.toNumber(-1234567)).toBeEqualTo("-1,234,567.000"); - expect(I18n.toNumber(-12345678)).toBeEqualTo("-12,345,678.000"); - expect(I18n.toNumber(-123456789)).toBeEqualTo("-123,456,789.000"); - }); - - specify("numbers with partial translation and default options", function(){ - I18n.translations.en.number = { - format: { - precision: 2 - } - }; - - expect(I18n.toNumber(1234)).toBeEqualTo("1,234.00"); - }); - - specify("numbers with full translation and default options", function(){ - I18n.translations.en.number = { - format: { - delimiter: ".", - separator: ",", - precision: 2 - } - }; - - expect(I18n.toNumber(1234)).toBeEqualTo("1.234,00"); - }); - - specify("numbers with some custom options that should be merged with default options", function(){ - expect(I18n.toNumber(1234, {precision: 0})).toBeEqualTo("1,234"); - expect(I18n.toNumber(1234, {separator: '-'})).toBeEqualTo("1,234-000"); - expect(I18n.toNumber(1234, {delimiter: '-'})).toBeEqualTo("1-234.000"); - }); - - specify("numbers considering options", function(){ - options = { - precision: 2, - separator: ",", - delimiter: "." - }; - - expect(I18n.toNumber(1, options)).toBeEqualTo("1,00"); - expect(I18n.toNumber(12, options)).toBeEqualTo("12,00"); - expect(I18n.toNumber(123, options)).toBeEqualTo("123,00"); - expect(I18n.toNumber(1234, options)).toBeEqualTo("1.234,00"); - expect(I18n.toNumber(123456, options)).toBeEqualTo("123.456,00"); - expect(I18n.toNumber(1234567, options)).toBeEqualTo("1.234.567,00"); - expect(I18n.toNumber(12345678, options)).toBeEqualTo("12.345.678,00"); - }); - - specify("numbers with different precisions", function(){ - options = {separator: ".", delimiter: ","}; - - options["precision"] = 2; - expect(I18n.toNumber(1.98, options)).toBeEqualTo("1.98"); - - options["precision"] = 3; - expect(I18n.toNumber(1.98, options)).toBeEqualTo("1.980"); - - options["precision"] = 2; - expect(I18n.toNumber(1.987, options)).toBeEqualTo("1.99"); - - options["precision"] = 1; - expect(I18n.toNumber(1.98, options)).toBeEqualTo("2.0"); - - options["precision"] = 0; - expect(I18n.toNumber(1.98, options)).toBeEqualTo("2"); - }); - - specify("currency with default settings", function(){ - expect(I18n.toCurrency(100.99)).toBeEqualTo("$100.99"); - expect(I18n.toCurrency(1000.99)).toBeEqualTo("$1,000.99"); - }); - - specify("currency with custom settings", function(){ - I18n.translations.en.number = { - currency: { - format: { - format: "%n %u", - unit: "USD", - delimiter: ".", - separator: ",", - precision: 2 - } - } - }; - - expect(I18n.toCurrency(12)).toBeEqualTo("12,00 USD"); - expect(I18n.toCurrency(123)).toBeEqualTo("123,00 USD"); - expect(I18n.toCurrency(1234.56)).toBeEqualTo("1.234,56 USD"); - }); - - specify("currency with custom settings and partial overriding", function(){ - I18n.translations.en.number = { - currency: { - format: { - format: "%n %u", - unit: "USD", - delimiter: ".", - separator: ",", - precision: 2 - } - } - }; - - expect(I18n.toCurrency(12, {precision: 0})).toBeEqualTo("12 USD"); - expect(I18n.toCurrency(123, {unit: "bucks"})).toBeEqualTo("123,00 bucks"); - }); - - specify("currency with some custom options that should be merged with default options", function(){ - expect(I18n.toCurrency(1234, {precision: 0})).toBeEqualTo("$1,234"); - expect(I18n.toCurrency(1234, {unit: "º"})).toBeEqualTo("º1,234.00"); - expect(I18n.toCurrency(1234, {separator: "-"})).toBeEqualTo("$1,234-00"); - expect(I18n.toCurrency(1234, {delimiter: "-"})).toBeEqualTo("$1-234.00"); - expect(I18n.toCurrency(1234, {format: "%u %n"})).toBeEqualTo("$ 1,234.00"); - }); - - specify("localize numbers", function(){ - expect(I18n.l("number", 1234567)).toBeEqualTo("1,234,567.000"); - }); - - specify("localize currency", function(){ - expect(I18n.l("currency", 1234567)).toBeEqualTo("$1,234,567.00"); - }); - - specify("parse date", function(){ - expected = new Date(2009, 0, 24, 0, 0, 0); - actual = I18n.parseDate("2009-01-24"); - expect(actual.toString()).toBeEqualTo(expected.toString()); - - expected = new Date(2009, 0, 24, 0, 15, 0); - actual = I18n.parseDate("2009-01-24 00:15:00"); - expect(actual.toString()).toBeEqualTo(expected.toString()); - - expected = new Date(2009, 0, 24, 0, 0, 15); - actual = I18n.parseDate("2009-01-24 00:00:15"); - expect(actual.toString()).toBeEqualTo(expected.toString()); - - expected = new Date(2009, 0, 24, 15, 33, 44); - actual = I18n.parseDate("2009-01-24 15:33:44"); - expect(actual.toString()).toBeEqualTo(expected.toString()); - - expected = new Date(2009, 0, 24, 0, 0, 0); - actual = I18n.parseDate(expected.getTime()); - expect(actual.toString()).toBeEqualTo(expected.toString()); - - expected = new Date(2009, 0, 24, 0, 0, 0); - actual = I18n.parseDate("01/24/2009"); - expect(actual.toString()).toBeEqualTo(expected.toString()); - - expected = new Date(2009, 0, 24, 14, 33, 55); - actual = I18n.parseDate(expected).toString(); - expect(actual).toBeEqualTo(expected.toString()); - - expected = new Date(2009, 0, 24, 15, 33, 44); - actual = I18n.parseDate("2009-01-24T15:33:44"); - expect(actual.toString()).toBeEqualTo(expected.toString()); - - expected = new Date(Date.UTC(2011, 6, 20, 12, 51, 55)); - actual = I18n.parseDate("2011-07-20T12:51:55+0000"); - expect(actual.toString()).toBeEqualTo(expected.toString()); - - expected = new Date(Date.UTC(2011, 6, 20, 13, 03, 39)); - actual = I18n.parseDate("Wed Jul 20 13:03:39 +0000 2011"); - expect(actual.toString()).toBeEqualTo(expected.toString()); - - expected = new Date(Date.UTC(2009, 0, 24, 15, 33, 44)); - actual = I18n.parseDate("2009-01-24T15:33:44Z"); - expect(actual.toString()).toBeEqualTo(expected.toString()); - }); - - specify("date formatting", function(){ - I18n.locale = "pt-BR"; - - // 2009-04-26 19:35:44 (Sunday) - var date = new Date(2009, 3, 26, 19, 35, 44); - - // short week day - expect(I18n.strftime(date, "%a")).toBeEqualTo("Dom"); - - // full week day - expect(I18n.strftime(date, "%A")).toBeEqualTo("Domingo"); - - // short month - expect(I18n.strftime(date, "%b")).toBeEqualTo("Abr"); - - // full month - expect(I18n.strftime(date, "%B")).toBeEqualTo("Abril"); - - // day - expect(I18n.strftime(date, "%d")).toBeEqualTo("26"); - - // 24-hour - expect(I18n.strftime(date, "%H")).toBeEqualTo("19"); - - // 12-hour - expect(I18n.strftime(date, "%I")).toBeEqualTo("07"); - - // month - expect(I18n.strftime(date, "%m")).toBeEqualTo("04"); - - // minutes - expect(I18n.strftime(date, "%M")).toBeEqualTo("35"); - - // meridian - expect(I18n.strftime(date, "%p")).toBeEqualTo("PM"); - - // seconds - expect(I18n.strftime(date, "%S")).toBeEqualTo("44"); - - // week day - expect(I18n.strftime(date, "%w")).toBeEqualTo("0"); - - // short year - expect(I18n.strftime(date, "%y")).toBeEqualTo("09"); - - // full year - expect(I18n.strftime(date, "%Y")).toBeEqualTo("2009"); - }); - - specify("date formatting without padding", function(){ - I18n.locale = "pt-BR"; - - // 2009-04-26 19:35:44 (Sunday) - var date = new Date(2009, 3, 9, 7, 8, 9); - - // 24-hour without padding - expect(I18n.strftime(date, "%-H")).toBeEqualTo("7"); - - // 12-hour without padding - expect(I18n.strftime(date, "%-I")).toBeEqualTo("7"); - - // minutes without padding - expect(I18n.strftime(date, "%-M")).toBeEqualTo("8"); - - // seconds without padding - expect(I18n.strftime(date, "%-S")).toBeEqualTo("9"); - - // short year without padding - expect(I18n.strftime(date, "%-y")).toBeEqualTo("9"); - - // month without padding - expect(I18n.strftime(date, "%-m")).toBeEqualTo("4"); - - // day without padding - expect(I18n.strftime(date, "%-d")).toBeEqualTo("9"); - expect(I18n.strftime(date, "%e")).toBeEqualTo("9"); - }); - - specify("date formatting with padding", function(){ - I18n.locale = "pt-BR"; - - // 2009-04-26 19:35:44 (Sunday) - var date = new Date(2009, 3, 9, 7, 8, 9); - - // 24-hour - expect(I18n.strftime(date, "%H")).toBeEqualTo("07"); - - // 12-hour - expect(I18n.strftime(date, "%I")).toBeEqualTo("07"); - - // minutes - expect(I18n.strftime(date, "%M")).toBeEqualTo("08"); - - // seconds - expect(I18n.strftime(date, "%S")).toBeEqualTo("09"); - - // short year - expect(I18n.strftime(date, "%y")).toBeEqualTo("09"); - - // month - expect(I18n.strftime(date, "%m")).toBeEqualTo("04"); - - // day - expect(I18n.strftime(date, "%d")).toBeEqualTo("09"); - }); - - specify("date formatting with negative time zone", function(){ - I18n.locale = "pt-BR"; - var date = new Date(2009, 3, 26, 19, 35, 44); - stub(date, "getTimezoneOffset()", 345); - - expect(I18n.strftime(date, "%z")).toMatch(/^(\+|-)[\d]{4}$/); - expect(I18n.strftime(date, "%z")).toBeEqualTo("-0545"); - }); - - specify("date formatting with positive time zone", function(){ - I18n.locale = "pt-BR"; - var date = new Date(2009, 3, 26, 19, 35, 44); - stub(date, "getTimezoneOffset()", -345); - - expect(I18n.strftime(date, "%z")).toMatch(/^(\+|-)[\d]{4}$/); - expect(I18n.strftime(date, "%z")).toBeEqualTo("+0545"); - }); - - specify("date formatting with custom meridian", function(){ - I18n.locale = "en-US"; - var date = new Date(2009, 3, 26, 19, 35, 44); - expect(I18n.strftime(date, "%p")).toBeEqualTo("pm"); - }); - - specify("date formatting meridian boundaries", function(){ - I18n.locale = "en-US"; - var date = new Date(2009, 3, 26, 0, 35, 44); - expect(I18n.strftime(date, "%p")).toBeEqualTo("am"); - - date = new Date(2009, 3, 26, 12, 35, 44); - expect(I18n.strftime(date, "%p")).toBeEqualTo("pm"); - }); - - specify("date formatting hour12 values", function(){ - I18n.locale = "pt-BR"; - var date = new Date(2009, 3, 26, 19, 35, 44); - expect(I18n.strftime(date, "%I")).toBeEqualTo("07"); - - date = new Date(2009, 3, 26, 12, 35, 44); - expect(I18n.strftime(date, "%I")).toBeEqualTo("12"); - - date = new Date(2009, 3, 26, 0, 35, 44); - expect(I18n.strftime(date, "%I")).toBeEqualTo("12"); - }); - - specify("localize date strings", function(){ - I18n.locale = "pt-BR"; - - expect(I18n.l("date.formats.default", "2009-11-29")).toBeEqualTo("29/11/2009"); - expect(I18n.l("date.formats.short", "2009-01-07")).toBeEqualTo("07 de Janeiro"); - expect(I18n.l("date.formats.long", "2009-01-07")).toBeEqualTo("07 de Janeiro de 2009"); - }); - - specify("localize time strings", function(){ - I18n.locale = "pt-BR"; - - expect(I18n.l("time.formats.default", "2009-11-29 15:07:59")).toBeEqualTo("Domingo, 29 de Novembro de 2009, 15:07 h"); - expect(I18n.l("time.formats.short", "2009-01-07 09:12:35")).toBeEqualTo("07/01, 09:12 h"); - expect(I18n.l("time.formats.long", "2009-11-29 15:07:59")).toBeEqualTo("Domingo, 29 de Novembro de 2009, 15:07 h"); - }); - - specify("localize percentage", function(){ - I18n.locale = "pt-BR"; - expect(I18n.l("percentage", 123.45)).toBeEqualTo("123,45%"); - }); - - specify("default value for simple translation", function(){ - actual = I18n.t("warning", {defaultValue: "Warning!"}); - expect(actual).toBeEqualTo("Warning!"); - }); - - specify("default value for unknown locale", function(){ - I18n.locale = "fr"; - actual = I18n.t("warning", {defaultValue: "Warning!"}); - expect(actual).toBeEqualTo("Warning!"); - }); - - specify("default value with interpolation", function(){ - actual = I18n.t( - "alert", - {defaultValue: "Attention! {{message}}", message: "You're out of quota!"} - ); - - expect(actual).toBeEqualTo("Attention! You're out of quota!"); - }); - - specify("default value should not be used when scope exist", function(){ - actual = I18n.t("hello", {defaultValue: "What's up?"}); - expect(actual).toBeEqualTo("Hello World!"); - }); - - specify("default value for pluralize", function(){ - options = {defaultValue: { - none: "No things here!", - one: "There is {{count}} thing here!", - other: "There are {{count}} things here!" - }}; - - expect(I18n.p(0, "things", options)).toBeEqualTo("No things here!"); - expect(I18n.p(1, "things", options)).toBeEqualTo("There is 1 thing here!"); - expect(I18n.p(5, "things", options)).toBeEqualTo("There are 5 things here!"); - }); - - specify("default value for pluralize should not be used when scope exist", function(){ - options = {defaultValue: { - none: "No things here!", - one: "There is {{count}} thing here!", - other: "There are {{count}} things here!" - }}; - - expect(I18n.pluralize(0, "inbox", options)).toBeEqualTo("You have no messages"); - expect(I18n.pluralize(1, "inbox", options)).toBeEqualTo("You have 1 message"); - expect(I18n.pluralize(5, "inbox", options)).toBeEqualTo("You have 5 messages"); - }); - - specify("prepare options", function(){ - options = I18n.prepareOptions( - {name: "Mary Doe"}, - {name: "John Doe", role: "user"} - ); - - expect(options["name"]).toBeEqualTo("Mary Doe"); - expect(options["role"]).toBeEqualTo("user"); - }); - - specify("prepare options with multiple options", function(){ - options = I18n.prepareOptions( - {name: "Mary Doe"}, - {name: "John Doe", role: "user"}, - {age: 33}, - {email: "mary@doe.com", url: "http://marydoe.com"}, - {role: "admin", email: "john@doe.com"} - ); - - expect(options["name"]).toBeEqualTo("Mary Doe"); - expect(options["role"]).toBeEqualTo("user"); - expect(options["age"]).toBeEqualTo(33); - expect(options["email"]).toBeEqualTo("mary@doe.com"); - expect(options["url"]).toBeEqualTo("http://marydoe.com"); - }); - - specify("prepare options should return an empty hash when values are null", function(){ - expect({}).toBeEqualTo(I18n.prepareOptions(null, null)); - }); - - specify("percentage with defaults", function(){ - expect(I18n.toPercentage(1234)).toBeEqualTo("1234.000%"); - }); - - specify("percentage with custom options", function(){ - actual = I18n.toPercentage(1234, {delimiter: "_", precision: 0}); - expect(actual).toBeEqualTo("1_234%"); - }); - - specify("percentage with translation", function(){ - I18n.translations.en.number = { - percentage: { - format: { - precision: 2, - delimiter: ".", - separator: "," - } - } - }; - - expect(I18n.toPercentage(1234)).toBeEqualTo("1.234,00%"); - }); - - specify("percentage with translation and custom options", function(){ - I18n.translations.en.number = { - percentage: { - format: { - precision: 2, - delimiter: ".", - separator: "," - } - } - }; - - actual = I18n.toPercentage(1234, {precision: 4, delimiter: "-", separator: "+"}); - expect(actual).toBeEqualTo("1-234+0000%"); - }); - - specify("scope option as string", function(){ - actual = I18n.t("stranger", {scope: "greetings"}); - expect(actual).toBeEqualTo("Hello stranger!"); - }); - - specify("scope as array", function(){ - actual = I18n.t(["greetings", "stranger"]); - expect(actual).toBeEqualTo("Hello stranger!"); - }); - - specify("new placeholder syntax", function(){ - I18n.translations["en"]["new_syntax"] = "Hi %{name}!"; - actual = I18n.t("new_syntax", {name: "John"}); - expect(actual).toBeEqualTo("Hi John!"); - }); - - specify("return translation for custom scope separator", function(){ - I18n.defaultSeparator = "•"; - actual = I18n.t("greetings•stranger"); - expect(actual).toBeEqualTo("Hello stranger!"); - }); - - specify("return number as human size", function(){ - kb = 1024; - - expect(I18n.toHumanSize(1)).toBeEqualTo("1Byte"); - expect(I18n.toHumanSize(100)).toBeEqualTo("100Bytes"); - - expect(I18n.toHumanSize(kb)).toBeEqualTo("1KB"); - expect(I18n.toHumanSize(kb * 1.5)).toBeEqualTo("1.5KB"); - - expect(I18n.toHumanSize(kb * kb)).toBeEqualTo("1MB"); - expect(I18n.toHumanSize(kb * kb * 1.5)).toBeEqualTo("1.5MB"); - - expect(I18n.toHumanSize(kb * kb * kb)).toBeEqualTo("1GB"); - expect(I18n.toHumanSize(kb * kb * kb * 1.5)).toBeEqualTo("1.5GB"); - - expect(I18n.toHumanSize(kb * kb * kb * kb)).toBeEqualTo("1TB"); - expect(I18n.toHumanSize(kb * kb * kb * kb * 1.5)).toBeEqualTo("1.5TB"); - - expect(I18n.toHumanSize(kb * kb * kb * kb * kb)).toBeEqualTo("1024TB"); - }); - - specify("return number as human size using custom options", function(){ - expect(I18n.toHumanSize(1024 * 1.6, {precision: 0})).toBeEqualTo("2KB"); - }); - - specify("return number without insignificant zeros", function(){ - options = {precision: 4, strip_insignificant_zeros: true}; - - expect(I18n.toNumber(65, options)).toBeEqualTo("65"); - expect(I18n.toNumber(1.2, options)).toBeEqualTo("1.2"); - expect(I18n.toCurrency(1.2, options)).toBeEqualTo("$1.2"); - expect(I18n.toHumanSize(1.2, options)).toBeEqualTo("1.2Bytes"); - }); - - specify("return plural key for given count for english locale", function(){ - expect(I18n.pluralizationRules.en(0)).toBeEqualTo(["zero", "none", "other"]); - expect(I18n.pluralizationRules.en(1)).toBeEqualTo("one"); - expect(I18n.pluralizationRules.en(2)).toBeEqualTo("other"); - }); - - specify("return given pluralizer", function(){ - I18n.pluralizationRules.en = "en"; - expect(I18n.pluralizer("ru")).toBeEqualTo("en"); - I18n.pluralizationRules.ru = "ru"; - expect(I18n.pluralizer("ru")).toBeEqualTo("ru"); - }); - - specify("find and translate valid node", function(){ - expect(I18n.findAndTranslateValidNode(["one", "other"], {one: "one", other: "other"})).toBeEqualTo("one"); - expect(I18n.findAndTranslateValidNode(["one", "other"], {other: "other"})).toBeEqualTo("other"); - expect(I18n.findAndTranslateValidNode(["one"], {})).toBeEqualTo(null); - }); -}); diff --git a/spec/i18n_spec.rb b/spec/i18n_spec.rb deleted file mode 100644 index d874bd7e..00000000 --- a/spec/i18n_spec.rb +++ /dev/null @@ -1,216 +0,0 @@ -require "spec_helper" - -if File.basename(Rails.root) != "tmp" - abort <<-TXT -\e[31;5m -WARNING: That will remove your project! -Please go to #{File.expand_path(File.dirname(__FILE__) + "/..")} and run `rake spec`\e[0m -TXT -end - -describe SimplesIdeias::I18n do - before do - # Remove temporary directory if already present - FileUtils.rm_r(Rails.root) if File.exist?(Rails.root) - - # Create temporary directory to test the files generation - %w( config public/javascripts ).each do |path| - FileUtils.mkdir_p Rails.root.join(path) - end - - # Overwrite defaut locales path to use fixtures - I18n.load_path = [File.dirname(__FILE__) + "/resources/locales.yml"] - end - - after do - # Remove temporary directory - FileUtils.rm_r(Rails.root) - end - - it "copies the configuration file" do - File.should_not be_file(SimplesIdeias::I18n.config_file) - SimplesIdeias::I18n.setup! - File.should be_file(SimplesIdeias::I18n.config_file) - end - - it "keeps existing configuration file" do - File.open(SimplesIdeias::I18n.config_file, "w+") {|f| f << "ORIGINAL"} - SimplesIdeias::I18n.setup! - - File.read(SimplesIdeias::I18n.config_file).should == "ORIGINAL" - end - - context "copying i18n.js" do - let(:path) { Rails.root.join("public/javascripts/i18n.js") } - - it "copies JavaScript library" do - File.should_not be_file(path) - SimplesIdeias::I18n.setup! - File.should be_file(path) - end - - it "should copy i18n.js when has_asset_pipeline? is false and Rails version >= 3.1" do - SimplesIdeias::I18n.stub(:has_asset_pipeline?).and_return(false) - Rails.stub(:version).and_return("3.1") - - File.should_not be_file(path) - SimplesIdeias::I18n.setup! - File.should be_file(path) - end - end - - it "loads configuration file" do - set_config "default.yml" - SimplesIdeias::I18n.setup! - - SimplesIdeias::I18n.config?.should be_true - SimplesIdeias::I18n.config.should be_kind_of(HashWithIndifferentAccess) - SimplesIdeias::I18n.config.should_not be_empty - end - - it "sets empty hash as configuration when no file is found" do - SimplesIdeias::I18n.config?.should be_false - SimplesIdeias::I18n.config.should == {} - end - - it "exports messages to default path when configuration file doesn't exist" do - SimplesIdeias::I18n.export! - Rails.root.join(SimplesIdeias::I18n.export_dir, "translations.js").should be_file - end - - it "exports messages using custom output path" do - set_config "custom_path.yml" - SimplesIdeias::I18n.should_receive(:save).with(translations, "public/javascripts/translations/all.js") - SimplesIdeias::I18n.export! - end - - it "sets default scope to * when not specified" do - set_config "no_scope.yml" - SimplesIdeias::I18n.should_receive(:save).with(translations, "public/javascripts/no_scope.js") - SimplesIdeias::I18n.export! - end - - it "exports to multiple files" do - set_config "multiple_files.yml" - SimplesIdeias::I18n.export! - - File.should be_file(Rails.root.join("public/javascripts/all.js")) - File.should be_file(Rails.root.join("public/javascripts/tudo.js")) - end - - it "ignores an empty config file" do - set_config "no_config.yml" - SimplesIdeias::I18n.export! - Rails.root.join(SimplesIdeias::I18n.export_dir, "translations.js").should be_file - end - - it "exports to a JS file per available locale" do - set_config "js_file_per_locale.yml" - SimplesIdeias::I18n.export! - - File.should be_file(Rails.root.join("public/javascripts/i18n/en.js")) - end - - it "exports with multiple conditions" do - set_config "multiple_conditions.yml" - SimplesIdeias::I18n.export! - File.should be_file(Rails.root.join("public/javascripts/bitsnpieces.js")) - end - - it "filters translations using scope *.date.formats" do - result = SimplesIdeias::I18n.filter(translations, "*.date.formats") - result[:en][:date].keys.should == [:formats] - result[:fr][:date].keys.should == [:formats] - end - - it "filters translations using scope [*.date.formats, *.number.currency.format]" do - result = SimplesIdeias::I18n.scoped_translations(["*.date.formats", "*.number.currency.format"]) - result[:en].keys.collect(&:to_s).sort.should == %w[ date number ] - result[:fr].keys.collect(&:to_s).sort.should == %w[ date number ] - end - - it "filters translations using multi-star scope" do - result = SimplesIdeias::I18n.scoped_translations("*.*.formats") - - result[:en].keys.collect(&:to_s).sort.should == %w[ date time ] - result[:fr].keys.collect(&:to_s).sort.should == %w[ date time ] - - result[:en][:date].keys.should == [:formats] - result[:en][:time].keys.should == [:formats] - - result[:fr][:date].keys.should == [:formats] - result[:fr][:time].keys.should == [:formats] - end - - it "filters translations using alternated stars" do - result = SimplesIdeias::I18n.scoped_translations("*.admin.*.title") - - result[:en][:admin].keys.collect(&:to_s).sort.should == %w[ edit show ] - result[:fr][:admin].keys.collect(&:to_s).sort.should == %w[ edit show ] - - result[:en][:admin][:show][:title].should == "Show" - result[:fr][:admin][:show][:title].should == "Visualiser" - - result[:en][:admin][:edit][:title].should == "Edit" - result[:fr][:admin][:edit][:title].should == "Editer" - end - - it "performs a deep merge" do - target = {:a => {:b => 1}} - result = SimplesIdeias::I18n.deep_merge(target, {:a => {:c => 2}}) - - result[:a].should == {:b => 1, :c => 2} - end - - it "performs a banged deep merge" do - target = {:a => {:b => 1}} - SimplesIdeias::I18n.deep_merge!(target, {:a => {:c => 2}}) - - target[:a].should == {:b => 1, :c => 2} - end - - it "updates the javascript library" do - FakeWeb.register_uri(:get, "https://raw.github.com/fnando/i18n-js/master/vendor/assets/javascripts/i18n.js", :body => "UPDATED") - - SimplesIdeias::I18n.setup! - SimplesIdeias::I18n.update! - File.read(SimplesIdeias::I18n.javascript_file).should == "UPDATED" - end - - describe "#export_dir" do - it "detects asset pipeline support" do - SimplesIdeias::I18n.stub :has_asset_pipeline? => true - SimplesIdeias::I18n.export_dir == "vendor/assets/javascripts" - end - - it "detects older Rails" do - SimplesIdeias::I18n.stub :has_asset_pipeline? => false - SimplesIdeias::I18n.export_dir.to_s.should == "public/javascripts" - end - end - - describe "#has_asset_pipeline?" do - it "detects support" do - Rails.stub_chain(:configuration, :assets, :enabled => true) - SimplesIdeias::I18n.should have_asset_pipeline - end - - it "skips support" do - SimplesIdeias::I18n.should_not have_asset_pipeline - end - end - - private - # Set the configuration as the current one - def set_config(path) - config = HashWithIndifferentAccess.new(YAML.load_file(File.dirname(__FILE__) + "/resources/#{path}")) - SimplesIdeias::I18n.stub(:config? => true) - SimplesIdeias::I18n.stub(:config => config) - end - - # Shortcut to SimplesIdeias::I18n.translations - def translations - SimplesIdeias::I18n.translations - end -end - diff --git a/spec/js/currency.spec.js b/spec/js/currency.spec.js new file mode 100644 index 00000000..6e363c2b --- /dev/null +++ b/spec/js/currency.spec.js @@ -0,0 +1,60 @@ +var I18n = require("../../app/assets/javascripts/i18n") + , Translations = require("./translations") +; + +describe("Currency", function(){ + var actual, expected; + + beforeEach(function() { + I18n.reset(); + I18n.translations = Translations(); + }); + + it("formats currency with default settings", function(){ + expect(I18n.toCurrency(100.99)).toEqual("$100.99"); + expect(I18n.toCurrency(1000.99)).toEqual("$1,000.99"); + }); + + it("formats currency with custom settings", function(){ + I18n.translations.en.number = { + currency: { + format: { + format: "%n %u", + unit: "USD", + delimiter: ".", + separator: ",", + precision: 2 + } + } + }; + + expect(I18n.toCurrency(12)).toEqual("12,00 USD"); + expect(I18n.toCurrency(123)).toEqual("123,00 USD"); + expect(I18n.toCurrency(1234.56)).toEqual("1.234,56 USD"); + }); + + it("formats currency with custom settings and partial overriding", function(){ + I18n.translations.en.number = { + currency: { + format: { + format: "%n %u", + unit: "USD", + delimiter: ".", + separator: ",", + precision: 2 + } + } + }; + + expect(I18n.toCurrency(12, {precision: 0})).toEqual("12 USD"); + expect(I18n.toCurrency(123, {unit: "bucks"})).toEqual("123,00 bucks"); + }); + + it("formats currency with some custom options that should be merged with default options", function(){ + expect(I18n.toCurrency(1234, {precision: 0})).toEqual("$1,234"); + expect(I18n.toCurrency(1234, {unit: "º"})).toEqual("º1,234.00"); + expect(I18n.toCurrency(1234, {separator: "-"})).toEqual("$1,234-00"); + expect(I18n.toCurrency(1234, {delimiter: "-"})).toEqual("$1-234.00"); + expect(I18n.toCurrency(1234, {format: "%u %n"})).toEqual("$ 1,234.00"); + }); +}); \ No newline at end of file diff --git a/spec/js/current_locale.spec.js b/spec/js/current_locale.spec.js new file mode 100644 index 00000000..0025e255 --- /dev/null +++ b/spec/js/current_locale.spec.js @@ -0,0 +1,19 @@ +var I18n = require("../../app/assets/javascripts/i18n"); + +describe("Current locale", function(){ + beforeEach(function(){ + I18n.reset(); + }); + + it("returns I18n.locale", function(){ + I18n.locale = "pt-BR"; + expect(I18n.currentLocale()).toEqual("pt-BR"); + }); + + it("returns I18n.defaultLocale", function(){ + I18n.locale = null; + I18n.defaultLocale = "pt-BR"; + + expect(I18n.currentLocale()).toEqual("pt-BR"); + }); +}); diff --git a/spec/js/dates.spec.js b/spec/js/dates.spec.js new file mode 100644 index 00000000..0539cb71 --- /dev/null +++ b/spec/js/dates.spec.js @@ -0,0 +1,218 @@ +var I18n = require("../../app/assets/javascripts/i18n") + , Translations = require("./translations") +; + +describe("Dates", function(){ + var actual, expected; + + beforeEach(function() { + I18n.reset(); + I18n.translations = Translations(); + }); + + it("parses date", function(){ + expected = new Date(2009, 0, 24, 0, 0, 0); + actual = I18n.parseDate("2009-01-24"); + expect(actual.toString()).toEqual(expected.toString()); + + expected = new Date(2009, 0, 24, 0, 15, 0); + actual = I18n.parseDate("2009-01-24 00:15:00"); + expect(actual.toString()).toEqual(expected.toString()); + + expected = new Date(2009, 0, 24, 0, 0, 15); + actual = I18n.parseDate("2009-01-24 00:00:15"); + expect(actual.toString()).toEqual(expected.toString()); + + expected = new Date(2009, 0, 24, 15, 33, 44); + actual = I18n.parseDate("2009-01-24 15:33:44"); + expect(actual.toString()).toEqual(expected.toString()); + + expected = new Date(2009, 0, 24, 0, 0, 0); + actual = I18n.parseDate(expected.getTime()); + expect(actual.toString()).toEqual(expected.toString()); + + expected = new Date(2009, 0, 24, 0, 0, 0); + actual = I18n.parseDate("01/24/2009"); + expect(actual.toString()).toEqual(expected.toString()); + + expected = new Date(2009, 0, 24, 14, 33, 55); + actual = I18n.parseDate(expected).toString(); + expect(actual).toEqual(expected.toString()); + + expected = new Date(2009, 0, 24, 15, 33, 44); + actual = I18n.parseDate("2009-01-24T15:33:44"); + expect(actual.toString()).toEqual(expected.toString()); + + expected = new Date(Date.UTC(2011, 6, 20, 12, 51, 55)); + actual = I18n.parseDate("2011-07-20T12:51:55+0000"); + expect(actual.toString()).toEqual(expected.toString()); + + expected = new Date(Date.UTC(2011, 6, 20, 13, 03, 39)); + actual = I18n.parseDate("Wed Jul 20 13:03:39 +0000 2011"); + expect(actual.toString()).toEqual(expected.toString()); + + expected = new Date(Date.UTC(2009, 0, 24, 15, 33, 44)); + actual = I18n.parseDate("2009-01-24T15:33:44Z"); + expect(actual.toString()).toEqual(expected.toString()); + }); + + it("formats date", function(){ + I18n.locale = "pt-BR"; + + // 2009-04-26 19:35:44 (Sunday) + var date = new Date(2009, 3, 26, 19, 35, 44); + + // short week day + expect(I18n.strftime(date, "%a")).toEqual("Dom"); + + // full week day + expect(I18n.strftime(date, "%A")).toEqual("Domingo"); + + // short month + expect(I18n.strftime(date, "%b")).toEqual("Abr"); + + // full month + expect(I18n.strftime(date, "%B")).toEqual("Abril"); + + // day + expect(I18n.strftime(date, "%d")).toEqual("26"); + + // 24-hour + expect(I18n.strftime(date, "%H")).toEqual("19"); + + // 12-hour + expect(I18n.strftime(date, "%I")).toEqual("07"); + + // month + expect(I18n.strftime(date, "%m")).toEqual("04"); + + // minutes + expect(I18n.strftime(date, "%M")).toEqual("35"); + + // meridian + expect(I18n.strftime(date, "%p")).toEqual("PM"); + + // seconds + expect(I18n.strftime(date, "%S")).toEqual("44"); + + // week day + expect(I18n.strftime(date, "%w")).toEqual("0"); + + // short year + expect(I18n.strftime(date, "%y")).toEqual("09"); + + // full year + expect(I18n.strftime(date, "%Y")).toEqual("2009"); + }); + + it("formats date without padding", function(){ + I18n.locale = "pt-BR"; + + // 2009-04-26 19:35:44 (Sunday) + var date = new Date(2009, 3, 9, 7, 8, 9); + + // 24-hour without padding + expect(I18n.strftime(date, "%-H")).toEqual("7"); + + // 12-hour without padding + expect(I18n.strftime(date, "%-I")).toEqual("7"); + + // minutes without padding + expect(I18n.strftime(date, "%-M")).toEqual("8"); + + // seconds without padding + expect(I18n.strftime(date, "%-S")).toEqual("9"); + + // short year without padding + expect(I18n.strftime(date, "%-y")).toEqual("9"); + + // month without padding + expect(I18n.strftime(date, "%-m")).toEqual("4"); + + // day without padding + expect(I18n.strftime(date, "%-d")).toEqual("9"); + expect(I18n.strftime(date, "%e")).toEqual("9"); + }); + + it("formats date with padding", function(){ + I18n.locale = "pt-BR"; + + // 2009-04-26 19:35:44 (Sunday) + var date = new Date(2009, 3, 9, 7, 8, 9); + + // 24-hour + expect(I18n.strftime(date, "%H")).toEqual("07"); + + // 12-hour + expect(I18n.strftime(date, "%I")).toEqual("07"); + + // minutes + expect(I18n.strftime(date, "%M")).toEqual("08"); + + // seconds + expect(I18n.strftime(date, "%S")).toEqual("09"); + + // short year + expect(I18n.strftime(date, "%y")).toEqual("09"); + + // month + expect(I18n.strftime(date, "%m")).toEqual("04"); + + // day + expect(I18n.strftime(date, "%d")).toEqual("09"); + }); + + it("formats date with negative time zone", function(){ + I18n.locale = "pt-BR"; + var date = new Date(2009, 3, 26, 19, 35, 44); + + spyOn(date, "getTimezoneOffset").andReturn(345); + + expect(I18n.strftime(date, "%z")).toMatch(/^(\+|-)[\d]{4}$/); + expect(I18n.strftime(date, "%z")).toEqual("-0545"); + }); + + it("formats date with positive time zone", function(){ + I18n.locale = "pt-BR"; + var date = new Date(2009, 3, 26, 19, 35, 44); + + spyOn(date, "getTimezoneOffset").andReturn(-345); + + expect(I18n.strftime(date, "%z")).toMatch(/^(\+|-)[\d]{4}$/); + expect(I18n.strftime(date, "%z")).toEqual("+0545"); + }); + + it("formats date with custom meridian", function(){ + I18n.locale = "en-US"; + var date = new Date(2009, 3, 26, 19, 35, 44); + expect(I18n.strftime(date, "%p")).toEqual("pm"); + }); + + it("formats date with meridian boundaries", function(){ + I18n.locale = "en-US"; + var date = new Date(2009, 3, 26, 0, 35, 44); + expect(I18n.strftime(date, "%p")).toEqual("am"); + + date = new Date(2009, 3, 26, 12, 35, 44); + expect(I18n.strftime(date, "%p")).toEqual("pm"); + }); + + it("formats date using 12-hours format", function(){ + I18n.locale = "pt-BR"; + var date = new Date(2009, 3, 26, 19, 35, 44); + expect(I18n.strftime(date, "%I")).toEqual("07"); + + date = new Date(2009, 3, 26, 12, 35, 44); + expect(I18n.strftime(date, "%I")).toEqual("12"); + + date = new Date(2009, 3, 26, 0, 35, 44); + expect(I18n.strftime(date, "%I")).toEqual("12"); + }); + + it("defaults to English", function() { + I18n.locale = "wk"; + + var date = new Date(2009, 3, 26, 19, 35, 44); + expect(I18n.strftime(date, "%a")).toEqual("Sun"); + }); +}); diff --git a/spec/js/defaults.spec.js b/spec/js/defaults.spec.js new file mode 100644 index 00000000..b6de0815 --- /dev/null +++ b/spec/js/defaults.spec.js @@ -0,0 +1,23 @@ +var I18n = require("../../app/assets/javascripts/i18n"); + +describe("Defaults", function(){ + beforeEach(function(){ + I18n.reset(); + }); + + it("sets the default locale", function(){ + expect(I18n.defaultLocale).toEqual("en"); + }); + + it("sets current locale", function(){ + expect(I18n.locale).toEqual("en"); + }); + + it("sets default separator", function(){ + expect(I18n.defaultSeparator).toEqual("."); + }); + + it("sets fallback", function(){ + expect(I18n.fallbacks).toEqual(false); + }); +}); diff --git a/spec/js/interpolation.spec.js b/spec/js/interpolation.spec.js new file mode 100644 index 00000000..dd2007b5 --- /dev/null +++ b/spec/js/interpolation.spec.js @@ -0,0 +1,28 @@ +var I18n = require("../../app/assets/javascripts/i18n") + , Translations = require("./translations") +; + +describe("Interpolation", function(){ + var actual, expected; + + beforeEach(function(){ + I18n.reset(); + I18n.translations = Translations(); + }); + + it("performs single interpolation", function(){ + actual = I18n.t("greetings.name", {name: "John Doe"}); + expect(actual).toEqual("Hello John Doe!"); + }); + + it("performs multiple interpolations", function(){ + actual = I18n.t("profile.details", {name: "John Doe", age: 27}); + expect(actual).toEqual("John Doe is 27-years old"); + }); + + it("performs interpolation with the count option", function(){ + expect(I18n.t("inbox", {count: 0})).toEqual("You have no messages"); + expect(I18n.t("inbox", {count: 1})).toEqual("You have 1 message"); + expect(I18n.t("inbox", {count: 5})).toEqual("You have 5 messages"); + }); +}); diff --git a/spec/js/jasmine/MIT.LICENSE b/spec/js/jasmine/MIT.LICENSE new file mode 100644 index 00000000..7c435baa --- /dev/null +++ b/spec/js/jasmine/MIT.LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2008-2011 Pivotal Labs + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/spec/js/jasmine/jasmine-html.js b/spec/js/jasmine/jasmine-html.js new file mode 100644 index 00000000..73834010 --- /dev/null +++ b/spec/js/jasmine/jasmine-html.js @@ -0,0 +1,190 @@ +jasmine.TrivialReporter = function(doc) { + this.document = doc || document; + this.suiteDivs = {}; + this.logRunningSpecs = false; +}; + +jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { + var el = document.createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(document.createTextNode(child)); + } else { + if (child) { el.appendChild(child); } + } + } + + for (var attr in attrs) { + if (attr == "className") { + el[attr] = attrs[attr]; + } else { + el.setAttribute(attr, attrs[attr]); + } + } + + return el; +}; + +jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { + var showPassed, showSkipped; + + this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' }, + this.createDom('div', { className: 'banner' }, + this.createDom('div', { className: 'logo' }, + this.createDom('span', { className: 'title' }, "Jasmine"), + this.createDom('span', { className: 'version' }, runner.env.versionString())), + this.createDom('div', { className: 'options' }, + "Show ", + showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), + this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), + showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), + this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") + ) + ), + + this.runnerDiv = this.createDom('div', { className: 'runner running' }, + this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), + this.runnerMessageSpan = this.createDom('span', {}, "Running..."), + this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) + ); + + this.document.body.appendChild(this.outerDiv); + + var suites = runner.suites(); + for (var i = 0; i < suites.length; i++) { + var suite = suites[i]; + var suiteDiv = this.createDom('div', { className: 'suite' }, + this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), + this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); + this.suiteDivs[suite.id] = suiteDiv; + var parentDiv = this.outerDiv; + if (suite.parentSuite) { + parentDiv = this.suiteDivs[suite.parentSuite.id]; + } + parentDiv.appendChild(suiteDiv); + } + + this.startedAt = new Date(); + + var self = this; + showPassed.onclick = function(evt) { + if (showPassed.checked) { + self.outerDiv.className += ' show-passed'; + } else { + self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); + } + }; + + showSkipped.onclick = function(evt) { + if (showSkipped.checked) { + self.outerDiv.className += ' show-skipped'; + } else { + self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); + } + }; +}; + +jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { + var results = runner.results(); + var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; + this.runnerDiv.setAttribute("class", className); + //do it twice for IE + this.runnerDiv.setAttribute("className", className); + var specs = runner.specs(); + var specCount = 0; + for (var i = 0; i < specs.length; i++) { + if (this.specFilter(specs[i])) { + specCount++; + } + } + var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); + message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; + this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); + + this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); +}; + +jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { + var results = suite.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.totalCount === 0) { // todo: change this to check results.skipped + status = 'skipped'; + } + this.suiteDivs[suite.id].className += " " + status; +}; + +jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { + if (this.logRunningSpecs) { + this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); + } +}; + +jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { + var results = spec.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.skipped) { + status = 'skipped'; + } + var specDiv = this.createDom('div', { className: 'spec ' + status }, + this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), + this.createDom('a', { + className: 'description', + href: '?spec=' + encodeURIComponent(spec.getFullName()), + title: spec.getFullName() + }, spec.description)); + + + var resultItems = results.getItems(); + var messagesDiv = this.createDom('div', { className: 'messages' }); + for (var i = 0; i < resultItems.length; i++) { + var result = resultItems[i]; + + if (result.type == 'log') { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); + } else if (result.type == 'expect' && result.passed && !result.passed()) { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); + + if (result.trace.stack) { + messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); + } + } + } + + if (messagesDiv.childNodes.length > 0) { + specDiv.appendChild(messagesDiv); + } + + this.suiteDivs[spec.suite.id].appendChild(specDiv); +}; + +jasmine.TrivialReporter.prototype.log = function() { + var console = jasmine.getGlobal().console; + if (console && console.log) { + if (console.log.apply) { + console.log.apply(console, arguments); + } else { + console.log(arguments); // ie fix: console.log.apply doesn't exist on ie + } + } +}; + +jasmine.TrivialReporter.prototype.getLocation = function() { + return this.document.location; +}; + +jasmine.TrivialReporter.prototype.specFilter = function(spec) { + var paramMap = {}; + var params = this.getLocation().search.substring(1).split('&'); + for (var i = 0; i < params.length; i++) { + var p = params[i].split('='); + paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); + } + + if (!paramMap.spec) { + return true; + } + return spec.getFullName().indexOf(paramMap.spec) === 0; +}; diff --git a/spec/js/jasmine/jasmine.css b/spec/js/jasmine/jasmine.css new file mode 100644 index 00000000..6583fe7c --- /dev/null +++ b/spec/js/jasmine/jasmine.css @@ -0,0 +1,166 @@ +body { + font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; +} + + +.jasmine_reporter a:visited, .jasmine_reporter a { + color: #303; +} + +.jasmine_reporter a:hover, .jasmine_reporter a:active { + color: blue; +} + +.run_spec { + float:right; + padding-right: 5px; + font-size: .8em; + text-decoration: none; +} + +.jasmine_reporter { + margin: 0 5px; +} + +.banner { + color: #303; + background-color: #fef; + padding: 5px; +} + +.logo { + float: left; + font-size: 1.1em; + padding-left: 5px; +} + +.logo .version { + font-size: .6em; + padding-left: 1em; +} + +.runner.running { + background-color: yellow; +} + + +.options { + text-align: right; + font-size: .8em; +} + + + + +.suite { + border: 1px outset gray; + margin: 5px 0; + padding-left: 1em; +} + +.suite .suite { + margin: 5px; +} + +.suite.passed { + background-color: #dfd; +} + +.suite.failed { + background-color: #fdd; +} + +.spec { + margin: 5px; + padding-left: 1em; + clear: both; +} + +.spec.failed, .spec.passed, .spec.skipped { + padding-bottom: 5px; + border: 1px solid gray; +} + +.spec.failed { + background-color: #fbb; + border-color: red; +} + +.spec.passed { + background-color: #bfb; + border-color: green; +} + +.spec.skipped { + background-color: #bbb; +} + +.messages { + border-left: 1px dashed gray; + padding-left: 1em; + padding-right: 1em; +} + +.passed { + background-color: #cfc; + display: none; +} + +.failed { + background-color: #fbb; +} + +.skipped { + color: #777; + background-color: #eee; + display: none; +} + + +/*.resultMessage {*/ + /*white-space: pre;*/ +/*}*/ + +.resultMessage span.result { + display: block; + line-height: 2em; + color: black; +} + +.resultMessage .mismatch { + color: black; +} + +.stackTrace { + white-space: pre; + font-size: .8em; + margin-left: 10px; + max-height: 5em; + overflow: auto; + border: 1px inset red; + padding: 1em; + background: #eef; +} + +.finished-at { + padding-left: 1em; + font-size: .6em; +} + +.show-passed .passed, +.show-skipped .skipped { + display: block; +} + + +#jasmine_content { + position:fixed; + right: 100%; +} + +.runner { + border: 1px solid gray; + display: block; + margin: 5px 0; + padding: 2px 0 2px 10px; +} diff --git a/spec/js/jasmine/jasmine.js b/spec/js/jasmine/jasmine.js new file mode 100644 index 00000000..c3d2dc7d --- /dev/null +++ b/spec/js/jasmine/jasmine.js @@ -0,0 +1,2476 @@ +var isCommonJS = typeof window == "undefined"; + +/** + * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. + * + * @namespace + */ +var jasmine = {}; +if (isCommonJS) exports.jasmine = jasmine; +/** + * @private + */ +jasmine.unimplementedMethod_ = function() { + throw new Error("unimplemented method"); +}; + +/** + * Use jasmine.undefined instead of undefined, since undefined is just + * a plain old variable and may be redefined by somebody else. + * + * @private + */ +jasmine.undefined = jasmine.___undefined___; + +/** + * Show diagnostic messages in the console if set to true + * + */ +jasmine.VERBOSE = false; + +/** + * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed. + * + */ +jasmine.DEFAULT_UPDATE_INTERVAL = 250; + +/** + * Default timeout interval in milliseconds for waitsFor() blocks. + */ +jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; + +jasmine.getGlobal = function() { + function getGlobal() { + return this; + } + + return getGlobal(); +}; + +/** + * Allows for bound functions to be compared. Internal use only. + * + * @ignore + * @private + * @param base {Object} bound 'this' for the function + * @param name {Function} function to find + */ +jasmine.bindOriginal_ = function(base, name) { + var original = base[name]; + if (original.apply) { + return function() { + return original.apply(base, arguments); + }; + } else { + // IE support + return jasmine.getGlobal()[name]; + } +}; + +jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); +jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); +jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); +jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); + +jasmine.MessageResult = function(values) { + this.type = 'log'; + this.values = values; + this.trace = new Error(); // todo: test better +}; + +jasmine.MessageResult.prototype.toString = function() { + var text = ""; + for (var i = 0; i < this.values.length; i++) { + if (i > 0) text += " "; + if (jasmine.isString_(this.values[i])) { + text += this.values[i]; + } else { + text += jasmine.pp(this.values[i]); + } + } + return text; +}; + +jasmine.ExpectationResult = function(params) { + this.type = 'expect'; + this.matcherName = params.matcherName; + this.passed_ = params.passed; + this.expected = params.expected; + this.actual = params.actual; + this.message = this.passed_ ? 'Passed.' : params.message; + + var trace = (params.trace || new Error(this.message)); + this.trace = this.passed_ ? '' : trace; +}; + +jasmine.ExpectationResult.prototype.toString = function () { + return this.message; +}; + +jasmine.ExpectationResult.prototype.passed = function () { + return this.passed_; +}; + +/** + * Getter for the Jasmine environment. Ensures one gets created + */ +jasmine.getEnv = function() { + var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); + return env; +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isArray_ = function(value) { + return jasmine.isA_("Array", value); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isString_ = function(value) { + return jasmine.isA_("String", value); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isNumber_ = function(value) { + return jasmine.isA_("Number", value); +}; + +/** + * @ignore + * @private + * @param {String} typeName + * @param value + * @returns {Boolean} + */ +jasmine.isA_ = function(typeName, value) { + return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; +}; + +/** + * Pretty printer for expecations. Takes any object and turns it into a human-readable string. + * + * @param value {Object} an object to be outputted + * @returns {String} + */ +jasmine.pp = function(value) { + var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); + stringPrettyPrinter.format(value); + return stringPrettyPrinter.string; +}; + +/** + * Returns true if the object is a DOM Node. + * + * @param {Object} obj object to check + * @returns {Boolean} + */ +jasmine.isDomNode = function(obj) { + return obj.nodeType > 0; +}; + +/** + * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. + * + * @example + * // don't care about which function is passed in, as long as it's a function + * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); + * + * @param {Class} clazz + * @returns matchable object of the type clazz + */ +jasmine.any = function(clazz) { + return new jasmine.Matchers.Any(clazz); +}; + +/** + * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. + * + * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine + * expectation syntax. Spies can be checked if they were called or not and what the calling params were. + * + * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). + * + * Spies are torn down at the end of every spec. + * + * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. + * + * @example + * // a stub + * var myStub = jasmine.createSpy('myStub'); // can be used anywhere + * + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // actual foo.not will not be called, execution stops + * spyOn(foo, 'not'); + + // foo.not spied upon, execution will continue to implementation + * spyOn(foo, 'not').andCallThrough(); + * + * // fake example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // foo.not(val) will return val + * spyOn(foo, 'not').andCallFake(function(value) {return value;}); + * + * // mock example + * foo.not(7 == 7); + * expect(foo.not).toHaveBeenCalled(); + * expect(foo.not).toHaveBeenCalledWith(true); + * + * @constructor + * @see spyOn, jasmine.createSpy, jasmine.createSpyObj + * @param {String} name + */ +jasmine.Spy = function(name) { + /** + * The name of the spy, if provided. + */ + this.identity = name || 'unknown'; + /** + * Is this Object a spy? + */ + this.isSpy = true; + /** + * The actual function this spy stubs. + */ + this.plan = function() { + }; + /** + * Tracking of the most recent call to the spy. + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy.mostRecentCall.args = [1, 2]; + */ + this.mostRecentCall = {}; + + /** + * Holds arguments for each call to the spy, indexed by call count + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy(7, 8); + * mySpy.mostRecentCall.args = [7, 8]; + * mySpy.argsForCall[0] = [1, 2]; + * mySpy.argsForCall[1] = [7, 8]; + */ + this.argsForCall = []; + this.calls = []; +}; + +/** + * Tells a spy to call through to the actual implemenatation. + * + * @example + * var foo = { + * bar: function() { // do some stuff } + * } + * + * // defining a spy on an existing property: foo.bar + * spyOn(foo, 'bar').andCallThrough(); + */ +jasmine.Spy.prototype.andCallThrough = function() { + this.plan = this.originalValue; + return this; +}; + +/** + * For setting the return value of a spy. + * + * @example + * // defining a spy from scratch: foo() returns 'baz' + * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); + * + * // defining a spy on an existing property: foo.bar() returns 'baz' + * spyOn(foo, 'bar').andReturn('baz'); + * + * @param {Object} value + */ +jasmine.Spy.prototype.andReturn = function(value) { + this.plan = function() { + return value; + }; + return this; +}; + +/** + * For throwing an exception when a spy is called. + * + * @example + * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' + * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); + * + * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' + * spyOn(foo, 'bar').andThrow('baz'); + * + * @param {String} exceptionMsg + */ +jasmine.Spy.prototype.andThrow = function(exceptionMsg) { + this.plan = function() { + throw exceptionMsg; + }; + return this; +}; + +/** + * Calls an alternate implementation when a spy is called. + * + * @example + * var baz = function() { + * // do some stuff, return something + * } + * // defining a spy from scratch: foo() calls the function baz + * var foo = jasmine.createSpy('spy on foo').andCall(baz); + * + * // defining a spy on an existing property: foo.bar() calls an anonymnous function + * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); + * + * @param {Function} fakeFunc + */ +jasmine.Spy.prototype.andCallFake = function(fakeFunc) { + this.plan = fakeFunc; + return this; +}; + +/** + * Resets all of a spy's the tracking variables so that it can be used again. + * + * @example + * spyOn(foo, 'bar'); + * + * foo.bar(); + * + * expect(foo.bar.callCount).toEqual(1); + * + * foo.bar.reset(); + * + * expect(foo.bar.callCount).toEqual(0); + */ +jasmine.Spy.prototype.reset = function() { + this.wasCalled = false; + this.callCount = 0; + this.argsForCall = []; + this.calls = []; + this.mostRecentCall = {}; +}; + +jasmine.createSpy = function(name) { + + var spyObj = function() { + spyObj.wasCalled = true; + spyObj.callCount++; + var args = jasmine.util.argsToArray(arguments); + spyObj.mostRecentCall.object = this; + spyObj.mostRecentCall.args = args; + spyObj.argsForCall.push(args); + spyObj.calls.push({object: this, args: args}); + return spyObj.plan.apply(this, arguments); + }; + + var spy = new jasmine.Spy(name); + + for (var prop in spy) { + spyObj[prop] = spy[prop]; + } + + spyObj.reset(); + + return spyObj; +}; + +/** + * Determines whether an object is a spy. + * + * @param {jasmine.Spy|Object} putativeSpy + * @returns {Boolean} + */ +jasmine.isSpy = function(putativeSpy) { + return putativeSpy && putativeSpy.isSpy; +}; + +/** + * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something + * large in one call. + * + * @param {String} baseName name of spy class + * @param {Array} methodNames array of names of methods to make spies + */ +jasmine.createSpyObj = function(baseName, methodNames) { + if (!jasmine.isArray_(methodNames) || methodNames.length === 0) { + throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); + } + var obj = {}; + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); + } + return obj; +}; + +/** + * All parameters are pretty-printed and concatenated together, then written to the current spec's output. + * + * Be careful not to leave calls to jasmine.log in production code. + */ +jasmine.log = function() { + var spec = jasmine.getEnv().currentSpec; + spec.log.apply(spec, arguments); +}; + +/** + * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. + * + * @example + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops + * + * @see jasmine.createSpy + * @param obj + * @param methodName + * @returns a Jasmine spy that can be chained with all spy methods + */ +var spyOn = function(obj, methodName) { + return jasmine.getEnv().currentSpec.spyOn(obj, methodName); +}; +if (isCommonJS) exports.spyOn = spyOn; + +/** + * Creates a Jasmine spec that will be added to the current suite. + * + * // TODO: pending tests + * + * @example + * it('should be true', function() { + * expect(true).toEqual(true); + * }); + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var it = function(desc, func) { + return jasmine.getEnv().it(desc, func); +}; +if (isCommonJS) exports.it = it; + +/** + * Creates a disabled Jasmine spec. + * + * A convenience method that allows existing specs to be disabled temporarily during development. + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var xit = function(desc, func) { + return jasmine.getEnv().xit(desc, func); +}; +if (isCommonJS) exports.xit = xit; + +/** + * Starts a chain for a Jasmine expectation. + * + * It is passed an Object that is the actual value and should chain to one of the many + * jasmine.Matchers functions. + * + * @param {Object} actual Actual value to test against and expected value + */ +var expect = function(actual) { + return jasmine.getEnv().currentSpec.expect(actual); +}; +if (isCommonJS) exports.expect = expect; + +/** + * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. + * + * @param {Function} func Function that defines part of a jasmine spec. + */ +var runs = function(func) { + jasmine.getEnv().currentSpec.runs(func); +}; +if (isCommonJS) exports.runs = runs; + +/** + * Waits a fixed time period before moving to the next block. + * + * @deprecated Use waitsFor() instead + * @param {Number} timeout milliseconds to wait + */ +var waits = function(timeout) { + jasmine.getEnv().currentSpec.waits(timeout); +}; +if (isCommonJS) exports.waits = waits; + +/** + * Waits for the latchFunction to return true before proceeding to the next block. + * + * @param {Function} latchFunction + * @param {String} optional_timeoutMessage + * @param {Number} optional_timeout + */ +var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { + jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); +}; +if (isCommonJS) exports.waitsFor = waitsFor; + +/** + * A function that is called before each spec in a suite. + * + * Used for spec setup, including validating assumptions. + * + * @param {Function} beforeEachFunction + */ +var beforeEach = function(beforeEachFunction) { + jasmine.getEnv().beforeEach(beforeEachFunction); +}; +if (isCommonJS) exports.beforeEach = beforeEach; + +/** + * A function that is called after each spec in a suite. + * + * Used for restoring any state that is hijacked during spec execution. + * + * @param {Function} afterEachFunction + */ +var afterEach = function(afterEachFunction) { + jasmine.getEnv().afterEach(afterEachFunction); +}; +if (isCommonJS) exports.afterEach = afterEach; + +/** + * Defines a suite of specifications. + * + * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared + * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization + * of setup in some tests. + * + * @example + * // TODO: a simple suite + * + * // TODO: a simple suite with a nested describe block + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var describe = function(description, specDefinitions) { + return jasmine.getEnv().describe(description, specDefinitions); +}; +if (isCommonJS) exports.describe = describe; + +/** + * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var xdescribe = function(description, specDefinitions) { + return jasmine.getEnv().xdescribe(description, specDefinitions); +}; +if (isCommonJS) exports.xdescribe = xdescribe; + + +// Provide the XMLHttpRequest class for IE 5.x-6.x: +jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { + function tryIt(f) { + try { + return f(); + } catch(e) { + } + return null; + } + + var xhr = tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP.6.0"); + }) || + tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP.3.0"); + }) || + tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP"); + }) || + tryIt(function() { + return new ActiveXObject("Microsoft.XMLHTTP"); + }); + + if (!xhr) throw new Error("This browser does not support XMLHttpRequest."); + + return xhr; +} : XMLHttpRequest; +/** + * @namespace + */ +jasmine.util = {}; + +/** + * Declare that a child class inherit it's prototype from the parent class. + * + * @private + * @param {Function} childClass + * @param {Function} parentClass + */ +jasmine.util.inherit = function(childClass, parentClass) { + /** + * @private + */ + var subclass = function() { + }; + subclass.prototype = parentClass.prototype; + childClass.prototype = new subclass(); +}; + +jasmine.util.formatException = function(e) { + var lineNumber; + if (e.line) { + lineNumber = e.line; + } + else if (e.lineNumber) { + lineNumber = e.lineNumber; + } + + var file; + + if (e.sourceURL) { + file = e.sourceURL; + } + else if (e.fileName) { + file = e.fileName; + } + + var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); + + if (file && lineNumber) { + message += ' in ' + file + ' (line ' + lineNumber + ')'; + } + + return message; +}; + +jasmine.util.htmlEscape = function(str) { + if (!str) return str; + return str.replace(/&/g, '&') + .replace(//g, '>'); +}; + +jasmine.util.argsToArray = function(args) { + var arrayOfArgs = []; + for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); + return arrayOfArgs; +}; + +jasmine.util.extend = function(destination, source) { + for (var property in source) destination[property] = source[property]; + return destination; +}; + +/** + * Environment for Jasmine + * + * @constructor + */ +jasmine.Env = function() { + this.currentSpec = null; + this.currentSuite = null; + this.currentRunner_ = new jasmine.Runner(this); + + this.reporter = new jasmine.MultiReporter(); + + this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; + this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; + this.lastUpdate = 0; + this.specFilter = function() { + return true; + }; + + this.nextSpecId_ = 0; + this.nextSuiteId_ = 0; + this.equalityTesters_ = []; + + // wrap matchers + this.matchersClass = function() { + jasmine.Matchers.apply(this, arguments); + }; + jasmine.util.inherit(this.matchersClass, jasmine.Matchers); + + jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); +}; + + +jasmine.Env.prototype.setTimeout = jasmine.setTimeout; +jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; +jasmine.Env.prototype.setInterval = jasmine.setInterval; +jasmine.Env.prototype.clearInterval = jasmine.clearInterval; + +/** + * @returns an object containing jasmine version build info, if set. + */ +jasmine.Env.prototype.version = function () { + if (jasmine.version_) { + return jasmine.version_; + } else { + throw new Error('Version not set'); + } +}; + +/** + * @returns string containing jasmine version build info, if set. + */ +jasmine.Env.prototype.versionString = function() { + if (!jasmine.version_) { + return "version unknown"; + } + + var version = this.version(); + var versionString = version.major + "." + version.minor + "." + version.build; + if (version.release_candidate) { + versionString += ".rc" + version.release_candidate; + } + versionString += " revision " + version.revision; + return versionString; +}; + +/** + * @returns a sequential integer starting at 0 + */ +jasmine.Env.prototype.nextSpecId = function () { + return this.nextSpecId_++; +}; + +/** + * @returns a sequential integer starting at 0 + */ +jasmine.Env.prototype.nextSuiteId = function () { + return this.nextSuiteId_++; +}; + +/** + * Register a reporter to receive status updates from Jasmine. + * @param {jasmine.Reporter} reporter An object which will receive status updates. + */ +jasmine.Env.prototype.addReporter = function(reporter) { + this.reporter.addReporter(reporter); +}; + +jasmine.Env.prototype.execute = function() { + this.currentRunner_.execute(); +}; + +jasmine.Env.prototype.describe = function(description, specDefinitions) { + var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); + + var parentSuite = this.currentSuite; + if (parentSuite) { + parentSuite.add(suite); + } else { + this.currentRunner_.add(suite); + } + + this.currentSuite = suite; + + var declarationError = null; + try { + specDefinitions.call(suite); + } catch(e) { + declarationError = e; + } + + if (declarationError) { + this.it("encountered a declaration exception", function() { + throw declarationError; + }); + } + + this.currentSuite = parentSuite; + + return suite; +}; + +jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { + if (this.currentSuite) { + this.currentSuite.beforeEach(beforeEachFunction); + } else { + this.currentRunner_.beforeEach(beforeEachFunction); + } +}; + +jasmine.Env.prototype.currentRunner = function () { + return this.currentRunner_; +}; + +jasmine.Env.prototype.afterEach = function(afterEachFunction) { + if (this.currentSuite) { + this.currentSuite.afterEach(afterEachFunction); + } else { + this.currentRunner_.afterEach(afterEachFunction); + } + +}; + +jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { + return { + execute: function() { + } + }; +}; + +jasmine.Env.prototype.it = function(description, func) { + var spec = new jasmine.Spec(this, this.currentSuite, description); + this.currentSuite.add(spec); + this.currentSpec = spec; + + if (func) { + spec.runs(func); + } + + return spec; +}; + +jasmine.Env.prototype.xit = function(desc, func) { + return { + id: this.nextSpecId(), + runs: function() { + } + }; +}; + +jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { + if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { + return true; + } + + a.__Jasmine_been_here_before__ = b; + b.__Jasmine_been_here_before__ = a; + + var hasKey = function(obj, keyName) { + return obj !== null && obj[keyName] !== jasmine.undefined; + }; + + for (var property in b) { + if (!hasKey(a, property) && hasKey(b, property)) { + mismatchKeys.push("expected has key '" + property + "', but missing from actual."); + } + } + for (property in a) { + if (!hasKey(b, property) && hasKey(a, property)) { + mismatchKeys.push("expected missing key '" + property + "', but present in actual."); + } + } + for (property in b) { + if (property == '__Jasmine_been_here_before__') continue; + if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { + mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual."); + } + } + + if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { + mismatchValues.push("arrays were not the same length"); + } + + delete a.__Jasmine_been_here_before__; + delete b.__Jasmine_been_here_before__; + return (mismatchKeys.length === 0 && mismatchValues.length === 0); +}; + +jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { + mismatchKeys = mismatchKeys || []; + mismatchValues = mismatchValues || []; + + for (var i = 0; i < this.equalityTesters_.length; i++) { + var equalityTester = this.equalityTesters_[i]; + var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); + if (result !== jasmine.undefined) return result; + } + + if (a === b) return true; + + if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { + return (a == jasmine.undefined && b == jasmine.undefined); + } + + if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { + return a === b; + } + + if (a instanceof Date && b instanceof Date) { + return a.getTime() == b.getTime(); + } + + if (a instanceof jasmine.Matchers.Any) { + return a.matches(b); + } + + if (b instanceof jasmine.Matchers.Any) { + return b.matches(a); + } + + if (jasmine.isString_(a) && jasmine.isString_(b)) { + return (a == b); + } + + if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { + return (a == b); + } + + if (typeof a === "object" && typeof b === "object") { + return this.compareObjects_(a, b, mismatchKeys, mismatchValues); + } + + //Straight check + return (a === b); +}; + +jasmine.Env.prototype.contains_ = function(haystack, needle) { + if (jasmine.isArray_(haystack)) { + for (var i = 0; i < haystack.length; i++) { + if (this.equals_(haystack[i], needle)) return true; + } + return false; + } + return haystack.indexOf(needle) >= 0; +}; + +jasmine.Env.prototype.addEqualityTester = function(equalityTester) { + this.equalityTesters_.push(equalityTester); +}; +/** No-op base class for Jasmine reporters. + * + * @constructor + */ +jasmine.Reporter = function() { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportRunnerResults = function(runner) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSuiteResults = function(suite) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSpecStarting = function(spec) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSpecResults = function(spec) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.log = function(str) { +}; + +/** + * Blocks are functions with executable code that make up a spec. + * + * @constructor + * @param {jasmine.Env} env + * @param {Function} func + * @param {jasmine.Spec} spec + */ +jasmine.Block = function(env, func, spec) { + this.env = env; + this.func = func; + this.spec = spec; +}; + +jasmine.Block.prototype.execute = function(onComplete) { + try { + this.func.apply(this.spec); + } catch (e) { + this.spec.fail(e); + } + onComplete(); +}; +/** JavaScript API reporter. + * + * @constructor + */ +jasmine.JsApiReporter = function() { + this.started = false; + this.finished = false; + this.suites_ = []; + this.results_ = {}; +}; + +jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { + this.started = true; + var suites = runner.topLevelSuites(); + for (var i = 0; i < suites.length; i++) { + var suite = suites[i]; + this.suites_.push(this.summarize_(suite)); + } +}; + +jasmine.JsApiReporter.prototype.suites = function() { + return this.suites_; +}; + +jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { + var isSuite = suiteOrSpec instanceof jasmine.Suite; + var summary = { + id: suiteOrSpec.id, + name: suiteOrSpec.description, + type: isSuite ? 'suite' : 'spec', + children: [] + }; + + if (isSuite) { + var children = suiteOrSpec.children(); + for (var i = 0; i < children.length; i++) { + summary.children.push(this.summarize_(children[i])); + } + } + return summary; +}; + +jasmine.JsApiReporter.prototype.results = function() { + return this.results_; +}; + +jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { + return this.results_[specId]; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { + this.finished = true; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { + this.results_[spec.id] = { + messages: spec.results().getItems(), + result: spec.results().failedCount > 0 ? "failed" : "passed" + }; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.log = function(str) { +}; + +jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ + var results = {}; + for (var i = 0; i < specIds.length; i++) { + var specId = specIds[i]; + results[specId] = this.summarizeResult_(this.results_[specId]); + } + return results; +}; + +jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ + var summaryMessages = []; + var messagesLength = result.messages.length; + for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { + var resultMessage = result.messages[messageIndex]; + summaryMessages.push({ + text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, + passed: resultMessage.passed ? resultMessage.passed() : true, + type: resultMessage.type, + message: resultMessage.message, + trace: { + stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined + } + }); + } + + return { + result : result.result, + messages : summaryMessages + }; +}; + +/** + * @constructor + * @param {jasmine.Env} env + * @param actual + * @param {jasmine.Spec} spec + */ +jasmine.Matchers = function(env, actual, spec, opt_isNot) { + this.env = env; + this.actual = actual; + this.spec = spec; + this.isNot = opt_isNot || false; + this.reportWasCalled_ = false; +}; + +// todo: @deprecated as of Jasmine 0.11, remove soon [xw] +jasmine.Matchers.pp = function(str) { + throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); +}; + +// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] +jasmine.Matchers.prototype.report = function(result, failing_message, details) { + throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); +}; + +jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) { + for (var methodName in prototype) { + if (methodName == 'report') continue; + var orig = prototype[methodName]; + matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); + } +}; + +jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { + return function() { + var matcherArgs = jasmine.util.argsToArray(arguments); + var result = matcherFunction.apply(this, arguments); + + if (this.isNot) { + result = !result; + } + + if (this.reportWasCalled_) return result; + + var message; + if (!result) { + if (this.message) { + message = this.message.apply(this, arguments); + if (jasmine.isArray_(message)) { + message = message[this.isNot ? 1 : 0]; + } + } else { + var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); + message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; + if (matcherArgs.length > 0) { + for (var i = 0; i < matcherArgs.length; i++) { + if (i > 0) message += ","; + message += " " + jasmine.pp(matcherArgs[i]); + } + } + message += "."; + } + } + var expectationResult = new jasmine.ExpectationResult({ + matcherName: matcherName, + passed: result, + expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], + actual: this.actual, + message: message + }); + this.spec.addMatcherResult(expectationResult); + return jasmine.undefined; + }; +}; + + + + +/** + * toBe: compares the actual to the expected using === + * @param expected + */ +jasmine.Matchers.prototype.toBe = function(expected) { + return this.actual === expected; +}; + +/** + * toNotBe: compares the actual to the expected using !== + * @param expected + * @deprecated as of 1.0. Use not.toBe() instead. + */ +jasmine.Matchers.prototype.toNotBe = function(expected) { + return this.actual !== expected; +}; + +/** + * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. + * + * @param expected + */ +jasmine.Matchers.prototype.toEqual = function(expected) { + return this.env.equals_(this.actual, expected); +}; + +/** + * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual + * @param expected + * @deprecated as of 1.0. Use not.toNotEqual() instead. + */ +jasmine.Matchers.prototype.toNotEqual = function(expected) { + return !this.env.equals_(this.actual, expected); +}; + +/** + * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes + * a pattern or a String. + * + * @param expected + */ +jasmine.Matchers.prototype.toMatch = function(expected) { + return new RegExp(expected).test(this.actual); +}; + +/** + * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch + * @param expected + * @deprecated as of 1.0. Use not.toMatch() instead. + */ +jasmine.Matchers.prototype.toNotMatch = function(expected) { + return !(new RegExp(expected).test(this.actual)); +}; + +/** + * Matcher that compares the actual to jasmine.undefined. + */ +jasmine.Matchers.prototype.toBeDefined = function() { + return (this.actual !== jasmine.undefined); +}; + +/** + * Matcher that compares the actual to jasmine.undefined. + */ +jasmine.Matchers.prototype.toBeUndefined = function() { + return (this.actual === jasmine.undefined); +}; + +/** + * Matcher that compares the actual to null. + */ +jasmine.Matchers.prototype.toBeNull = function() { + return (this.actual === null); +}; + +/** + * Matcher that boolean not-nots the actual. + */ +jasmine.Matchers.prototype.toBeTruthy = function() { + return !!this.actual; +}; + + +/** + * Matcher that boolean nots the actual. + */ +jasmine.Matchers.prototype.toBeFalsy = function() { + return !this.actual; +}; + + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was called. + */ +jasmine.Matchers.prototype.toHaveBeenCalled = function() { + if (arguments.length > 0) { + throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); + } + + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy " + this.actual.identity + " to have been called.", + "Expected spy " + this.actual.identity + " not to have been called." + ]; + }; + + return this.actual.wasCalled; +}; + +/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ +jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was not called. + * + * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead + */ +jasmine.Matchers.prototype.wasNotCalled = function() { + if (arguments.length > 0) { + throw new Error('wasNotCalled does not take arguments'); + } + + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy " + this.actual.identity + " to not have been called.", + "Expected spy " + this.actual.identity + " to have been called." + ]; + }; + + return !this.actual.wasCalled; +}; + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. + * + * @example + * + */ +jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { + var expectedArgs = jasmine.util.argsToArray(arguments); + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + this.message = function() { + if (this.actual.callCount === 0) { + // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw] + return [ + "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.", + "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was." + ]; + } else { + return [ + "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall), + "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall) + ]; + } + }; + + return this.env.contains_(this.actual.argsForCall, expectedArgs); +}; + +/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ +jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; + +/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ +jasmine.Matchers.prototype.wasNotCalledWith = function() { + var expectedArgs = jasmine.util.argsToArray(arguments); + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was", + "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" + ]; + }; + + return !this.env.contains_(this.actual.argsForCall, expectedArgs); +}; + +/** + * Matcher that checks that the expected item is an element in the actual Array. + * + * @param {Object} expected + */ +jasmine.Matchers.prototype.toContain = function(expected) { + return this.env.contains_(this.actual, expected); +}; + +/** + * Matcher that checks that the expected item is NOT an element in the actual Array. + * + * @param {Object} expected + * @deprecated as of 1.0. Use not.toNotContain() instead. + */ +jasmine.Matchers.prototype.toNotContain = function(expected) { + return !this.env.contains_(this.actual, expected); +}; + +jasmine.Matchers.prototype.toBeLessThan = function(expected) { + return this.actual < expected; +}; + +jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { + return this.actual > expected; +}; + +/** + * Matcher that checks that the expected item is equal to the actual item + * up to a given level of decimal precision (default 2). + * + * @param {Number} expected + * @param {Number} precision + */ +jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) { + if (!(precision === 0)) { + precision = precision || 2; + } + var multiplier = Math.pow(10, precision); + var actual = Math.round(this.actual * multiplier); + expected = Math.round(expected * multiplier); + return expected == actual; +}; + +/** + * Matcher that checks that the expected exception was thrown by the actual. + * + * @param {String} expected + */ +jasmine.Matchers.prototype.toThrow = function(expected) { + var result = false; + var exception; + if (typeof this.actual != 'function') { + throw new Error('Actual is not a function'); + } + try { + this.actual(); + } catch (e) { + exception = e; + } + if (exception) { + result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); + } + + var not = this.isNot ? "not " : ""; + + this.message = function() { + if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { + return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' '); + } else { + return "Expected function to throw an exception."; + } + }; + + return result; +}; + +jasmine.Matchers.Any = function(expectedClass) { + this.expectedClass = expectedClass; +}; + +jasmine.Matchers.Any.prototype.matches = function(other) { + if (this.expectedClass == String) { + return typeof other == 'string' || other instanceof String; + } + + if (this.expectedClass == Number) { + return typeof other == 'number' || other instanceof Number; + } + + if (this.expectedClass == Function) { + return typeof other == 'function' || other instanceof Function; + } + + if (this.expectedClass == Object) { + return typeof other == 'object'; + } + + return other instanceof this.expectedClass; +}; + +jasmine.Matchers.Any.prototype.toString = function() { + return ''; +}; + +/** + * @constructor + */ +jasmine.MultiReporter = function() { + this.subReporters_ = []; +}; +jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); + +jasmine.MultiReporter.prototype.addReporter = function(reporter) { + this.subReporters_.push(reporter); +}; + +(function() { + var functionNames = [ + "reportRunnerStarting", + "reportRunnerResults", + "reportSuiteResults", + "reportSpecStarting", + "reportSpecResults", + "log" + ]; + for (var i = 0; i < functionNames.length; i++) { + var functionName = functionNames[i]; + jasmine.MultiReporter.prototype[functionName] = (function(functionName) { + return function() { + for (var j = 0; j < this.subReporters_.length; j++) { + var subReporter = this.subReporters_[j]; + if (subReporter[functionName]) { + subReporter[functionName].apply(subReporter, arguments); + } + } + }; + })(functionName); + } +})(); +/** + * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults + * + * @constructor + */ +jasmine.NestedResults = function() { + /** + * The total count of results + */ + this.totalCount = 0; + /** + * Number of passed results + */ + this.passedCount = 0; + /** + * Number of failed results + */ + this.failedCount = 0; + /** + * Was this suite/spec skipped? + */ + this.skipped = false; + /** + * @ignore + */ + this.items_ = []; +}; + +/** + * Roll up the result counts. + * + * @param result + */ +jasmine.NestedResults.prototype.rollupCounts = function(result) { + this.totalCount += result.totalCount; + this.passedCount += result.passedCount; + this.failedCount += result.failedCount; +}; + +/** + * Adds a log message. + * @param values Array of message parts which will be concatenated later. + */ +jasmine.NestedResults.prototype.log = function(values) { + this.items_.push(new jasmine.MessageResult(values)); +}; + +/** + * Getter for the results: message & results. + */ +jasmine.NestedResults.prototype.getItems = function() { + return this.items_; +}; + +/** + * Adds a result, tracking counts (total, passed, & failed) + * @param {jasmine.ExpectationResult|jasmine.NestedResults} result + */ +jasmine.NestedResults.prototype.addResult = function(result) { + if (result.type != 'log') { + if (result.items_) { + this.rollupCounts(result); + } else { + this.totalCount++; + if (result.passed()) { + this.passedCount++; + } else { + this.failedCount++; + } + } + } + this.items_.push(result); +}; + +/** + * @returns {Boolean} True if everything below passed + */ +jasmine.NestedResults.prototype.passed = function() { + return this.passedCount === this.totalCount; +}; +/** + * Base class for pretty printing for expectation results. + */ +jasmine.PrettyPrinter = function() { + this.ppNestLevel_ = 0; +}; + +/** + * Formats a value in a nice, human-readable string. + * + * @param value + */ +jasmine.PrettyPrinter.prototype.format = function(value) { + if (this.ppNestLevel_ > 40) { + throw new Error('jasmine.PrettyPrinter: format() nested too deeply!'); + } + + this.ppNestLevel_++; + try { + if (value === jasmine.undefined) { + this.emitScalar('undefined'); + } else if (value === null) { + this.emitScalar('null'); + } else if (value === jasmine.getGlobal()) { + this.emitScalar(''); + } else if (value instanceof jasmine.Matchers.Any) { + this.emitScalar(value.toString()); + } else if (typeof value === 'string') { + this.emitString(value); + } else if (jasmine.isSpy(value)) { + this.emitScalar("spy on " + value.identity); + } else if (value instanceof RegExp) { + this.emitScalar(value.toString()); + } else if (typeof value === 'function') { + this.emitScalar('Function'); + } else if (typeof value.nodeType === 'number') { + this.emitScalar('HTMLNode'); + } else if (value instanceof Date) { + this.emitScalar('Date(' + value + ')'); + } else if (value.__Jasmine_been_here_before__) { + this.emitScalar(''); + } else if (jasmine.isArray_(value) || typeof value == 'object') { + value.__Jasmine_been_here_before__ = true; + if (jasmine.isArray_(value)) { + this.emitArray(value); + } else { + this.emitObject(value); + } + delete value.__Jasmine_been_here_before__; + } else { + this.emitScalar(value.toString()); + } + } finally { + this.ppNestLevel_--; + } +}; + +jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { + for (var property in obj) { + if (property == '__Jasmine_been_here_before__') continue; + fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && + obj.__lookupGetter__(property) !== null) : false); + } +}; + +jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; + +jasmine.StringPrettyPrinter = function() { + jasmine.PrettyPrinter.call(this); + + this.string = ''; +}; +jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); + +jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { + this.append(value); +}; + +jasmine.StringPrettyPrinter.prototype.emitString = function(value) { + this.append("'" + value + "'"); +}; + +jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { + this.append('[ '); + for (var i = 0; i < array.length; i++) { + if (i > 0) { + this.append(', '); + } + this.format(array[i]); + } + this.append(' ]'); +}; + +jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { + var self = this; + this.append('{ '); + var first = true; + + this.iterateObject(obj, function(property, isGetter) { + if (first) { + first = false; + } else { + self.append(', '); + } + + self.append(property); + self.append(' : '); + if (isGetter) { + self.append(''); + } else { + self.format(obj[property]); + } + }); + + this.append(' }'); +}; + +jasmine.StringPrettyPrinter.prototype.append = function(value) { + this.string += value; +}; +jasmine.Queue = function(env) { + this.env = env; + this.blocks = []; + this.running = false; + this.index = 0; + this.offset = 0; + this.abort = false; +}; + +jasmine.Queue.prototype.addBefore = function(block) { + this.blocks.unshift(block); +}; + +jasmine.Queue.prototype.add = function(block) { + this.blocks.push(block); +}; + +jasmine.Queue.prototype.insertNext = function(block) { + this.blocks.splice((this.index + this.offset + 1), 0, block); + this.offset++; +}; + +jasmine.Queue.prototype.start = function(onComplete) { + this.running = true; + this.onComplete = onComplete; + this.next_(); +}; + +jasmine.Queue.prototype.isRunning = function() { + return this.running; +}; + +jasmine.Queue.LOOP_DONT_RECURSE = true; + +jasmine.Queue.prototype.next_ = function() { + var self = this; + var goAgain = true; + + while (goAgain) { + goAgain = false; + + if (self.index < self.blocks.length && !this.abort) { + var calledSynchronously = true; + var completedSynchronously = false; + + var onComplete = function () { + if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { + completedSynchronously = true; + return; + } + + if (self.blocks[self.index].abort) { + self.abort = true; + } + + self.offset = 0; + self.index++; + + var now = new Date().getTime(); + if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { + self.env.lastUpdate = now; + self.env.setTimeout(function() { + self.next_(); + }, 0); + } else { + if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { + goAgain = true; + } else { + self.next_(); + } + } + }; + self.blocks[self.index].execute(onComplete); + + calledSynchronously = false; + if (completedSynchronously) { + onComplete(); + } + + } else { + self.running = false; + if (self.onComplete) { + self.onComplete(); + } + } + } +}; + +jasmine.Queue.prototype.results = function() { + var results = new jasmine.NestedResults(); + for (var i = 0; i < this.blocks.length; i++) { + if (this.blocks[i].results) { + results.addResult(this.blocks[i].results()); + } + } + return results; +}; + + +/** + * Runner + * + * @constructor + * @param {jasmine.Env} env + */ +jasmine.Runner = function(env) { + var self = this; + self.env = env; + self.queue = new jasmine.Queue(env); + self.before_ = []; + self.after_ = []; + self.suites_ = []; +}; + +jasmine.Runner.prototype.execute = function() { + var self = this; + if (self.env.reporter.reportRunnerStarting) { + self.env.reporter.reportRunnerStarting(this); + } + self.queue.start(function () { + self.finishCallback(); + }); +}; + +jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { + beforeEachFunction.typeName = 'beforeEach'; + this.before_.splice(0,0,beforeEachFunction); +}; + +jasmine.Runner.prototype.afterEach = function(afterEachFunction) { + afterEachFunction.typeName = 'afterEach'; + this.after_.splice(0,0,afterEachFunction); +}; + + +jasmine.Runner.prototype.finishCallback = function() { + this.env.reporter.reportRunnerResults(this); +}; + +jasmine.Runner.prototype.addSuite = function(suite) { + this.suites_.push(suite); +}; + +jasmine.Runner.prototype.add = function(block) { + if (block instanceof jasmine.Suite) { + this.addSuite(block); + } + this.queue.add(block); +}; + +jasmine.Runner.prototype.specs = function () { + var suites = this.suites(); + var specs = []; + for (var i = 0; i < suites.length; i++) { + specs = specs.concat(suites[i].specs()); + } + return specs; +}; + +jasmine.Runner.prototype.suites = function() { + return this.suites_; +}; + +jasmine.Runner.prototype.topLevelSuites = function() { + var topLevelSuites = []; + for (var i = 0; i < this.suites_.length; i++) { + if (!this.suites_[i].parentSuite) { + topLevelSuites.push(this.suites_[i]); + } + } + return topLevelSuites; +}; + +jasmine.Runner.prototype.results = function() { + return this.queue.results(); +}; +/** + * Internal representation of a Jasmine specification, or test. + * + * @constructor + * @param {jasmine.Env} env + * @param {jasmine.Suite} suite + * @param {String} description + */ +jasmine.Spec = function(env, suite, description) { + if (!env) { + throw new Error('jasmine.Env() required'); + } + if (!suite) { + throw new Error('jasmine.Suite() required'); + } + var spec = this; + spec.id = env.nextSpecId ? env.nextSpecId() : null; + spec.env = env; + spec.suite = suite; + spec.description = description; + spec.queue = new jasmine.Queue(env); + + spec.afterCallbacks = []; + spec.spies_ = []; + + spec.results_ = new jasmine.NestedResults(); + spec.results_.description = description; + spec.matchersClass = null; +}; + +jasmine.Spec.prototype.getFullName = function() { + return this.suite.getFullName() + ' ' + this.description + '.'; +}; + + +jasmine.Spec.prototype.results = function() { + return this.results_; +}; + +/** + * All parameters are pretty-printed and concatenated together, then written to the spec's output. + * + * Be careful not to leave calls to jasmine.log in production code. + */ +jasmine.Spec.prototype.log = function() { + return this.results_.log(arguments); +}; + +jasmine.Spec.prototype.runs = function (func) { + var block = new jasmine.Block(this.env, func, this); + this.addToQueue(block); + return this; +}; + +jasmine.Spec.prototype.addToQueue = function (block) { + if (this.queue.isRunning()) { + this.queue.insertNext(block); + } else { + this.queue.add(block); + } +}; + +/** + * @param {jasmine.ExpectationResult} result + */ +jasmine.Spec.prototype.addMatcherResult = function(result) { + this.results_.addResult(result); +}; + +jasmine.Spec.prototype.expect = function(actual) { + var positive = new (this.getMatchersClass_())(this.env, actual, this); + positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); + return positive; +}; + +/** + * Waits a fixed time period before moving to the next block. + * + * @deprecated Use waitsFor() instead + * @param {Number} timeout milliseconds to wait + */ +jasmine.Spec.prototype.waits = function(timeout) { + var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); + this.addToQueue(waitsFunc); + return this; +}; + +/** + * Waits for the latchFunction to return true before proceeding to the next block. + * + * @param {Function} latchFunction + * @param {String} optional_timeoutMessage + * @param {Number} optional_timeout + */ +jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { + var latchFunction_ = null; + var optional_timeoutMessage_ = null; + var optional_timeout_ = null; + + for (var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; + switch (typeof arg) { + case 'function': + latchFunction_ = arg; + break; + case 'string': + optional_timeoutMessage_ = arg; + break; + case 'number': + optional_timeout_ = arg; + break; + } + } + + var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); + this.addToQueue(waitsForFunc); + return this; +}; + +jasmine.Spec.prototype.fail = function (e) { + var expectationResult = new jasmine.ExpectationResult({ + passed: false, + message: e ? jasmine.util.formatException(e) : 'Exception', + trace: { stack: e.stack } + }); + this.results_.addResult(expectationResult); +}; + +jasmine.Spec.prototype.getMatchersClass_ = function() { + return this.matchersClass || this.env.matchersClass; +}; + +jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { + var parent = this.getMatchersClass_(); + var newMatchersClass = function() { + parent.apply(this, arguments); + }; + jasmine.util.inherit(newMatchersClass, parent); + jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); + this.matchersClass = newMatchersClass; +}; + +jasmine.Spec.prototype.finishCallback = function() { + this.env.reporter.reportSpecResults(this); +}; + +jasmine.Spec.prototype.finish = function(onComplete) { + this.removeAllSpies(); + this.finishCallback(); + if (onComplete) { + onComplete(); + } +}; + +jasmine.Spec.prototype.after = function(doAfter) { + if (this.queue.isRunning()) { + this.queue.add(new jasmine.Block(this.env, doAfter, this)); + } else { + this.afterCallbacks.unshift(doAfter); + } +}; + +jasmine.Spec.prototype.execute = function(onComplete) { + var spec = this; + if (!spec.env.specFilter(spec)) { + spec.results_.skipped = true; + spec.finish(onComplete); + return; + } + + this.env.reporter.reportSpecStarting(this); + + spec.env.currentSpec = spec; + + spec.addBeforesAndAftersToQueue(); + + spec.queue.start(function () { + spec.finish(onComplete); + }); +}; + +jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { + var runner = this.env.currentRunner(); + var i; + + for (var suite = this.suite; suite; suite = suite.parentSuite) { + for (i = 0; i < suite.before_.length; i++) { + this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); + } + } + for (i = 0; i < runner.before_.length; i++) { + this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); + } + for (i = 0; i < this.afterCallbacks.length; i++) { + this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this)); + } + for (suite = this.suite; suite; suite = suite.parentSuite) { + for (i = 0; i < suite.after_.length; i++) { + this.queue.add(new jasmine.Block(this.env, suite.after_[i], this)); + } + } + for (i = 0; i < runner.after_.length; i++) { + this.queue.add(new jasmine.Block(this.env, runner.after_[i], this)); + } +}; + +jasmine.Spec.prototype.explodes = function() { + throw 'explodes function should not have been called'; +}; + +jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { + if (obj == jasmine.undefined) { + throw "spyOn could not find an object to spy upon for " + methodName + "()"; + } + + if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { + throw methodName + '() method does not exist'; + } + + if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { + throw new Error(methodName + ' has already been spied upon'); + } + + var spyObj = jasmine.createSpy(methodName); + + this.spies_.push(spyObj); + spyObj.baseObj = obj; + spyObj.methodName = methodName; + spyObj.originalValue = obj[methodName]; + + obj[methodName] = spyObj; + + return spyObj; +}; + +jasmine.Spec.prototype.removeAllSpies = function() { + for (var i = 0; i < this.spies_.length; i++) { + var spy = this.spies_[i]; + spy.baseObj[spy.methodName] = spy.originalValue; + } + this.spies_ = []; +}; + +/** + * Internal representation of a Jasmine suite. + * + * @constructor + * @param {jasmine.Env} env + * @param {String} description + * @param {Function} specDefinitions + * @param {jasmine.Suite} parentSuite + */ +jasmine.Suite = function(env, description, specDefinitions, parentSuite) { + var self = this; + self.id = env.nextSuiteId ? env.nextSuiteId() : null; + self.description = description; + self.queue = new jasmine.Queue(env); + self.parentSuite = parentSuite; + self.env = env; + self.before_ = []; + self.after_ = []; + self.children_ = []; + self.suites_ = []; + self.specs_ = []; +}; + +jasmine.Suite.prototype.getFullName = function() { + var fullName = this.description; + for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { + fullName = parentSuite.description + ' ' + fullName; + } + return fullName; +}; + +jasmine.Suite.prototype.finish = function(onComplete) { + this.env.reporter.reportSuiteResults(this); + this.finished = true; + if (typeof(onComplete) == 'function') { + onComplete(); + } +}; + +jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { + beforeEachFunction.typeName = 'beforeEach'; + this.before_.unshift(beforeEachFunction); +}; + +jasmine.Suite.prototype.afterEach = function(afterEachFunction) { + afterEachFunction.typeName = 'afterEach'; + this.after_.unshift(afterEachFunction); +}; + +jasmine.Suite.prototype.results = function() { + return this.queue.results(); +}; + +jasmine.Suite.prototype.add = function(suiteOrSpec) { + this.children_.push(suiteOrSpec); + if (suiteOrSpec instanceof jasmine.Suite) { + this.suites_.push(suiteOrSpec); + this.env.currentRunner().addSuite(suiteOrSpec); + } else { + this.specs_.push(suiteOrSpec); + } + this.queue.add(suiteOrSpec); +}; + +jasmine.Suite.prototype.specs = function() { + return this.specs_; +}; + +jasmine.Suite.prototype.suites = function() { + return this.suites_; +}; + +jasmine.Suite.prototype.children = function() { + return this.children_; +}; + +jasmine.Suite.prototype.execute = function(onComplete) { + var self = this; + this.queue.start(function () { + self.finish(onComplete); + }); +}; +jasmine.WaitsBlock = function(env, timeout, spec) { + this.timeout = timeout; + jasmine.Block.call(this, env, null, spec); +}; + +jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); + +jasmine.WaitsBlock.prototype.execute = function (onComplete) { + if (jasmine.VERBOSE) { + this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); + } + this.env.setTimeout(function () { + onComplete(); + }, this.timeout); +}; +/** + * A block which waits for some condition to become true, with timeout. + * + * @constructor + * @extends jasmine.Block + * @param {jasmine.Env} env The Jasmine environment. + * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. + * @param {Function} latchFunction A function which returns true when the desired condition has been met. + * @param {String} message The message to display if the desired condition hasn't been met within the given time period. + * @param {jasmine.Spec} spec The Jasmine spec. + */ +jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { + this.timeout = timeout || env.defaultTimeoutInterval; + this.latchFunction = latchFunction; + this.message = message; + this.totalTimeSpentWaitingForLatch = 0; + jasmine.Block.call(this, env, null, spec); +}; +jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); + +jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; + +jasmine.WaitsForBlock.prototype.execute = function(onComplete) { + if (jasmine.VERBOSE) { + this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); + } + var latchFunctionResult; + try { + latchFunctionResult = this.latchFunction.apply(this.spec); + } catch (e) { + this.spec.fail(e); + onComplete(); + return; + } + + if (latchFunctionResult) { + onComplete(); + } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { + var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); + this.spec.fail({ + name: 'timeout', + message: message + }); + + this.abort = true; + onComplete(); + } else { + this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; + var self = this; + this.env.setTimeout(function() { + self.execute(onComplete); + }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); + } +}; +// Mock setTimeout, clearTimeout +// Contributed by Pivotal Computer Systems, www.pivotalsf.com + +jasmine.FakeTimer = function() { + this.reset(); + + var self = this; + self.setTimeout = function(funcToCall, millis) { + self.timeoutsMade++; + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); + return self.timeoutsMade; + }; + + self.setInterval = function(funcToCall, millis) { + self.timeoutsMade++; + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); + return self.timeoutsMade; + }; + + self.clearTimeout = function(timeoutKey) { + self.scheduledFunctions[timeoutKey] = jasmine.undefined; + }; + + self.clearInterval = function(timeoutKey) { + self.scheduledFunctions[timeoutKey] = jasmine.undefined; + }; + +}; + +jasmine.FakeTimer.prototype.reset = function() { + this.timeoutsMade = 0; + this.scheduledFunctions = {}; + this.nowMillis = 0; +}; + +jasmine.FakeTimer.prototype.tick = function(millis) { + var oldMillis = this.nowMillis; + var newMillis = oldMillis + millis; + this.runFunctionsWithinRange(oldMillis, newMillis); + this.nowMillis = newMillis; +}; + +jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { + var scheduledFunc; + var funcsToRun = []; + for (var timeoutKey in this.scheduledFunctions) { + scheduledFunc = this.scheduledFunctions[timeoutKey]; + if (scheduledFunc != jasmine.undefined && + scheduledFunc.runAtMillis >= oldMillis && + scheduledFunc.runAtMillis <= nowMillis) { + funcsToRun.push(scheduledFunc); + this.scheduledFunctions[timeoutKey] = jasmine.undefined; + } + } + + if (funcsToRun.length > 0) { + funcsToRun.sort(function(a, b) { + return a.runAtMillis - b.runAtMillis; + }); + for (var i = 0; i < funcsToRun.length; ++i) { + try { + var funcToRun = funcsToRun[i]; + this.nowMillis = funcToRun.runAtMillis; + funcToRun.funcToCall(); + if (funcToRun.recurring) { + this.scheduleFunction(funcToRun.timeoutKey, + funcToRun.funcToCall, + funcToRun.millis, + true); + } + } catch(e) { + } + } + this.runFunctionsWithinRange(oldMillis, nowMillis); + } +}; + +jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { + this.scheduledFunctions[timeoutKey] = { + runAtMillis: this.nowMillis + millis, + funcToCall: funcToCall, + recurring: recurring, + timeoutKey: timeoutKey, + millis: millis + }; +}; + +/** + * @namespace + */ +jasmine.Clock = { + defaultFakeTimer: new jasmine.FakeTimer(), + + reset: function() { + jasmine.Clock.assertInstalled(); + jasmine.Clock.defaultFakeTimer.reset(); + }, + + tick: function(millis) { + jasmine.Clock.assertInstalled(); + jasmine.Clock.defaultFakeTimer.tick(millis); + }, + + runFunctionsWithinRange: function(oldMillis, nowMillis) { + jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); + }, + + scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { + jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); + }, + + useMock: function() { + if (!jasmine.Clock.isInstalled()) { + var spec = jasmine.getEnv().currentSpec; + spec.after(jasmine.Clock.uninstallMock); + + jasmine.Clock.installMock(); + } + }, + + installMock: function() { + jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; + }, + + uninstallMock: function() { + jasmine.Clock.assertInstalled(); + jasmine.Clock.installed = jasmine.Clock.real; + }, + + real: { + setTimeout: jasmine.getGlobal().setTimeout, + clearTimeout: jasmine.getGlobal().clearTimeout, + setInterval: jasmine.getGlobal().setInterval, + clearInterval: jasmine.getGlobal().clearInterval + }, + + assertInstalled: function() { + if (!jasmine.Clock.isInstalled()) { + throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); + } + }, + + isInstalled: function() { + return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; + }, + + installed: null +}; +jasmine.Clock.installed = jasmine.Clock.real; + +//else for IE support +jasmine.getGlobal().setTimeout = function(funcToCall, millis) { + if (jasmine.Clock.installed.setTimeout.apply) { + return jasmine.Clock.installed.setTimeout.apply(this, arguments); + } else { + return jasmine.Clock.installed.setTimeout(funcToCall, millis); + } +}; + +jasmine.getGlobal().setInterval = function(funcToCall, millis) { + if (jasmine.Clock.installed.setInterval.apply) { + return jasmine.Clock.installed.setInterval.apply(this, arguments); + } else { + return jasmine.Clock.installed.setInterval(funcToCall, millis); + } +}; + +jasmine.getGlobal().clearTimeout = function(timeoutKey) { + if (jasmine.Clock.installed.clearTimeout.apply) { + return jasmine.Clock.installed.clearTimeout.apply(this, arguments); + } else { + return jasmine.Clock.installed.clearTimeout(timeoutKey); + } +}; + +jasmine.getGlobal().clearInterval = function(timeoutKey) { + if (jasmine.Clock.installed.clearTimeout.apply) { + return jasmine.Clock.installed.clearInterval.apply(this, arguments); + } else { + return jasmine.Clock.installed.clearInterval(timeoutKey); + } +}; + +jasmine.version_= { + "major": 1, + "minor": 1, + "build": 0, + "revision": 1315677058 +}; diff --git a/spec/js/jasmine/jasmine_favicon.png b/spec/js/jasmine/jasmine_favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..218f3b43713598fa5a3e78b57aceb909c33f46df GIT binary patch literal 905 zcmV;419tq0P)Px#AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_0008u zNkl3{fod28|PjmA)7fYg4w8-(2my9xtBGOs}K`n&t1VzxMO^X)M zrW+Ln1udc?q6TP)z5gAjt)P&D!M$+HJK#x<`xnD030zwD?KrxxY!2tlA zGc-58?0D7SsT)7Km=v+tNVNUk`?s@;^OxCF)y6P}_mL;~7;S<@b|MzmKq)m8l@yky zT1~ECpxZw@64!nkI34QLiUsA%i%N>-$&zGYR7WJyi9ERMyS(%kf z7A_r)X>!90&m(FwDQZ>q;+nOa*KR2+E6Fz)QwU=W1Oyo*4>_qlm|~joa|{4_A_3W8 z#FFZzRp-xMIx5a7D_Fj3&#r^TbIY@cND1d0f*^qDIs{!pw!IWGQ_%l4#ASm_D5Vet z0%ek7^)@xPihX_G0&hIc9*14ca=D!8oG}vW?H%~w^F?f_s>zU|fKrNJXJ_d6{v!t( zpEoqMws_yQws>3o?VW8Txq~#->dJG^ELW5irR!s`(_JvD^6;r+ho~eIK@ia8_lH(h zt*-p?CFC1_h2MV=?jP){uW!7WjLjCaO&c1D+tf582!XEaoB#xWAYcN5f$sLtf$koW zQs{{>)ZTq?FC6|J_%n}AWbiFK(Bo-%^-{H`*)E(ucjo-r%SYm)W5f6tN=xz=S646E fNXW#U{x?4WXWJ= 1 && count <= 5) { return ["few", "other"]; } + return ["other"]; + }; + + I18n.translations["custom"] = { + "things": { + "zero": "No things" + , "few": "A few things" + , "other": "%{count} things" + } + } + + expect(I18n.p(0, "things")).toEqual("No things"); + expect(I18n.p(4, "things")).toEqual("A few things"); + expect(I18n.p(10, "things")).toEqual("10 things"); + }); + + it("pluralizes default value", function(){ + options = {defaultValue: { + zero: "No things here!" + , one: "There is {{count}} thing here!" + , other: "There are {{count}} things here!" + }}; + + expect(I18n.p(0, "things", options)).toEqual("No things here!"); + expect(I18n.p(1, "things", options)).toEqual("There is 1 thing here!"); + expect(I18n.p(5, "things", options)).toEqual("There are 5 things here!"); + }); + + it("ignores pluralization when scope exists", function(){ + options = {defaultValue: { + zero: "No things here!" + , one: "There is {{count}} thing here!" + , other: "There are {{count}} things here!" + }}; + + expect(I18n.p(0, "inbox", options)).toEqual("You have no messages"); + expect(I18n.p(1, "inbox", options)).toEqual("You have 1 message"); + expect(I18n.p(5, "inbox", options)).toEqual("You have 5 messages"); + }); +}); diff --git a/spec/js/prepare_options.spec.js b/spec/js/prepare_options.spec.js new file mode 100644 index 00000000..d4e6d8ca --- /dev/null +++ b/spec/js/prepare_options.spec.js @@ -0,0 +1,41 @@ +var I18n = require("../../app/assets/javascripts/i18n"); + +describe("Prepare options", function(){ + beforeEach(function(){ + I18n.reset(); + }); + + it("merges two objects", function(){ + var options = I18n.prepareOptions( + {name: "Mary Doe"}, + {name: "John Doe", role: "user"} + ); + + expect(options.name).toEqual("Mary Doe"); + expect(options.role).toEqual("user"); + }); + + it("merges multiple objects", function(){ + var options = I18n.prepareOptions( + {name: "Mary Doe"}, + {name: "John Doe", role: "user"}, + {age: 33}, + {email: "mary@doe.com", url: "http://marydoe.com"}, + {role: "admin", email: "john@doe.com"} + ); + + expect(options.name).toEqual("Mary Doe"); + expect(options.role).toEqual("user"); + expect(options.age).toEqual(33); + expect(options.email).toEqual("mary@doe.com"); + expect(options.url).toEqual("http://marydoe.com"); + }); + + it("returns an empty object when values are null", function(){ + expect(I18n.prepareOptions(null, null)).toEqual({}); + }); + + it("returns an empty object when values are undefined", function(){ + expect(I18n.prepareOptions(undefined, undefined)).toEqual({}); + }); +}); diff --git a/spec/js/specs.html b/spec/js/specs.html new file mode 100644 index 00000000..b5e14c8d --- /dev/null +++ b/spec/js/specs.html @@ -0,0 +1,46 @@ + + + + + Jasmine Spec Runner + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spec/js/translate.spec.js b/spec/js/translate.spec.js new file mode 100644 index 00000000..bea1a946 --- /dev/null +++ b/spec/js/translate.spec.js @@ -0,0 +1,120 @@ +var I18n = require("../../app/assets/javascripts/i18n") + , Translations = require("./translations") +; + +describe("Translate", function(){ + var actual, expected; + + beforeEach(function(){ + I18n.reset(); + I18n.translations = Translations(); + }); + + it("returns translation for single scope", function(){ + expect(I18n.t("hello")).toEqual("Hello World!"); + }); + + it("returns translation as object", function(){ + expect(I18n.t("greetings")).toEqual(I18n.translations.en.greetings); + }); + + it("returns missing message translation for invalid scope", function(){ + actual = I18n.t("invalid.scope"); + expected = '[missing "en.invalid.scope" translation]'; + expect(actual).toEqual(expected); + }); + + it("returns translation for single scope on a custom locale", function(){ + I18n.locale = "pt-BR"; + expect(I18n.t("hello")).toEqual("Olá Mundo!"); + }); + + it("returns translation for multiple scopes", function(){ + expect(I18n.t("greetings.stranger")).toEqual("Hello stranger!"); + }); + + it("returns translation with default locale option", function(){ + expect(I18n.t("hello", {locale: "en"})).toEqual("Hello World!"); + expect(I18n.t("hello", {locale: "pt-BR"})).toEqual("Olá Mundo!"); + }); + + it("fallbacks to the default locale when I18n.fallbackss is enabled", function(){ + I18n.locale = "pt-BR"; + I18n.fallbacks = true; + expect(I18n.t("greetings.stranger")).toEqual("Hello stranger!"); + }); + + it("fallbacks to default locale when providing an unknown locale", function(){ + I18n.locale = "fr"; + I18n.fallbacks = true; + expect(I18n.t("greetings.stranger")).toEqual("Hello stranger!"); + }); + + it("fallbacks to less specific locale", function(){ + I18n.locale = "de-DE"; + I18n.fallbacks = true; + expect(I18n.t("hello")).toEqual("Hallo Welt!"); + }); + + it("fallbacks using custom rules (function)", function(){ + I18n.locale = "no"; + I18n.fallbacks = true; + I18n.locales["no"] = function() { + return ["nb"]; + }; + + expect(I18n.t("hello")).toEqual("Hei Verden!"); + }); + + it("fallbacks using custom rules (array)", function() { + I18n.locale = "no"; + I18n.fallbacks = true; + I18n.locales["no"] = ["no", "nb"]; + + expect(I18n.t("hello")).toEqual("Hei Verden!"); + }); + + it("fallbacks using custom rules (string)", function() { + I18n.locale = "no"; + I18n.fallbacks = true; + I18n.locales["no"] = "nb"; + + expect(I18n.t("hello")).toEqual("Hei Verden!"); + }); + + it("uses default value for simple translation", function(){ + actual = I18n.t("warning", {defaultValue: "Warning!"}); + expect(actual).toEqual("Warning!"); + }); + + it("uses default value for unknown locale", function(){ + I18n.locale = "fr"; + actual = I18n.t("warning", {defaultValue: "Warning!"}); + expect(actual).toEqual("Warning!"); + }); + + it("uses default value with interpolation", function(){ + actual = I18n.t( + "alert", + {defaultValue: "Attention! {{message}}", message: "You're out of quota!"} + ); + + expect(actual).toEqual("Attention! You're out of quota!"); + }); + + it("ignores default value when scope exists", function(){ + actual = I18n.t("hello", {defaultValue: "What's up?"}); + expect(actual).toEqual("Hello World!"); + }); + + it("returns translation for custom scope separator", function(){ + I18n.defaultSeparator = "•"; + actual = I18n.t("greetings•stranger"); + expect(actual).toEqual("Hello stranger!"); + }); + + it("returns boolean values", function() { + expect(I18n.t("booleans.yes")).toEqual(true); + expect(I18n.t("booleans.no")).toEqual(false); + }); +}); diff --git a/spec/js/translations.js b/spec/js/translations.js new file mode 100644 index 00000000..951c0821 --- /dev/null +++ b/spec/js/translations.js @@ -0,0 +1,120 @@ +;(function(){ + var generator = function() { + var Translations = {}; + + Translations.en = { + hello: "Hello World!" + + , booleans: { + yes: true, + no: false + } + + , greetings: { + stranger: "Hello stranger!" + , name: "Hello {{name}}!" + } + + , profile: { + details: "{{name}} is {{age}}-years old" + } + + , inbox: { + one: "You have {{count}} message" + , other: "You have {{count}} messages" + , zero: "You have no messages" + } + + , unread: { + one: "You have 1 new message ({{unread}} unread)" + , other: "You have {{count}} new messages ({{unread}} unread)" + , zero: "You have no new messages ({{unread}} unread)" + } + + , number: { + human: { + storage_units: { + units: { + "byte": { + one: "Byte" + , other: "Bytes" + } + , "kb": "KB" + , "mb": "MB" + , "gb": "GB" + , "tb": "TB" + } + } + } + } + }; + + Translations["en-US"] = { + date: { + formats: { + "default": "%d/%m/%Y" + , "short": "%d de %B" + , "long": "%d de %B de %Y" + } + + , day_names: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] + , abbr_day_names: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] + , month_names: [null, "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] + , abbr_month_names: [null, "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"] + , meridian: ["am", "pm"] + } + }; + + Translations["pt-BR"] = { + hello: "Olá Mundo!" + + , number: { + percentage: { + format: { + delimiter: "" + , separator: "," + , precision: 2 + } + } + } + + , date: { + formats: { + "default": "%d/%m/%Y" + , "short": "%d de %B" + , "long": "%d de %B de %Y" + } + , day_names: ["Domingo", "Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sábado"] + , abbr_day_names: ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"] + , month_names: [null, "Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"] + , abbr_month_names: [null, "Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"] + } + + , time: { + formats: { + "default": "%A, %d de %B de %Y, %H:%M h" + , "short": "%d/%m, %H:%M h" + , "long": "%A, %d de %B de %Y, %H:%M h" + } + , am: "AM" + , pm: "PM" + } + }; + + Translations["de"] = { + hello: "Hallo Welt!" + }; + + Translations["nb"] = { + hello: "Hei Verden!" + }; + + return Translations; + }; + + if (typeof(exports) === "undefined") { + window.Translations = generator; + } else { + module.exports = generator; + } +})(); diff --git a/spec/resources/js_file_per_locale.yml b/spec/resources/js_file_per_locale.yml deleted file mode 100644 index 7348b0dc..00000000 --- a/spec/resources/js_file_per_locale.yml +++ /dev/null @@ -1,3 +0,0 @@ -translations: - - file: "public/javascripts/i18n/%{locale}.js" - only: '*' diff --git a/spec/resources/multiple_conditions.yml b/spec/resources/multiple_conditions.yml deleted file mode 100644 index 0de258c9..00000000 --- a/spec/resources/multiple_conditions.yml +++ /dev/null @@ -1,6 +0,0 @@ -# Find more details about this configuration file at http://github.com/fnando/i18n-js -translations: - - file: "public/javascripts/bitsnpieces.js" - only: - - "*.date.formats" - - "*.number.currency" diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index bfdb2a9e..9f5676c0 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,19 +1,41 @@ -require "active_support/all" -require "active_support/version" -require "active_support/test_case" -require "ostruct" -require "pathname" require "i18n" require "json" -require "fakeweb" -FakeWeb.allow_net_connect = false +require "active_support/all" +require "i18n/js" + +module Helpers + # Set the configuration as the current one + def set_config(path) + config = HashWithIndifferentAccess.new(YAML.load_file(File.dirname(__FILE__) + "/fixtures/#{path}")) + I18n::JS.stub(:config? => true, :config => config) + end + + # Shortcut to I18n::JS.translations + def translations + I18n::JS.translations + end + + def file_should_exist(name) + file_path = File.join(I18n::JS.export_dir, name) + File.should be_file(file_path) + end + + def temp_path(file_name = "") + File.expand_path("../../tmp/i18n-js/#{file_name}", __FILE__) + end +end + +RSpec.configure do |config| + config.before do + I18n.load_path = [File.dirname(__FILE__) + "/fixtures/locales.yml"] + FileUtils.rm_rf(temp_path) + end + + config.after do + FileUtils.rm_rf(temp_path) + end -# Stub Rails.root, so we don"t need to load the whole Rails environment. -# Be careful! The specified folder will be removed! -Rails = OpenStruct.new({ - :root => Pathname.new(File.dirname(__FILE__) + "/tmp"), - :version => "0" -}) + config.include Helpers +end -require "i18n-js" diff --git a/vendor/assets/javascripts/i18n.js b/vendor/assets/javascripts/i18n.js deleted file mode 100644 index 944c55d3..00000000 --- a/vendor/assets/javascripts/i18n.js +++ /dev/null @@ -1,531 +0,0 @@ -// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf -if (!Array.prototype.indexOf) { - Array.prototype.indexOf = function(searchElement /*, fromIndex */) { - "use strict"; - - if (this === void 0 || this === null) { - throw new TypeError(); - } - - var t = Object(this); - var len = t.length >>> 0; - - if (len === 0) { - return -1; - } - - var n = 0; - if (arguments.length > 0) { - n = Number(arguments[1]); - if (n !== n) { // shortcut for verifying if it's NaN - n = 0; - } else if (n !== 0 && n !== (Infinity) && n !== -(Infinity)) { - n = (n > 0 || -1) * Math.floor(Math.abs(n)); - } - } - - if (n >= len) { - return -1; - } - - var k = n >= 0 - ? n - : Math.max(len - Math.abs(n), 0); - - for (; k < len; k++) { - if (k in t && t[k] === searchElement) { - return k; - } - } - - return -1; - }; -} - -// Instantiate the object -var I18n = I18n || {}; - -// Set default locale to english -I18n.defaultLocale = "en"; - -// Set default handling of translation fallbacks to false -I18n.fallbacks = false; - -// Set default separator -I18n.defaultSeparator = "."; - -// Set current locale to null -I18n.locale = null; - -// Set the placeholder format. Accepts `{{placeholder}}` and `%{placeholder}`. -I18n.PLACEHOLDER = /(?:\{\{|%\{)(.*?)(?:\}\}?)/gm; - -I18n.fallbackRules = { -}; - -I18n.pluralizationRules = { - en: function (n) { - return n == 0 ? ["zero", "none", "other"] : n == 1 ? "one" : "other"; - } -}; - -I18n.getFallbacks = function(locale) { - if (locale === I18n.defaultLocale) { - return []; - } else if (!I18n.fallbackRules[locale]) { - var rules = [] - , components = locale.split("-"); - - for (var l = 1; l < components.length; l++) { - rules.push(components.slice(0, l).join("-")); - } - - rules.push(I18n.defaultLocale); - - I18n.fallbackRules[locale] = rules; - } - - return I18n.fallbackRules[locale]; -} - -I18n.isValidNode = function(obj, node, undefined) { - return obj[node] !== null && obj[node] !== undefined; -}; - -I18n.lookup = function(scope, options) { - var options = options || {} - , lookupInitialScope = scope - , translations = this.prepareOptions(I18n.translations) - , locale = options.locale || I18n.currentLocale() - , messages = translations[locale] || {} - , options = this.prepareOptions(options) - , currentScope - ; - - if (typeof(scope) == "object") { - scope = scope.join(this.defaultSeparator); - } - - if (options.scope) { - scope = options.scope.toString() + this.defaultSeparator + scope; - } - - scope = scope.split(this.defaultSeparator); - - while (messages && scope.length > 0) { - currentScope = scope.shift(); - messages = messages[currentScope]; - } - - if (!messages) { - if (I18n.fallbacks) { - var fallbacks = this.getFallbacks(locale); - for (var fallback = 0; fallback < fallbacks.length; fallbacks++) { - messages = I18n.lookup(lookupInitialScope, this.prepareOptions({locale: fallbacks[fallback]}, options)); - if (messages) { - break; - } - } - } - - if (!messages && this.isValidNode(options, "defaultValue")) { - messages = options.defaultValue; - } - } - - return messages; -}; - -// Merge serveral hash options, checking if value is set before -// overwriting any value. The precedence is from left to right. -// -// I18n.prepareOptions({name: "John Doe"}, {name: "Mary Doe", role: "user"}); -// #=> {name: "John Doe", role: "user"} -// -I18n.prepareOptions = function() { - var options = {} - , opts - , count = arguments.length - ; - - for (var i = 0; i < count; i++) { - opts = arguments[i]; - - if (!opts) { - continue; - } - - for (var key in opts) { - if (!this.isValidNode(options, key)) { - options[key] = opts[key]; - } - } - } - - return options; -}; - -I18n.interpolate = function(message, options) { - options = this.prepareOptions(options); - var matches = message.match(this.PLACEHOLDER) - , placeholder - , value - , name - ; - - if (!matches) { - return message; - } - - for (var i = 0; placeholder = matches[i]; i++) { - name = placeholder.replace(this.PLACEHOLDER, "$1"); - - value = options[name]; - - if (!this.isValidNode(options, name)) { - value = "[missing " + placeholder + " value]"; - } - - regex = new RegExp(placeholder.replace(/\{/gm, "\\{").replace(/\}/gm, "\\}")); - message = message.replace(regex, value); - } - - return message; -}; - -I18n.translate = function(scope, options) { - options = this.prepareOptions(options); - var translation = this.lookup(scope, options); - - try { - if (typeof(translation) == "object") { - if (typeof(options.count) == "number") { - return this.pluralize(options.count, scope, options); - } else { - return translation; - } - } else { - return this.interpolate(translation, options); - } - } catch(err) { - return this.missingTranslation(scope); - } -}; - -I18n.localize = function(scope, value) { - switch (scope) { - case "currency": - return this.toCurrency(value); - case "number": - scope = this.lookup("number.format"); - return this.toNumber(value, scope); - case "percentage": - return this.toPercentage(value); - default: - if (scope.match(/^(date|time)/)) { - return this.toTime(scope, value); - } else { - return value.toString(); - } - } -}; - -I18n.parseDate = function(date) { - var matches, convertedDate; - - // we have a date, so just return it. - if (typeof(date) == "object") { - return date; - }; - - // it matches the following formats: - // yyyy-mm-dd - // yyyy-mm-dd[ T]hh:mm::ss - // yyyy-mm-dd[ T]hh:mm::ss - // yyyy-mm-dd[ T]hh:mm::ssZ - // yyyy-mm-dd[ T]hh:mm::ss+0000 - // - matches = date.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2}))?(Z|\+0000)?/); - - if (matches) { - for (var i = 1; i <= 6; i++) { - matches[i] = parseInt(matches[i], 10) || 0; - } - - // month starts on 0 - matches[2] -= 1; - - if (matches[7]) { - convertedDate = new Date(Date.UTC(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6])); - } else { - convertedDate = new Date(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6]); - } - } else if (typeof(date) == "number") { - // UNIX timestamp - convertedDate = new Date(); - convertedDate.setTime(date); - } else if (date.match(/\d+ \d+:\d+:\d+ [+-]\d+ \d+/)) { - // a valid javascript format with timezone info - convertedDate = new Date(); - convertedDate.setTime(Date.parse(date)) - } else { - // an arbitrary javascript string - convertedDate = new Date(); - convertedDate.setTime(Date.parse(date)); - } - - return convertedDate; -}; - -I18n.toTime = function(scope, d) { - var date = this.parseDate(d) - , format = this.lookup(scope) - ; - - if (date.toString().match(/invalid/i)) { - return date.toString(); - } - - if (!format) { - return date.toString(); - } - - return this.strftime(date, format); -}; - -I18n.strftime = function(date, format) { - var options = this.lookup("date"); - - if (!options) { - return date.toString(); - } - - options.meridian = options.meridian || ["AM", "PM"]; - - var weekDay = date.getDay() - , day = date.getDate() - , year = date.getFullYear() - , month = date.getMonth() + 1 - , hour = date.getHours() - , hour12 = hour - , meridian = hour > 11 ? 1 : 0 - , secs = date.getSeconds() - , mins = date.getMinutes() - , offset = date.getTimezoneOffset() - , absOffsetHours = Math.floor(Math.abs(offset / 60)) - , absOffsetMinutes = Math.abs(offset) - (absOffsetHours * 60) - , timezoneoffset = (offset > 0 ? "-" : "+") + (absOffsetHours.toString().length < 2 ? "0" + absOffsetHours : absOffsetHours) + (absOffsetMinutes.toString().length < 2 ? "0" + absOffsetMinutes : absOffsetMinutes) - ; - - if (hour12 > 12) { - hour12 = hour12 - 12; - } else if (hour12 === 0) { - hour12 = 12; - } - - var padding = function(n) { - var s = "0" + n.toString(); - return s.substr(s.length - 2); - }; - - var f = format; - f = f.replace("%a", options.abbr_day_names[weekDay]); - f = f.replace("%A", options.day_names[weekDay]); - f = f.replace("%b", options.abbr_month_names[month]); - f = f.replace("%B", options.month_names[month]); - f = f.replace("%d", padding(day)); - f = f.replace("%e", day); - f = f.replace("%-d", day); - f = f.replace("%H", padding(hour)); - f = f.replace("%-H", hour); - f = f.replace("%I", padding(hour12)); - f = f.replace("%-I", hour12); - f = f.replace("%m", padding(month)); - f = f.replace("%-m", month); - f = f.replace("%M", padding(mins)); - f = f.replace("%-M", mins); - f = f.replace("%p", options.meridian[meridian]); - f = f.replace("%S", padding(secs)); - f = f.replace("%-S", secs); - f = f.replace("%w", weekDay); - f = f.replace("%y", padding(year)); - f = f.replace("%-y", padding(year).replace(/^0+/, "")); - f = f.replace("%Y", year); - f = f.replace("%z", timezoneoffset); - - return f; -}; - -I18n.toNumber = function(number, options) { - options = this.prepareOptions( - options, - this.lookup("number.format"), - {precision: 3, separator: ".", delimiter: ",", strip_insignificant_zeros: false} - ); - - var negative = number < 0 - , string = Math.abs(number).toFixed(options.precision).toString() - , parts = string.split(".") - , precision - , buffer = [] - , formattedNumber - ; - - number = parts[0]; - precision = parts[1]; - - while (number.length > 0) { - buffer.unshift(number.substr(Math.max(0, number.length - 3), 3)); - number = number.substr(0, number.length -3); - } - - formattedNumber = buffer.join(options.delimiter); - - if (options.precision > 0) { - formattedNumber += options.separator + parts[1]; - } - - if (negative) { - formattedNumber = "-" + formattedNumber; - } - - if (options.strip_insignificant_zeros) { - var regex = { - separator: new RegExp(options.separator.replace(/\./, "\\.") + "$") - , zeros: /0+$/ - }; - - formattedNumber = formattedNumber - .replace(regex.zeros, "") - .replace(regex.separator, "") - ; - } - - return formattedNumber; -}; - -I18n.toCurrency = function(number, options) { - options = this.prepareOptions( - options, - this.lookup("number.currency.format"), - this.lookup("number.format"), - {unit: "$", precision: 2, format: "%u%n", delimiter: ",", separator: "."} - ); - - number = this.toNumber(number, options); - number = options.format - .replace("%u", options.unit) - .replace("%n", number) - ; - - return number; -}; - -I18n.toHumanSize = function(number, options) { - var kb = 1024 - , size = number - , iterations = 0 - , unit - , precision - ; - - while (size >= kb && iterations < 4) { - size = size / kb; - iterations += 1; - } - - if (iterations === 0) { - unit = this.t("number.human.storage_units.units.byte", {count: size}); - precision = 0; - } else { - unit = this.t("number.human.storage_units.units." + [null, "kb", "mb", "gb", "tb"][iterations]); - precision = (size - Math.floor(size) === 0) ? 0 : 1; - } - - options = this.prepareOptions( - options, - {precision: precision, format: "%n%u", delimiter: ""} - ); - - number = this.toNumber(size, options); - number = options.format - .replace("%u", unit) - .replace("%n", number) - ; - - return number; -}; - -I18n.toPercentage = function(number, options) { - options = this.prepareOptions( - options, - this.lookup("number.percentage.format"), - this.lookup("number.format"), - {precision: 3, separator: ".", delimiter: ""} - ); - - number = this.toNumber(number, options); - return number + "%"; -}; - -I18n.pluralizer = function(locale) { - pluralizer = this.pluralizationRules[locale]; - if (pluralizer !== undefined) return pluralizer; - return this.pluralizationRules["en"]; -}; - -I18n.findAndTranslateValidNode = function(keys, translation) { - for (i = 0; i < keys.length; i++) { - key = keys[i]; - if (this.isValidNode(translation, key)) return translation[key]; - } - return null; -}; - -I18n.pluralize = function(count, scope, options) { - var translation; - - try { - translation = this.lookup(scope, options); - } catch (error) {} - - if (!translation) { - return this.missingTranslation(scope); - } - - var message; - options = this.prepareOptions(options); - options.count = count.toString(); - - pluralizer = this.pluralizer(this.currentLocale()); - key = pluralizer(Math.abs(count)); - keys = ((typeof key == "object") && (key instanceof Array)) ? key : [key]; - - message = this.findAndTranslateValidNode(keys, translation); - if (message == null) message = this.missingTranslation(scope, keys[0]); - - return this.interpolate(message, options); -}; - -I18n.missingTranslation = function() { - var message = '[missing "' + this.currentLocale() - , count = arguments.length - ; - - for (var i = 0; i < count; i++) { - message += "." + arguments[i]; - } - - message += '" translation]'; - - return message; -}; - -I18n.currentLocale = function() { - return (I18n.locale || I18n.defaultLocale); -}; - -// shortcuts -I18n.t = I18n.translate; -I18n.l = I18n.localize; -I18n.p = I18n.pluralize; diff --git a/vendor/assets/javascripts/i18n/translations.js.erb b/vendor/assets/javascripts/i18n/translations.js.erb deleted file mode 100644 index bb7c51ff..00000000 --- a/vendor/assets/javascripts/i18n/translations.js.erb +++ /dev/null @@ -1,9 +0,0 @@ -<%# encoding: utf-8%> - -<% SimplesIdeias::I18n.assert_usable_configuration! %> -var I18n = I18n || {}; -I18n.translations = <%= - SimplesIdeias::I18n.translation_segments.each_with_object({}) do |(name, segment),translations| - translations.merge!(segment) - end.to_json -%>; From 84f4ac44ae95bba81ef53a7550d61d06ed52e595 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 10 Apr 2014 15:47:23 +0800 Subject: [PATCH 003/466] * Stop tracking Gemfile.lock --- .gitignore | 1 + Gemfile.lock | 52 ---------------------------------------------------- 2 files changed, 1 insertion(+), 52 deletions(-) delete mode 100644 Gemfile.lock diff --git a/.gitignore b/.gitignore index 632d413e..5def7332 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ pkg node_modules +Gemfile.lock diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 5447642f..00000000 --- a/Gemfile.lock +++ /dev/null @@ -1,52 +0,0 @@ -PATH - remote: . - specs: - i18n-js (3.0.0.rc5) - i18n - -GEM - remote: https://rubygems.org/ - specs: - activesupport (3.2.12) - i18n (~> 0.6) - multi_json (~> 1.0) - awesome_print (1.1.0) - coderay (1.0.9) - diff-lcs (1.2.1) - i18n (0.6.4) - method_source (0.8.1) - multi_json (1.6.1) - pry (0.9.12) - coderay (~> 1.0.5) - method_source (~> 0.8) - slop (~> 3.4) - pry-meta (0.0.5) - awesome_print - pry - pry-nav - pry-remote - pry-nav (0.2.3) - pry (~> 0.9.10) - pry-remote (0.1.7) - pry (~> 0.9) - slop (~> 3.0) - rake (10.0.3) - rspec (2.13.0) - rspec-core (~> 2.13.0) - rspec-expectations (~> 2.13.0) - rspec-mocks (~> 2.13.0) - rspec-core (2.13.0) - rspec-expectations (2.13.0) - diff-lcs (>= 1.1.3, < 2.0) - rspec-mocks (2.13.0) - slop (3.4.3) - -PLATFORMS - ruby - -DEPENDENCIES - activesupport - i18n-js! - pry-meta - rake - rspec From f6355a6805c821b93c3a88e4ae2d70a0535d3d56 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 23 Dec 2013 12:17:21 +0800 Subject: [PATCH 004/466] * Allow I18n options to be set before I18n is loaded (Add README later) (No spec due to how require works) --- app/assets/javascripts/i18n.js | 50 ++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 24c50375..c53a5541 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -60,28 +60,60 @@ // Set meridian. var MERIDIAN = ["AM", "PM"]; + // Other default options + var DEFAULT_OPTIONS = { + defaultLocale: "en", + locale: "en", + defaultSeparator: ".", + placeholder: /(?:\{\{|%\{)(.*?)(?:\}\}?)/gm, + fallbacks: false, + translations: {}, + } + I18n.reset = function() { // Set default locale. This locale will be used when fallback is enabled and // the translation doesn't exist in a particular locale. - this.defaultLocale = "en"; + this.defaultLocale = DEFAULT_OPTIONS.defaultLocale; // Set the current locale to `en`. - this.locale = "en"; + this.locale = DEFAULT_OPTIONS.locale; // Set the translation key separator. - this.defaultSeparator = "."; + this.defaultSeparator = DEFAULT_OPTIONS.defaultSeparator; // Set the placeholder format. Accepts `{{placeholder}}` and `%{placeholder}`. - this.placeholder = /(?:\{\{|%\{)(.*?)(?:\}\}?)/gm; + this.placeholder = DEFAULT_OPTIONS.placeholder; // Set if engine should fallback to the default locale when a translation // is missing. - this.fallbacks = false; + this.fallbacks = DEFAULT_OPTIONS.fallbacks; // Set the default translation object. - this.translations = {}; + this.translations = DEFAULT_OPTIONS.translations; }; + // Much like `reset`, but only assign options if not already assigned + I18n.initializeOptions = function() { + if (typeof(this.defaultLocale) === "undefined" && this.defaultLocale !== null) + this.defaultLocale = DEFAULT_OPTIONS.defaultLocale; + + if (typeof(this.locale) === "undefined" && this.locale !== null) + this.locale = DEFAULT_OPTIONS.locale; + + if (typeof(this.defaultSeparator) === "undefined" && this.defaultSeparator !== null) + this.defaultSeparator = DEFAULT_OPTIONS.defaultSeparator; + + if (typeof(this.placeholder) === "undefined" && this.placeholder !== null) + this.placeholder = DEFAULT_OPTIONS.placeholder; + + if (typeof(this.fallbacks) === "undefined" && this.fallbacks !== null) + this.fallbacks = DEFAULT_OPTIONS.fallbacks; + + if (typeof(this.translations) === "undefined" && this.translations !== null) + this.translations = DEFAULT_OPTIONS.translations; + } + I18n.initializeOptions(); + // Return a list of all locales that must be tried before returning the // missing translation message. By default, this will consider the inline option, // current locale and fallback locale. @@ -181,10 +213,6 @@ } }; - // Reset all default attributes. This is specially useful - // while running tests. - I18n.reset(); - // Return current locale. If no locale has been set, then // the current locale will be the default locale. I18n.currentLocale = function() { @@ -681,4 +709,4 @@ I18n.t = I18n.translate; I18n.l = I18n.localize; I18n.p = I18n.pluralize; -})(typeof(exports) === "undefined" ? (this.I18n = {}) : exports); +})(typeof(exports) === "undefined" ? (this.I18n || (this.I18n = {})) : exports); From 2de104486811a6a32c1267e2765bc39c57346352 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 23 Dec 2013 12:32:37 +0800 Subject: [PATCH 005/466] * Make default rake task to run spec --- Rakefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Rakefile b/Rakefile index b3f0c912..1e708e82 100644 --- a/Rakefile +++ b/Rakefile @@ -11,3 +11,7 @@ end desc "Run all specs" task :spec => ["spec:ruby", "spec:js"] + +task :default do + Rake::Task[:spec].invoke +end From bb9d4c512e7777eb5b52bf6e63eaa2250af79d3a Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 17 Apr 2014 16:32:33 +0800 Subject: [PATCH 006/466] ~ Add Changelog --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..d56d3cf5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +## unreleased + +### enhancements +- You can now assign `I18n.locale` & `I18n.default_locale` before loading `i18n.js` in `application.html.*` + (merged to `i18n-js-pika` already) + + +## Before 3.0.0.rc5 + +- Things happened. From 65f0239f4d76bbc3c79d4d4aca9ad1a96b48505c Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 17 Apr 2014 16:40:26 +0800 Subject: [PATCH 007/466] ~ Change README to match the change --- README.md | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5cb035d4..8fd53ce5 100644 --- a/README.md +++ b/README.md @@ -59,13 +59,24 @@ by hand or using your favorite programming language. More info below. You **don't** need to set up a thing. The default settings will work just okay. But if you want to split translations into several files or specify specific contexts, you can follow the rest of this setting up section. Set your locale is easy as - - I18n.defaultLocale = "pt-BR"; - I18n.locale = "pt-BR"; - I18n.currentLocale(); - // pt-BR - -**NOTE:** Just make sure you apply your configuration **after i18n.js** is loaded. Otherwise, your settings will be ignored. +```javascript +I18n.defaultLocale = "pt-BR"; +I18n.locale = "pt-BR"; +I18n.currentLocale(); +// pt-BR +``` + +**NOTE:** You can now apply your configuration **before I18n** is loaded like this: +```javascript +I18n = {} // You must define this object in top namespace, which should be `window` +I18n.defaultLocale = "pt-BR"; +I18n.locale = "pt-BR"; + +// Load I18n from `i18n.js`, `application.js` or whatever + +I18n.currentLocale(); +// pt-BR +``` In practice, you'll have something like the following in your `application.html.erb`: From 3c97d9f945967219c58d215f6d17dfec57856a4d Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 31 Dec 2013 09:41:23 +0800 Subject: [PATCH 008/466] * Ignore RubyMine project files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5def7332..b5b43dd6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ pkg node_modules Gemfile.lock +.idea/ From 07978b0558c30866c8e6640dcd14aed2db332e5b Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 17 Apr 2014 16:50:19 +0800 Subject: [PATCH 009/466] * Detect asset pipeline and other dependencies in a separate module (Code from gem `jasmine-gem`) --- lib/i18n/js.rb | 9 ++---- lib/i18n/js/dependencies.rb | 61 +++++++++++++++++++++++++++++++++++++ lib/i18n/js/engine.rb | 2 +- 3 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 lib/i18n/js/dependencies.rb diff --git a/lib/i18n/js.rb b/lib/i18n/js.rb index d4ed5594..0a7052b8 100644 --- a/lib/i18n/js.rb +++ b/lib/i18n/js.rb @@ -3,7 +3,8 @@ module I18n module JS - if defined?(Rails) + require "i18n/js/dependencies" + if JS::Dependencies.rails? require "i18n/js/middleware" require "i18n/js/engine" end @@ -13,12 +14,6 @@ module JS Hash === v1 && Hash === v2 ? v1.merge(v2, &MERGER) : v2 end - # Detect if Rails app has asset pipeline support. - # - def self.has_asset_pipeline? - Rails.configuration.respond_to?(:assets) && Rails.configuration.assets.enabled - end - # The configuration file. This defaults to the `config/i18n-js.yml` file. # def self.config_file diff --git a/lib/i18n/js/dependencies.rb b/lib/i18n/js/dependencies.rb new file mode 100644 index 00000000..055b6fcc --- /dev/null +++ b/lib/i18n/js/dependencies.rb @@ -0,0 +1,61 @@ +module I18n + module JS + module Dependencies + class << self + def rails3? + safe_gem_check("rails", "~> 3") && running_rails3? + end + + def rails4? + safe_gem_check("rails", "~> 4") && running_rails4? + end + + def rails? + rails_available? && running_rails? + end + + def rails_available? + safe_gem_check("rails", '>= 3') + end + + def using_asset_pipeline? + assets_pipeline_available = + (rails3? || rails4?) && + Rails.respond_to?(:application) && + Rails.application.respond_to?(:assets) + rails3_assets_enabled = + rails3? && + assets_pipeline_available && + Rails.application.config.assets.enabled != false + + assets_pipeline_available && (rails4? || rails3_assets_enabled) + end + + private + + def running_rails3? + running_rails? && Rails.version.to_i == 3 + end + + def running_rails4? + running_rails? && Rails.version.to_i == 4 + end + + def running_rails? + defined?(Rails) && Rails.respond_to?(:version) + end + + def safe_gem_check(gem_name, version_string) + if Gem::Specification.respond_to?(:find_by_name) + Gem::Specification.find_by_name(gem_name, version_string) + elsif Gem.respond_to?(:available?) + Gem.available?(gem_name, version_string) + end + rescue Gem::LoadError + false + end + + end + end + end +end diff --git a/lib/i18n/js/engine.rb b/lib/i18n/js/engine.rb index ab346b38..38e9e8a8 100644 --- a/lib/i18n/js/engine.rb +++ b/lib/i18n/js/engine.rb @@ -5,7 +5,7 @@ module JS class Engine < ::Rails::Engine initializer :after => "sprockets.environment" do ActiveSupport.on_load(:after_initialize, :yield => true) do - next unless JS.has_asset_pipeline? + next unless JS::Dependencies.using_asset_pipeline? next unless Rails.configuration.assets.compile registry = Sprockets.respond_to?("register_preprocessor") ? Sprockets : Rails.application.assets From 48aa4796f6b498fa78415091b2fb7010a9dc0d0b Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Sun, 4 Aug 2013 14:07:29 -0300 Subject: [PATCH 010/466] Revert "Rails 4." This reverts commit 4e5c525ff6e1ec4d3449852746fa5651a0577d68. --- lib/i18n/js/engine.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/i18n/js/engine.rb b/lib/i18n/js/engine.rb index 38e9e8a8..a92366e0 100644 --- a/lib/i18n/js/engine.rb +++ b/lib/i18n/js/engine.rb @@ -8,9 +8,7 @@ class Engine < ::Rails::Engine next unless JS::Dependencies.using_asset_pipeline? next unless Rails.configuration.assets.compile - registry = Sprockets.respond_to?("register_preprocessor") ? Sprockets : Rails.application.assets - - registry.register_preprocessor "application/javascript", :"i18n-js_dependencies" do |context, source| + Rails.application.assets.register_preprocessor "application/javascript", :"i18n-js_dependencies" do |context, source| next source unless context.logical_path == "i18n/filtered" ::I18n.load_path.each {|path| context.depend_on(File.expand_path(path))} source From a83c298e9688e73819641c397782a23781a63449 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 17 Apr 2014 16:58:06 +0800 Subject: [PATCH 011/466] ~ Update CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d56d3cf5..f213570f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ - You can now assign `I18n.locale` & `I18n.default_locale` before loading `i18n.js` in `application.html.*` (merged to `i18n-js-pika` already) +### bug fixes +- Fix regression: asset not being reloaded in development when translation changed + ## Before 3.0.0.rc5 From 0f2ce87f329f318cca1ccbcf85ac3a6faf45e325 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 17 Apr 2014 17:03:53 +0800 Subject: [PATCH 012/466] ! Use another way to fix the `TypeError` from `sprockets` --- lib/i18n/js/engine.rb | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/i18n/js/engine.rb b/lib/i18n/js/engine.rb index a92366e0..1568e982 100644 --- a/lib/i18n/js/engine.rb +++ b/lib/i18n/js/engine.rb @@ -8,10 +8,19 @@ class Engine < ::Rails::Engine next unless JS::Dependencies.using_asset_pipeline? next unless Rails.configuration.assets.compile - Rails.application.assets.register_preprocessor "application/javascript", :"i18n-js_dependencies" do |context, source| - next source unless context.logical_path == "i18n/filtered" - ::I18n.load_path.each {|path| context.depend_on(File.expand_path(path))} - source + begin + Rails.application.assets.register_preprocessor "application/javascript", :"i18n-js_dependencies" do |context, source| + if context.logical_path == "i18n/filtered" + ::I18n.load_path.each {|path| context.depend_on(File.expand_path(path))} + end + + source + end + rescue TypeError # I don't think there is a more specific error to rescue + # Could be raised by `Sprockets::Index`/`Sprockets::CachedEnvironment` + # when doing `register_preprocessor` (which calls `expire_cache!` somehow) + # + # In that case it is immutable, we don't need to do anything end end end From fa50dd75e27792b56e1691bbe7e6471bd2732132 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 17 Apr 2014 17:32:52 +0800 Subject: [PATCH 013/466] * Change i18n gem version dependency to '~> 0.6' (0.5 does not work at all) --- .gitignore | 1 + Appraisals | 4 ++++ CHANGELOG.md | 1 + Rakefile | 13 ++++++++++--- gemfiles/i18n_0_6.gemfile | 7 +++++++ i18n-js.gemspec | 5 +++-- 6 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 Appraisals create mode 100644 gemfiles/i18n_0_6.gemfile diff --git a/.gitignore b/.gitignore index b5b43dd6..533e2d1f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ pkg node_modules Gemfile.lock .idea/ +gemfiles/*.gemfile.lock diff --git a/Appraisals b/Appraisals new file mode 100644 index 00000000..e7afa30a --- /dev/null +++ b/Appraisals @@ -0,0 +1,4 @@ + +appraise "i18n-0-6" do + gem 'i18n', '0.6.9' +end diff --git a/CHANGELOG.md b/CHANGELOG.md index f213570f..0de331bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### bug fixes - Fix regression: asset not being reloaded in development when translation changed +- Requires `i18n` to be `~> 0.6`, `0.5` does not work at all ## Before 3.0.0.rc5 diff --git a/Rakefile b/Rakefile index 1e708e82..32ef4d44 100644 --- a/Rakefile +++ b/Rakefile @@ -1,7 +1,10 @@ +require "appraisal" +require "rubygems" require "bundler" +require "rspec/core/rake_task" + Bundler::GemHelper.install_tasks -require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:"spec:ruby") desc "Run JavaScript specs" @@ -12,6 +15,10 @@ end desc "Run all specs" task :spec => ["spec:ruby", "spec:js"] -task :default do - Rake::Task[:spec].invoke +if !ENV["APPRAISAL_INITIALIZED"] && !ENV["TRAVIS"] + task :default do + sh "rake appraisal:install && rake appraisal spec" + end +else + task :default => :spec end diff --git a/gemfiles/i18n_0_6.gemfile b/gemfiles/i18n_0_6.gemfile new file mode 100644 index 00000000..a1896208 --- /dev/null +++ b/gemfiles/i18n_0_6.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "i18n", "0.6.9" + +gemspec :path => "../" diff --git a/i18n-js.gemspec b/i18n-js.gemspec index 3bb8fc89..697969ff 100644 --- a/i18n-js.gemspec +++ b/i18n-js.gemspec @@ -17,8 +17,9 @@ Gem::Specification.new do |s| s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] - s.add_dependency "i18n" - s.add_development_dependency "activesupport" + s.add_dependency "i18n", "~> 0.6" + s.add_development_dependency "appraisal", "~> 1.0" + s.add_development_dependency "activesupport", ">= 3" s.add_development_dependency "rspec" s.add_development_dependency "rake" s.add_development_dependency "pry-meta" From 7912f68148300e5a85dd588b081eca01f52e16bf Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 6 Dec 2013 14:28:04 +0800 Subject: [PATCH 014/466] To not override translation when manually run `I18n::JS.export` --- lib/i18n/js.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/i18n/js.rb b/lib/i18n/js.rb index 0a7052b8..dcc34897 100644 --- a/lib/i18n/js.rb +++ b/lib/i18n/js.rb @@ -98,9 +98,10 @@ def self.save(translations, file) FileUtils.mkdir_p File.dirname(file) File.open(file, "w+") do |f| - f << %(I18n.translations = ); - f << translations.to_json - f << %(;) + f << %(I18n.translations || (I18n.translations = {});\n) + translations.each do |locale, translations_for_locale| + f << %(I18n.translations["#{locale}"] = #{translations_for_locale.to_json};\n); + end end end From 35f8402f201859538f8271cc3570c057af98279b Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 6 Dec 2013 14:41:24 +0800 Subject: [PATCH 015/466] Move missing placeholder text generation into its own function --- app/assets/javascripts/i18n.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index c53a5541..4c0228a2 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -337,7 +337,7 @@ value = options[name]; if (!this.isSet(options[name])) { - value = "[missing " + placeholder + " value]"; + value = this.missingPlaceholder(placeholder, message); } regex = new RegExp(placeholder.replace(/\{/gm, "\\{").replace(/\}/gm, "\\}")); @@ -391,6 +391,11 @@ return message; }; + // Return a missing placeholder message for given parameters + I18n.missingPlaceholder = function(placeholder, message) { + return "[missing " + placeholder + " value]"; + }; + // Format number using localization rules. // The options will be retrieved from the `number.format` scope. // If this isn't present, then the following options will be used: From 48d9469c730f3b7fe132ed51961906a0c3d1e9f4 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 6 Dec 2013 15:22:00 +0800 Subject: [PATCH 016/466] + Add support for +00:00 style time zone designator --- app/assets/javascripts/i18n.js | 3 ++- spec/js/dates.spec.js | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 4c0228a2..831560e8 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -514,6 +514,7 @@ // yyyy-mm-dd[ T]hh:mm::ss // yyyy-mm-dd[ T]hh:mm::ssZ // yyyy-mm-dd[ T]hh:mm::ss+0000 + // yyyy-mm-dd[ T]hh:mm::ss+00:00 // I18n.parseDate = function(date) { var matches, convertedDate; @@ -523,7 +524,7 @@ return date; }; - matches = date.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2}))?(Z|\+0000)?/); + matches = date.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2}))?(Z|\+00:?00)?/); if (matches) { for (var i = 1; i <= 6; i++) { diff --git a/spec/js/dates.spec.js b/spec/js/dates.spec.js index 0539cb71..9fe5e56e 100644 --- a/spec/js/dates.spec.js +++ b/spec/js/dates.spec.js @@ -47,6 +47,10 @@ describe("Dates", function(){ actual = I18n.parseDate("2011-07-20T12:51:55+0000"); expect(actual.toString()).toEqual(expected.toString()); + expected = new Date(Date.UTC(2011, 6, 20, 12, 51, 55)); + actual = I18n.parseDate("2011-07-20T12:51:55+00:00"); + expect(actual.toString()).toEqual(expected.toString()); + expected = new Date(Date.UTC(2011, 6, 20, 13, 03, 39)); actual = I18n.parseDate("Wed Jul 20 13:03:39 +0000 2011"); expect(actual.toString()).toEqual(expected.toString()); From 878bb7e0be3bf915b734eab076c5883f4b1e3ec4 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 6 Dec 2013 17:52:09 +0800 Subject: [PATCH 017/466] + Add rake task for exporting translations to JS --- README.md | 2 +- lib/tasks/export.rake | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 lib/tasks/export.rake diff --git a/README.md b/README.md index 8fd53ce5..61e3e5eb 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ it by running the following command. Move the middleware line to your `config/environments/development.rb` file and run the following command before deploying. - $ rails runner I18n::JS.export + $ rake i18n:js:export This will export all translation files, including the custom scopes you may have defined on `config/i18n-js.yml`. diff --git a/lib/tasks/export.rake b/lib/tasks/export.rake new file mode 100644 index 00000000..6402380b --- /dev/null +++ b/lib/tasks/export.rake @@ -0,0 +1,8 @@ +namespace :i18n do + namespace :js do + desc "Export translations to JS file(s)" + task :export => :environment do + I18n::JS.export + end + end +end From 4df8f44f098f0c33db8f295b5fbd5568d2ffa492 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 17 Apr 2014 17:48:19 +0800 Subject: [PATCH 018/466] ~ Update change log --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0de331bd..0a2c4928 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ ### enhancements - You can now assign `I18n.locale` & `I18n.default_locale` before loading `i18n.js` in `application.html.*` (merged to `i18n-js-pika` already) +- Add support for +00:00 style time zone designator (https://github.com/fnando/i18n-js/pull/167) +- Add back rake task for export (`rake i18n:js:export`) +- Not overriding translation when manually run `I18n::JS.export` (https://github.com/fnando/i18n-js/pull/171) +- Move missing placeholder text generation into its own function (for easier debugging) (https://github.com/fnando/i18n-js/pull/169) ### bug fixes - Fix regression: asset not being reloaded in development when translation changed From 7a5ed4795cf4f8f07b10cb4ab3419f6f558b253f Mon Sep 17 00:00:00 2001 From: Krzysztof Herod Date: Thu, 24 Apr 2014 14:23:14 +0200 Subject: [PATCH 019/466] do not ignore fractions of a second --- app/assets/javascripts/i18n.js | 13 ++++++++----- spec/js/dates.spec.js | 5 +++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 831560e8..1e353301 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -515,16 +515,17 @@ // yyyy-mm-dd[ T]hh:mm::ssZ // yyyy-mm-dd[ T]hh:mm::ss+0000 // yyyy-mm-dd[ T]hh:mm::ss+00:00 + // yyyy-mm-dd[ T]hh:mm::ss.123Z // I18n.parseDate = function(date) { - var matches, convertedDate; + var matches, convertedDate, fraction; // we have a date, so just return it. if (typeof(date) == "object") { return date; }; - matches = date.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2}))?(Z|\+00:?00)?/); + matches = date.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2})([\.|,]\d{1,3})?)?(Z|\+00:?00)?/); if (matches) { for (var i = 1; i <= 6; i++) { @@ -534,10 +535,12 @@ // month starts on 0 matches[2] -= 1; - if (matches[7]) { - convertedDate = new Date(Date.UTC(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6])); + fraction = matches[7] ? 1000 * ("0" + matches[7]) : null + + if (matches[8]) { + convertedDate = new Date(Date.UTC(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6], fraction)); } else { - convertedDate = new Date(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6]); + convertedDate = new Date(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6], fraction); } } else if (typeof(date) == "number") { // UNIX timestamp diff --git a/spec/js/dates.spec.js b/spec/js/dates.spec.js index 9fe5e56e..21aefb7a 100644 --- a/spec/js/dates.spec.js +++ b/spec/js/dates.spec.js @@ -58,6 +58,11 @@ describe("Dates", function(){ expected = new Date(Date.UTC(2009, 0, 24, 15, 33, 44)); actual = I18n.parseDate("2009-01-24T15:33:44Z"); expect(actual.toString()).toEqual(expected.toString()); + + expected = new Date(Date.UTC(2009, 0, 24, 15, 34, 44, 200)); + actual = I18n.parseDate("2009-01-24T15:34:44.200Z"); + expect(actual.toString()).toEqual(expected.toString()); + expect(actual.getMilliseconds()).toEqual(expected.getMilliseconds()) }); it("formats date", function(){ From 30e99d0a6e35a20ad1b8bd29077e20914adf276b Mon Sep 17 00:00:00 2001 From: Krzysztof Herod Date: Thu, 24 Apr 2014 16:05:43 +0200 Subject: [PATCH 020/466] remove redundant pipe from regexp --- app/assets/javascripts/i18n.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 1e353301..4dcc8618 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -525,7 +525,7 @@ return date; }; - matches = date.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2})([\.|,]\d{1,3})?)?(Z|\+00:?00)?/); + matches = date.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2})([\.,]\d{1,3})?)?(Z|\+00:?00)?/); if (matches) { for (var i = 1; i <= 6; i++) { From 26b76b7e85992553bd42a1defe5cb996d733b0fe Mon Sep 17 00:00:00 2001 From: Krzysztof Herod Date: Fri, 25 Apr 2014 09:27:57 +0200 Subject: [PATCH 021/466] add more tests for milliseconds --- spec/js/dates.spec.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/spec/js/dates.spec.js b/spec/js/dates.spec.js index 21aefb7a..3255bd46 100644 --- a/spec/js/dates.spec.js +++ b/spec/js/dates.spec.js @@ -63,6 +63,16 @@ describe("Dates", function(){ actual = I18n.parseDate("2009-01-24T15:34:44.200Z"); expect(actual.toString()).toEqual(expected.toString()); expect(actual.getMilliseconds()).toEqual(expected.getMilliseconds()) + + expected = new Date(Date.UTC(2009, 0, 24, 15, 34, 45, 200)); + actual = I18n.parseDate("2009-01-24T15:34:45.200+0000"); + expect(actual.toString()).toEqual(expected.toString()); + expect(actual.getMilliseconds()).toEqual(expected.getMilliseconds()) + + expected = new Date(Date.UTC(2009, 0, 24, 15, 34, 46, 200)); + actual = I18n.parseDate("2009-01-24T15:34:46.200+00:00"); + expect(actual.toString()).toEqual(expected.toString()); + expect(actual.getMilliseconds()).toEqual(expected.getMilliseconds()) }); it("formats date", function(){ From 80dab90337714a684a323b8ad6ba27186c1dfb79 Mon Sep 17 00:00:00 2001 From: Krzysztof Herod Date: Fri, 25 Apr 2014 15:40:33 +0200 Subject: [PATCH 022/466] add entry about milliseconds to the changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a2c4928..a37a12b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Add back rake task for export (`rake i18n:js:export`) - Not overriding translation when manually run `I18n::JS.export` (https://github.com/fnando/i18n-js/pull/171) - Move missing placeholder text generation into its own function (for easier debugging) (https://github.com/fnando/i18n-js/pull/169) +- Add support for milliseconds (`lll` in `yyyy-mm-ddThh:mm:ss.lllZ`) (https://github.com/fnando/i18n-js/pull/192) ### bug fixes - Fix regression: asset not being reloaded in development when translation changed From d36831f6c78b66218929e198d3195816030f8436 Mon Sep 17 00:00:00 2001 From: Joe Lencioni Date: Thu, 24 Apr 2014 11:48:51 -0700 Subject: [PATCH 023/466] Fix minor errors in readme There was a typo and some incorrect capitalization that I wanted to fix in here. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 61e3e5eb..380fce75 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # I18n.js -It's a small library to provide the Rails I18n translations on the Javascript. +It's a small library to provide the Rails I18n translations on the JavaScript. Features: @@ -88,7 +88,7 @@ In practice, you'll have something like the following in your `application.html. You can use translate your messages: I18n.t("some.scoped.translation"); - // or translate with explicite setting of locale + // or translate with explicit setting of locale I18n.t("some.scoped.translation", {locale: "fr"}); You can also interpolate values: @@ -125,7 +125,7 @@ are three different ways of doing it so: I18n.locales.no = "nb"; I18n.locales.no = function(locale){ return ["nb"]; }; -Pluralization is possible as well and by default provides english rules: +Pluralization is possible as well and by default provides English rules: I18n.t("inbox.counting", {count: 10}); // You have 10 messages From 623aa5b7858f7a15251204cb2be9dab9fa01cee5 Mon Sep 17 00:00:00 2001 From: Luciano Sousa Date: Thu, 1 May 2014 23:27:50 -0300 Subject: [PATCH 024/466] Update README.md just because we need avoid https error --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 61e3e5eb..e7451a60 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Features: Add the gem to your Gemfile. - source :rubygems + source "https://rubygems.org" gem "rails", "3.2.3" gem "i18n-js" From d4ca88e2de7c0bcd85932baace8c255eece12427 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 23 Dec 2013 12:34:59 +0800 Subject: [PATCH 025/466] + Add travis config --- .travis.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..d3364910 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: ruby +rvm: + - 1.9.3 + - 2.0.0 + - 2.1.1 +script: "bundle exec rake spec" +before_install: # Need to install npm to test js +- sudo apt-get update +- sudo apt-get install npm +- npm install jasmine-node From 4027860f6ff4e5d090598737802063a555211203 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 2 May 2014 13:26:49 +0800 Subject: [PATCH 026/466] ~ Add Travis badge to README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index e7451a60..967a7404 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,10 @@ Features: - Asset pipeline support - Lots more! :) +## Code Status + +- [![Build Status](https://travis-ci.org/fnando/i18n-js.svg?branch=master)](https://travis-ci.org/fnando/i18n-js) + ## Usage ### Installation From c53ad1ea80823089cb18d682660e13fa5503ff09 Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Fri, 2 May 2014 03:58:38 -0300 Subject: [PATCH 027/466] Fix lib path. --- spec/js/specs.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/js/specs.html b/spec/js/specs.html index b5e14c8d..8c2ab1f5 100644 --- a/spec/js/specs.html +++ b/spec/js/specs.html @@ -12,7 +12,7 @@ - + + + + + + + + + + + diff --git a/spec/js/translations.js b/spec/js/translations.js index 7d0d5e8b..fddfad32 100644 --- a/spec/js/translations.js +++ b/spec/js/translations.js @@ -127,7 +127,9 @@ var DEBUG = false; return Translations; }; - if (typeof(exports) === "undefined") { + if (typeof define === 'function' && define.amd) { + define(function() { return generator; }); + } else if (typeof(exports) === "undefined") { window.Translations = generator; } else { module.exports = generator; From bfc931589cf296d38cebc80bc0708572b99b6824 Mon Sep 17 00:00:00 2001 From: Brian Gillis Date: Thu, 5 Mar 2015 00:02:22 -0500 Subject: [PATCH 159/466] Fixed require mapping problem with explicit module naming --- spec/js/specs_requirejs.html | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/spec/js/specs_requirejs.html b/spec/js/specs_requirejs.html index 23e9674a..1590ed5c 100644 --- a/spec/js/specs_requirejs.html +++ b/spec/js/specs_requirejs.html @@ -44,13 +44,22 @@ "paths": { "i18n": "../../app/assets/javascripts/i18n" }, + /* Since the i18n.js file explicitly names its module 'i18n', but each + spec uses the full path in the require statement, it appears that + loading fails. This map will map the full path to the short form. + */ + "map": { + "*": { + "../../app/assets/javascripts/i18n": "i18n" + } + }, "shim": shims }); - require((["../../app/assets/javascripts/i18n", './translations']).concat(testScripts), + require((["i18n", './translations']).concat(testScripts), function(I18n, translationFactory) { // Example for using the translation factory - //I18n.translations = translationFactory(); - //console.log(I18n.t('hello')); + I18n.translations = translationFactory(); + console.log(I18n.t('hello')); // Execute the specs jasmine.getEnv().addReporter(new jasmine.TrivialReporter()); From de1e869b128637f0e77613d7593f7e19fe58a818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Go=C5=9Bcicki?= Date: Thu, 5 Mar 2015 14:58:30 +0100 Subject: [PATCH 160/466] Add .deep_reject method to JS::Utils It is necessary for the :except option (to deep reject keys from the translations hash that we don't want to include in the outputted translations). --- lib/i18n/js/utils.rb | 12 ++++++++++-- spec/utils_spec.rb | 30 ++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/lib/i18n/js/utils.rb b/lib/i18n/js/utils.rb index e7d0b06c..b8b537d7 100644 --- a/lib/i18n/js/utils.rb +++ b/lib/i18n/js/utils.rb @@ -5,11 +5,11 @@ module Utils MERGER = proc do |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &MERGER) : v2 end + HASH_NIL_VALUE_CLEANER_PROC = proc do |k, v| - v.kind_of?(Hash) ? (v.delete_if(&HASH_NIL_VALUE_CLEANER_PROC); false) : v.nil? + v.kind_of?(Hash) ? (v.delete_if(&HASH_NIL_VALUE_CLEANER_PROC); false) : v.nil? end - def self.strip_keys_with_nil_values(hash) hash.dup.delete_if(&HASH_NIL_VALUE_CLEANER_PROC) end @@ -21,6 +21,14 @@ def self.deep_merge(target_hash, hash) # :nodoc: def self.deep_merge!(target_hash, hash) # :nodoc: target_hash.merge!(hash, &MERGER) end + + def self.deep_reject(hash, &block) + hash.each_with_object({}) do |(k, v), memo| + unless block.call(k, v) + memo[k] = v.kind_of?(Hash) ? deep_reject(v, &block) : v + end + end + end end end end diff --git a/spec/utils_spec.rb b/spec/utils_spec.rb index b311853a..e30152b0 100644 --- a/spec/utils_spec.rb +++ b/spec/utils_spec.rb @@ -1,3 +1,5 @@ +require "spec_helper" + describe I18n::JS::Utils do describe ".strip_keys_with_nil_values" do @@ -36,4 +38,32 @@ target[:a].should eql({:b => 1, :c => 2}) end end + + describe ".deep_reject" do + it "performs a deep keys rejection" do + hash = {:a => {:b => 1}} + + result = described_class.deep_reject(hash) { |k, v| k == :b } + + result.should eql({:a => {}}) + end + + it "performs a deep keys rejection prunning the whole tree if necessary" do + hash = {:a => {:b => {:c => {:d => 1, :e => 2}}}} + + result = described_class.deep_reject(hash) { |k, v| k == :b } + + result.should eql({:a => {}}) + end + + + it "performs a deep keys rejection without changing the original hash" do + hash = {:a => {:b => 1, :c => 2}} + + result = described_class.deep_reject(hash) { |k, v| k == :b } + + result.should eql({:a => {:c => 2}}) + hash.should eql({:a => {:b => 1, :c => 2}}) + end + end end From 3d33225682435b31a039d297aacb22c42a79fac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Go=C5=9Bcicki?= Date: Thu, 5 Mar 2015 15:07:48 +0100 Subject: [PATCH 161/466] Add support for :except config option It allows you to exclude certain keys from both the exported JS translations file and from the file generated for the Rails' assests pipeline. Example: ```yaml translations: - except: ['active_admin', 'ransack'] ``` This will make it so that the generated/exported JS file including all translations will skip any phrases which are listed under the `active_admin` or `ransack` keys (which makes sense as you don't need translations from those gems in the frontend; they only unnecessarily make your translations file bigger). This configuration is valid for each "segment" in the config file, so you can have various exception rules for each segment. --- lib/i18n/js.rb | 41 ++++++++++++++++++++--------- spec/fixtures/except_condition.yml | 5 ++++ spec/i18n_js_spec.rb | 42 ++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 12 deletions(-) create mode 100644 spec/fixtures/except_condition.yml diff --git a/lib/i18n/js.rb b/lib/i18n/js.rb index 0dbc0697..510476b1 100644 --- a/lib/i18n/js.rb +++ b/lib/i18n/js.rb @@ -34,22 +34,22 @@ def self.export translation_segments.each(&:save!) end - def self.segments_per_locale(pattern, scope, options) + def self.segments_per_locale(pattern, scope, exceptions, options) I18n.available_locales.each_with_object([]) do |locale, segments| scope = [scope] unless scope.respond_to?(:each) - result = scoped_translations(scope.collect{|s| "#{locale}.#{s}"}) - merge_with_fallbacks!(result, locale, scope) if use_fallbacks? + result = scoped_translations(scope.collect{|s| "#{locale}.#{s}"}, exceptions) + merge_with_fallbacks!(result, locale, scope, exceptions) if use_fallbacks? next if result.empty? segments << Segment.new(::I18n.interpolate(pattern, {:locale => locale}), result, options) end end - def self.segment_for_scope(scope) + def self.segment_for_scope(scope, exceptions) if scope == "*" - translations + exclude(translations, exceptions) else - scoped_translations(scope) + scoped_translations(scope, exceptions) end end @@ -57,14 +57,17 @@ def self.configured_segments config[:translations].inject([]) do |segments, options| file = options[:file] only = options[:only] || '*' + exceptions = [options[:except] || []].flatten + segment_options = options.slice(:namespace, :pretty_print) if file =~ ::I18n::INTERPOLATION_PATTERN - segments += segments_per_locale(file, only, segment_options) + segments += segments_per_locale(file, only, exceptions, segment_options) else - result = segment_for_scope(only) + result = segment_for_scope(only, exceptions) segments << Segment.new(file, result, segment_options) unless result.empty? end + segments end end @@ -101,16 +104,30 @@ def self.config? File.file? config_file_path end - def self.scoped_translations(scopes) # :nodoc: + def self.scoped_translations(scopes, exceptions = []) # :nodoc: result = {} [scopes].flatten.each do |scope| - Utils.deep_merge! result, filter(translations, scope) + translations_without_exceptions = exclude(translations, exceptions) + filtered_translations = filter(translations_without_exceptions, scope) + + Utils.deep_merge! result, filtered_translations end result end + # Exclude keys from translations listed in the `except:` section in the config file + def self.exclude(translations, exceptions) + return translations if exceptions.empty? + + exceptions.inject(translations) do |memo, exception| + Utils.deep_reject(memo) do |key, value| + key.to_s == exception.to_s + end + end + end + # Filter translations according to the specified scope. def self.filter(translations, scopes) scopes = scopes.split(".") if scopes.is_a?(String) @@ -150,12 +167,12 @@ def self.fallbacks end # deep_merge! given result with result for fallback locale - def self.merge_with_fallbacks!(result, locale, scope) + def self.merge_with_fallbacks!(result, locale, scope, exceptions) result[locale] ||= {} fallback_locales = FallbackLocales.new(fallbacks, locale) fallback_locales.each do |fallback_locale| - fallback_result = scoped_translations(scope.collect{|s| "#{fallback_locale}.#{s}"}) # NOTE: Duplicated code here + fallback_result = scoped_translations(scope.collect{|s| "#{fallback_locale}.#{s}"}, exceptions) # NOTE: Duplicated code here result[locale] = Utils.deep_merge(fallback_result[fallback_locale], result[locale]) end end diff --git a/spec/fixtures/except_condition.yml b/spec/fixtures/except_condition.yml new file mode 100644 index 00000000..5370a8c7 --- /dev/null +++ b/spec/fixtures/except_condition.yml @@ -0,0 +1,5 @@ +translations: + - file: "tmp/i18n-js/trimmed.js" + except: + - "active_admin" + - "mailers" diff --git a/spec/i18n_js_spec.rb b/spec/i18n_js_spec.rb index 22a64c28..378d7357 100644 --- a/spec/i18n_js_spec.rb +++ b/spec/i18n_js_spec.rb @@ -86,6 +86,13 @@ end end + it "exports with :except condition" do + set_config "except_condition.yml" + I18n::JS.export + + file_should_exist "trimmed.js" + end + it "calls .export_i18n_js" do allow(described_class).to receive(:export_i18n_js) I18n::JS.export @@ -133,6 +140,41 @@ end end + context "exceptions" do + it "does not include a key listed in the exceptions list" do + result = I18n::JS.scoped_translations("*", ['admin']) + + result[:en][:admin].should be_nil + result[:fr][:admin].should be_nil + end + + it "does not include multiple keys listed in the exceptions list" do + result = I18n::JS.scoped_translations("*", ['title', 'note']) + + result[:en][:admin][:show].should be_empty + result[:en][:admin][:edit].should be_empty + + result[:fr][:admin][:show].should be_empty + result[:fr][:admin][:show].should be_empty + result[:fr][:admin][:edit].should be_empty + end + + it "does not include a key listed in the exceptions list and respecs the 'only' option" do + result = I18n::JS.scoped_translations("fr.*", ['date', 'time', 'number', 'show']) + + result[:en].should be_nil + result[:de].should be_nil + result[:ja].should be_nil + + result[:fr][:date].should be_nil + result[:fr][:time].should be_nil + result[:fr][:number].should be_nil + result[:fr][:admin][:show].should be_nil + + result[:fr][:admin][:edit][:title].should be_a(String) + end + end + context "fallbacks" do subject do I18n::JS.translation_segments.inject({}) do |hash, segment| From 8bc7480ff54d96a378fee1371e636c0798c41430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Go=C5=9Bcicki?= Date: Thu, 5 Mar 2015 15:19:30 +0100 Subject: [PATCH 162/466] Add CHANGELOG entry for :except option --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c835dda5..aae75a4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ - Add option `namespace` & `pretty_print` ([#300](https://github.com/fnando/i18n-js/pull/300)) - Add option `export_i18n_js` ([#301](https://github.com/fnando/i18n-js/pull/301)) - Now the gem also detects pre-release versions of `rails` +- Add `:except` option to exclude certain phrases or groups of phrases from the + outputted translations ([#312](https://github.com/fnando/i18n-js/pull/312)) ### bug fixes From 7d197eed43f7d10ef979a580dc415605a11e5a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Go=C5=9Bcicki?= Date: Thu, 5 Mar 2015 15:25:03 +0100 Subject: [PATCH 163/466] Add README note about `except` config option --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 1a0a27f3..6011c3e1 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,17 @@ translations: <% end %> ``` +You are able to exclude certain phrases or whole groups of phrases by +specifying the YAML key(s) in the `except` configuration option. The outputted +JS translations file (exported or generated by the middleware) will omit any +keys listed in `except` configuration param: + +```yaml +translations: + - except: ['active_admin', 'ransack'] +``` + + #### Export Configuration (For other things) - `I18n::JS.config_file_path` From 09332cd81ffa3b3675ed919f2d6821746d41cd8f Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 6 Mar 2015 09:47:28 +0800 Subject: [PATCH 164/466] * 1.9.3 is EOL --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a525a1a9..2b405d62 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: ruby rvm: - - 1.9.3 - 2.0 - 2.1 - 2.2 From a2d81ed84bd0999d6c7c2f111d23a3e5923c678c Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 10 Mar 2015 11:31:54 +0800 Subject: [PATCH 165/466] * Update JS spec for interpolation to test more different cases --- spec/js/interpolation.spec.js | 62 ++++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/spec/js/interpolation.spec.js b/spec/js/interpolation.spec.js index d02f60ac..eaba0a6a 100644 --- a/spec/js/interpolation.spec.js +++ b/spec/js/interpolation.spec.js @@ -20,10 +20,64 @@ describe("Interpolation", function(){ expect(actual).toEqual("John Doe is 27-years old"); }); - it("performs interpolation with the count option", function(){ - expect(I18n.t("inbox", {count: 0})).toEqual("You have no messages"); - expect(I18n.t("inbox", {count: 1})).toEqual("You have 1 message"); - expect(I18n.t("inbox", {count: 5})).toEqual("You have 5 messages"); + describe("Pluralization", function() { + var translation_key; + + describe("when count is passed in", function() { + describe("and translation key does contain pluralization", function() { + beforeEach(function() { + translation_key = "inbox"; + }); + + it("return translated and pluralized string", function() { + expect(I18n.t(translation_key, {count: 0})).toEqual("You have no messages"); + expect(I18n.t(translation_key, {count: 1})).toEqual("You have 1 message"); + expect(I18n.t(translation_key, {count: 5})).toEqual("You have 5 messages"); + }); + }); + describe("and translation key does NOT contain pluralization", function() { + beforeEach(function() { + translation_key = "hello"; + }); + + it("return translated string ONLY", function() { + expect(I18n.t(translation_key, {count: 0})).toEqual("Hello World!"); + expect(I18n.t(translation_key, {count: 1})).toEqual("Hello World!"); + expect(I18n.t(translation_key, {count: 5})).toEqual("Hello World!"); + }); + }); + }); + + describe("when count is NOT passed in", function() { + describe("and translation key does contain pluralization", function() { + beforeEach(function() { + translation_key = "inbox"; + }); + + var expected_translation_object = { + one : 'You have {{count}} message', + other : 'You have {{count}} messages', + zero : 'You have no messages' + } + + it("return translated and pluralized string", function() { + expect(I18n.t(translation_key, {not_count: 0})).toEqual(expected_translation_object); + expect(I18n.t(translation_key, {not_count: 1})).toEqual(expected_translation_object); + expect(I18n.t(translation_key, {not_count: 5})).toEqual(expected_translation_object); + }); + }); + describe("and translation key does NOT contain pluralization", function() { + beforeEach(function() { + translation_key = "hello"; + }); + + it("return translated string ONLY", function() { + expect(I18n.t(translation_key, {not_count: 0})).toEqual("Hello World!"); + expect(I18n.t(translation_key, {not_count: 1})).toEqual("Hello World!"); + expect(I18n.t(translation_key, {not_count: 5})).toEqual("Hello World!"); + }); + }); + }); }); it("outputs missing placeholder message if interpolation value is missing", function(){ From ab4bc2af10221141f04c738e1b1eb94ec381a661 Mon Sep 17 00:00:00 2001 From: Brian Gillis Date: Wed, 11 Mar 2015 11:40:32 -0400 Subject: [PATCH 166/466] Issue #314 - RequireJS optimization doesn't correctly detect define method in i18n.js --- app/assets/javascripts/i18n.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index af5cd5f2..b5513d4a 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -18,7 +18,8 @@ module.exports = factory(this); } else if (typeof define === 'function' && define.amd) { // AMD - define('i18n', (function(global){ return function(){ return factory(global); }})(this)); + var global=this; + define('i18n', function(){ return factory(global);}); } else { // Browser globals this.I18n = factory(this); From 3a086ba45dcd41ae4aa6d3c16ecdbf8269ed5f69 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 12 Mar 2015 10:58:22 +0800 Subject: [PATCH 167/466] ~ Fix CHANGELOG [ci skip] --- CHANGELOG.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aae75a4e..1a0aa3d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,19 +1,18 @@ ## Unreleased -- You can now set `I18n.missingBehavior='guess'` to have the scope string output as text instead of of the - "[missing `scope`]" message when no translation is available. -- Combined that with `I18n.missingTranslationPrefix='SOMETHING'` and you can - still identify those missing strings. - ### enhancements -- Force currency number sign to be at first place using `sign_first` option, default to `true` -- Add option `namespace` & `pretty_print` ([#300](https://github.com/fnando/i18n-js/pull/300)) -- Add option `export_i18n_js` ([#301](https://github.com/fnando/i18n-js/pull/301)) -- Now the gem also detects pre-release versions of `rails` -- Add `:except` option to exclude certain phrases or groups of phrases from the +- [JS] Force currency number sign to be at first place using `sign_first` option, default to `true` +- [Ruby] Add option `namespace` & `pretty_print` ([#300](https://github.com/fnando/i18n-js/pull/300)) +- [Ruby] Add option `export_i18n_js` ([#301](https://github.com/fnando/i18n-js/pull/301)) +- [Ruby] Now the gem also detects pre-release versions of `rails` +- [Ruby] Add `:except` option to exclude certain phrases or groups of phrases from the outputted translations ([#312](https://github.com/fnando/i18n-js/pull/312)) +- [JS] You can now set `I18n.missingBehavior='guess'` to have the scope string output as text instead of of the + "[missing `scope`]" message when no translation is available. +- [JS] Combined that with `I18n.missingTranslationPrefix='SOMETHING'` and you can + still identify those missing strings. ### bug fixes From 48892bcd128d7888f0c727aae26f9c7926264664 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 12 Mar 2015 11:00:41 +0800 Subject: [PATCH 168/466] ^ Bump version to 3.0.0.rc9 --- lib/i18n/js/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index e2878845..4945f080 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -4,7 +4,7 @@ module Version MAJOR = 3 MINOR = 0 PATCH = 0 - STRING = "#{MAJOR}.#{MINOR}.#{PATCH}.rc8" + STRING = "#{MAJOR}.#{MINOR}.#{PATCH}.rc9" end end end From 900fd31e132f14b7586af28e20271568a700cea3 Mon Sep 17 00:00:00 2001 From: David Vega Date: Fri, 13 Mar 2015 13:17:02 -0700 Subject: [PATCH 169/466] Missing closing ` on README.md L146 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6011c3e1..f216b910 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ export_i18n_js: "my/path" translations: - ... -`` +``` To find more examples on how to use the configuration file please refer to the tests. From 182a25ce601c80bd84a0992ea436e0d4a03b6cc9 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 16 Mar 2015 09:34:39 +0800 Subject: [PATCH 170/466] ~ Update CHANGELOG for released version [ci skip] --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a0aa3d3..af4d7c58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ ### enhancements +### bug fixes + + +## 3.0.0.rc9 + +### enhancements + - [JS] Force currency number sign to be at first place using `sign_first` option, default to `true` - [Ruby] Add option `namespace` & `pretty_print` ([#300](https://github.com/fnando/i18n-js/pull/300)) - [Ruby] Add option `export_i18n_js` ([#301](https://github.com/fnando/i18n-js/pull/301)) From ae7bc897eddce79427a81f02063209c56fc84d01 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 16 Mar 2015 09:38:09 +0800 Subject: [PATCH 171/466] ~ Add back link for PR and merge entries into one entry for one PR inside CHANGELOG [ci skip] --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af4d7c58..3ba3259a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,9 +17,10 @@ - [Ruby] Add `:except` option to exclude certain phrases or groups of phrases from the outputted translations ([#312](https://github.com/fnando/i18n-js/pull/312)) - [JS] You can now set `I18n.missingBehavior='guess'` to have the scope string output as text instead of of the - "[missing `scope`]" message when no translation is available. -- [JS] Combined that with `I18n.missingTranslationPrefix='SOMETHING'` and you can + "[missing `scope`]" message when no translation is available. + Combined that with `I18n.missingTranslationPrefix='SOMETHING'` and you can still identify those missing strings. + ([#304](https://github.com/fnando/i18n-js/pull/304)) ### bug fixes From f7fdcd1ccdbaf05d936a5f8db3ea461d0c148c08 Mon Sep 17 00:00:00 2001 From: Matt Jonker Date: Tue, 24 Mar 2015 13:50:58 -0400 Subject: [PATCH 172/466] Deep sort hash keys before converting to JSON --- app/assets/javascripts/i18n/filtered.js.erb | 2 +- lib/i18n/js/segment.rb | 5 +++-- lib/i18n/js/utils.rb | 10 ++++++++++ spec/utils_spec.rb | 19 +++++++++++++++++++ 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/i18n/filtered.js.erb b/app/assets/javascripts/i18n/filtered.js.erb index 8a0cae62..4f629beb 100644 --- a/app/assets/javascripts/i18n/filtered.js.erb +++ b/app/assets/javascripts/i18n/filtered.js.erb @@ -14,5 +14,5 @@ }(function(I18n) { "use strict"; - I18n.translations = <%= I18n::JS.filtered_translations.to_json %>; + I18n.translations = <%= I18n::JS::Utils.deep_key_sort(I18n::JS.filtered_translations).to_json %>; })); diff --git a/lib/i18n/js/segment.rb b/lib/i18n/js/segment.rb index bb6d0ef1..87d12bfe 100644 --- a/lib/i18n/js/segment.rb +++ b/lib/i18n/js/segment.rb @@ -28,10 +28,11 @@ def save! # Outputs pretty or ugly JSON depending on :pretty_print option def print_json(translations) + sorted_translations = Utils.deep_key_sort(translations) if pretty_print - JSON.pretty_generate(translations) + JSON.pretty_generate(sorted_translations) else - translations.to_json + sorted_translations.to_json end end end diff --git a/lib/i18n/js/utils.rb b/lib/i18n/js/utils.rb index b8b537d7..ef570e7b 100644 --- a/lib/i18n/js/utils.rb +++ b/lib/i18n/js/utils.rb @@ -29,6 +29,16 @@ def self.deep_reject(hash, &block) end end end + + def self.deep_key_sort(hash, &block) + hash.keys.sort(&block).reduce({}) do |seed, key| + seed[key] = hash[key] + if seed[key].is_a?(Hash) + seed[key] = deep_key_sort(seed[key], &block) + end + seed + end + end end end end diff --git a/spec/utils_spec.rb b/spec/utils_spec.rb index e30152b0..e8062b6e 100644 --- a/spec/utils_spec.rb +++ b/spec/utils_spec.rb @@ -66,4 +66,23 @@ hash.should eql({:a => {:b => 1, :c => 2}}) end end + + describe ".deep_key_sort" do + it "performs a deep keys sort" do + hash = {:z => {:b => 1, :a => 2}, :y => 3} + + result = described_class.deep_key_sort(hash) + + result.should eql({:y => 3, :z => {:a => 2, :b => 1}}) + end + + it "performs a deep keys sort without changing the original hash" do + hash = {:z => {:b => 1, :a => 2}, :y => 3} + + result = described_class.deep_key_sort(hash) + + result.should eql({:y => 3, :z => {:a => 2, :b => 1}}) + hash.should eql({:z => {:b => 1, :a => 2}, :y => 3}) + end + end end From 01f7ce38f7cec18a1c878353c2b85645bd4faba5 Mon Sep 17 00:00:00 2001 From: Johnny Shields Date: Sun, 29 Mar 2015 07:21:00 +0900 Subject: [PATCH 173/466] Unify/simplify the behavior of per-locale and non-per-locale files by moving per-locale generation to Segment class --- lib/i18n/js.rb | 45 ++++++--------- lib/i18n/js/segment.rb | 24 ++++++-- spec/fixtures/custom_path.yml | 3 +- spec/fixtures/default.yml | 3 +- spec/fixtures/erb.yml | 3 +- spec/fixtures/except_condition.yml | 2 + spec/fixtures/js_export_dir_custom.yml | 1 + spec/fixtures/js_export_dir_none.yml | 2 +- spec/fixtures/js_file_per_locale.yml | 6 +- ...s_file_with_namespace_and_pretty_print.yml | 2 + spec/fixtures/multiple_conditions.yml | 2 + .../multiple_conditions_per_locale.yml | 6 +- spec/fixtures/multiple_files.yml | 3 +- spec/fixtures/no_scope.yml | 3 +- spec/fixtures/simple_scope.yml | 3 +- spec/i18n_js_spec.rb | 56 +++++++++++++------ spec/segment_spec.rb | 31 ++++++++-- 17 files changed, 130 insertions(+), 65 deletions(-) diff --git a/lib/i18n/js.rb b/lib/i18n/js.rb index 510476b1..199b77d4 100644 --- a/lib/i18n/js.rb +++ b/lib/i18n/js.rb @@ -34,17 +34,6 @@ def self.export translation_segments.each(&:save!) end - def self.segments_per_locale(pattern, scope, exceptions, options) - I18n.available_locales.each_with_object([]) do |locale, segments| - scope = [scope] unless scope.respond_to?(:each) - result = scoped_translations(scope.collect{|s| "#{locale}.#{s}"}, exceptions) - merge_with_fallbacks!(result, locale, scope, exceptions) if use_fallbacks? - - next if result.empty? - segments << Segment.new(::I18n.interpolate(pattern, {:locale => locale}), result, options) - end - end - def self.segment_for_scope(scope, exceptions) if scope == "*" exclude(translations, exceptions) @@ -61,17 +50,26 @@ def self.configured_segments segment_options = options.slice(:namespace, :pretty_print) - if file =~ ::I18n::INTERPOLATION_PATTERN - segments += segments_per_locale(file, only, exceptions, segment_options) - else - result = segment_for_scope(only, exceptions) - segments << Segment.new(file, result, segment_options) unless result.empty? - end + result = segment_for_scope(only, exceptions) + + merge_with_fallbacks!(result) if fallbacks + + segments << Segment.new(file, result, segment_options) unless result.empty? segments end end + # deep_merge! given result with result for fallback locale + def self.merge_with_fallbacks!(result) + I18n.available_locales.each do |locale| + fallback_locales = FallbackLocales.new(fallbacks, locale) + fallback_locales.each do |fallback_locale| + result[locale] = Utils.deep_merge(result[fallback_locale], result[locale] || {}) + end + end + end + def self.filtered_translations {}.tap do |result| translation_segments.each do |segment| @@ -111,7 +109,7 @@ def self.scoped_translations(scopes, exceptions = []) # :nodoc: translations_without_exceptions = exclude(translations, exceptions) filtered_translations = filter(translations_without_exceptions, scope) - Utils.deep_merge! result, filtered_translations + Utils.deep_merge! result, filtered_translations || {} end result @@ -166,17 +164,6 @@ def self.fallbacks end end - # deep_merge! given result with result for fallback locale - def self.merge_with_fallbacks!(result, locale, scope, exceptions) - result[locale] ||= {} - fallback_locales = FallbackLocales.new(fallbacks, locale) - - fallback_locales.each do |fallback_locale| - fallback_result = scoped_translations(scope.collect{|s| "#{fallback_locale}.#{s}"}, exceptions) # NOTE: Duplicated code here - result[locale] = Utils.deep_merge(fallback_result[fallback_locale], result[locale]) - end - end - ### Export i18n.js begin diff --git a/lib/i18n/js/segment.rb b/lib/i18n/js/segment.rb index bb6d0ef1..0fa14227 100644 --- a/lib/i18n/js/segment.rb +++ b/lib/i18n/js/segment.rb @@ -5,6 +5,8 @@ module JS class Segment attr_accessor :file, :translations, :namespace, :pretty_print + LOCALE_INTERPOLATOR = /%\{locale\}/ + def initialize(file, translations, options = {}) @file = file @translations = translations @@ -14,12 +16,21 @@ def initialize(file, translations, options = {}) # Saves JSON file containing translations def save! - FileUtils.mkdir_p File.dirname(self.file) + if file =~ LOCALE_INTERPOLATOR + I18n.available_locales.each do |locale| + write_file(file_for_locale(locale), self.translations.slice(locale)) + end + else + write_file(self.file, self.translations) + end + end - File.open(self.file, "w+") do |f| + def write_file(_file, _translations) + FileUtils.mkdir_p File.dirname(_file) + File.open(_file, "w+") do |f| f << %(#{self.namespace}.translations || (#{self.namespace}.translations = {});\n) - self.translations.each do |locale, translations| - f << %(#{self.namespace}.translations["#{locale}"] = #{print_json(translations)};\n); + _translations.each do |locale, translations_for_locale| + f << %(#{self.namespace}.translations["#{locale}"] = #{print_json(translations_for_locale)};\n); end end end @@ -34,6 +45,11 @@ def print_json(translations) translations.to_json end end + + # interpolates filename + def file_for_locale(locale) + self.file.gsub(LOCALE_INTERPOLATOR, locale.to_s) + end end end end diff --git a/spec/fixtures/custom_path.yml b/spec/fixtures/custom_path.yml index 73597cd0..0ba16862 100755 --- a/spec/fixtures/custom_path.yml +++ b/spec/fixtures/custom_path.yml @@ -1,4 +1,5 @@ -# Find more details about this configuration file at http://github.com/fnando/i18n-js +fallbacks: false + translations: - file: "tmp/i18n-js/all.js" only: "*" diff --git a/spec/fixtures/default.yml b/spec/fixtures/default.yml index f05086fd..fdaeb81b 100755 --- a/spec/fixtures/default.yml +++ b/spec/fixtures/default.yml @@ -1,4 +1,5 @@ -# Find more details about this configuration file at http://github.com/fnando/i18n-js +fallbacks: false + translations: - file: "tmp/i18n-js/translations.js" only: "*" diff --git a/spec/fixtures/erb.yml b/spec/fixtures/erb.yml index db8fe686..3917c9e5 100644 --- a/spec/fixtures/erb.yml +++ b/spec/fixtures/erb.yml @@ -1,4 +1,5 @@ -# Find more details about this configuration file at http://github.com/fnando/i18n-js +fallbacks: false + translations: - file: "tmp/i18n-js/translations.js" only: '<%= "*." + "date." + "formats" %>' diff --git a/spec/fixtures/except_condition.yml b/spec/fixtures/except_condition.yml index 5370a8c7..7388c560 100644 --- a/spec/fixtures/except_condition.yml +++ b/spec/fixtures/except_condition.yml @@ -1,3 +1,5 @@ +fallbacks: false + translations: - file: "tmp/i18n-js/trimmed.js" except: diff --git a/spec/fixtures/js_export_dir_custom.yml b/spec/fixtures/js_export_dir_custom.yml index dbc82b74..5bcb49c4 100644 --- a/spec/fixtures/js_export_dir_custom.yml +++ b/spec/fixtures/js_export_dir_custom.yml @@ -1,3 +1,4 @@ +fallbacks: false export_i18n_js: 'tmp/i18n-js/foo' diff --git a/spec/fixtures/js_export_dir_none.yml b/spec/fixtures/js_export_dir_none.yml index 1367a370..1936624c 100644 --- a/spec/fixtures/js_export_dir_none.yml +++ b/spec/fixtures/js_export_dir_none.yml @@ -1,4 +1,4 @@ - +fallbacks: false export_i18n_js: false translations: diff --git a/spec/fixtures/js_file_per_locale.yml b/spec/fixtures/js_file_per_locale.yml index 315f98cc..60a30abf 100755 --- a/spec/fixtures/js_file_per_locale.yml +++ b/spec/fixtures/js_file_per_locale.yml @@ -1,3 +1,7 @@ +fallbacks: false + translations: - file: "tmp/i18n-js/%{locale}.js" - only: '*' + only: + - '*.date.*' + - '*.admin.*' diff --git a/spec/fixtures/js_file_with_namespace_and_pretty_print.yml b/spec/fixtures/js_file_with_namespace_and_pretty_print.yml index b485db7c..56fe52bc 100644 --- a/spec/fixtures/js_file_with_namespace_and_pretty_print.yml +++ b/spec/fixtures/js_file_with_namespace_and_pretty_print.yml @@ -1,3 +1,5 @@ +fallbacks: false + translations: - file: "tmp/i18n-js/%{locale}.js" only: '*' diff --git a/spec/fixtures/multiple_conditions.yml b/spec/fixtures/multiple_conditions.yml index 85c24d13..3a94d9da 100755 --- a/spec/fixtures/multiple_conditions.yml +++ b/spec/fixtures/multiple_conditions.yml @@ -1,3 +1,5 @@ +fallbacks: false + translations: - file: "tmp/i18n-js/bitsnpieces.js" only: diff --git a/spec/fixtures/multiple_conditions_per_locale.yml b/spec/fixtures/multiple_conditions_per_locale.yml index da2962a9..5677c0f1 100644 --- a/spec/fixtures/multiple_conditions_per_locale.yml +++ b/spec/fixtures/multiple_conditions_per_locale.yml @@ -1,5 +1,7 @@ +fallbacks: false + translations: - file: "tmp/i18n-js/bits.%{locale}.js" only: - - "date.formats" - - "number.currency" + - "*.date.formats.*" + - "*.number.currency.*" diff --git a/spec/fixtures/multiple_files.yml b/spec/fixtures/multiple_files.yml index 9a94fe02..1d55a882 100755 --- a/spec/fixtures/multiple_files.yml +++ b/spec/fixtures/multiple_files.yml @@ -1,4 +1,5 @@ -# Find more details about this configuration file at http://github.com/fnando/i18n-js +fallbacks: false + translations: - file: "tmp/i18n-js/all.js" only: "*" diff --git a/spec/fixtures/no_scope.yml b/spec/fixtures/no_scope.yml index 911f960a..ce93d30d 100755 --- a/spec/fixtures/no_scope.yml +++ b/spec/fixtures/no_scope.yml @@ -1,3 +1,4 @@ -# Find more details about this configuration file at http://github.com/fnando/i18n-js +fallbacks: false + translations: - file: "tmp/i18n-js/no_scope.js" diff --git a/spec/fixtures/simple_scope.yml b/spec/fixtures/simple_scope.yml index 4169a2d9..1aab4f65 100755 --- a/spec/fixtures/simple_scope.yml +++ b/spec/fixtures/simple_scope.yml @@ -1,4 +1,5 @@ -# Find more details about this configuration file at http://github.com/fnando/i18n-js +fallbacks: false + translations: - file: "tmp/i18n-js/simple_scope.js" only: "*.date.formats" diff --git a/spec/i18n_js_spec.rb b/spec/i18n_js_spec.rb index 378d7357..d93b4e55 100644 --- a/spec/i18n_js_spec.rb +++ b/spec/i18n_js_spec.rb @@ -1,6 +1,7 @@ require "spec_helper" describe I18n::JS do + describe '.config_file_path' do let(:default_path) { I18n::JS::DEFAULT_CONFIG_PATH } let(:new_path) { File.join("tmp", default_path) } @@ -62,6 +63,19 @@ file_should_exist "en.js" file_should_exist "fr.js" + + en_output = File.read(File.join(I18n::JS.export_i18n_js_dir_path, "en.js")) + expect(en_output).to eq(< { "test" => "Test" }, "fr" => { "test" => "Test2" } } } + let(:translations){ { en: { "test" => "Test" }, fr: { "test" => "Test2" } } } let(:namespace) { "MyNamespace" } let(:pretty_print){ nil } let(:options) { {namespace: namespace, pretty_print: pretty_print} } @@ -58,14 +58,35 @@ before { allow(I18n::JS).to receive(:export_i18n_js_dir_path).and_return(temp_path) } before { subject.save! } - it "should write the file" do - file_should_exist "segment.js" + context "when file does not include %{locale}" do + it "should write the file" do + file_should_exist "segment.js" - File.open(File.join(temp_path, "segment.js")){|f| f.read}.should eql <<-EOF + File.open(File.join(temp_path, "segment.js")){|f| f.read}.should eql <<-EOF MyNamespace.translations || (MyNamespace.translations = {}); MyNamespace.translations["en"] = {"test":"Test"}; MyNamespace.translations["fr"] = {"test":"Test2"}; -EOF + EOF + end + end + + context "when file includes %{locale}" do + let(:file){ "tmp/i18n-js/%{locale}.js" } + + it "should write files" do + file_should_exist "en.js" + file_should_exist "fr.js" + + File.open(File.join(temp_path, "en.js")){|f| f.read}.should eql <<-EOF +MyNamespace.translations || (MyNamespace.translations = {}); +MyNamespace.translations["en"] = {"test":"Test"}; + EOF + + File.open(File.join(temp_path, "fr.js")){|f| f.read}.should eql <<-EOF +MyNamespace.translations || (MyNamespace.translations = {}); +MyNamespace.translations["fr"] = {"test":"Test2"}; + EOF + end end end end From a63f7dc2a9f737bc99c5107226151936f8d73426 Mon Sep 17 00:00:00 2001 From: Johnny Shields Date: Sun, 29 Mar 2015 18:26:31 +0900 Subject: [PATCH 174/466] Fix comments from PR review --- lib/i18n/js.rb | 4 ++-- lib/i18n/js/segment.rb | 10 +++++----- spec/i18n_js_spec.rb | 1 - 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/i18n/js.rb b/lib/i18n/js.rb index 199b77d4..c46b6513 100644 --- a/lib/i18n/js.rb +++ b/lib/i18n/js.rb @@ -107,9 +107,9 @@ def self.scoped_translations(scopes, exceptions = []) # :nodoc: [scopes].flatten.each do |scope| translations_without_exceptions = exclude(translations, exceptions) - filtered_translations = filter(translations_without_exceptions, scope) + filtered_translations = filter(translations_without_exceptions, scope) || {} - Utils.deep_merge! result, filtered_translations || {} + Utils.deep_merge!(result, filtered_translations) end result diff --git a/lib/i18n/js/segment.rb b/lib/i18n/js/segment.rb index 0fa14227..0ca016bb 100644 --- a/lib/i18n/js/segment.rb +++ b/lib/i18n/js/segment.rb @@ -16,16 +16,18 @@ def initialize(file, translations, options = {}) # Saves JSON file containing translations def save! - if file =~ LOCALE_INTERPOLATOR + if self.file =~ LOCALE_INTERPOLATOR I18n.available_locales.each do |locale| write_file(file_for_locale(locale), self.translations.slice(locale)) end else - write_file(self.file, self.translations) + write_file end end - def write_file(_file, _translations) + protected + + def write_file(_file = self.file, _translations = self.translations) FileUtils.mkdir_p File.dirname(_file) File.open(_file, "w+") do |f| f << %(#{self.namespace}.translations || (#{self.namespace}.translations = {});\n) @@ -35,8 +37,6 @@ def write_file(_file, _translations) end end - protected - # Outputs pretty or ugly JSON depending on :pretty_print option def print_json(translations) if pretty_print diff --git a/spec/i18n_js_spec.rb b/spec/i18n_js_spec.rb index d93b4e55..f955c6cc 100644 --- a/spec/i18n_js_spec.rb +++ b/spec/i18n_js_spec.rb @@ -283,7 +283,6 @@ end context "I18n.available_locales" do - # before { allow(I18n::JS).to receive(:fallbacks).and_return(false) } context "when I18n.available_locales is not set" do it "should allow all locales" do From a72cbd1195b6f5c0731f1b5e38909e81f92e44d8 Mon Sep 17 00:00:00 2001 From: johnnyshields Date: Wed, 1 Apr 2015 14:21:05 +0900 Subject: [PATCH 175/466] Update CHANGELOG.md and README.md --- CHANGELOG.md | 6 ++++++ README.md | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ba3259a..34426938 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ ## 3.0.0.rc9 +### breaking changes + +- [Ruby] In `config/i18n-js.yml`, if you are using `%{locale}` in your filename and are referencing specific translations keys, please add `*.` to the beginning of those keys. ([#320](https://github.com/fnando/i18n-js/pull/320)) + ### enhancements - [JS] Force currency number sign to be at first place using `sign_first` option, default to `true` @@ -21,6 +25,8 @@ Combined that with `I18n.missingTranslationPrefix='SOMETHING'` and you can still identify those missing strings. ([#304](https://github.com/fnando/i18n-js/pull/304)) +- [Ruby] Make handling of per-locale and not-per-locale exporting to be more consistent ([#320](https://github.com/fnando/i18n-js/pull/320)) +- [Ruby] Fix fallback logic to work with not-per-locale files ([#320](https://github.com/fnando/i18n-js/pull/320)) ### bug fixes diff --git a/README.md b/README.md index f216b910..6265078f 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ translations: - file: "public/javascripts/i18n/%{locale}.js" only: '*' - file: "public/javascripts/frontend/i18n/%{locale}.js" - only: ['frontend', 'users'] + only: ['*.frontend', '*.users.*'] ``` You can also include ERB in your config file. From 0755e8937ab20f04ca5eae5c71bdef3df0cc8254 Mon Sep 17 00:00:00 2001 From: johnnyshields Date: Wed, 1 Apr 2015 14:41:31 +0900 Subject: [PATCH 176/466] Fix CHANGELOG.md --- CHANGELOG.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34426938..a70db391 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,20 @@ ## Unreleased +### breaking changes + +- [Ruby] In `config/i18n-js.yml`, if you are using `%{locale}` in your filename and are referencing specific translations keys, please add `*.` to the beginning of those keys. ([#320](https://github.com/fnando/i18n-js/pull/320)) + ### enhancements -### bug fixes +- [Ruby] Make handling of per-locale and not-per-locale exporting to be more consistent ([#320](https://github.com/fnando/i18n-js/pull/320)) +### bug fixes -## 3.0.0.rc9 +- [Ruby] Fix fallback logic to work with not-per-locale files ([#320](https://github.com/fnando/i18n-js/pull/320)) -### breaking changes -- [Ruby] In `config/i18n-js.yml`, if you are using `%{locale}` in your filename and are referencing specific translations keys, please add `*.` to the beginning of those keys. ([#320](https://github.com/fnando/i18n-js/pull/320)) +## 3.0.0.rc9 ### enhancements @@ -25,8 +29,6 @@ Combined that with `I18n.missingTranslationPrefix='SOMETHING'` and you can still identify those missing strings. ([#304](https://github.com/fnando/i18n-js/pull/304)) -- [Ruby] Make handling of per-locale and not-per-locale exporting to be more consistent ([#320](https://github.com/fnando/i18n-js/pull/320)) -- [Ruby] Fix fallback logic to work with not-per-locale files ([#320](https://github.com/fnando/i18n-js/pull/320)) ### bug fixes From 74c8424d58272b229de713002e6ddeca6130e8fa Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 13 Apr 2015 17:52:19 +0800 Subject: [PATCH 177/466] ^ Upgrade development dependency `appraisal` --- i18n-js.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n-js.gemspec b/i18n-js.gemspec index 39ffbcfd..767c4c10 100644 --- a/i18n-js.gemspec +++ b/i18n-js.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |s| s.require_paths = ["lib"] s.add_dependency "i18n", "~> 0.6" - s.add_development_dependency "appraisal", "~> 1.0" + s.add_development_dependency "appraisal", "~> 2.0" s.add_development_dependency "activesupport", ">= 3" s.add_development_dependency "rspec", "~> 3.0" s.add_development_dependency "rake" From da2775de8cf520fa2bd7cc1ae986c4f22a9853b4 Mon Sep 17 00:00:00 2001 From: Matt Jonker Date: Tue, 21 Apr 2015 05:15:11 -0400 Subject: [PATCH 178/466] Changed translation key sorting to be controlled by a config setting --- README.md | 11 ++- app/assets/javascripts/i18n/filtered.js.erb | 2 +- lib/i18n/js.rb | 13 +++- lib/i18n/js/segment.rb | 8 +- .../js_sort_translation_keys_false.yml | 6 ++ .../js_sort_translation_keys_true.yml | 6 ++ .../js_sort_translation_keys_true_output.js | 2 + spec/i18n_js_spec.rb | 74 +++++++++++++++++++ spec/segment_spec.rb | 4 +- 9 files changed, 117 insertions(+), 9 deletions(-) create mode 100644 spec/fixtures/js_sort_translation_keys_false.yml create mode 100644 spec/fixtures/js_sort_translation_keys_true.yml create mode 100644 spec/fixtures/js_sort_translation_keys_true_output.js diff --git a/README.md b/README.md index f216b910..286e8639 100644 --- a/README.md +++ b/README.md @@ -134,13 +134,22 @@ translations: - Any `String`: considered as a relative path for a folder to `Rails.root` and export `i18n.js` to that folder for `rake i18n:js:export` - Any non-`String` (`nil`, `false`, `:none`, etc): Disable `i18n.js` exporting -- You may also set `export_i18n_js` in your config file, e.g.: +- `I18n::JS.sort_translation_keys` + Expected Type: `Boolean` + Default: `true` + Behaviour: + - Sets whether or not to deep sort all translation keys in order to generate identical output for the same translations + - Set to true to ensure identical asset fingerprints for the asset pipeline + +- You may also set `export_i18n_js` and `sort_translation_keys` in your config file, e.g.: ```yaml export_i18n_js_: false # OR export_i18n_js: "my/path" +sort_translation_keys: false + translations: - ... ``` diff --git a/app/assets/javascripts/i18n/filtered.js.erb b/app/assets/javascripts/i18n/filtered.js.erb index 4f629beb..8a0cae62 100644 --- a/app/assets/javascripts/i18n/filtered.js.erb +++ b/app/assets/javascripts/i18n/filtered.js.erb @@ -14,5 +14,5 @@ }(function(I18n) { "use strict"; - I18n.translations = <%= I18n::JS::Utils.deep_key_sort(I18n::JS.filtered_translations).to_json %>; + I18n.translations = <%= I18n::JS.filtered_translations.to_json %>; })); diff --git a/lib/i18n/js.rb b/lib/i18n/js.rb index 510476b1..0b80e4d6 100644 --- a/lib/i18n/js.rb +++ b/lib/i18n/js.rb @@ -73,11 +73,12 @@ def self.configured_segments end def self.filtered_translations - {}.tap do |result| + translations = {}.tap do |result| translation_segments.each do |segment| Utils.deep_merge!(result, segment.translations) end end + Utils.deep_key_sort(translations) if I18n::JS.sort_translation_keys? end def self.translation_segments @@ -177,6 +178,16 @@ def self.merge_with_fallbacks!(result, locale, scope, exceptions) end end + def self.sort_translation_keys? + @sort_translation_keys ||= (config[:sort_translation_keys]) if config.has_key?(:sort_translation_keys) + @sort_translation_keys = true if @sort_translation_keys.nil? + @sort_translation_keys + end + + def self.sort_translation_keys=(value) + @sort_translation_keys = !!value + end + ### Export i18n.js begin diff --git a/lib/i18n/js/segment.rb b/lib/i18n/js/segment.rb index 87d12bfe..82844a44 100644 --- a/lib/i18n/js/segment.rb +++ b/lib/i18n/js/segment.rb @@ -19,7 +19,8 @@ def save! File.open(self.file, "w+") do |f| f << %(#{self.namespace}.translations || (#{self.namespace}.translations = {});\n) self.translations.each do |locale, translations| - f << %(#{self.namespace}.translations["#{locale}"] = #{print_json(translations)};\n); + output_translations = I18n::JS.sort_translation_keys? ? Utils.deep_key_sort(translations) : translations + f << %(#{self.namespace}.translations["#{locale}"] = #{print_json(output_translations)};\n); end end end @@ -28,11 +29,10 @@ def save! # Outputs pretty or ugly JSON depending on :pretty_print option def print_json(translations) - sorted_translations = Utils.deep_key_sort(translations) if pretty_print - JSON.pretty_generate(sorted_translations) + JSON.pretty_generate(translations) else - sorted_translations.to_json + translations.to_json end end end diff --git a/spec/fixtures/js_sort_translation_keys_false.yml b/spec/fixtures/js_sort_translation_keys_false.yml new file mode 100644 index 00000000..f47a9489 --- /dev/null +++ b/spec/fixtures/js_sort_translation_keys_false.yml @@ -0,0 +1,6 @@ + +sort_translation_keys: false + +translations: + - file: "tmp/i18n-js/%{locale}.js" + only: '*' diff --git a/spec/fixtures/js_sort_translation_keys_true.yml b/spec/fixtures/js_sort_translation_keys_true.yml new file mode 100644 index 00000000..ada09b08 --- /dev/null +++ b/spec/fixtures/js_sort_translation_keys_true.yml @@ -0,0 +1,6 @@ + +sort_translation_keys: true + +translations: + - file: "tmp/i18n-js/%{locale}.js" + only: '*' diff --git a/spec/fixtures/js_sort_translation_keys_true_output.js b/spec/fixtures/js_sort_translation_keys_true_output.js new file mode 100644 index 00000000..59f55599 --- /dev/null +++ b/spec/fixtures/js_sort_translation_keys_true_output.js @@ -0,0 +1,2 @@ +I18n.translations || (I18n.translations = {}); +I18n.translations["en"] = {"admin":{"edit":{"title":"Edit"},"show":{"note":"more details","title":"Show"}},"date":{"abbr_day_names":["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],"abbr_month_names":[null,"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],"day_names":["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],"formats":{"default":"%Y-%m-%d","long":"%B %d, %Y","short":"%b %d"},"month_names":[null,"January","February","March","April","May","June","July","August","September","October","November","December"]},"fallback_test":"Success","foo":"Foo","number":{"currency":{"format":{"delimiter":",","format":"%u%n","precision":2,"separator":".","unit":"$"}},"format":{"delimiter":",","precision":3,"separator":"."}},"time":{"am":"am","formats":{"default":"%a, %d %b %Y %H:%M:%S %z","long":"%B %d, %Y %H:%M","short":"%d %b %H:%M"},"pm":"pm"}}; diff --git a/spec/i18n_js_spec.rb b/spec/i18n_js_spec.rb index 378d7357..0a62807a 100644 --- a/spec/i18n_js_spec.rb +++ b/spec/i18n_js_spec.rb @@ -401,4 +401,78 @@ end end end + + describe "translation key sorting" do + + describe ".sort_translation_keys with config" do + + subject { described_class.sort_translation_keys? } + + context 'when :sort_translation_keys is not set in config' do + before { set_config "default.yml"} + + it { should eq true } + end + + context 'when :sort_translation_keys set to true in config' do + before { set_config "js_sort_translation_keys_true.yml"} + + it { should eq true } + end + + context 'when :sort_translation_keys set to false in config' do + before { set_config "js_sort_translation_keys_false.yml"} + + it { should eq true } + end + end + + describe '.sort_translation_keys?' do + after { described_class.send(:remove_instance_variable, :@sort_translation_keys) } + + subject { described_class.sort_translation_keys? } + + context "when it is not set" do + it { should eq true } + end + + context "when it is set to true" do + before { described_class.sort_translation_keys = true } + + it { should eq true } + end + + context "when it is set to false" do + before { described_class.sort_translation_keys = false } + + it { should eq false } + end + end + + context "sort_translation_keys option" do + + subject { + I18n::JS.export + file_should_exist "en.js" + File.read(File.join(I18n::JS.export_i18n_js_dir_path, "en.js")) + } + + before do + stub_const('I18n::JS::DEFAULT_EXPORT_DIR_PATH', temp_path) + end + + context 'true' do + before :each do + set_config "js_sort_translation_keys_true.yml" + end + + it "exports with the keys sorted" do + correct_output = File.read(File.dirname(__FILE__) + "/fixtures/js_sort_translation_keys_true_output.js") + + expect(subject).to eq correct_output + end + end + end + + end end diff --git a/spec/segment_spec.rb b/spec/segment_spec.rb index b50afe1f..2eb902c0 100644 --- a/spec/segment_spec.rb +++ b/spec/segment_spec.rb @@ -3,7 +3,7 @@ describe I18n::JS::Segment do let(:file) { "tmp/i18n-js/segment.js" } - let(:translations){ { "en" => { "test" => "Test" }, "fr" => { "test" => "Test2" } } } + let(:translations){ { "en" => { "test" => "Test", "alphabetical_test" => "Test" }, "fr" => { "test" => "Test2" } } } let(:namespace) { "MyNamespace" } let(:pretty_print){ nil } let(:options) { {namespace: namespace, pretty_print: pretty_print} } @@ -63,7 +63,7 @@ File.open(File.join(temp_path, "segment.js")){|f| f.read}.should eql <<-EOF MyNamespace.translations || (MyNamespace.translations = {}); -MyNamespace.translations["en"] = {"test":"Test"}; +MyNamespace.translations["en"] = {"alphabetical_test":"Test","test":"Test"}; MyNamespace.translations["fr"] = {"test":"Test2"}; EOF end From 434e08e0a7129ff9768331df40ecb0674c1f5acf Mon Sep 17 00:00:00 2001 From: Matt Jonker Date: Tue, 21 Apr 2015 05:34:22 -0400 Subject: [PATCH 179/466] Updated spec output to be key sorted --- spec/i18n_js_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/i18n_js_spec.rb b/spec/i18n_js_spec.rb index dea23360..677c830b 100644 --- a/spec/i18n_js_spec.rb +++ b/spec/i18n_js_spec.rb @@ -67,13 +67,13 @@ en_output = File.read(File.join(I18n::JS.export_i18n_js_dir_path, "en.js")) expect(en_output).to eq(< Date: Tue, 21 Apr 2015 05:36:40 -0400 Subject: [PATCH 180/466] Converted spec comparison to be against string instead of file contents --- spec/fixtures/js_sort_translation_keys_true_output.js | 2 -- spec/i18n_js_spec.rb | 8 +++++--- 2 files changed, 5 insertions(+), 5 deletions(-) delete mode 100644 spec/fixtures/js_sort_translation_keys_true_output.js diff --git a/spec/fixtures/js_sort_translation_keys_true_output.js b/spec/fixtures/js_sort_translation_keys_true_output.js deleted file mode 100644 index 59f55599..00000000 --- a/spec/fixtures/js_sort_translation_keys_true_output.js +++ /dev/null @@ -1,2 +0,0 @@ -I18n.translations || (I18n.translations = {}); -I18n.translations["en"] = {"admin":{"edit":{"title":"Edit"},"show":{"note":"more details","title":"Show"}},"date":{"abbr_day_names":["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],"abbr_month_names":[null,"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],"day_names":["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],"formats":{"default":"%Y-%m-%d","long":"%B %d, %Y","short":"%b %d"},"month_names":[null,"January","February","March","April","May","June","July","August","September","October","November","December"]},"fallback_test":"Success","foo":"Foo","number":{"currency":{"format":{"delimiter":",","format":"%u%n","precision":2,"separator":".","unit":"$"}},"format":{"delimiter":",","precision":3,"separator":"."}},"time":{"am":"am","formats":{"default":"%a, %d %b %Y %H:%M:%S %z","long":"%B %d, %Y %H:%M","short":"%d %b %H:%M"},"pm":"pm"}}; diff --git a/spec/i18n_js_spec.rb b/spec/i18n_js_spec.rb index 677c830b..243445a0 100644 --- a/spec/i18n_js_spec.rb +++ b/spec/i18n_js_spec.rb @@ -488,9 +488,11 @@ end it "exports with the keys sorted" do - correct_output = File.read(File.dirname(__FILE__) + "/fixtures/js_sort_translation_keys_true_output.js") - - expect(subject).to eq correct_output + expect(subject).to eq(< Date: Tue, 21 Apr 2015 05:46:07 -0400 Subject: [PATCH 181/466] Added sort_translation_keys test to Segment#save! --- spec/segment_spec.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/spec/segment_spec.rb b/spec/segment_spec.rb index 7ba6c3af..bec322c4 100644 --- a/spec/segment_spec.rb +++ b/spec/segment_spec.rb @@ -88,5 +88,22 @@ EOF end end + + context "when sort_translation_keys? is true" do + before :each do + I18n::JS.sort_translation_keys = true + end + + let(:translations){ { en: { "b" => "Test", "a" => "Test" } } } + + it 'should output the keys as sorted' do + file_should_exist "segment.js" + + File.open(File.join(temp_path, "segment.js")){|f| f.read}.should eql <<-EOF +MyNamespace.translations || (MyNamespace.translations = {}); +MyNamespace.translations["en"] = {"a":"Test","b":"Test"}; + EOF + end + end end end From d6cb83dfe0e005f085271d0215da90c3e175340d Mon Sep 17 00:00:00 2001 From: Matt Jonker Date: Thu, 30 Apr 2015 10:02:21 -0400 Subject: [PATCH 182/466] Fixing pull request review items --- spec/i18n_js_spec.rb | 21 +++++++++++++-------- spec/utils_spec.rb | 17 ++++------------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/spec/i18n_js_spec.rb b/spec/i18n_js_spec.rb index 243445a0..e403e15c 100644 --- a/spec/i18n_js_spec.rb +++ b/spec/i18n_js_spec.rb @@ -426,31 +426,36 @@ describe "translation key sorting" do describe ".sort_translation_keys with config" do - + after { described_class.send(:remove_instance_variable, :@sort_translation_keys) } subject { described_class.sort_translation_keys? } context 'when :sort_translation_keys is not set in config' do - before { set_config "default.yml"} + before :each do + set_config "default.yml" + end it { should eq true } end context 'when :sort_translation_keys set to true in config' do - before { set_config "js_sort_translation_keys_true.yml"} + before :each do + set_config "js_sort_translation_keys_true.yml" + end it { should eq true } end context 'when :sort_translation_keys set to false in config' do - before { set_config "js_sort_translation_keys_false.yml"} + before :each do + set_config "js_sort_translation_keys_false.yml" + end - it { should eq true } + it { should eq false } end end describe '.sort_translation_keys?' do after { described_class.send(:remove_instance_variable, :@sort_translation_keys) } - subject { described_class.sort_translation_keys? } context "when it is not set" do @@ -472,11 +477,11 @@ context "sort_translation_keys option" do - subject { + subject do I18n::JS.export file_should_exist "en.js" File.read(File.join(I18n::JS.export_i18n_js_dir_path, "en.js")) - } + end before do stub_const('I18n::JS::DEFAULT_EXPORT_DIR_PATH', temp_path) diff --git a/spec/utils_spec.rb b/spec/utils_spec.rb index e8062b6e..9c9cd94b 100644 --- a/spec/utils_spec.rb +++ b/spec/utils_spec.rb @@ -68,21 +68,12 @@ end describe ".deep_key_sort" do - it "performs a deep keys sort" do - hash = {:z => {:b => 1, :a => 2}, :y => 3} - - result = described_class.deep_key_sort(hash) - - result.should eql({:y => 3, :z => {:a => 2, :b => 1}}) - end + let(:unsorted_hash) { {:z => {:b => 1, :a => 2}, :y => 3} } + subject { described_class.deep_key_sort(unsorted_hash) } it "performs a deep keys sort without changing the original hash" do - hash = {:z => {:b => 1, :a => 2}, :y => 3} - - result = described_class.deep_key_sort(hash) - - result.should eql({:y => 3, :z => {:a => 2, :b => 1}}) - hash.should eql({:z => {:b => 1, :a => 2}, :y => 3}) + should eql({:y => 3, :z => {:a => 2, :b => 1}}) + unsorted_hash.should eql({:z => {:b => 1, :a => 2}, :y => 3}) end end end From 2954998b42d46eca9798ee591d06d42499e9b865 Mon Sep 17 00:00:00 2001 From: Matt Jonker Date: Thu, 30 Apr 2015 10:44:47 -0400 Subject: [PATCH 183/466] Refactored .sort_translation_keys? specs --- spec/i18n_js_spec.rb | 67 ++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/spec/i18n_js_spec.rb b/spec/i18n_js_spec.rb index e403e15c..002d89e2 100644 --- a/spec/i18n_js_spec.rb +++ b/spec/i18n_js_spec.rb @@ -425,58 +425,59 @@ describe "translation key sorting" do - describe ".sort_translation_keys with config" do + describe ".sort_translation_keys?" do after { described_class.send(:remove_instance_variable, :@sort_translation_keys) } subject { described_class.sort_translation_keys? } - context 'when :sort_translation_keys is not set in config' do - before :each do - set_config "default.yml" - end - it { should eq true } - end + context "set with config" do - context 'when :sort_translation_keys set to true in config' do - before :each do - set_config "js_sort_translation_keys_true.yml" + context 'when :sort_translation_keys is not set in config' do + before :each do + set_config "default.yml" + end + + it { should eq true } end - it { should eq true } - end + context 'when :sort_translation_keys set to true in config' do + before :each do + set_config "js_sort_translation_keys_true.yml" + end - context 'when :sort_translation_keys set to false in config' do - before :each do - set_config "js_sort_translation_keys_false.yml" + it { should eq true } end - it { should eq false } + context 'when :sort_translation_keys set to false in config' do + before :each do + set_config "js_sort_translation_keys_false.yml" + end + + it { should eq false } + end end - end - describe '.sort_translation_keys?' do - after { described_class.send(:remove_instance_variable, :@sort_translation_keys) } - subject { described_class.sort_translation_keys? } + context 'set by .sort_translation_keys' do - context "when it is not set" do - it { should eq true } - end + context "when it is not set" do + it { should eq true } + end - context "when it is set to true" do - before { described_class.sort_translation_keys = true } + context "when it is set to true" do + before { described_class.sort_translation_keys = true } - it { should eq true } - end + it { should eq true } + end - context "when it is set to false" do - before { described_class.sort_translation_keys = false } + context "when it is set to false" do + before { described_class.sort_translation_keys = false } - it { should eq false } + it { should eq false } + end end end - context "sort_translation_keys option" do - + context "exporting" do subject do I18n::JS.export file_should_exist "en.js" @@ -487,7 +488,7 @@ stub_const('I18n::JS::DEFAULT_EXPORT_DIR_PATH', temp_path) end - context 'true' do + context 'sort_translation_keys is true' do before :each do set_config "js_sort_translation_keys_true.yml" end From 5c53d2d57e087dd866731595766502e57b4a65da Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 4 May 2015 09:58:38 +0800 Subject: [PATCH 184/466] ~ Update README to mention replace `I18n.missingTranslation` in JS [ci skip] --- README.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6265078f..2827379e 100644 --- a/README.md +++ b/README.md @@ -358,15 +358,22 @@ becomes "what is your favorite Christmas present" In order to still detect untranslated strings, you can i18n.missingTranslationPrefix to something like: - - I18n.missingTranslationPrefix = 'EE: ' +```javascript +I18n.missingTranslationPrefix = 'EE: '; +``` And result will be: - - "EE: what is your favorite Christmas present" +```javascript +"EE: what is your favorite Christmas present" +``` This will help you doing automated tests against your localisation assets. +Some people prefer returning `null` for missing translation: +```javascript +I18n.missingTranslation = function () { return undefined; }; +``` + Pluralization is possible as well and by default provides English rules: I18n.t("inbox.counting", {count: 10}); // You have 10 messages From 80675ba8b7b12143a4c196fd86c6338af258c14a Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 4 May 2015 17:27:06 +0800 Subject: [PATCH 185/466] ~ Remove URL from doc in the config template [ci skip] Since the section name change from time to time, and even the URL can be changed (although rarely) There is no point putting the URL in doc --- lib/rails/generators/i18n/js/config/templates/i18n-js.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/rails/generators/i18n/js/config/templates/i18n-js.yml b/lib/rails/generators/i18n/js/config/templates/i18n-js.yml index 745e0017..205c42a3 100644 --- a/lib/rails/generators/i18n/js/config/templates/i18n-js.yml +++ b/lib/rails/generators/i18n/js/config/templates/i18n-js.yml @@ -9,8 +9,7 @@ # so. # # For more informations about the export options with this file, please -# refer to the `Export Configuration` section in the README at : -# https://github.com/fnando/i18n-js#export-configuration +# refer to the README # # # If you're going to use the Rails 3.1 asset pipeline, change From 4f87d5f84f7be7c409b31e438bf98ca1fa1b811c Mon Sep 17 00:00:00 2001 From: Matt Jonker Date: Tue, 5 May 2015 11:31:03 -0400 Subject: [PATCH 186/466] Updated the CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a70db391..cc5712f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### enhancements - [Ruby] Make handling of per-locale and not-per-locale exporting to be more consistent ([#320](https://github.com/fnando/i18n-js/pull/320)) +- [Ruby] Add option `sort_translation_keys` to sort translation keys alphabetically ([#318](https://github.com/fnando/i18n-js/pull/318)) ### bug fixes From b21d5b373467212e7d9c9b205c487c22fc8e350e Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 11 May 2015 14:57:16 +0800 Subject: [PATCH 187/466] ! Fix regression introduced in https://github.com/fnando/i18n-js/pull/318 --- lib/i18n/js.rb | 3 ++- spec/i18n_js_spec.rb | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/lib/i18n/js.rb b/lib/i18n/js.rb index 1b1099ac..05f77137 100644 --- a/lib/i18n/js.rb +++ b/lib/i18n/js.rb @@ -76,7 +76,8 @@ def self.filtered_translations Utils.deep_merge!(result, segment.translations) end end - Utils.deep_key_sort(translations) if I18n::JS.sort_translation_keys? + return Utils.deep_key_sort(translations) if I18n::JS.sort_translation_keys? + translations end def self.translation_segments diff --git a/spec/i18n_js_spec.rb b/spec/i18n_js_spec.rb index 002d89e2..0029e12d 100644 --- a/spec/i18n_js_spec.rb +++ b/spec/i18n_js_spec.rb @@ -161,6 +161,50 @@ result[:en][:admin][:edit][:title].should eql("Edit") result[:fr][:admin][:edit][:title].should eql("Editer") end + + describe ".filtered_translations" do + subject do + I18n::JS.filtered_translations + end + + let!(:old_sort_translation_keys) { I18n::JS.sort_translation_keys? } + before { I18n::JS.sort_translation_keys = sort_translation_keys_value } + after { I18n::JS.sort_translation_keys = old_sort_translation_keys } + before { expect(I18n::JS.sort_translation_keys?).to eq(sort_translation_keys_value) } + + let(:sorted_hash) do + {sorted: :hash} + end + before do + allow(I18n::JS::Utils). + to receive(:deep_key_sort). + and_return(sorted_hash) + end + + shared_examples_for ".filtered_translations" do + subject do + I18n::JS.filtered_translations + end + + # This example is to prevent the regression from + # PR https://github.com/fnando/i18n-js/pull/318 + it {should be_a(Hash)} + # Might need to test the keys... or not + end + + context "when translation keys SHOULD be sorted" do + let(:sort_translation_keys_value) { true } + + it_behaves_like ".filtered_translations" + it {should eq(sorted_hash)} + end + context "when translation keys should NOT be sorted" do + let(:sort_translation_keys_value) { false } + + it_behaves_like ".filtered_translations" + it {should_not eq(sorted_hash)} + end + end end context "exceptions" do From 1e54b008e65ea3fcea80c9e78e85e2f43a5cf90c Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 11 May 2015 15:45:40 +0800 Subject: [PATCH 188/466] * Try to make Travis build faster while still being able to test JS --- .travis.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2b405d62..64ddc624 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,15 @@ +# Send builds to container-based infrastructure +# http://docs.travis-ci.com/user/workers/container-based-infrastructure/ +sudo: false language: ruby +cache: + - bundler rvm: - 2.0 - 2.1 - 2.2 - ruby-head -script: "bundle exec rake spec" -before_install: # Need to install npm to test js -- sudo apt-get update -- sudo apt-get install npm +before_install: # Need to install something extra to test JS - npm install jasmine-node@1.14.2 gemfile: - gemfiles/i18n_0_6.gemfile From 8852176a736bac0facf6e097d2115b44dbe98e5b Mon Sep 17 00:00:00 2001 From: Michiel de Wit Date: Fri, 8 May 2015 12:07:19 +0200 Subject: [PATCH 189/466] Added absolute exception specifiers --- lib/i18n/js.rb | 5 +++-- lib/i18n/js/utils.rb | 14 +++++++++++--- spec/i18n_js_spec.rb | 13 +++++++++++++ spec/utils_spec.rb | 15 +++++++++++++++ 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/lib/i18n/js.rb b/lib/i18n/js.rb index 05f77137..0ab0023c 100644 --- a/lib/i18n/js.rb +++ b/lib/i18n/js.rb @@ -122,8 +122,9 @@ def self.exclude(translations, exceptions) return translations if exceptions.empty? exceptions.inject(translations) do |memo, exception| - Utils.deep_reject(memo) do |key, value| - key.to_s == exception.to_s + exception_scopes = exception.to_s.split(".") + Utils.deep_reject(memo) do |key, value, scopes| + key.to_s == exception.to_s or Utils.scopes_match?(scopes, exception_scopes) end end end diff --git a/lib/i18n/js/utils.rb b/lib/i18n/js/utils.rb index ef570e7b..e126afb5 100644 --- a/lib/i18n/js/utils.rb +++ b/lib/i18n/js/utils.rb @@ -22,10 +22,18 @@ def self.deep_merge!(target_hash, hash) # :nodoc: target_hash.merge!(hash, &MERGER) end - def self.deep_reject(hash, &block) + def self.deep_reject(hash, scopes = [], &block) hash.each_with_object({}) do |(k, v), memo| - unless block.call(k, v) - memo[k] = v.kind_of?(Hash) ? deep_reject(v, &block) : v + unless block.call(k, v, scopes + [k.to_s]) + memo[k] = v.kind_of?(Hash) ? deep_reject(v, scopes + [k.to_s], &block) : v + end + end + end + + def self.scopes_match?(scopes1, scopes2) + if scopes1.length == scopes2.length + [scopes1, scopes2].transpose.all? do |scope1, scope2| + scope1.to_s == '*' or scope2.to_s == '*' or scope1.to_s == scope2.to_s end end end diff --git a/spec/i18n_js_spec.rb b/spec/i18n_js_spec.rb index 0029e12d..e42a62b8 100644 --- a/spec/i18n_js_spec.rb +++ b/spec/i18n_js_spec.rb @@ -226,6 +226,19 @@ result[:fr][:admin][:edit].should be_empty end + it "does not include scopes listed in the exceptions list" do + result = I18n::JS.scoped_translations("*", ['de.*', '*.admin', '*.*.currency']) + + result[:de].should be_empty + + result[:en][:admin].should be_nil + result[:fr][:admin].should be_nil + result[:ja][:admin].should be_nil + + result[:en][:number][:currency].should be_nil + result[:fr][:number][:currency].should be_nil + end + it "does not include a key listed in the exceptions list and respecs the 'only' option" do result = I18n::JS.scoped_translations("fr.*", ['date', 'time', 'number', 'show']) diff --git a/spec/utils_spec.rb b/spec/utils_spec.rb index 9c9cd94b..bda47912 100644 --- a/spec/utils_spec.rb +++ b/spec/utils_spec.rb @@ -76,4 +76,19 @@ unsorted_hash.should eql({:z => {:b => 1, :a => 2}, :y => 3}) end end + + describe ".scopes_match?" do + it "performs a comparison of literal scopes" do + described_class.scopes_match?([:a, :b], [:a, :b, :c]).should_not eql true + described_class.scopes_match?([:a, :b, :c], [:a, :b, :c]).should eql true + described_class.scopes_match?([:a, :b, :c], [:a, :b, :d]).should_not eql true + end + + it "performs a comparison of wildcard scopes" do + described_class.scopes_match?([:a, '*'], [:a, :b, :c]).should_not eql true + described_class.scopes_match?([:a, '*', :c], [:a, :b, :c]).should eql true + described_class.scopes_match?([:a, :b, :c], [:a, '*', :c]).should eql true + described_class.scopes_match?([:a, :b, :c], [:a, '*', '*']).should eql true + end + end end From e24760452b9679498fff857d006d6a79f6b6e1e9 Mon Sep 17 00:00:00 2001 From: Michiel de Wit Date: Mon, 11 May 2015 11:11:51 +0200 Subject: [PATCH 190/466] Replaced 'or' operator with '||' operator --- lib/i18n/js.rb | 2 +- lib/i18n/js/utils.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/i18n/js.rb b/lib/i18n/js.rb index 0ab0023c..ca3978d9 100644 --- a/lib/i18n/js.rb +++ b/lib/i18n/js.rb @@ -124,7 +124,7 @@ def self.exclude(translations, exceptions) exceptions.inject(translations) do |memo, exception| exception_scopes = exception.to_s.split(".") Utils.deep_reject(memo) do |key, value, scopes| - key.to_s == exception.to_s or Utils.scopes_match?(scopes, exception_scopes) + key.to_s == exception.to_s || Utils.scopes_match?(scopes, exception_scopes) end end end diff --git a/lib/i18n/js/utils.rb b/lib/i18n/js/utils.rb index e126afb5..3090f11f 100644 --- a/lib/i18n/js/utils.rb +++ b/lib/i18n/js/utils.rb @@ -33,7 +33,7 @@ def self.deep_reject(hash, scopes = [], &block) def self.scopes_match?(scopes1, scopes2) if scopes1.length == scopes2.length [scopes1, scopes2].transpose.all? do |scope1, scope2| - scope1.to_s == '*' or scope2.to_s == '*' or scope1.to_s == scope2.to_s + scope1.to_s == '*' || scope2.to_s == '*' || scope1.to_s == scope2.to_s end end end From ccf8c14737214ec49407fc93e8c8adcc1cbe0d24 Mon Sep 17 00:00:00 2001 From: Michiel de Wit Date: Mon, 11 May 2015 11:34:05 +0200 Subject: [PATCH 191/466] Removed support for relative :except filters --- README.md | 2 +- lib/i18n/js.rb | 2 +- spec/i18n_js_spec.rb | 53 +++++++++++++++++++++++--------------------- 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index ec1497b4..02caa1e8 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ keys listed in `except` configuration param: ```yaml translations: - - except: ['active_admin', 'ransack'] + - except: ['*.active_admin', '*.ransack', '*.activerecord.errors'] ``` diff --git a/lib/i18n/js.rb b/lib/i18n/js.rb index ca3978d9..3473279f 100644 --- a/lib/i18n/js.rb +++ b/lib/i18n/js.rb @@ -124,7 +124,7 @@ def self.exclude(translations, exceptions) exceptions.inject(translations) do |memo, exception| exception_scopes = exception.to_s.split(".") Utils.deep_reject(memo) do |key, value, scopes| - key.to_s == exception.to_s || Utils.scopes_match?(scopes, exception_scopes) + Utils.scopes_match?(scopes, exception_scopes) end end end diff --git a/spec/i18n_js_spec.rb b/spec/i18n_js_spec.rb index e42a62b8..6b087ba2 100644 --- a/spec/i18n_js_spec.rb +++ b/spec/i18n_js_spec.rb @@ -208,24 +208,6 @@ end context "exceptions" do - it "does not include a key listed in the exceptions list" do - result = I18n::JS.scoped_translations("*", ['admin']) - - result[:en][:admin].should be_nil - result[:fr][:admin].should be_nil - end - - it "does not include multiple keys listed in the exceptions list" do - result = I18n::JS.scoped_translations("*", ['title', 'note']) - - result[:en][:admin][:show].should be_empty - result[:en][:admin][:edit].should be_empty - - result[:fr][:admin][:show].should be_empty - result[:fr][:admin][:show].should be_empty - result[:fr][:admin][:edit].should be_empty - end - it "does not include scopes listed in the exceptions list" do result = I18n::JS.scoped_translations("*", ['de.*', '*.admin', '*.*.currency']) @@ -239,19 +221,40 @@ result[:fr][:number][:currency].should be_nil end - it "does not include a key listed in the exceptions list and respecs the 'only' option" do - result = I18n::JS.scoped_translations("fr.*", ['date', 'time', 'number', 'show']) + it "does not include scopes listed in the exceptions list and respects the 'only' option" do + result = I18n::JS.scoped_translations("fr.*", ['*.admin', '*.*.currency']) result[:en].should be_nil result[:de].should be_nil result[:ja].should be_nil - result[:fr][:date].should be_nil - result[:fr][:time].should be_nil - result[:fr][:number].should be_nil - result[:fr][:admin][:show].should be_nil + result[:fr][:admin].should be_nil + result[:fr][:number][:currency].should be_nil + + result[:fr][:time][:am].should be_a(String) + end + + it "does exclude absolute scopes listed in the exceptions list" do + result = I18n::JS.scoped_translations("*", ['de', 'en.admin', 'fr.number.currency']) + + result[:de].should be_nil + + result[:en].should be_a(Hash) + result[:en][:admin].should be_nil + + result[:fr][:number].should be_a(Hash) + result[:fr][:number][:currency].should be_nil + end + + it "does not exclude non-absolute scopes listed in the exceptions list" do + result = I18n::JS.scoped_translations("*", ['admin', 'currency']) + + result[:en][:admin].should be_a(Hash) + result[:fr][:admin].should be_a(Hash) + result[:ja][:admin].should be_a(Hash) - result[:fr][:admin][:edit][:title].should be_a(String) + result[:en][:number][:currency].should be_a(Hash) + result[:fr][:number][:currency].should be_a(Hash) end end From 78121254856f669b99ee2709bc74f562f4d37313 Mon Sep 17 00:00:00 2001 From: Michiel de Wit Date: Tue, 12 May 2015 08:45:12 +0200 Subject: [PATCH 192/466] Added description of new 'except' option behavior to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc5712f6..f249ba84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### breaking changes - [Ruby] In `config/i18n-js.yml`, if you are using `%{locale}` in your filename and are referencing specific translations keys, please add `*.` to the beginning of those keys. ([#320](https://github.com/fnando/i18n-js/pull/320)) +- [Ruby] The `:except` option to exclude certain phrases now (only) accepts the same patterns the `:only` option accepts ### enhancements From 78030390e36529b2611f960bda88df1d9e01aafd Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 14 May 2015 15:51:16 +0800 Subject: [PATCH 193/466] ~ Update README for #283 [ci skip] --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 02caa1e8..d8855c83 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,8 @@ then you must add the following line to your `app/assets/javascripts/application // This is optional (in case you have `I18n is not defined` error) // If you want to put this line, you must put it BEFORE `i18n/translations` //= require i18n +// Some people even need to add the extension to make it work, see https://github.com/fnando/i18n-js/issues/283 +//= require i18n.js // // This is a must //= require i18n/translations From 28f6d05c94ef4622f1d839b119ce981fdcb8ed83 Mon Sep 17 00:00:00 2001 From: Andres Hernandez Date: Mon, 25 May 2015 14:31:18 -0600 Subject: [PATCH 194/466] Fix directory validation --- lib/i18n/js/middleware.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/i18n/js/middleware.rb b/lib/i18n/js/middleware.rb index ef9c63bb..52cb8ef2 100644 --- a/lib/i18n/js/middleware.rb +++ b/lib/i18n/js/middleware.rb @@ -31,7 +31,7 @@ def cache end def save_cache(new_cache) - FileUtils.mkdir_p(cache_dir) + FileUtils.mkdir_p(cache_dir) unless File.exists?(cache_dir) File.open(cache_path, "w+") do |file| file << new_cache.to_yaml end From db2922059d2e0fa213f92b5a4f191cb652a48dc2 Mon Sep 17 00:00:00 2001 From: Andres Hernandez Date: Mon, 25 May 2015 20:44:03 -0600 Subject: [PATCH 195/466] Add comments --- CHANGELOG.md | 17 +++++++++-------- lib/i18n/js/middleware.rb | 1 + 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f249ba84..c66341a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ - [Ruby] Add `:except` option to exclude certain phrases or groups of phrases from the outputted translations ([#312](https://github.com/fnando/i18n-js/pull/312)) - [JS] You can now set `I18n.missingBehavior='guess'` to have the scope string output as text instead of of the - "[missing `scope`]" message when no translation is available. + "[missing `scope`]" message when no translation is available. Combined that with `I18n.missingTranslationPrefix='SOMETHING'` and you can still identify those missing strings. ([#304](https://github.com/fnando/i18n-js/pull/304)) @@ -35,6 +35,7 @@ ### bug fixes - [JS] Fix missing translation message when scope is passed in options +- [Ruby] Fix save cache directory verification when path is a symbolic link ([#329](https://github.com/fnando/i18n-js/pull/329)) ## 3.0.0.rc8 @@ -42,8 +43,8 @@ ### enhancements - Add support for loading via AMD and CommonJS module loaders ([#266](https://github.com/fnando/i18n-js/pull/266)) -- Add `I18n.nullPlaceholder` - Defaults to I18n.missingPlaceholder (`[missing {{name}} value]`) +- Add `I18n.nullPlaceholder` + Defaults to I18n.missingPlaceholder (`[missing {{name}} value]`) Set to `function() {return "";}` to match Ruby `I18n.t("name: %{name}", name: nil)` - For date formatting, you can now also add placeholders to the date format, see README for detail - Add fallbacks option to `i18n-js.yml`, defaults to `true` @@ -53,8 +54,8 @@ - Fix factory initialization so that the Node/CommonJS branch only gets executed if the environment is Node/CommonJS (it currently will execute if module is defined in the global scope, which occurs with QUnit, for example) - Fix pluralization rules selection for negative `count` (e.g. `-1` was lead to use `one` for pluralization) ([#268](https://github.com/fnando/i18n-js/pull/268)) -- Remove check for `Rails.configuration.assets.compile` before telling Sprockets the dependency of translations JS file - This might be the reason of many "cache not expired" issues +- Remove check for `Rails.configuration.assets.compile` before telling Sprockets the dependency of translations JS file + This might be the reason of many "cache not expired" issues Discovered/reported in #277 ## 3.0.0.rc7 @@ -62,11 +63,11 @@ ### enhancements - The Rails Engine initializer is now named as `i18n-js.register_preprocessor` (https://github.com/fnando/i18n-js/pull/261) -- Rename `I18n::JS.config_file` to `I18n::JS.config_file_path` and make it configurable +- Rename `I18n::JS.config_file` to `I18n::JS.config_file_path` and make it configurable Expected a `String`, default is still `config/i18n-js.yml` - When running `rake i18n:js:export`, the `i18n.js` will also be exported to `I18n::JS.export_i18n_js_dir_path` by default -- Add `I18n::JS.export_i18n_js_dir_path` - Expected a `String`, default is `public/javascripts` +- Add `I18n::JS.export_i18n_js_dir_path` + Expected a `String`, default is `public/javascripts` Set to `nil` will disable exporting `i18n.js` ### bug fixes diff --git a/lib/i18n/js/middleware.rb b/lib/i18n/js/middleware.rb index 52cb8ef2..4255b4ca 100644 --- a/lib/i18n/js/middleware.rb +++ b/lib/i18n/js/middleware.rb @@ -31,6 +31,7 @@ def cache end def save_cache(new_cache) + # path could be a symbolic link FileUtils.mkdir_p(cache_dir) unless File.exists?(cache_dir) File.open(cache_path, "w+") do |file| file << new_cache.to_yaml From d8b8f9cff1f6f014a2a08895ce41934fa696d070 Mon Sep 17 00:00:00 2001 From: Andres Hernandez Date: Mon, 25 May 2015 20:45:05 -0600 Subject: [PATCH 196/466] Add comments --- CHANGELOG.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c66341a7..6f4dcb6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ - [Ruby] Add `:except` option to exclude certain phrases or groups of phrases from the outputted translations ([#312](https://github.com/fnando/i18n-js/pull/312)) - [JS] You can now set `I18n.missingBehavior='guess'` to have the scope string output as text instead of of the - "[missing `scope`]" message when no translation is available. + "[missing `scope`]" message when no translation is available. Combined that with `I18n.missingTranslationPrefix='SOMETHING'` and you can still identify those missing strings. ([#304](https://github.com/fnando/i18n-js/pull/304)) @@ -43,8 +43,8 @@ ### enhancements - Add support for loading via AMD and CommonJS module loaders ([#266](https://github.com/fnando/i18n-js/pull/266)) -- Add `I18n.nullPlaceholder` - Defaults to I18n.missingPlaceholder (`[missing {{name}} value]`) +- Add `I18n.nullPlaceholder` + Defaults to I18n.missingPlaceholder (`[missing {{name}} value]`) Set to `function() {return "";}` to match Ruby `I18n.t("name: %{name}", name: nil)` - For date formatting, you can now also add placeholders to the date format, see README for detail - Add fallbacks option to `i18n-js.yml`, defaults to `true` @@ -54,8 +54,8 @@ - Fix factory initialization so that the Node/CommonJS branch only gets executed if the environment is Node/CommonJS (it currently will execute if module is defined in the global scope, which occurs with QUnit, for example) - Fix pluralization rules selection for negative `count` (e.g. `-1` was lead to use `one` for pluralization) ([#268](https://github.com/fnando/i18n-js/pull/268)) -- Remove check for `Rails.configuration.assets.compile` before telling Sprockets the dependency of translations JS file - This might be the reason of many "cache not expired" issues +- Remove check for `Rails.configuration.assets.compile` before telling Sprockets the dependency of translations JS file + This might be the reason of many "cache not expired" issues Discovered/reported in #277 ## 3.0.0.rc7 @@ -63,11 +63,11 @@ ### enhancements - The Rails Engine initializer is now named as `i18n-js.register_preprocessor` (https://github.com/fnando/i18n-js/pull/261) -- Rename `I18n::JS.config_file` to `I18n::JS.config_file_path` and make it configurable +- Rename `I18n::JS.config_file` to `I18n::JS.config_file_path` and make it configurable Expected a `String`, default is still `config/i18n-js.yml` - When running `rake i18n:js:export`, the `i18n.js` will also be exported to `I18n::JS.export_i18n_js_dir_path` by default -- Add `I18n::JS.export_i18n_js_dir_path` - Expected a `String`, default is `public/javascripts` +- Add `I18n::JS.export_i18n_js_dir_path` + Expected a `String`, default is `public/javascripts` Set to `nil` will disable exporting `i18n.js` ### bug fixes From c357d7b7d43cf636bb81b05a1b19fdff59afa992 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 27 May 2015 15:33:20 +0800 Subject: [PATCH 197/466] ! Avoid non Symbol / String keys from causing ArgumentError during key sorting No CHANGELOG since this is for an unreleased feature --- lib/i18n/js/utils.rb | 13 ++++++------- spec/utils_spec.rb | 14 +++++++++++++- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/i18n/js/utils.rb b/lib/i18n/js/utils.rb index 3090f11f..1c8a07b4 100644 --- a/lib/i18n/js/utils.rb +++ b/lib/i18n/js/utils.rb @@ -38,13 +38,12 @@ def self.scopes_match?(scopes1, scopes2) end end - def self.deep_key_sort(hash, &block) - hash.keys.sort(&block).reduce({}) do |seed, key| - seed[key] = hash[key] - if seed[key].is_a?(Hash) - seed[key] = deep_key_sort(seed[key], &block) - end - seed + def self.deep_key_sort(hash) + # Avoid things like `true` or `1` from YAML which causes error + hash.keys.sort {|a, b| a.to_s <=> b.to_s}. + each_with_object({}) do |key, seed| + value = hash[key] + seed[key] = value.is_a?(Hash) ? deep_key_sort(value) : value end end end diff --git a/spec/utils_spec.rb b/spec/utils_spec.rb index bda47912..d55e3eec 100644 --- a/spec/utils_spec.rb +++ b/spec/utils_spec.rb @@ -69,12 +69,24 @@ describe ".deep_key_sort" do let(:unsorted_hash) { {:z => {:b => 1, :a => 2}, :y => 3} } - subject { described_class.deep_key_sort(unsorted_hash) } + subject(:sorting) { described_class.deep_key_sort(unsorted_hash) } it "performs a deep keys sort without changing the original hash" do should eql({:y => 3, :z => {:a => 2, :b => 1}}) unsorted_hash.should eql({:z => {:b => 1, :a => 2}, :y => 3}) end + + # Idea from gem `rails_admin` + context "when hash contain non-Symbol as key" do + let(:unsorted_hash) { {:z => {1 => 1, true => 2}, :y => 3} } + + it "performs a deep keys sort without error" do + expect{ sorting }.to_not raise_error + end + it "converts keys to symbols" do + should eql({:y => 3, :z => {1 => 1, true => 2}}) + end + end end describe ".scopes_match?" do From 128c91dcb314024699b75fd79a1c091e0edbafa9 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 28 May 2015 09:55:45 +0800 Subject: [PATCH 198/466] ^ Release 3.0.0.rc10 --- CHANGELOG.md | 9 +++++++++ lib/i18n/js/version.rb | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f4dcb6d..af168a5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,15 @@ ### breaking changes +### enhancements + +### bug fixes + + +## 3.0.0.rc10 + +### breaking changes + - [Ruby] In `config/i18n-js.yml`, if you are using `%{locale}` in your filename and are referencing specific translations keys, please add `*.` to the beginning of those keys. ([#320](https://github.com/fnando/i18n-js/pull/320)) - [Ruby] The `:except` option to exclude certain phrases now (only) accepts the same patterns the `:only` option accepts diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index 4945f080..f25d8d8d 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -4,7 +4,7 @@ module Version MAJOR = 3 MINOR = 0 PATCH = 0 - STRING = "#{MAJOR}.#{MINOR}.#{PATCH}.rc9" + STRING = "#{MAJOR}.#{MINOR}.#{PATCH}.rc10" end end end From a3db398cf386116ceceb49a68b03bee5f66ffc58 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 29 May 2015 12:04:41 +0800 Subject: [PATCH 199/466] ~ Update badges to versions at shields.io in README ~ Also add Badge for Gitter [ci skip] --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d8855c83..0afda0fe 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # I18n.js -[![Build Status](https://travis-ci.org/fnando/i18n-js.svg?branch=master)](https://travis-ci.org/fnando/i18n-js) -[![Code Climate](https://codeclimate.com/github/fnando/i18n-js.png)](https://codeclimate.com/github/fnando/i18n-js) +[![Build Status](http://img.shields.io/travis/fnando/i18n-js.svg?style=flat-square)](https://travis-ci.org/fnando/i18n-js) +[![Code Climate](http://img.shields.io/codeclimate/github/fnando/i18n-js.svg?style=flat-square)](https://codeclimate.com/github/fnando/i18n-js) +[![Gitter](https://img.shields.io/badge/gitter-join%20chat-1dce73.svg?style=flat-square)](https://gitter.im/fnando/i18n-js) It's a small library to provide the Rails I18n translations on the JavaScript. From 53a5e3e8464d47b9e573ed58259263aea4ca53e9 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 18 Jun 2015 10:02:02 +0800 Subject: [PATCH 200/466] * Change version constraint for `activesupport` to avoid using version affected by CVE (at least for 3.x) * Add version constraint for `rake` --- i18n-js.gemspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n-js.gemspec b/i18n-js.gemspec index 767c4c10..4cdeb72f 100644 --- a/i18n-js.gemspec +++ b/i18n-js.gemspec @@ -20,9 +20,9 @@ Gem::Specification.new do |s| s.add_dependency "i18n", "~> 0.6" s.add_development_dependency "appraisal", "~> 2.0" - s.add_development_dependency "activesupport", ">= 3" + s.add_development_dependency "activesupport", ">= 3.2.22" s.add_development_dependency "rspec", "~> 3.0" - s.add_development_dependency "rake" + s.add_development_dependency "rake", "~> 10.0" s.add_development_dependency "gem-release", ">= 0.7" s.required_ruby_version = ">= 1.9.3" From 4f9ec7cbf2827c18bfe5a98d61897565793c1acd Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 18 Jun 2015 15:15:09 +0800 Subject: [PATCH 201/466] + Add spec that fails like #334 --- ...s_locale_without_fallback_translations.yml | 4 ++++ spec/i18n_js_spec.rb | 19 ++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/js_file_per_locale_with_fallbacks_as_locale_without_fallback_translations.yml diff --git a/spec/fixtures/js_file_per_locale_with_fallbacks_as_locale_without_fallback_translations.yml b/spec/fixtures/js_file_per_locale_with_fallbacks_as_locale_without_fallback_translations.yml new file mode 100644 index 00000000..622ce2be --- /dev/null +++ b/spec/fixtures/js_file_per_locale_with_fallbacks_as_locale_without_fallback_translations.yml @@ -0,0 +1,4 @@ +fallbacks: :pirate + +translations: +- file: "tmp/i18n-js/%{locale}.js" diff --git a/spec/i18n_js_spec.rb b/spec/i18n_js_spec.rb index 6b087ba2..b1c1d825 100644 --- a/spec/i18n_js_spec.rb +++ b/spec/i18n_js_spec.rb @@ -259,7 +259,7 @@ end context "fallbacks" do - subject do + subject(:translations) do I18n::JS.translation_segments.first.translations end @@ -283,6 +283,23 @@ subject[:fr][:fallback_test].should eql("Erfolg") end + context "when given locale is in `I18n.available_locales` but its translation is missing" do + subject { translations[:fr][:fallback_test] } + + let(:new_locale) { :pirate } + let!(:old_available_locales) { I18n.config.available_locales } + let!(:new_available_locales) { I18n.config.available_locales + [new_locale] } + before do + I18n.config.available_locales = new_available_locales + set_config "js_file_per_locale_with_fallbacks_as_locale_without_fallback_translations.yml" + end + after do + I18n.config.available_locales = old_available_locales + end + + it {should eql(nil)} + end + context "with I18n::Fallbacks enabled" do let(:backend_with_fallbacks) { backend_class_with_fallbacks.new } let!(:old_backebad) { I18n.backend } From 1721d2d6f09203283ae944ff3c7455bf89edbb0c Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 18 Jun 2015 15:15:29 +0800 Subject: [PATCH 202/466] ! Fix the issue by assuming translations for fallback locale could also be missing --- lib/i18n/js.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/i18n/js.rb b/lib/i18n/js.rb index 3473279f..90aed296 100644 --- a/lib/i18n/js.rb +++ b/lib/i18n/js.rb @@ -65,7 +65,8 @@ def self.merge_with_fallbacks!(result) I18n.available_locales.each do |locale| fallback_locales = FallbackLocales.new(fallbacks, locale) fallback_locales.each do |fallback_locale| - result[locale] = Utils.deep_merge(result[fallback_locale], result[locale] || {}) + # `result[fallback_locale]` could be missing + result[locale] = Utils.deep_merge(result[fallback_locale] || {}, result[locale] || {}) end end end From 3cabac6c29c82122e343cbf63f1003c99dd6adfc Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 18 Jun 2015 15:27:11 +0800 Subject: [PATCH 203/466] ~ Update README to fix strange statement which causes https://github.com/fnando/i18n-js/issues/332 [ci skip] --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0afda0fe..11c77816 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,11 @@ Then get the JS files following the instructions below. #### Export Configuration (For translations) -Exported translation files generated by `I18n::JS::Middleware` or `rake i18n:js:export` can be customized with config file `config/i18n-js.yml` (use `rails generate i18n:js:config` to create it). You can even get more files generated to different folders and with different translations to best suit your needs. But this does not affect anything if you use Asset Pipeline. +Exported translation files generated by `I18n::JS::Middleware` or `rake i18n:js:export` can be customized with config file `config/i18n-js.yml` +(use `rails generate i18n:js:config` to create it). +You can even get more files generated to different folders and with different translations to best suit your needs. +The config file also affects developers using Asset Pipeline to require translations. +Except the option `file`, since all translations are required by adding `//= require i18n/translations`. Examples: ```yaml From 885569c7b1fb5735abb99f15a5503abe02126f91 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 22 Jun 2015 14:44:44 +0800 Subject: [PATCH 204/466] ~ Add CHANGELOG entry for the fix of #334 [ci skip] --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index af168a5f..e46df6c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ ### bug fixes +- [Ruby] Handle fallback locale without any translation properly + ## 3.0.0.rc10 From 16da443932ec07a2f1f149d62d97bce3a5355955 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 22 Jun 2015 15:02:14 +0800 Subject: [PATCH 205/466] + Add spec that fails like #338 --- spec/fixtures/locales.yml | 2 ++ spec/i18n_js_spec.rb | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/spec/fixtures/locales.yml b/spec/fixtures/locales.yml index 99d7041e..a00d60b6 100755 --- a/spec/fixtures/locales.yml +++ b/spec/fixtures/locales.yml @@ -36,9 +36,11 @@ en: title: "Edit" foo: "Foo" fallback_test: "Success" + null_test: "fallback for null" de: fallback_test: "Erfolg" + null_test: ~ fr: date: diff --git a/spec/i18n_js_spec.rb b/spec/i18n_js_spec.rb index b1c1d825..6db0e5d8 100644 --- a/spec/i18n_js_spec.rb +++ b/spec/i18n_js_spec.rb @@ -266,21 +266,29 @@ it "exports without fallback when disabled" do set_config "js_file_per_locale_without_fallbacks.yml" subject[:fr][:fallback_test].should eql(nil) + subject[:fr][:null_test].should eql(nil) + subject[:de][:null_test].should eql(nil) end it "exports with default_locale as fallback when enabled" do set_config "js_file_per_locale_with_fallbacks_enabled.yml" subject[:fr][:fallback_test].should eql("Success") + subject[:fr][:null_test].should eql("fallback for null") + subject[:de][:null_test].should eql("fallback for null") end it "exports with default_locale as fallback when enabled with :default_locale" do set_config "js_file_per_locale_with_fallbacks_as_default_locale_symbol.yml" subject[:fr][:fallback_test].should eql("Success") + subject[:fr][:null_test].should eql("fallback for null") + subject[:de][:null_test].should eql("fallback for null") end it "exports with given locale as fallback" do set_config "js_file_per_locale_with_fallbacks_as_locale.yml" subject[:fr][:fallback_test].should eql("Erfolg") + subject[:fr][:null_test].should eql(nil) + subject[:de][:null_test].should eql(nil) end context "when given locale is in `I18n.available_locales` but its translation is missing" do @@ -573,7 +581,7 @@ it "exports with the keys sorted" do expect(subject).to eq(< Date: Mon, 22 Jun 2015 15:04:22 +0800 Subject: [PATCH 206/466] ! Fix the issue by checking if new value is `nil` --- lib/i18n/js/utils.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/i18n/js/utils.rb b/lib/i18n/js/utils.rb index 1c8a07b4..02db4764 100644 --- a/lib/i18n/js/utils.rb +++ b/lib/i18n/js/utils.rb @@ -2,8 +2,9 @@ module I18n module JS module Utils # deep_merge by Stefan Rusterholz, see . - MERGER = proc do |key, v1, v2| - Hash === v1 && Hash === v2 ? v1.merge(v2, &MERGER) : v2 + # The last result is modified to treat `nil` as missing key + MERGER = proc do |_key, v1, v2| + Hash === v1 && Hash === v2 ? v1.merge(v2, &MERGER) : (v2.nil? ? v1 : v2) end HASH_NIL_VALUE_CLEANER_PROC = proc do |k, v| From 01d19547656c0615d24de05e860b385f2306cd9b Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 22 Jun 2015 16:38:24 +0800 Subject: [PATCH 207/466] ~ Add CHANGELOG entry [ci skip] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e46df6c9..710a0f7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### bug fixes - [Ruby] Handle fallback locale without any translation properly +- [Ruby] Prevent translation entry with null value to override value in fallback locale(s), if enabled ## 3.0.0.rc10 From af900f4e622f69b4f256c9ee30d3d5b2d2b48af0 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 22 Jun 2015 16:42:57 +0800 Subject: [PATCH 208/466] ~ Add back issue reference to recent CHANGELOG entries [ci skip] --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 710a0f7b..abf28b8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,8 @@ ### bug fixes -- [Ruby] Handle fallback locale without any translation properly -- [Ruby] Prevent translation entry with null value to override value in fallback locale(s), if enabled +- [Ruby] Handle fallback locale without any translation properly ([#338](https://github.com/fnando/i18n-js/pull/338)) +- [Ruby] Prevent translation entry with null value to override value in fallback locale(s), if enabled ([#334](https://github.com/fnando/i18n-js/pull/334)) ## 3.0.0.rc10 From 3d26bf136baedbd2454b4e1767ed919a91ca05c2 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 26 Jun 2015 09:06:21 +0800 Subject: [PATCH 209/466] ^ Release 3.0.0.rc11 --- CHANGELOG.md | 10 ++++++++++ lib/i18n/js/version.rb | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abf28b8b..1cecc306 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ ### bug fixes + + +## 3.0.0.rc11 + +### breaking changes + +### enhancements + +### bug fixes + - [Ruby] Handle fallback locale without any translation properly ([#338](https://github.com/fnando/i18n-js/pull/338)) - [Ruby] Prevent translation entry with null value to override value in fallback locale(s), if enabled ([#334](https://github.com/fnando/i18n-js/pull/334)) diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index f25d8d8d..deae3288 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -4,7 +4,7 @@ module Version MAJOR = 3 MINOR = 0 PATCH = 0 - STRING = "#{MAJOR}.#{MINOR}.#{PATCH}.rc10" + STRING = "#{MAJOR}.#{MINOR}.#{PATCH}.rc11" end end end From 913e77ebfd011ef16ac462f46fd04fcee78c2e2c Mon Sep 17 00:00:00 2001 From: Dmitry Babenko Date: Sat, 4 Jul 2015 13:56:57 +0300 Subject: [PATCH 210/466] Add syntax highlighting to README --- README.md | 238 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 142 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index 11c77816..582f730f 100644 --- a/README.md +++ b/README.md @@ -22,17 +22,18 @@ Features: #### Rails app Add the gem to your Gemfile. - - source "https://rubygems.org" - gem "rails", "your_rails_version" - # You only need this RC version constraint during the development of `3.0.0`, once stable version is released you can remove `rc8` suffix - # `3.0.0.rc8` is the latest version of released RC version when this entry is changed, you might want to change it later - gem "i18n-js", ">= 3.0.0.rc8" +```ruby +source "https://rubygems.org" +gem "rails", "your_rails_version" +# You only need this RC version constraint during the development of `3.0.0`, once stable version is released you can remove `rc11` suffix +# `3.0.0.rc11` is the latest version of released RC version when this entry is changed, you might want to change it later +gem "i18n-js", ">= 3.0.0.rc11" #### Rails app with [Asset Pipeline](http://guides.rubyonrails.org/asset_pipeline.html) If you're using the [asset pipeline](http://guides.rubyonrails.org/asset_pipeline.html), then you must add the following line to your `app/assets/javascripts/application.js`. +``` ```javascript // @@ -296,65 +297,79 @@ I18n.currentLocale(); In practice, you'll have something like the following in your `application.html.erb`: - +```erb + +``` You can use translate your messages: - I18n.t("some.scoped.translation"); - // or translate with explicit setting of locale - I18n.t("some.scoped.translation", {locale: "fr"}); +```javascript +I18n.t("some.scoped.translation"); +// or translate with explicit setting of locale +I18n.t("some.scoped.translation", {locale: "fr"}); +``` You can also interpolate values: - I18n.t("hello", {name: "John Doe"}); - +```javascript +I18n.t("hello", {name: "John Doe"}); +``` You can set default values for missing scopes: +```javascript +// simple translation +I18n.t("some.missing.scope", {defaultValue: "A default message"}); - // simple translation - I18n.t("some.missing.scope", {defaultValue: "A default message"}); - - // with interpolation - I18n.t("noun", {defaultValue: "I'm a {{noun}}", noun: "Mac"}); +// with interpolation +I18n.t("noun", {defaultValue: "I'm a {{noun}}", noun: "Mac"}); +``` You can also provide a list of default fallbacks for missing scopes: - // As a scope - I18n.t("some.missing.scope", {defaults: [{scope: "some.existing.scope"}]}); +```javascript +// As a scope +I18n.t("some.missing.scope", {defaults: [{scope: "some.existing.scope"}]}); - // As a simple translation - I18n.t("some.missing.scope", {defaults: [{message: "Some message"}]}); +// As a simple translation +I18n.t("some.missing.scope", {defaults: [{message: "Some message"}]}); +``` - Default values must be provided as an array of hashs where the key is the - type of translation desired, a `scope` or a `message`. The translation returned - will be either the first scope recognized, or the first message defined. +Default values must be provided as an array of hashs where the key is the +type of translation desired, a `scope` or a `message`. The translation returned +will be either the first scope recognized, or the first message defined. - The translation will fallback to the `defaultValue` translation if no scope - in `defaults` matches and if no default of type `message` is found. +The translation will fallback to the `defaultValue` translation if no scope +in `defaults` matches and if no default of type `message` is found. Translation fallback can be enabled by enabling the `I18n.fallbacks` option: - +```erb + +``` By default missing translations will first be looked for in less specific versions of the requested locale and if that fails by taking them from your `I18n.defaultLocale`. - // if I18n.defaultLocale = "en" and translation doesn't exist - // for I18n.locale = "de-DE" this key will be taken from "de" locale scope - // or, if that also doesn't exist, from "en" locale scope - I18n.t("some.missing.scope"); +```javascript +// if I18n.defaultLocale = "en" and translation doesn't exist +// for I18n.locale = "de-DE" this key will be taken from "de" locale scope +// or, if that also doesn't exist, from "en" locale scope +I18n.t("some.missing.scope"); +``` Custom fallback rules can also be specified for a particular language. There are three different ways of doing it so: - I18n.locales.no = ["nb", "en"]; - I18n.locales.no = "nb"; - I18n.locales.no = function(locale){ return ["nb"]; }; +```javascript +I18n.locales.no = ["nb", "en"]; +I18n.locales.no = "nb"; +I18n.locales.no = function(locale){ return ["nb"]; }; +``` By default a missing translation will be displayed as @@ -363,7 +378,9 @@ By default a missing translation will be displayed as While you are developing or if you do not want to provide a translation in the default language you can set +```javascript I18n.missingBehaviour='guess'; +``` this will take the last section of your scope and guess the intended value. Camel case becomes lower cased text and underscores are replaced with space @@ -392,61 +409,75 @@ I18n.missingTranslation = function () { return undefined; }; Pluralization is possible as well and by default provides English rules: - I18n.t("inbox.counting", {count: 10}); // You have 10 messages +```javascript +I18n.t("inbox.counting", {count: 10}); // You have 10 messages +``` The sample above expects the following translation: - en: - inbox: - counting: - one: You have 1 new message - other: You have {{count}} new messages - zero: You have no messages +```yaml +en: + inbox: + counting: + one: You have 1 new message + other: You have {{count}} new messages + zero: You have no messages +``` **NOTE:** Rails I18n recognizes the `zero` option. If you need special rules just define them for your language, for example Russian, just add a new pluralizer: - I18n.pluralization["ru"] = function (count) { - var key = count % 10 == 1 && count % 100 != 11 ? "one" : [2, 3, 4].indexOf(count % 10) >= 0 && [12, 13, 14].indexOf(count % 100) < 0 ? "few" : count % 10 == 0 || [5, 6, 7, 8, 9].indexOf(count % 10) >= 0 || [11, 12, 13, 14].indexOf(count % 100) >= 0 ? "many" : "other"; - return [key]; - }; +```javascript +I18n.pluralization["ru"] = function (count) { + var key = count % 10 == 1 && count % 100 != 11 ? "one" : [2, 3, 4].indexOf(count % 10) >= 0 && [12, 13, 14].indexOf(count % 100) < 0 ? "few" : count % 10 == 0 || [5, 6, 7, 8, 9].indexOf(count % 10) >= 0 || [11, 12, 13, 14].indexOf(count % 100) >= 0 ? "many" : "other"; + return [key]; +}; +``` You can find all rules on . If you're using the same scope over and over again, you may use the `scope` option. - var options = {scope: "activerecord.attributes.user"}; +```javascript +var options = {scope: "activerecord.attributes.user"}; - I18n.t("name", options); - I18n.t("email", options); - I18n.t("username", options); +I18n.t("name", options); +I18n.t("email", options); +I18n.t("username", options); +``` You can also provide an array as scope. - // use the greetings.hello scope - I18n.t(["greetings", "hello"]); +```javascript +// use the greetings.hello scope +I18n.t(["greetings", "hello"]); +``` #### Number formatting Similar to Rails helpers, you have localized number and currency formatting. - I18n.l("currency", 1990.99); - // $1,990.99 +```javascript +I18n.l("currency", 1990.99); +// $1,990.99 - I18n.l("number", 1990.99); - // 1,990.99 +I18n.l("number", 1990.99); +// 1,990.99 - I18n.l("percentage", 123.45); - // 123.450% +I18n.l("percentage", 123.45); +// 123.450% +``` To have more control over number formatting, you can use the `I18n.toNumber`, `I18n.toPercentage`, `I18n.toCurrency` and `I18n.toHumanSize` functions. - I18n.toNumber(1000); // 1,000.000 - I18n.toCurrency(1000); // $1,000.00 - I18n.toPercentage(100); // 100.000% +```javascript +I18n.toNumber(1000); // 1,000.000 +I18n.toCurrency(1000); // $1,000.00 +I18n.toPercentage(100); // 100.000% +``` The `toNumber` and `toPercentage` functions accept the following options: @@ -457,9 +488,11 @@ The `toNumber` and `toPercentage` functions accept the following options: See some number formatting examples: - I18n.toNumber(1000, {precision: 0}); // 1,000 - I18n.toNumber(1000, {delimiter: ".", separator: ","}); // 1.000,000 - I18n.toNumber(1000, {delimiter: ".", precision: 0}); // 1.000 +```javascript +I18n.toNumber(1000, {precision: 0}); // 1,000 +I18n.toNumber(1000, {delimiter: ".", separator: ","}); // 1.000,000 +I18n.toNumber(1000, {delimiter: ".", precision: 0}); // 1.000 +``` The `toCurrency` function accepts the following options: @@ -473,7 +506,9 @@ The `toCurrency` function accepts the following options: You can provide only the options you want to override: - I18n.toCurrency(1000, {precision: 0}); // $1,000 +```javascript +I18n.toCurrency(1000, {precision: 0}); // $1,000 +``` The `toHumanSize` function accepts the following options: @@ -485,35 +520,44 @@ The `toHumanSize` function accepts the following options: - I18n.toHumanSize(1234); // 1KB - I18n.toHumanSize(1234 * 1024); // 1MB +```javascript +I18n.toHumanSize(1234); // 1KB +I18n.toHumanSize(1234 * 1024); // 1MB +``` + #### Date formatting - // accepted formats - I18n.l("date.formats.short", "2009-09-18"); // yyyy-mm-dd - I18n.l("time.formats.short", "2009-09-18 23:12:43"); // yyyy-mm-dd hh:mm:ss - I18n.l("time.formats.short", "2009-11-09T18:10:34"); // JSON format with local Timezone (part of ISO-8601) - I18n.l("time.formats.short", "2009-11-09T18:10:34Z"); // JSON format in UTC (part of ISO-8601) - I18n.l("date.formats.short", 1251862029000); // Epoch time - I18n.l("date.formats.short", "09/18/2009"); // mm/dd/yyyy - I18n.l("date.formats.short", (new Date())); // Date object +```javascript +// accepted formats +I18n.l("date.formats.short", "2009-09-18"); // yyyy-mm-dd +I18n.l("time.formats.short", "2009-09-18 23:12:43"); // yyyy-mm-dd hh:mm:ss +I18n.l("time.formats.short", "2009-11-09T18:10:34"); // JSON format with local Timezone (part of ISO-8601) +I18n.l("time.formats.short", "2009-11-09T18:10:34Z"); // JSON format in UTC (part of ISO-8601) +I18n.l("date.formats.short", 1251862029000); // Epoch time +I18n.l("date.formats.short", "09/18/2009"); // mm/dd/yyyy +I18n.l("date.formats.short", (new Date())); // Date object +``` You can also add placeholders to the date format: - I18n.translations["en"] = { - date: { - formats: { - ordinal_day: "%B %{day}" - } - } +```javascript +I18n.translations["en"] = { + date: { + formats: { + ordinal_day: "%B %{day}" } - I18n.l("date.formats.ordinal_day", "2009-09-18", { day: '18th' }); // Sep 18th + } +} +I18n.l("date.formats.ordinal_day", "2009-09-18", { day: '18th' }); // Sep 18th +``` If you prefer, you can use the `I18n.strftime` function to format dates. - var date = new Date(); - I18n.strftime(date, "%d/%m/%Y"); +```javascript +var date = new Date(); +I18n.strftime(date, "%d/%m/%Y"); +``` The accepted formats are: @@ -547,15 +591,17 @@ Check out `spec/*.spec.js` files for more examples! The JavaScript library is language agnostic; so you can use it with PHP, Python, [your favorite language here]. The only requirement is that you need to set the `translations` attribute like following: - I18n.translations = {}; +```javascript +I18n.translations = {}; - I18n.translations["en"] = { - message: "Some special message for you" - } +I18n.translations["en"] = { + message: "Some special message for you" +} - I18n.translations["pt-BR"] = { - message: "Uma mensagem especial para você" - } +I18n.translations["pt-BR"] = { + message: "Uma mensagem especial para você" +} +``` ## Known Issues @@ -580,7 +626,7 @@ Change `config.assets.version` If you are precompiling assets on target machine(s), old assets might be removed and cannot be served in cached pages. -Please see issue #213 for detail & related discussion. +Please see issue [#213](https://github.com/fnando/i18n-js/issues/213) for detail & related discussion. ## Maintainer From 04db90db9df2b13b79f8f9446dd73d9fef5da3c3 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 6 Jul 2015 10:26:23 +0800 Subject: [PATCH 211/466] ~ Fix minor "mistake" in README [ci skip] --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 582f730f..4c257edb 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ source "https://rubygems.org" gem "rails", "your_rails_version" # You only need this RC version constraint during the development of `3.0.0`, once stable version is released you can remove `rc11` suffix # `3.0.0.rc11` is the latest version of released RC version when this entry is changed, you might want to change it later -gem "i18n-js", ">= 3.0.0.rc11" +gem "i18n-js", ">= 3.0.0.rc11" #### Rails app with [Asset Pipeline](http://guides.rubyonrails.org/asset_pipeline.html) @@ -67,9 +67,9 @@ Then get the JS files following the instructions below. 2. If you can't or prefer not to generate this file, you can move the middleware line to your `config/environments/development.rb` file and run `rake i18n:js:export` before deploying. - This will export all translation files, including the custom scopes + This will export all translation files, including the custom scopes you may have defined on `config/i18n-js.yml`. - If `I18n.available_locales` is set (e.g. in your Rails `config/application.rb` file) + If `I18n.available_locales` is set (e.g. in your Rails `config/application.rb` file) then only the specified locales will be exported. Current version of `i18n.js` will also be exported to avoid version mismatching by downloading. @@ -379,7 +379,7 @@ While you are developing or if you do not want to provide a translation in the default language you can set ```javascript - I18n.missingBehaviour='guess'; +I18n.missingBehaviour='guess'; ``` this will take the last section of your scope and guess the intended value. From 968837d676e23a8d630a866f925e09b3a0f8fba6 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 15 Jul 2015 09:48:45 +0800 Subject: [PATCH 212/466] ~ Fix minor "mistake" in README [ci skip] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4c257edb..dd1fcfbb 100644 --- a/README.md +++ b/README.md @@ -28,12 +28,12 @@ gem "rails", "your_rails_version" # You only need this RC version constraint during the development of `3.0.0`, once stable version is released you can remove `rc11` suffix # `3.0.0.rc11` is the latest version of released RC version when this entry is changed, you might want to change it later gem "i18n-js", ">= 3.0.0.rc11" +``` #### Rails app with [Asset Pipeline](http://guides.rubyonrails.org/asset_pipeline.html) If you're using the [asset pipeline](http://guides.rubyonrails.org/asset_pipeline.html), then you must add the following line to your `app/assets/javascripts/application.js`. -``` ```javascript // From 0cd8bdf427964e6ea53d7152b5835c2bbf7bffb7 Mon Sep 17 00:00:00 2001 From: Joseph Cota Date: Thu, 20 Aug 2015 17:38:33 -0500 Subject: [PATCH 213/466] Allow for multiple translation files to be loaded on a page, without stomping all over the locale. --- lib/i18n/js/segment.rb | 17 ++++++++++++----- spec/i18n_js_spec.rb | 25 ++++++++++++++++++++----- spec/segment_spec.rb | 16 +++++++++++----- 3 files changed, 43 insertions(+), 15 deletions(-) diff --git a/lib/i18n/js/segment.rb b/lib/i18n/js/segment.rb index 407dff84..70949ed8 100644 --- a/lib/i18n/js/segment.rb +++ b/lib/i18n/js/segment.rb @@ -33,17 +33,24 @@ def write_file(_file = self.file, _translations = self.translations) f << %(#{self.namespace}.translations || (#{self.namespace}.translations = {});\n) _translations.each do |locale, translations_for_locale| output_translations = I18n::JS.sort_translation_keys? ? Utils.deep_key_sort(translations_for_locale) : translations_for_locale - f << %(#{self.namespace}.translations["#{locale}"] = #{print_json(output_translations)};\n); + f << %(#{self.namespace}.translations["#{locale}"] = #{self.namespace}.translations["#{locale}"] || {};\n) + output_translations.keys.each do |key| + f << %(#{self.namespace}.translations["#{locale}"]["#{key}"] = #{print_json(output_translations[key])};\n); + end end end end # Outputs pretty or ugly JSON depending on :pretty_print option def print_json(translations) - if pretty_print - JSON.pretty_generate(translations) - else - translations.to_json + begin + if pretty_print + JSON.pretty_generate(translations) + else + translations.to_json + end + rescue JSON::GeneratorError + return translations.inspect end end diff --git a/spec/i18n_js_spec.rb b/spec/i18n_js_spec.rb index 6db0e5d8..d5c6e113 100644 --- a/spec/i18n_js_spec.rb +++ b/spec/i18n_js_spec.rb @@ -67,13 +67,17 @@ en_output = File.read(File.join(I18n::JS.export_i18n_js_dir_path, "en.js")) expect(en_output).to eq(< Date: Thu, 20 Aug 2015 18:30:29 -0500 Subject: [PATCH 214/466] I like this solution better. Added a basic object extension method to I18n. This will allow multiple translation files to be loaded on a page. (global, route, module, widget, ..., etc) --- app/assets/javascripts/i18n.js | 23 ++++++++++++++++++- lib/i18n/js/segment.rb | 5 +---- spec/i18n_js_spec.rb | 25 +++++---------------- spec/js/extend.spec.js | 41 ++++++++++++++++++++++++++++++++++ spec/js/specs.html | 1 + spec/js/specs_requirejs.html | 5 +++-- spec/segment_spec.rb | 16 +++++-------- 7 files changed, 78 insertions(+), 38 deletions(-) create mode 100644 spec/js/extend.spec.js diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index b5513d4a..ff7c5b8e 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -857,7 +857,28 @@ } return scope; - } + }; + /** + * Merge obj1 with obj2 + * @param {Object} obj1 Default settings + * @param {Object} obj2 User obj2 + * @returns {Object} Merged values of obj1 and obj2 + */ + I18n.extend = function ( obj1, obj2 ) { + var extended = {}; + var prop; + for (prop in obj1) { + if (Object.prototype.hasOwnProperty.call(obj1, prop)) { + extended[prop] = obj1[prop]; + } + } + for (prop in obj2) { + if (Object.prototype.hasOwnProperty.call(obj2, prop)) { + extended[prop] = obj2[prop]; + } + } + return extended; + }; // Set aliases, so we can save some typing. I18n.t = I18n.translate; diff --git a/lib/i18n/js/segment.rb b/lib/i18n/js/segment.rb index 70949ed8..c2e26124 100644 --- a/lib/i18n/js/segment.rb +++ b/lib/i18n/js/segment.rb @@ -33,10 +33,7 @@ def write_file(_file = self.file, _translations = self.translations) f << %(#{self.namespace}.translations || (#{self.namespace}.translations = {});\n) _translations.each do |locale, translations_for_locale| output_translations = I18n::JS.sort_translation_keys? ? Utils.deep_key_sort(translations_for_locale) : translations_for_locale - f << %(#{self.namespace}.translations["#{locale}"] = #{self.namespace}.translations["#{locale}"] || {};\n) - output_translations.keys.each do |key| - f << %(#{self.namespace}.translations["#{locale}"]["#{key}"] = #{print_json(output_translations[key])};\n); - end + f << %(#{self.namespace}.translations["#{locale}"] = I18n.extend((#{self.namespace}.translations["#{locale}"] || {}), #{print_json(output_translations)});\n) end end end diff --git a/spec/i18n_js_spec.rb b/spec/i18n_js_spec.rb index d5c6e113..a6b00ef1 100644 --- a/spec/i18n_js_spec.rb +++ b/spec/i18n_js_spec.rb @@ -67,17 +67,13 @@ en_output = File.read(File.join(I18n::JS.export_i18n_js_dir_path, "en.js")) expect(en_output).to eq(< + - + + From a883675788886e5dda7d83248a1611d1aab0bddb Mon Sep 17 00:00:00 2001 From: Trevor Burnham Date: Mon, 21 Dec 2015 10:53:28 -0500 Subject: [PATCH 241/466] Replace instanceof checks with their Underscore.js equivalents This fixes some edge cases when values are passed across VMs. --- app/assets/javascripts/i18n.js | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index cb1fdd13..bffa7d1c 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -44,6 +44,22 @@ return decimalAdjust('round', number, -precision).toFixed(precision); }; + // Is a given variable an object? + // Borrowed from Underscore.js + var isObject = function(obj) { + var type = typeof obj; + return type === 'function' || type === 'object' && !!obj; + }; + + // Is a given value an array? + // Borrowed from Underscore.js + var isArray = function(obj) { + if (Array.isArray) { + return Array.isArray(obj); + }; + return Object.prototype.toString.call(obj) === '[object Array]'; + }; + var decimalAdjust = function(type, value, exp) { // If the exp is undefined or zero... if (typeof exp === 'undefined' || +exp === 0) { @@ -204,7 +220,7 @@ result = result(locale); } - if (result instanceof Array === false) { + if (isArray(result) === false) { result = [result]; } @@ -433,7 +449,7 @@ if (typeof(translation) === "string") { translation = this.interpolate(translation, options); - } else if (translation instanceof Object && this.isSet(options.count)) { + } else if (isObject(translation) && this.isSet(options.count)) { translation = this.pluralize(options.count, translation, options); } @@ -482,7 +498,7 @@ options = this.prepareOptions(options); var translations, pluralizer, keys, key, message; - if (scope instanceof Object) { + if (isObject(scope)) { translations = scope; } else { translations = this.lookup(scope, options); From 16a44329781e1a841a0504564629f397bdd7d5b9 Mon Sep 17 00:00:00 2001 From: Trevor Burnham Date: Mon, 21 Dec 2015 10:53:28 -0500 Subject: [PATCH 242/466] Update CHANGELOG with isObject bugfix description --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e5473f1..524f1c4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - [Ruby] Fix of missing initializer at sprockets. ([#371](https://github.com/fnando/i18n-js/pull/371)) - [Ruby] Use proper method to register preprocessor documented by sprockets-rails. ([#376](https://github.com/fnando/i18n-js/pull/376)) - [JS] Correctly round unprecise floating point numbers. +- [JS] Ensure objects are recognized when passed in from an iframe. ([#375](https://github.com/fnando/i18n-js/pull/375)) ## 3.0.0.rc11 From 3a1e516a7d18c07c2bcb0b605db28acbebd026e9 Mon Sep 17 00:00:00 2001 From: Trevor Burnham Date: Tue, 22 Dec 2015 00:06:36 -0500 Subject: [PATCH 243/466] Pass options object to nullPlaceholder and missingPlaceholder This allows arbitrary debugging data provided to I18n.translate to be used in an overridden I18n.missingPlaceholder method. --- app/assets/javascripts/i18n.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 1087b447..89bb4528 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -463,9 +463,9 @@ if (this.isSet(options[name])) { value = options[name].toString().replace(/\$/gm, "_#$#_"); } else if (name in options) { - value = this.nullPlaceholder(placeholder, message); + value = this.nullPlaceholder(placeholder, message, options); } else { - value = this.missingPlaceholder(placeholder, message); + value = this.missingPlaceholder(placeholder, message, options); } regex = new RegExp(placeholder.replace(/\{/gm, "\\{").replace(/\}/gm, "\\}")); @@ -527,7 +527,7 @@ }; // Return a missing placeholder message for given parameters - I18n.missingPlaceholder = function(placeholder, message) { + I18n.missingPlaceholder = function(placeholder, message, options) { return "[missing " + placeholder + " value]"; }; From 75936a7bbd7d363a25fa057ff8366f517b40f878 Mon Sep 17 00:00:00 2001 From: Trevor Burnham Date: Tue, 22 Dec 2015 00:06:36 -0500 Subject: [PATCH 244/466] Add spec for missingPlaceholder override arguments --- spec/js/interpolation.spec.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/spec/js/interpolation.spec.js b/spec/js/interpolation.spec.js index eaba0a6a..e8e93a9a 100644 --- a/spec/js/interpolation.spec.js +++ b/spec/js/interpolation.spec.js @@ -97,4 +97,17 @@ describe("Interpolation", function(){ expect(actual).toEqual("Hello !"); I18n.nullPlaceholder = orig; }); + + it("provides missingPlaceholder with the placeholder, message, and options object", function(){ + var orig = I18n.missingPlaceholder; + I18n.missingPlaceholder = function(placeholder, message, options) { + expect(placeholder).toEqual('{{name}}'); + expect(message).toEqual('Hello {{name}}!'); + expect(options.debugScope).toEqual('landing-page'); + return '[missing-placeholder-debug]'; + }; + actual = I18n.t("greetings.name", {debugScope: 'landing-page'}); + expect(actual).toEqual("Hello [missing-placeholder-debug]!"); + I18n.missingPlaceholder = orig; + }); }); From 4449e02f12f3284af122a8975841f6ecfef4aa13 Mon Sep 17 00:00:00 2001 From: Trevor Burnham Date: Tue, 22 Dec 2015 00:06:36 -0500 Subject: [PATCH 245/466] Update CHANGELOG for missingPlaceholder argument addition --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b2fc7ca..5b4bb095 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### enhancements - [JS] Allow extending of translation files ([#354](https://github.com/fnando/i18n-js/pull/354)) + - [JS] Allow missingPlaceholder to receive extra data for debugging ([#380](https://github.com/fnando/i18n-js/pull/380)) ### bug fixes - [Ruby] Fix of missing initializer at sprockets. ([#371](https://github.com/fnando/i18n-js/pull/371)) From 82fc5ad8d5b55884c9214c7a521e854d299f7cab Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 30 Dec 2015 15:03:07 +0800 Subject: [PATCH 246/466] ^ Release 3.0.0.rc12 --- CHANGELOG.md | 39 +++++++++++++++++++++++++++++++++------ lib/i18n/js/version.rb | 2 +- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecb0d700..cd6f3494 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,35 @@ +# Change Log +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). -## Unreleased +## [Unreleased] -### breaking changes +### Added -### enhancements - - [JS] Allow extending of translation files ([#354](https://github.com/fnando/i18n-js/pull/354)) - - [JS] Allow missingPlaceholder to receive extra data for debugging ([#380](https://github.com/fnando/i18n-js/pull/380)) +- Nothing + +### Changed + +- Nothing + +### Fixed + +- Nothing + + +## [3.0.0.rc12] - 2015-12-30 + +### Added + +- [JS] Allow extending of translation files ([#354](https://github.com/fnando/i18n-js/pull/354)) +- [JS] Allow missingPlaceholder to receive extra data for debugging ([#380](https://github.com/fnando/i18n-js/pull/380)) + +### Changed + +- Nothing + +### Fixed -### bug fixes - [Ruby] Fix of missing initializer at sprockets. ([#371](https://github.com/fnando/i18n-js/pull/371)) - [Ruby] Use proper method to register preprocessor documented by sprockets-rails. ([#376](https://github.com/fnando/i18n-js/pull/376)) - [JS] Correctly round unprecise floating point numbers. @@ -133,3 +155,8 @@ ## Before 3.0.0.rc5 - Things happened. + + + +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc12...HEAD +[3.0.0.rc12]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc11...v3.0.0.rc12 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index deae3288..33124a80 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -4,7 +4,7 @@ module Version MAJOR = 3 MINOR = 0 PATCH = 0 - STRING = "#{MAJOR}.#{MINOR}.#{PATCH}.rc11" + STRING = "#{MAJOR}.#{MINOR}.#{PATCH}.rc12" end end end From d0e698e4dca2579b02b2855adab077f18dcb03b8 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 4 Jan 2016 16:26:17 +0800 Subject: [PATCH 247/466] * Test against MRI 2.3.0 --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 64ddc624..8abad201 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,9 @@ rvm: - 2.0 - 2.1 - 2.2 + # Since the Travis Build Env does not recognize `2.3` alias yet + # We need to specify the version precisely + - 2.3.0 - ruby-head before_install: # Need to install something extra to test JS - npm install jasmine-node@1.14.2 From f0d4d31bac8c0c568bf7e120aff7bac733c38805 Mon Sep 17 00:00:00 2001 From: Shinnosuke Watanabe Date: Wed, 6 Jan 2016 16:48:44 +0900 Subject: [PATCH 248/466] Throw an error when I18n.strftime takes an invalid date Currently, I18n.strftime produces a wrongly formatted string when it takes an invalid date object. For example, ``` I18n.strftime(new Date('foo'), '%Y%m%d'); //=> 'NaNaNaN' ``` --- CHANGELOG.md | 3 ++- app/assets/javascripts/i18n.js | 4 ++++ spec/js/dates.spec.js | 7 +++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd6f3494..5da6b758 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed -- Nothing +- [JS] Throw an error when `I18n.strftime()` takes an invalid date ([#383](https://github.com/fnando/i18n-js/pull/383)) + ## [3.0.0.rc12] - 2015-12-30 diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 9a97af0e..c35d1f6d 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -772,6 +772,10 @@ options = this.prepareOptions(options, DATE); + if (isNaN(date.getTime())) { + throw new Error('I18n.strftime() requires a valid date object, but received an invalid date.'); + } + var weekDay = date.getDay() , day = date.getDate() , year = date.getFullYear() diff --git a/spec/js/dates.spec.js b/spec/js/dates.spec.js index 7997fbb3..88be3f86 100644 --- a/spec/js/dates.spec.js +++ b/spec/js/dates.spec.js @@ -255,4 +255,11 @@ describe("Dates", function(){ date = new Date(2009, 3, 26, 7, 35, 44); expect(I18n.strftime(date, "%p")).toEqual("de:AM"); }); + + it("fails to format invalid date", function(){ + var date = new Date('foo'); + expect(function() { + I18n.strftime(date, "%a"); + }).toThrow('I18n.strftime() requires a valid date object, but received an invalid date.'); + }); }); From 0de86694911b9d4eaa55c25ef689350e4bce215c Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 12 Jan 2016 09:56:00 +0800 Subject: [PATCH 249/466] ~ Add version notice to README [ci skip] --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 4ed2c748..bcd73db5 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,10 @@ Features: - Asset pipeline support - Lots more! :) +## Version Notice +The `master` branch (including this README) is for latest `3.0.0.rc` instead of `2.x`. + + ## Usage ### Installation From 98530094c56a0d3ed831ef4fc1a833019d5626d6 Mon Sep 17 00:00:00 2001 From: Johnny Shields Date: Sun, 13 Mar 2016 04:18:37 +0900 Subject: [PATCH 250/466] Adds "js_extend" option which controls whether or not to use "I18n.extend" in your output files. This can be specified at both segment (single file) and parent level (all files). I've also made "sort_translation_keys" to be settable at both segment and parent level to be consistent. --- lib/i18n/js.rb | 16 +++++++-- lib/i18n/js/segment.rb | 40 +++++++++++++++------ spec/fixtures/js_extend_parent.yml | 6 ++++ spec/fixtures/js_extend_segment.yml | 6 ++++ spec/ruby/i18n/js/segment_spec.rb | 56 ++++++++++++++++++++++++++--- spec/ruby/i18n/js_spec.rb | 39 ++++++++++++++++++-- 6 files changed, 144 insertions(+), 19 deletions(-) create mode 100644 spec/fixtures/js_extend_parent.yml create mode 100644 spec/fixtures/js_extend_segment.yml diff --git a/lib/i18n/js.rb b/lib/i18n/js.rb index 90aed296..7a20e39b 100644 --- a/lib/i18n/js.rb +++ b/lib/i18n/js.rb @@ -48,13 +48,11 @@ def self.configured_segments only = options[:only] || '*' exceptions = [options[:except] || []].flatten - segment_options = options.slice(:namespace, :pretty_print) - result = segment_for_scope(only, exceptions) merge_with_fallbacks!(result) if fallbacks - segments << Segment.new(file, result, segment_options) unless result.empty? + segments << Segment.new(file, result, extract_segment_options(options)) unless result.empty? segments end @@ -168,6 +166,13 @@ def self.fallbacks end end + def self.js_extend + config.fetch(:js_extend) do + # default value + true + end + end + def self.sort_translation_keys? @sort_translation_keys ||= (config[:sort_translation_keys]) if config.has_key?(:sort_translation_keys) @sort_translation_keys = true if @sort_translation_keys.nil? @@ -178,6 +183,11 @@ def self.sort_translation_keys=(value) @sort_translation_keys = !!value end + def self.extract_segment_options(options) + segment_options = {js_extend: js_extend, sort_translation_keys: sort_translation_keys?}.with_indifferent_access + segment_options.merge(options.slice(*Segment::OPTIONS)) + end + ### Export i18n.js begin diff --git a/lib/i18n/js/segment.rb b/lib/i18n/js/segment.rb index 323274f7..b984bbae 100644 --- a/lib/i18n/js/segment.rb +++ b/lib/i18n/js/segment.rb @@ -3,22 +3,25 @@ module JS # Class which enscapulates a translations hash and outputs a single JSON translation file class Segment - attr_accessor :file, :translations, :namespace, :pretty_print - + OPTIONS = [:namespace, :pretty_print, :js_extend, :sort_translation_keys] LOCALE_INTERPOLATOR = /%\{locale\}/ + attr_reader *([:file, :translations] | OPTIONS) + def initialize(file, translations, options = {}) @file = file @translations = translations @namespace = options[:namespace] || 'I18n' @pretty_print = !!options[:pretty_print] + @js_extend = options.has_key?(:js_extend) ? !!options[:js_extend] : true + @sort_translation_keys = options.has_key?(:sort_translation_keys) ? !!options[:sort_translation_keys] : true end # Saves JSON file containing translations def save! - if self.file =~ LOCALE_INTERPOLATOR + if @file =~ LOCALE_INTERPOLATOR I18n.available_locales.each do |locale| - write_file(file_for_locale(locale), self.translations.slice(locale)) + write_file(file_for_locale(locale), @translations.slice(locale)) end else write_file @@ -27,20 +30,37 @@ def save! protected - def write_file(_file = self.file, _translations = self.translations) + def write_file(_file = @file, _translations = @translations) FileUtils.mkdir_p File.dirname(_file) File.open(_file, "w+") do |f| - f << %(#{self.namespace}.translations || (#{self.namespace}.translations = {});\n) + f << js_header _translations.each do |locale, translations_for_locale| - output_translations = I18n::JS.sort_translation_keys? ? Utils.deep_key_sort(translations_for_locale) : translations_for_locale - f << %(#{self.namespace}.translations["#{locale}"] = I18n.extend((#{self.namespace}.translations["#{locale}"] || {}), #{print_json(output_translations)});\n) + f << js_translations(locale, translations_for_locale) end end end + def js_header + %(#{@namespace}.translations || (#{@namespace}.translations = {});\n) + end + + def js_translations(locale, translations) + translations = Utils.deep_key_sort(translations) if @sort_translation_keys + translations = print_json(translations) + js_translations_line(locale, translations) + end + + def js_translations_line(locale, translations) + if @js_extend + %(#{@namespace}.translations["#{locale}"] = I18n.extend((#{@namespace}.translations["#{locale}"] || {}), #{translations});\n) + else + %(#{@namespace}.translations["#{locale}"] = #{translations};\n) + end + end + # Outputs pretty or ugly JSON depending on :pretty_print option def print_json(translations) - if pretty_print + if @pretty_print JSON.pretty_generate(translations) else translations.to_json @@ -49,7 +69,7 @@ def print_json(translations) # interpolates filename def file_for_locale(locale) - self.file.gsub(LOCALE_INTERPOLATOR, locale.to_s) + @file.gsub(LOCALE_INTERPOLATOR, locale.to_s) end end end diff --git a/spec/fixtures/js_extend_parent.yml b/spec/fixtures/js_extend_parent.yml new file mode 100644 index 00000000..2adc95fd --- /dev/null +++ b/spec/fixtures/js_extend_parent.yml @@ -0,0 +1,6 @@ +fallbacks: false +js_extend: false + +translations: + - file: "tmp/i18n-js/js_extend_parent.js" + only: "*.date.formats" diff --git a/spec/fixtures/js_extend_segment.yml b/spec/fixtures/js_extend_segment.yml new file mode 100644 index 00000000..3a99b316 --- /dev/null +++ b/spec/fixtures/js_extend_segment.yml @@ -0,0 +1,6 @@ +fallbacks: false + +translations: + - file: "tmp/i18n-js/js_extend_segment.js" + js_extend: false + only: "*.date.formats" diff --git a/spec/ruby/i18n/js/segment_spec.rb b/spec/ruby/i18n/js/segment_spec.rb index 9b95f3e9..7baa09d6 100644 --- a/spec/ruby/i18n/js/segment_spec.rb +++ b/spec/ruby/i18n/js/segment_spec.rb @@ -6,7 +6,12 @@ let(:translations){ { en: { "test" => "Test" }, fr: { "test" => "Test2" } } } let(:namespace) { "MyNamespace" } let(:pretty_print){ nil } - let(:options) { {namespace: namespace, pretty_print: pretty_print} } + let(:js_extend) { nil } + let(:sort_translation_keys){ nil } + let(:options) { { namespace: namespace, + pretty_print: pretty_print, + js_extend: js_extend, + sort_translation_keys: sort_translation_keys }.delete_if{|k,v| v.nil?} } subject { I18n::JS::Segment.new(file, translations, options) } describe ".new" do @@ -89,10 +94,38 @@ end end - context "when sort_translation_keys? is true" do - before :each do - I18n::JS.sort_translation_keys = true + context "when js_extend is true" do + let(:js_extend){ true } + + let(:translations){ { en: { "b" => "Test", "a" => "Test" } } } + + it 'should output the keys as sorted' do + file_should_exist "segment.js" + + File.open(File.join(temp_path, "segment.js")){|f| f.read}.should eql <<-EOF +MyNamespace.translations || (MyNamespace.translations = {}); +MyNamespace.translations["en"] = I18n.extend((MyNamespace.translations["en"] || {}), {"a":"Test","b":"Test"}); + EOF + end + end + + context "when js_extend is false" do + let(:js_extend){ false } + + let(:translations){ { en: { "b" => "Test", "a" => "Test" } } } + + it 'should output the keys as sorted' do + file_should_exist "segment.js" + + File.open(File.join(temp_path, "segment.js")){|f| f.read}.should eql <<-EOF +MyNamespace.translations || (MyNamespace.translations = {}); +MyNamespace.translations["en"] = {"a":"Test","b":"Test"}; + EOF end + end + + context "when sort_translation_keys is true" do + let(:sort_translation_keys){ true } let(:translations){ { en: { "b" => "Test", "a" => "Test" } } } @@ -105,5 +138,20 @@ EOF end end + + context "when sort_translation_keys is false" do + let(:sort_translation_keys){ false } + + let(:translations){ { en: { "b" => "Test", "a" => "Test" } } } + + it 'should output the keys as sorted' do + file_should_exist "segment.js" + + File.open(File.join(temp_path, "segment.js")){|f| f.read}.should eql <<-EOF +MyNamespace.translations || (MyNamespace.translations = {}); +MyNamespace.translations["en"] = I18n.extend((MyNamespace.translations["en"] || {}), {"b":"Test","a":"Test"}); + EOF + end + end end end diff --git a/spec/ruby/i18n/js_spec.rb b/spec/ruby/i18n/js_spec.rb index a6b00ef1..ed36c061 100644 --- a/spec/ruby/i18n/js_spec.rb +++ b/spec/ruby/i18n/js_spec.rb @@ -30,14 +30,14 @@ it "exports messages using custom output path" do set_config "custom_path.yml" - I18n::JS::Segment.should_receive(:new).with("tmp/i18n-js/all.js", translations, {}).and_call_original + I18n::JS::Segment.should_receive(:new).with("tmp/i18n-js/all.js", translations, {js_extend: true, sort_translation_keys: true}).and_call_original I18n::JS::Segment.any_instance.should_receive(:save!).with(no_args) I18n::JS.export end it "sets default scope to * when not specified" do set_config "no_scope.yml" - I18n::JS::Segment.should_receive(:new).with("tmp/i18n-js/no_scope.js", translations, {}).and_call_original + I18n::JS::Segment.should_receive(:new).with("tmp/i18n-js/no_scope.js", translations, {js_extend: true, sort_translation_keys: true}).and_call_original I18n::JS::Segment.any_instance.should_receive(:save!).with(no_args) I18n::JS.export end @@ -587,6 +587,41 @@ end end end + end + + describe "js_extend option" do + before do + stub_const('I18n::JS::DEFAULT_EXPORT_DIR_PATH', temp_path) + end + + it "exports with js_extend option at parent level" do + set_config "js_extend_parent.yml" + I18n::JS.export + + file_should_exist "js_extend_parent.js" + + output = File.read(File.join(I18n::JS.export_i18n_js_dir_path, "js_extend_parent.js")) + expect(output).to eq(< Date: Sun, 13 Mar 2016 04:25:24 +0900 Subject: [PATCH 251/466] README for js_extend option --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index bcd73db5..8c6b570b 100644 --- a/README.md +++ b/README.md @@ -256,6 +256,20 @@ translations: ``` +#### Javscript Deep Merge (:js_extend option) + +By default, the output file Javascript will call the `I18n.extend` method to ensure that newly loaded locale +files are deep-merged with any locale data already in memory. To disable this either globally or per-file, +set the `js_extend` option to false + +```yaml +js_extend: false # can set here +translations: +- file: "public/javascripts/i18n/translations.js" + js_extend: false # also here +``` + + #### Vanilla JavaScript Just add the `i18n.js` file to your page. You'll have to build the translations object From e26ade82a1bc1653ca2eaf960c1f27dbddd308f1 Mon Sep 17 00:00:00 2001 From: Ryan Wallace Date: Wed, 16 Mar 2016 13:01:37 -0400 Subject: [PATCH 252/466] Init missingBehaviour and missingTranslationPrefix Fixes a JS error that occurs in `I18n.missingTranslation` when `missingBehaviour` is set to `guess` without setting `missingTranslationPrefix`. --- app/assets/javascripts/i18n.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index c35d1f6d..4d7d4bf2 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -191,6 +191,12 @@ if (typeof(this.translations) === "undefined" && this.translations !== null) this.translations = DEFAULT_OPTIONS.translations; + + if (typeof(this.missingBehaviour) === "undefined" && this.missingBehaviour !== null) + this.missingBehaviour = DEFAULT_OPTIONS.missingBehaviour; + + if (typeof(this.missingTranslationPrefix) === "undefined" && this.missingTranslationPrefix !== null) + this.missingTranslationPrefix = DEFAULT_OPTIONS.missingTranslationPrefix; }; I18n.initializeOptions(); From 6fdc31702a663210923ee355368a7bafcd28fc5f Mon Sep 17 00:00:00 2001 From: Johnny Shields Date: Thu, 17 Mar 2016 14:38:42 +0900 Subject: [PATCH 253/466] Fix comments, replace #has_key? with #key? across the repo --- README.md | 4 ++-- lib/i18n/js.rb | 6 +++--- lib/i18n/js/segment.rb | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 8c6b570b..216526c6 100644 --- a/README.md +++ b/README.md @@ -263,10 +263,10 @@ files are deep-merged with any locale data already in memory. To disable this ei set the `js_extend` option to false ```yaml -js_extend: false # can set here +js_extend: false # this will disable Javascript I18n.extend globally translations: - file: "public/javascripts/i18n/translations.js" - js_extend: false # also here + js_extend: false # this will disable Javascript I18n.extend for this file ``` diff --git a/lib/i18n/js.rb b/lib/i18n/js.rb index 7a20e39b..6114f7bc 100644 --- a/lib/i18n/js.rb +++ b/lib/i18n/js.rb @@ -141,7 +141,7 @@ def self.filter(translations, scopes) results[scope.to_sym] = tmp unless tmp.nil? end return results - elsif translations.respond_to?(:has_key?) && translations.has_key?(scope.to_sym) + elsif translations.respond_to?(:key?) && translations.key?(scope.to_sym) return {scope.to_sym => scopes.empty? ? translations[scope.to_sym] : filter(translations[scope.to_sym], scopes)} end nil @@ -174,7 +174,7 @@ def self.js_extend end def self.sort_translation_keys? - @sort_translation_keys ||= (config[:sort_translation_keys]) if config.has_key?(:sort_translation_keys) + @sort_translation_keys ||= (config[:sort_translation_keys]) if config.key?(:sort_translation_keys) @sort_translation_keys = true if @sort_translation_keys.nil? @sort_translation_keys end @@ -202,7 +202,7 @@ def self.export_i18n_js end def self.export_i18n_js_dir_path - @export_i18n_js_dir_path ||= (config[:export_i18n_js] || :none) if config.has_key?(:export_i18n_js) + @export_i18n_js_dir_path ||= (config[:export_i18n_js] || :none) if config.key?(:export_i18n_js) @export_i18n_js_dir_path ||= DEFAULT_EXPORT_DIR_PATH @export_i18n_js_dir_path end diff --git a/lib/i18n/js/segment.rb b/lib/i18n/js/segment.rb index b984bbae..f17a8f18 100644 --- a/lib/i18n/js/segment.rb +++ b/lib/i18n/js/segment.rb @@ -3,7 +3,7 @@ module JS # Class which enscapulates a translations hash and outputs a single JSON translation file class Segment - OPTIONS = [:namespace, :pretty_print, :js_extend, :sort_translation_keys] + OPTIONS = [:namespace, :pretty_print, :js_extend, :sort_translation_keys].freeze LOCALE_INTERPOLATOR = /%\{locale\}/ attr_reader *([:file, :translations] | OPTIONS) @@ -13,8 +13,8 @@ def initialize(file, translations, options = {}) @translations = translations @namespace = options[:namespace] || 'I18n' @pretty_print = !!options[:pretty_print] - @js_extend = options.has_key?(:js_extend) ? !!options[:js_extend] : true - @sort_translation_keys = options.has_key?(:sort_translation_keys) ? !!options[:sort_translation_keys] : true + @js_extend = options.key?(:js_extend) ? !!options[:js_extend] : true + @sort_translation_keys = options.key?(:sort_translation_keys) ? !!options[:sort_translation_keys] : true end # Saves JSON file containing translations From 1cd5e54c84aadaab9c331fb8b43fbea694c8c547 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 17 Mar 2016 13:55:43 +0800 Subject: [PATCH 254/466] ~ Add CHANGELOG entries for merged PRs [ci skip] --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5da6b758..e80a8728 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added -- Nothing +- [Ruby] Added option `js_extend` to not generate JS code for translations with usage of `I18n.extend` ([#397](https://github.com/fnando/i18n-js/pull/397)) ### Changed @@ -14,6 +14,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed +- [JS] Initialize option `missingBehaviour` & `missingTranslationPrefix` with default values ([#398](https://github.com/fnando/i18n-js/pull/398)) - [JS] Throw an error when `I18n.strftime()` takes an invalid date ([#383](https://github.com/fnando/i18n-js/pull/383)) From bec5f6e430963f92325f57e5424d93d85889d4f7 Mon Sep 17 00:00:00 2001 From: Pascal Ernst Date: Thu, 7 Apr 2016 02:15:41 +0200 Subject: [PATCH 255/466] Update link to NPM-Package to v3.0.0.rc12 from rc8 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 216526c6..2f17fe92 100644 --- a/README.md +++ b/README.md @@ -280,7 +280,7 @@ by hand or using your favorite programming language. More info below. Add the following line to your package.json dependencies (where version is the version you want - n.b. npm install requires it to be the gzipped tarball, see [npm install](https://www.npmjs.org/doc/cli/npm-install.html)) ```javascript -"i18n-js": "http://github.com/fnando/i18n-js/archive/v3.0.0.rc8.tar.gz" +"i18n-js": "http://github.com/fnando/i18n-js/archive/v3.0.0.rc12.tar.gz" ``` Run npm install then use via ```javascript From 5ade670e43e72a12d63803a77dac97a8e14702c1 Mon Sep 17 00:00:00 2001 From: Thomas Leishman Date: Tue, 12 Apr 2016 23:46:13 -0500 Subject: [PATCH 256/466] clear middleware cache each time middleware is initialized which will resolve issue #350 upon restarting rails --- lib/i18n/js/middleware.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/i18n/js/middleware.rb b/lib/i18n/js/middleware.rb index 4255b4ca..8bca9b07 100644 --- a/lib/i18n/js/middleware.rb +++ b/lib/i18n/js/middleware.rb @@ -3,6 +3,7 @@ module JS class Middleware def initialize(app) @app = app + clear_cache end def call(env) @@ -30,6 +31,10 @@ def cache end end + def clear_cache + File.delete(cache_path) if File.exist?(cache_path) + end + def save_cache(new_cache) # path could be a symbolic link FileUtils.mkdir_p(cache_dir) unless File.exists?(cache_dir) From 5535fc8597f06a8c9a10fa4bb63d4826863eef2f Mon Sep 17 00:00:00 2001 From: Thomas Leishman Date: Wed, 13 Apr 2016 00:36:29 -0500 Subject: [PATCH 257/466] changelog update for #350 --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e80a8728..f6193396 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). - [JS] Initialize option `missingBehaviour` & `missingTranslationPrefix` with default values ([#398](https://github.com/fnando/i18n-js/pull/398)) - [JS] Throw an error when `I18n.strftime()` takes an invalid date ([#383](https://github.com/fnando/i18n-js/pull/383)) - +- [Ruby] Reset middleware cache on rails startup +([#402](https://github.com/fnando/i18n-js/pull/402)) ## [3.0.0.rc12] - 2015-12-30 From d2fd8f29ca0c01b81c534f0eb8cee661af379565 Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Tue, 17 May 2016 09:40:14 -0300 Subject: [PATCH 258/466] Add another verification. --- spec/js/translate.spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/js/translate.spec.js b/spec/js/translate.spec.js index 07768106..c8cd4bfb 100644 --- a/spec/js/translate.spec.js +++ b/spec/js/translate.spec.js @@ -180,6 +180,7 @@ describe("Translate", function(){ it("escapes $ when doing substitution (IE)", function(){ I18n.locale = "en"; + expect(I18n.t("paid", {price: "$0"})).toEqual("You were paid $0"); expect(I18n.t("paid", {price: "$0.12"})).toEqual("You were paid $0.12"); expect(I18n.t("paid", {price: "$1.35"})).toEqual("You were paid $1.35"); }); From 025baf25afe39132542558ce6a32c0a4ebda0514 Mon Sep 17 00:00:00 2001 From: Nate Sullivan Date: Tue, 21 Jun 2016 11:00:43 -0700 Subject: [PATCH 259/466] Correct deep/shallow merge comment in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2f17fe92..b31b369e 100644 --- a/README.md +++ b/README.md @@ -256,10 +256,10 @@ translations: ``` -#### Javscript Deep Merge (:js_extend option) +#### Javscript Merge (:js_extend option) By default, the output file Javascript will call the `I18n.extend` method to ensure that newly loaded locale -files are deep-merged with any locale data already in memory. To disable this either globally or per-file, +files are shallow-merged with any locale data already in memory. To disable this either globally or per-file, set the `js_extend` option to false ```yaml From ef57682a1ba3ca82b351148d4b2c225f7872abaa Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 29 Jun 2016 12:00:49 +0800 Subject: [PATCH 260/466] ! Fix default missing translation message did not consider locale in option Fix #408 --- app/assets/javascripts/i18n.js | 5 +++-- spec/js/translate.spec.js | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 4d7d4bf2..9126e0e0 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -191,7 +191,7 @@ if (typeof(this.translations) === "undefined" && this.translations !== null) this.translations = DEFAULT_OPTIONS.translations; - + if (typeof(this.missingBehaviour) === "undefined" && this.missingBehaviour !== null) this.missingBehaviour = DEFAULT_OPTIONS.missingBehaviour; @@ -542,8 +542,9 @@ function(match, p1, p2) {return p1 + ' ' + p2.toLowerCase()} ); } + var localeForTranslation = (options != null && options.locale != null) ? options.locale : this.currentLocale(); var fullScope = this.getFullScope(scope, options); - var fullScopeWithLocale = [this.currentLocale(), fullScope].join(this.defaultSeparator); + var fullScopeWithLocale = [localeForTranslation, fullScope].join(this.defaultSeparator); return '[missing "' + fullScopeWithLocale + '" translation]'; }; diff --git a/spec/js/translate.spec.js b/spec/js/translate.spec.js index c8cd4bfb..fc4d4afd 100644 --- a/spec/js/translate.spec.js +++ b/spec/js/translate.spec.js @@ -24,6 +24,12 @@ describe("Translate", function(){ expect(actual).toEqual(expected); }); + it("returns missing message translation with provided locale for invalid scope", function(){ + actual = I18n.t("invalid.scope", { locale: "ja" }); + expected = '[missing "ja.invalid.scope" translation]'; + expect(actual).toEqual(expected); + }); + it("returns guessed translation if missingBehaviour is set to guess", function(){ I18n.missingBehaviour = 'guess' actual = I18n.t("invalid.thisIsAutomaticallyGeneratedTranslation"); From a2b8e3b38c282f2f4737db42a7ba0160594f4941 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 29 Jun 2016 13:04:50 +0800 Subject: [PATCH 261/466] ~ Update CHANGELOG [ci skip] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6193396..b2bf95f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - [JS] Initialize option `missingBehaviour` & `missingTranslationPrefix` with default values ([#398](https://github.com/fnando/i18n-js/pull/398)) - [JS] Throw an error when `I18n.strftime()` takes an invalid date ([#383](https://github.com/fnando/i18n-js/pull/383)) +- [JS] Fix default error message when translation missing to consider locale passed in options - [Ruby] Reset middleware cache on rails startup ([#402](https://github.com/fnando/i18n-js/pull/402)) From 3928666288743f55cfc7a8463326773d083158c3 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 29 Jun 2016 13:39:23 +0800 Subject: [PATCH 262/466] ^ Release 3.0.0.rc13 --- CHANGELOG.md | 17 ++++++++++++++++- lib/i18n/js/version.rb | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2bf95f8..8a6b1714 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,20 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added +- Nothing + +### Changed + +- Nothing + +### Fixed + +- Nothing + +## [3.0.0.rc13] - 2016-06-29 + +### Added + - [Ruby] Added option `js_extend` to not generate JS code for translations with usage of `I18n.extend` ([#397](https://github.com/fnando/i18n-js/pull/397)) ### Changed @@ -162,5 +176,6 @@ This project adheres to [Semantic Versioning](http://semver.org/). -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc12...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc13...HEAD +[3.0.0.rc13]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc12...v3.0.0.rc13 [3.0.0.rc12]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc11...v3.0.0.rc12 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index 33124a80..af742585 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -4,7 +4,7 @@ module Version MAJOR = 3 MINOR = 0 PATCH = 0 - STRING = "#{MAJOR}.#{MINOR}.#{PATCH}.rc12" + STRING = "#{MAJOR}.#{MINOR}.#{PATCH}.rc13" end end end From 6dac31dd3ccd9a5235b3d810de2823775a45bfb1 Mon Sep 17 00:00:00 2001 From: Aubin LORIEUX Date: Fri, 15 Jul 2016 15:01:30 +0200 Subject: [PATCH 263/466] Bugfix: make i18n-js compatible with rails 5 --- lib/i18n/js/dependencies.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/i18n/js/dependencies.rb b/lib/i18n/js/dependencies.rb index 0fdd1c1f..e709ed15 100644 --- a/lib/i18n/js/dependencies.rb +++ b/lib/i18n/js/dependencies.rb @@ -12,6 +12,10 @@ def rails4? safe_gem_check("rails", "~> 4.0", ">= 4.0.0.beta1") && running_rails4? end + def rails5? + safe_gem_check("rails", "~> 5.0", ">= 5.0.0.beta1") && running_rails5? + end + def sprockets_supports_register_preprocessor? defined?(Sprockets) && Sprockets.respond_to?(:register_preprocessor) end @@ -26,7 +30,7 @@ def rails_available? def using_asset_pipeline? assets_pipeline_available = - (rails3? || rails4?) && + (rails3? || rails4? || rails5?) && Rails.respond_to?(:application) && Rails.application.respond_to?(:assets) rails3_assets_enabled = @@ -34,7 +38,7 @@ def using_asset_pipeline? assets_pipeline_available && Rails.application.config.assets.enabled != false - assets_pipeline_available && (rails4? || rails3_assets_enabled) + assets_pipeline_available && (rails4? || rails5? || rails3_assets_enabled) end private @@ -47,6 +51,10 @@ def running_rails4? running_rails? && Rails.version.to_i == 4 end + def running_rails5? + running_rails? && Rails.version.to_i == 5 + end + def running_rails? defined?(Rails) && Rails.respond_to?(:version) end From 56b8b2b39c95aa25563a6b2d397d4c76a7965495 Mon Sep 17 00:00:00 2001 From: Takashi Nakagawa Date: Wed, 3 Aug 2016 15:50:50 +0900 Subject: [PATCH 264/466] Merge two objects that has same key in separated files --- README.md | 42 +++++++++++++++++----------------- app/assets/javascripts/i18n.js | 29 +++++++++++++---------- spec/js/extend.spec.js | 21 +++++++++++++++++ 3 files changed, 59 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index b31b369e..faed2d7b 100644 --- a/README.md +++ b/README.md @@ -65,23 +65,23 @@ Then get the JS files following the instructions below. **There are two ways to get `translations.js`.** -1. This `translations.js` file can be automatically generated by the `I18n::JS::Middleware`. - Just add `config.middleware.use I18n::JS::Middleware` to your `config/application.rb` file. +1. This `translations.js` file can be automatically generated by the `I18n::JS::Middleware`. + Just add `config.middleware.use I18n::JS::Middleware` to your `config/application.rb` file. 2. If you can't or prefer not to generate this file, you can move the middleware line to your `config/environments/development.rb` file and run `rake i18n:js:export` before deploying. This will export all translation files, including the custom scopes - you may have defined on `config/i18n-js.yml`. + you may have defined on `config/i18n-js.yml`. If `I18n.available_locales` is set (e.g. in your Rails `config/application.rb` file) - then only the specified locales will be exported. + then only the specified locales will be exported. Current version of `i18n.js` will also be exported to avoid version mismatching by downloading. #### Export Configuration (For translations) -Exported translation files generated by `I18n::JS::Middleware` or `rake i18n:js:export` can be customized with config file `config/i18n-js.yml` -(use `rails generate i18n:js:config` to create it). -You can even get more files generated to different folders and with different translations to best suit your needs. -The config file also affects developers using Asset Pipeline to require translations. +Exported translation files generated by `I18n::JS::Middleware` or `rake i18n:js:export` can be customized with config file `config/i18n-js.yml` +(use `rails generate i18n:js:config` to create it). +You can even get more files generated to different folders and with different translations to best suit your needs. +The config file also affects developers using Asset Pipeline to require translations. Except the option `file`, since all translations are required by adding `//= require i18n/translations`. Examples: @@ -134,14 +134,14 @@ translations: #### Export Configuration (For other things) - `I18n::JS.config_file_path` - Expected Type: `String` - Default: `config/i18n-js.yml` - Behaviour: Try to read the config file from that location + Expected Type: `String` + Default: `config/i18n-js.yml` + Behaviour: Try to read the config file from that location - `I18n::JS.export_i18n_js_dir_path` - Expected Type: `String` - Default: `public/javascripts` - Behaviour: + Expected Type: `String` + Default: `public/javascripts` + Behaviour: - Any `String`: considered as a relative path for a folder to `Rails.root` and export `i18n.js` to that folder for `rake i18n:js:export` - Any non-`String` (`nil`, `false`, `:none`, etc): Disable `i18n.js` exporting @@ -169,7 +169,7 @@ To find more examples on how to use the configuration file please refer to the t #### Fallbacks -If you specify the `fallbacks` option, you will be able to fill missing translations with those inside fallback locale(s). +If you specify the `fallbacks` option, you will be able to fill missing translations with those inside fallback locale(s). Default value is `true`. Examples: @@ -256,10 +256,10 @@ translations: ``` -#### Javscript Merge (:js_extend option) +#### Javscript Deep Merge (:js_extend option) By default, the output file Javascript will call the `I18n.extend` method to ensure that newly loaded locale -files are shallow-merged with any locale data already in memory. To disable this either globally or per-file, +files are deep-merged with any locale data already in memory. To disable this either globally or per-file, set the `js_extend` option to false ```yaml @@ -617,7 +617,7 @@ This method is useful for very large apps where a single contained translations. + file: "app/assets/javascript/nls/welcome.js" only: + '*.welcome.*' - + + file: "app/assets/javascript/nls/albums.js" only: + '*.albums.*' @@ -687,13 +687,13 @@ To use this with require.js we are only going to change a few things from above. deps: + i18n - # Finally in your modules + # Finally in your modules modules: + name: 'application' include: + i18n + 'nls/global' - + + name: 'welcome' exclude: + application @@ -767,7 +767,7 @@ This means that new locale files will not be detected, and so they will not trig $ rake assets:precompile ``` or similar commands. If you are precompiling assets on the target machine(s), cached pages may be broken by this, so they will need to be refreshed. - + 2. You can change something in a different locale file. 3. Finally, you can change `config.assets.version`. diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 9126e0e0..3b9f961c 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -79,6 +79,20 @@ return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)); } + var merge = function (dest, obj) { + var key, value; + for (key in obj) if (obj.hasOwnProperty(key)) { + value = obj[key]; + if (Object.prototype.toString.call(value) === '[object String]') { + dest[key] = value; + } else { + if (dest[key] == null) dest[key] = {}; + merge(dest[key], value); + } + } + return dest; + }; + // Set default days/months translations. var DATE = { day_names: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] @@ -921,19 +935,10 @@ * https://stackoverflow.com/questions/8157700/object-has-no-hasownproperty-method-i-e-its-undefined-ie8 */ I18n.extend = function ( obj1, obj2 ) { - var extended = {}; - var prop; - for (prop in obj1) { - if (Object.prototype.hasOwnProperty.call(obj1, prop)) { - extended[prop] = obj1[prop]; - } - } - for (prop in obj2) { - if (Object.prototype.hasOwnProperty.call(obj2, prop)) { - extended[prop] = obj2[prop]; - } + if (typeof(obj1) === "undefined" && typeof(obj2) === "undefined") { + return {}; } - return extended; + return merge(obj1, obj2); }; // Set aliases, so we can save some typing. diff --git a/spec/js/extend.spec.js b/spec/js/extend.spec.js index f5e8c29a..9c5501ea 100644 --- a/spec/js/extend.spec.js +++ b/spec/js/extend.spec.js @@ -38,4 +38,25 @@ describe("Extend", function () { expect(I18n.extend(obj1,obj2)).toEqual(expected); }); + + it("should merge deeply from obj1 with the same key of obj2", function() { + var obj1 = { + test1: { + test2: "abc" + } + } + , obj2 = { + test1: { + test3: "xyz" + } + } + , expected = { + test1: { + test2: "abc" + , test3: "xyz" + } + } + + expect(I18n.extend(obj1, obj2)).toEqual(expected); + }) }); From f37bec498d9acf659cffdd0f690a656e7f205f2f Mon Sep 17 00:00:00 2001 From: Takashi Nakagawa Date: Wed, 10 Aug 2016 18:27:50 +0900 Subject: [PATCH 265/466] Update CHANGELOG.md --- CHANGELOG.md | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a6b1714..e7783682 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,20 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.0.0.rc13] - 2016-08-10 + +### Added + +- Nothing + +### Changed + +- [JS] Method `I18n.extend()` behave as deep merging instead of shallow merging. + +### Fixed + +- Nothing + ## [3.0.0.rc13] - 2016-06-29 ### Added @@ -31,7 +45,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - [JS] Initialize option `missingBehaviour` & `missingTranslationPrefix` with default values ([#398](https://github.com/fnando/i18n-js/pull/398)) - [JS] Throw an error when `I18n.strftime()` takes an invalid date ([#383](https://github.com/fnando/i18n-js/pull/383)) - [JS] Fix default error message when translation missing to consider locale passed in options -- [Ruby] Reset middleware cache on rails startup +- [Ruby] Reset middleware cache on rails startup ([#402](https://github.com/fnando/i18n-js/pull/402)) @@ -94,7 +108,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - [Ruby] Add `:except` option to exclude certain phrases or groups of phrases from the outputted translations ([#312](https://github.com/fnando/i18n-js/pull/312)) - [JS] You can now set `I18n.missingBehavior='guess'` to have the scope string output as text instead of of the - "[missing `scope`]" message when no translation is available. + "[missing `scope`]" message when no translation is available. Combined that with `I18n.missingTranslationPrefix='SOMETHING'` and you can still identify those missing strings. ([#304](https://github.com/fnando/i18n-js/pull/304)) @@ -110,8 +124,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### enhancements - Add support for loading via AMD and CommonJS module loaders ([#266](https://github.com/fnando/i18n-js/pull/266)) -- Add `I18n.nullPlaceholder` - Defaults to I18n.missingPlaceholder (`[missing {{name}} value]`) +- Add `I18n.nullPlaceholder` + Defaults to I18n.missingPlaceholder (`[missing {{name}} value]`) Set to `function() {return "";}` to match Ruby `I18n.t("name: %{name}", name: nil)` - For date formatting, you can now also add placeholders to the date format, see README for detail - Add fallbacks option to `i18n-js.yml`, defaults to `true` @@ -121,8 +135,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Fix factory initialization so that the Node/CommonJS branch only gets executed if the environment is Node/CommonJS (it currently will execute if module is defined in the global scope, which occurs with QUnit, for example) - Fix pluralization rules selection for negative `count` (e.g. `-1` was lead to use `one` for pluralization) ([#268](https://github.com/fnando/i18n-js/pull/268)) -- Remove check for `Rails.configuration.assets.compile` before telling Sprockets the dependency of translations JS file - This might be the reason of many "cache not expired" issues +- Remove check for `Rails.configuration.assets.compile` before telling Sprockets the dependency of translations JS file + This might be the reason of many "cache not expired" issues Discovered/reported in #277 ## 3.0.0.rc7 @@ -130,11 +144,11 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### enhancements - The Rails Engine initializer is now named as `i18n-js.register_preprocessor` (https://github.com/fnando/i18n-js/pull/261) -- Rename `I18n::JS.config_file` to `I18n::JS.config_file_path` and make it configurable +- Rename `I18n::JS.config_file` to `I18n::JS.config_file_path` and make it configurable Expected a `String`, default is still `config/i18n-js.yml` - When running `rake i18n:js:export`, the `i18n.js` will also be exported to `I18n::JS.export_i18n_js_dir_path` by default -- Add `I18n::JS.export_i18n_js_dir_path` - Expected a `String`, default is `public/javascripts` +- Add `I18n::JS.export_i18n_js_dir_path` + Expected a `String`, default is `public/javascripts` Set to `nil` will disable exporting `i18n.js` ### bug fixes From c97ad33e3415b892eaabc80a6b165cbf1d1ac747 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 11 Aug 2016 09:28:20 +0800 Subject: [PATCH 266/466] ~ Fix CHANGELOG entries Remove duplicate version entry added in PR --- CHANGELOG.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7783682..192950ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,20 +10,6 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- Nothing - -### Fixed - -- Nothing - -## [3.0.0.rc13] - 2016-08-10 - -### Added - -- Nothing - -### Changed - - [JS] Method `I18n.extend()` behave as deep merging instead of shallow merging. ### Fixed From eaa769b00ae84ee24f7f4b8891e5344a691ca565 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 22 Aug 2016 15:17:43 +0800 Subject: [PATCH 267/466] * Support sprockets 2, 3 and 4 when registering preprocessor --- lib/i18n/js/dependencies.rb | 4 ++ lib/i18n/js/engine.rb | 108 ++++++++++++++++++++++++++---------- 2 files changed, 84 insertions(+), 28 deletions(-) diff --git a/lib/i18n/js/dependencies.rb b/lib/i18n/js/dependencies.rb index e709ed15..4048cf43 100644 --- a/lib/i18n/js/dependencies.rb +++ b/lib/i18n/js/dependencies.rb @@ -28,6 +28,10 @@ def rails_available? safe_gem_check("rails", '>= 3.0.0.beta') end + # This cannot be called at class definition time + # Since not all libraries are loaded + # + # Call this in an initializer def using_asset_pipeline? assets_pipeline_available = (rails3? || rails4? || rails5?) && diff --git a/lib/i18n/js/engine.rb b/lib/i18n/js/engine.rb index ce622fd9..35b68175 100644 --- a/lib/i18n/js/engine.rb +++ b/lib/i18n/js/engine.rb @@ -2,39 +2,91 @@ module I18n module JS + # @api private + # The class cannot be private + class SprocketsExtension + # Actual definition is placed below + end + class Engine < ::Rails::Engine - # `sprockets.environment` was used for 1.x of `sprockets-rails` - # https://github.com/rails/sprockets-rails/issues/227 - # - # References for current values: - # - # Here is where sprockets are attached with Rails. There is no 'sprockets.environment' mentioned. - # https://github.com/rails/sprockets-rails/blob/master/lib/sprockets/railtie.rb - # - # Finisher hook is the place which should be used as border. - # http://guides.rubyonrails.org/configuring.html#initializers - # - # For detail see Pull Request: - # https://github.com/fnando/i18n-js/pull/371 - initializer "i18n-js.register_preprocessor", after: :engines_blank_point, before: :finisher_hook do - next unless JS::Dependencies.using_asset_pipeline? - next unless JS::Dependencies.sprockets_supports_register_preprocessor? - - # From README of 2.x & 3.x of `sprockets-rails` - # It seems the `configure` block is preferred way to call `register_preprocessor` - # Not sure if this will break older versions of rails + if JS::Dependencies.sprockets_supports_register_preprocessor? + # constant `Sprockets` should be available here after + # `.sprockets_supports_register_preprocessor?` called + sprockets_version = Gem::Version.new(Sprockets::VERSION).release + v2_only = Gem::Dependency.new("", " ~> 2") + v3_plus = Gem::Dependency.new("", " >= 3") + + # See https://github.com/rails/sprockets/blob/master/guides/extending_sprockets.md#supporting-all-versions-of-sprockets-in-processors + # for reference of supporting multiple versions + + # `sprockets.environment` was used for 1.x of `sprockets-rails` + # https://github.com/rails/sprockets-rails/issues/227 + # + # References for current values: + # + # Here is where sprockets are attached with Rails. There is no 'sprockets.environment' mentioned. + # https://github.com/rails/sprockets-rails/blob/master/lib/sprockets/railtie.rb # - # https://github.com/rails/sprockets-rails/blob/v2.3.3/README.md - # https://github.com/rails/sprockets-rails/blob/v3.0.0/README.md - Rails.application.config.assets.configure do |config| - config.register_preprocessor "application/javascript", :"i18n-js_dependencies" do |context, source| - if context.logical_path == "i18n/filtered" - ::I18n.load_path.each {|path| context.depend_on(File.expand_path(path))} - end - source + # Finisher hook is the place which should be used as border. + # http://guides.rubyonrails.org/configuring.html#initializers + # + # For detail see Pull Request: + # https://github.com/fnando/i18n-js/pull/371 + initializer_args = case sprockets_version + when -> (v) { v2_only.match?("", v) || v3_plus.match?("", v) } + { after: :engines_blank_point, before: :finisher_hook } + else + raise StandardError, "Sprockets version #{sprockets_version} is not supported" + end + + initializer "i18n-js.register_preprocessor", initializer_args do + # This must be called inside initializer block + # For details see comments for `using_asset_pipeline?` + next unless JS::Dependencies.using_asset_pipeline? + + # From README of 2.x & 3.x of `sprockets-rails` + # It seems the `configure` block is preferred way to call `register_preprocessor` + # Not sure if this will break older versions of rails + # + # https://github.com/rails/sprockets-rails/blob/v2.3.3/README.md + # https://github.com/rails/sprockets-rails/blob/v3.0.0/README.md + Rails.application.config.assets.configure do |config| + config.register_preprocessor( + "application/javascript", + ::I18n::JS::SprocketsExtension, + ) end end end end + + # @api private + class SprocketsExtension + def initialize(filename, &block) + @filename = filename + @source = block.call + end + + def render(context, empty_hash_wtf) + self.class.run(@filename, @source, context) + end + + def self.run(filename, source, context) + if context.logical_path == "i18n/filtered" + ::I18n.load_path.each { |path| context.depend_on(File.expand_path(path)) } + end + + source + end + + def self.call(input) + filename = input[:filename] + source = input[:data] + context = input[:environment].context_class.new(input) + + result = run(filename, source, context) + context.metadata.merge(data: result) + end + end end end From 5a29ac4562fbb4ff7954037b510f792deeffa16d Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 29 Aug 2016 13:25:25 +0800 Subject: [PATCH 268/466] ~ Update CHANGELOG [ci skip] --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 192950ad..fd7a86ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,11 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- [JS] Method `I18n.extend()` behave as deep merging instead of shallow merging. +- [JS] Method `I18n.extend()` behave as deep merging instead of shallow merging. (https://github.com/fnando/i18n-js/pull/416) +- [Ruby] Use object/class instead of block when registering Sprockets preprocessor (https://github.com/fnando/i18n-js/pull/418) + To ensure that your cache will expire properly based on locale file content after upgrading, + you should run `rake assets:clobber` and/or other rake tasks that clear the asset cache once gem updated +- [Ruby] Detect & support rails 5 (https://github.com/fnando/i18n-js/pull/413) ### Fixed From babb881ae64976bc0026f0e7519a16ba4132e309 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 29 Aug 2016 13:29:44 +0800 Subject: [PATCH 269/466] ^ Release 3.0.0.rc14 --- CHANGELOG.md | 17 +++++++++++++---- lib/i18n/js/version.rb | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd7a86ab..551886c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,15 +10,23 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed +- Nothing + +### Fixed + +- Nothing + + +## [3.0.0.rc14] - 2016-08-29 + +### Changed + - [JS] Method `I18n.extend()` behave as deep merging instead of shallow merging. (https://github.com/fnando/i18n-js/pull/416) - [Ruby] Use object/class instead of block when registering Sprockets preprocessor (https://github.com/fnando/i18n-js/pull/418) To ensure that your cache will expire properly based on locale file content after upgrading, you should run `rake assets:clobber` and/or other rake tasks that clear the asset cache once gem updated - [Ruby] Detect & support rails 5 (https://github.com/fnando/i18n-js/pull/413) -### Fixed - -- Nothing ## [3.0.0.rc13] - 2016-06-29 @@ -180,6 +188,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc13...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc14...HEAD +[3.0.0.rc14]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc13...v3.0.0.rc14 [3.0.0.rc13]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc12...v3.0.0.rc13 [3.0.0.rc12]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc11...v3.0.0.rc12 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index af742585..0b9f0198 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -4,7 +4,7 @@ module Version MAJOR = 3 MINOR = 0 PATCH = 0 - STRING = "#{MAJOR}.#{MINOR}.#{PATCH}.rc13" + STRING = "#{MAJOR}.#{MINOR}.#{PATCH}.rc14" end end end From a2ce930a92de299a20b1affc63b4914ba7084944 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 31 Aug 2016 11:10:03 +0800 Subject: [PATCH 270/466] * Use old syntax to define lambda for compatibility with older Rubies --- lib/i18n/js/engine.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/i18n/js/engine.rb b/lib/i18n/js/engine.rb index 35b68175..274c0605 100644 --- a/lib/i18n/js/engine.rb +++ b/lib/i18n/js/engine.rb @@ -32,8 +32,11 @@ class Engine < ::Rails::Engine # # For detail see Pull Request: # https://github.com/fnando/i18n-js/pull/371 + # + # Not using -> for JRuby compatibility + # See https://github.com/fnando/i18n-js/issues/419 initializer_args = case sprockets_version - when -> (v) { v2_only.match?("", v) || v3_plus.match?("", v) } + when lambda {|v| v2_only.match?("", v) || v3_plus.match?("", v) } { after: :engines_blank_point, before: :finisher_hook } else raise StandardError, "Sprockets version #{sprockets_version} is not supported" From 06652b6fd31daafb3a7c5f07dddc9d32c6079a92 Mon Sep 17 00:00:00 2001 From: Dan Brooks Date: Fri, 2 Sep 2016 09:41:05 +0100 Subject: [PATCH 271/466] Allow numeric values in YAML --- app/assets/javascripts/i18n.js | 16 ++++++++++++---- spec/js/extend.spec.js | 23 ++++++++++++++++++++++- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 3b9f961c..57373eca 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -53,11 +53,19 @@ // Is a given value an array? // Borrowed from Underscore.js - var isArray = function(obj) { + var isArray = function(val) { if (Array.isArray) { - return Array.isArray(obj); + return Array.isArray(val); }; - return Object.prototype.toString.call(obj) === '[object Array]'; + return Object.prototype.toString.call(val) === '[object Array]'; + }; + + var isString = function(val) { + return typeof value == 'string' || Object.prototype.toString.call(val) === '[object String]' + }; + + var isNumber = function(val) { + return typeof val == 'number' || Object.prototype.toString.call(val) === '[object Number]' }; var decimalAdjust = function(type, value, exp) { @@ -83,7 +91,7 @@ var key, value; for (key in obj) if (obj.hasOwnProperty(key)) { value = obj[key]; - if (Object.prototype.toString.call(value) === '[object String]') { + if (isString(value) || isNumber(value)) { dest[key] = value; } else { if (dest[key] == null) dest[key] = {}; diff --git a/spec/js/extend.spec.js b/spec/js/extend.spec.js index 9c5501ea..fa1ebaa4 100644 --- a/spec/js/extend.spec.js +++ b/spec/js/extend.spec.js @@ -58,5 +58,26 @@ describe("Extend", function () { } expect(I18n.extend(obj1, obj2)).toEqual(expected); - }) + }); + + it("should merge both numbers and string", function() { + var obj1 = { + test1: { + test2: 43 + } + } + , obj2 = { + test1: { + test3: 23 + } + } + , expected = { + test1: { + test2: 43 + , test3: 23 + } + } + + expect(I18n.extend(obj1, obj2)).toEqual(expected); + }); }); From ea3262933a310ce93aa33377579d4479b3c31a28 Mon Sep 17 00:00:00 2001 From: Dan Brooks Date: Fri, 2 Sep 2016 12:45:31 +0100 Subject: [PATCH 272/466] Support booleans too --- app/assets/javascripts/i18n.js | 10 +++++++--- spec/js/extend.spec.js | 10 ++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 57373eca..ac1f39bc 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -61,11 +61,15 @@ }; var isString = function(val) { - return typeof value == 'string' || Object.prototype.toString.call(val) === '[object String]' + return typeof value == 'string' || Object.prototype.toString.call(val) === '[object String]'; }; var isNumber = function(val) { - return typeof val == 'number' || Object.prototype.toString.call(val) === '[object Number]' + return typeof val == 'number' || Object.prototype.toString.call(val) === '[object Number]'; + }; + + var isBoolean = function(val) { + return val === true || val === false; }; var decimalAdjust = function(type, value, exp) { @@ -91,7 +95,7 @@ var key, value; for (key in obj) if (obj.hasOwnProperty(key)) { value = obj[key]; - if (isString(value) || isNumber(value)) { + if (isString(value) || isNumber(value) || isBoolean(value)) { dest[key] = value; } else { if (dest[key] == null) dest[key] = {}; diff --git a/spec/js/extend.spec.js b/spec/js/extend.spec.js index fa1ebaa4..beaa71da 100644 --- a/spec/js/extend.spec.js +++ b/spec/js/extend.spec.js @@ -60,21 +60,23 @@ describe("Extend", function () { expect(I18n.extend(obj1, obj2)).toEqual(expected); }); - it("should merge both numbers and string", function() { + it("should correctly merge string, numberic and boolean values", function() { var obj1 = { test1: { - test2: 43 + test2: false } } , obj2 = { test1: { - test3: 23 + test3: 23, + test4: 'abc' } } , expected = { test1: { - test2: 43 + test2: false , test3: 23 + , test4: 'abc' } } From dcb59942d95e3224a5dd7402605e207f4929c465 Mon Sep 17 00:00:00 2001 From: Alexander Komarov Date: Sat, 17 Sep 2016 15:09:19 +0300 Subject: [PATCH 273/466] Remove fallback locale validation --- lib/i18n/js/fallback_locales.rb | 11 ----------- spec/ruby/i18n/js/fallback_locales_spec.rb | 12 +----------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/lib/i18n/js/fallback_locales.rb b/lib/i18n/js/fallback_locales.rb index 38729c72..2b5eee58 100644 --- a/lib/i18n/js/fallback_locales.rb +++ b/lib/i18n/js/fallback_locales.rb @@ -32,7 +32,6 @@ def locales end locales.map! { |locale| locale.to_sym } - ensure_valid_locales!(locales) locales end @@ -66,16 +65,6 @@ def ensure_valid_fallbacks_as_array! fail ArgumentError, "If fallbacks is passed as Array, it must ony include Strings or Symbols. Given: #{fallbacks}" end - - # Ensures that only valid locales are returned. - # - # @note - # This ignores option `I18n.enforce_available_locales` - def ensure_valid_locales!(locales) - if locales.any? { |locale| !::I18n.available_locales.include?(locale) } - fail ArgumentError, "Valid locales: #{::I18n.available_locales.join(", ")} - Given Locales: #{locales.join(", ")}" - end - end end end end diff --git a/spec/ruby/i18n/js/fallback_locales_spec.rb b/spec/ruby/i18n/js/fallback_locales_spec.rb index ee02bbf6..e80ed9dd 100644 --- a/spec/ruby/i18n/js/fallback_locales_spec.rb +++ b/spec/ruby/i18n/js/fallback_locales_spec.rb @@ -42,7 +42,7 @@ context "when given a invalid locale as fallbacks" do let(:fallbacks) { :invalid_locale } - it { expect(fetching_locales).to raise_error(ArgumentError) } + it { should eq([:invalid_locale]) } end context "when given a invalid type as fallbacks" do @@ -50,16 +50,6 @@ it { expect(fetching_locales).to raise_error(ArgumentError) } end - context "when given an invalid Array as fallbacks" do - let(:fallbacks) { [:de, :en, :invalid_locale] } - it { expect(fetching_locales).to raise_error(ArgumentError) } - end - - context "when given a invalid Hash as fallbacks" do - let(:fallbacks) do { :fr => [:de, :en, :invalid_locale] } end - it { expect(fetching_locales).to raise_error(ArgumentError) } - end - # I18n::Backend::Fallbacks context "when I18n::Backend::Fallbacks is used" do let(:backend_with_fallbacks) { backend_class_with_fallbacks.new } From dbb29aa06234ee29c70b7bb8b0b48fd04ce18fa9 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 19 Sep 2016 16:52:48 +0800 Subject: [PATCH 274/466] * Use new syntax for stubbing --- spec/spec_helper.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1f82824a..85c94379 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -8,7 +8,10 @@ module Helpers # Set the configuration as the current one def set_config(path) config_file_path = File.dirname(__FILE__) + "/fixtures/#{path}" - I18n::JS.stub(:config? => true, :config_file_path => config_file_path) + allow(I18n::JS).to receive_messages( + :config? => true, + :config_file_path => config_file_path, + ) end # Shortcut to I18n::JS.translations From b88747f665e151f9ee7d91c2416c902b60aecf87 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 22 Sep 2016 13:28:59 +0800 Subject: [PATCH 275/466] * Use our own implementation instead of `with_indifferent_access` from `active_support` Also fix a hidden issue that the gem does not declare dependency on `active_support` --- lib/i18n/js.rb | 36 ++++++++++++++------ lib/i18n/js/private/hash_with_symbol_keys.rb | 36 ++++++++++++++++++++ spec/ruby/i18n/js_spec.rb | 2 +- 3 files changed, 62 insertions(+), 12 deletions(-) create mode 100644 lib/i18n/js/private/hash_with_symbol_keys.rb diff --git a/lib/i18n/js.rb b/lib/i18n/js.rb index 6114f7bc..d28aa3ad 100644 --- a/lib/i18n/js.rb +++ b/lib/i18n/js.rb @@ -1,7 +1,9 @@ require "yaml" -require "i18n" require "fileutils" +require "i18n" + require "i18n/js/utils" +require "i18n/js/private/hash_with_symbol_keys" module I18n module JS @@ -43,16 +45,23 @@ def self.segment_for_scope(scope, exceptions) end def self.configured_segments - config[:translations].inject([]) do |segments, options| - file = options[:file] - only = options[:only] || '*' - exceptions = [options[:except] || []].flatten + config[:translations].inject([]) do |segments, options_hash| + options_hash_with_symbol_keys = Private::HashWithSymbolKeys.new(options_hash) + file = options_hash_with_symbol_keys[:file] + only = options_hash_with_symbol_keys[:only] || '*' + exceptions = [options_hash_with_symbol_keys[:except] || []].flatten result = segment_for_scope(only, exceptions) merge_with_fallbacks!(result) if fallbacks - segments << Segment.new(file, result, extract_segment_options(options)) unless result.empty? + unless result.empty? + segments << Segment.new( + file, + result, + extract_segment_options(options_hash_with_symbol_keys), + ) + end segments end @@ -91,11 +100,13 @@ def self.translation_segments # custom output directory def self.config if config? - erb = ERB.new(File.read(config_file_path)).result - (YAML.load(erb) || {}).with_indifferent_access + erb_result_from_yaml_file = ERB.new(File.read(config_file_path)).result + Private::HashWithSymbolKeys.new( + (::YAML.load(erb_result_from_yaml_file) || {}) + ) else - {} - end + Private::HashWithSymbolKeys.new({}) + end.freeze end # Check if configuration file exist @@ -184,7 +195,10 @@ def self.sort_translation_keys=(value) end def self.extract_segment_options(options) - segment_options = {js_extend: js_extend, sort_translation_keys: sort_translation_keys?}.with_indifferent_access + segment_options = Private::HashWithSymbolKeys.new({ + js_extend: js_extend, + sort_translation_keys: sort_translation_keys?, + }).freeze segment_options.merge(options.slice(*Segment::OPTIONS)) end diff --git a/lib/i18n/js/private/hash_with_symbol_keys.rb b/lib/i18n/js/private/hash_with_symbol_keys.rb new file mode 100644 index 00000000..e289acef --- /dev/null +++ b/lib/i18n/js/private/hash_with_symbol_keys.rb @@ -0,0 +1,36 @@ +module I18n + module JS + # @api private + module Private + # Hash with string keys converted to symbol keys + # Used for handling values read on YAML + # + # @api private + class HashWithSymbolKeys < ::Hash + # An instance can only be created by passing in another hash + def initialize(hash) + raise TypeError unless hash.is_a?(::Hash) + + hash.each_key do |key| + # Objects like `Integer` does not have `to_sym` + new_key = key.respond_to?(:to_sym) ? key.to_sym : key + self[new_key] = hash[key] + end + + self.default = hash.default if hash.default + self.default_proc = hash.default_proc if hash.default_proc + + freeze + end + + # From AS Core extension + def slice(*keys) + hash = keys.each_with_object(Hash.new) do |k, hash| + hash[k] = self[k] if has_key?(k) + end + self.class.new(hash) + end + end + end + end +end diff --git a/spec/ruby/i18n/js_spec.rb b/spec/ruby/i18n/js_spec.rb index ed36c061..60205d43 100644 --- a/spec/ruby/i18n/js_spec.rb +++ b/spec/ruby/i18n/js_spec.rb @@ -405,7 +405,7 @@ it "executes erb in config file" do set_config "erb.yml" - config_entry = I18n::JS.config["translations"].first + config_entry = I18n::JS.config[:translations].first config_entry["only"].should eq("*.date.formats") end end From 53735abc76ed5143af4bfa6d980aad442bffeddc Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 23 Sep 2016 18:24:38 +0800 Subject: [PATCH 276/466] * Remove active support entirely and use our own `#slice` method --- i18n-js.gemspec | 1 - lib/i18n/js.rb | 9 ++++++++- lib/i18n/js/segment.rb | 10 +++++++++- spec/spec_helper.rb | 1 - 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/i18n-js.gemspec b/i18n-js.gemspec index d9dc99f8..143f0dc6 100644 --- a/i18n-js.gemspec +++ b/i18n-js.gemspec @@ -20,7 +20,6 @@ Gem::Specification.new do |s| s.add_dependency "i18n", "~> 0.6", ">= 0.6.6" s.add_development_dependency "appraisal", "~> 2.0" - s.add_development_dependency "activesupport", ">= 3.2.22" s.add_development_dependency "rspec", "~> 3.0" s.add_development_dependency "rake", "~> 10.0" s.add_development_dependency "gem-release", ">= 0.7" diff --git a/lib/i18n/js.rb b/lib/i18n/js.rb index d28aa3ad..4fa62ddf 100644 --- a/lib/i18n/js.rb +++ b/lib/i18n/js.rb @@ -162,7 +162,14 @@ def self.filter(translations, scopes) def self.translations ::I18n.backend.instance_eval do init_translations unless initialized? - translations.slice(*::I18n.available_locales) + # When activesupport is absent, + # the core extension (`#slice`) from `i18n` gem will be used instead + # And it's causing errors (at least in test) + # + # So the input is wrapped by our class for better `#slice` + Private::HashWithSymbolKeys.new(translations). + slice(*::I18n.available_locales). + to_h end end diff --git a/lib/i18n/js/segment.rb b/lib/i18n/js/segment.rb index f17a8f18..4733338b 100644 --- a/lib/i18n/js/segment.rb +++ b/lib/i18n/js/segment.rb @@ -1,3 +1,5 @@ +require "i18n/js/private/hash_with_symbol_keys" + module I18n module JS @@ -10,7 +12,13 @@ class Segment def initialize(file, translations, options = {}) @file = file - @translations = translations + # `#slice` will be used + # But when activesupport is absent, + # the core extension from `i18n` gem will be used instead + # And it's causing errors (at least in test) + # + # So the input is wrapped by our class for better `#slice` + @translations = Private::HashWithSymbolKeys.new(translations) @namespace = options[:namespace] || 'I18n' @pretty_print = !!options[:pretty_print] @js_extend = options.key?(:js_extend) ? !!options[:js_extend] : true diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 85c94379..14023925 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,7 +1,6 @@ require "i18n" require "json" -require "active_support/all" require "i18n/js" module Helpers From 4e1737ac5bf0eab5434435913ebd6ccaf7439320 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 26 Sep 2016 16:15:57 +0800 Subject: [PATCH 277/466] ~ Update README for a known issue about loading order [ci skip] --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index faed2d7b..2952fcc2 100644 --- a/README.md +++ b/README.md @@ -742,6 +742,7 @@ I18n.translations["pt-BR"] = { } ``` + ## Known Issues ### Missing translations in precompiled file(s) after adding any new locale file @@ -774,6 +775,16 @@ This means that new locale files will not be detected, and so they will not trig **Note:** See issue [#213](https://github.com/fnando/i18n-js/issues/213) for more details and discussion of this issue. +### Translations in JS are not updated when Sprockets not loaded before this gem + +The "rails engine" declaration will try to detect existence of "sprockets" before adding the initailizer +If sprockets is loaded after this gem, the preprocessor for +making JS translations file cache to depend on content of locale files will not be hooked. +So ensure sprockets is loaded before this gem like moving entry of sprockets in Gemfile or adding "require" statements for sprockets somewhere. + +**Note:** See issue [#404](https://github.com/fnando/i18n-js/issues/404) for more details and discussion of this issue. + + ## Maintainer - Nando Vieira - From 90a4c18a588122ce6587f536100457ea13645f16 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 26 Sep 2016 16:50:39 +0800 Subject: [PATCH 278/466] ~ Update CHANGELOG [ci skip] --- CHANGELOG.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 551886c2..a872c2fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,17 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- Nothing +- [Ruby] Stop validating the fallback locales against `I18n.available_locales` + This allows some locales to be used as fallback locales, but not to be generated in JS. + (PR: https://github.com/fnando/i18n-js/pull/425) ### Fixed -- Nothing +- [JS] Stop converting numeric & boolean values into objects + when merging objects with `I18n.extend` + (PR: https://github.com/fnando/i18n-js/pull/420) +- [Ruby] Use old syntax to define lambda for compatibility with older Rubies + (Issue: https://github.com/fnando/i18n-js/issues/419) ## [3.0.0.rc14] - 2016-08-29 From 3a21a62b0f4e81bf2c96a6f3c3c9d147e185482b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Hoste?= Date: Fri, 7 Oct 2016 10:35:06 +0200 Subject: [PATCH 279/466] Fix bug with pluralization or interpolation of 'null' message + tests (#393) * Fix bug with pluralization or interpolation of 'null' message + tests --- app/assets/javascripts/i18n.js | 16 +++++++++------- spec/js/interpolation.spec.js | 11 +++++++++++ spec/js/pluralization.spec.js | 10 ++++++++++ spec/js/translations.js | 6 ++++++ 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index ac1f39bc..ec6635a9 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -482,7 +482,7 @@ if (typeof(translation) === "string") { translation = this.interpolate(translation, options); } else if (isObject(translation) && this.isSet(options.count)) { - translation = this.pluralize(options.count, translation, options); + translation = this.pluralize(options.count, scope, options); } return translation; @@ -530,11 +530,7 @@ options = this.prepareOptions(options); var translations, pluralizer, keys, key, message; - if (isObject(scope)) { - translations = scope; - } else { - translations = this.lookup(scope, options); - } + translations = this.lookup(scope, options); if (!translations) { return this.missingTranslation(scope, options); @@ -553,7 +549,13 @@ } options.count = String(count); - return this.interpolate(message, options); + + if (message != undefined) { + return this.interpolate(message, options); + } + else { + return this.missingTranslation(scope + '.' + pluralizer(count)[0], options); + } }; // Return a missing translation message for the given parameters. diff --git a/spec/js/interpolation.spec.js b/spec/js/interpolation.spec.js index e8e93a9a..65cba499 100644 --- a/spec/js/interpolation.spec.js +++ b/spec/js/interpolation.spec.js @@ -46,6 +46,17 @@ describe("Interpolation", function(){ expect(I18n.t(translation_key, {count: 5})).toEqual("Hello World!"); }); }); + describe("and translation key does contain pluralization with null content", function() { + beforeEach(function() { + translation_key = "sent"; + }); + + it("return empty string", function() { + expect(I18n.t(translation_key, {count: 0})).toEqual('[missing "en.sent.zero" translation]'); + expect(I18n.t(translation_key, {count: 1})).toEqual('[missing "en.sent.one" translation]'); + expect(I18n.t(translation_key, {count: 5})).toEqual('[missing "en.sent.other" translation]'); + }); + }); }); describe("when count is NOT passed in", function() { diff --git a/spec/js/pluralization.spec.js b/spec/js/pluralization.spec.js index 89ddd08e..1fff6290 100644 --- a/spec/js/pluralization.spec.js +++ b/spec/js/pluralization.spec.js @@ -57,6 +57,16 @@ describe("Pluralization", function(){ expect(I18n.p(0, "inbox")).toEqual(""); }); + it("returns missing message on null values", function(){ + I18n.translations["en"]["sent"]["zero"] = null; + I18n.translations["en"]["sent"]["one"] = null; + I18n.translations["en"]["sent"]["other"] = null; + + expect(I18n.p(0, "sent")).toEqual('[missing "en.sent.zero" translation]'); + expect(I18n.p(1, "sent")).toEqual('[missing "en.sent.one" translation]'); + expect(I18n.p(5, "sent")).toEqual('[missing "en.sent.other" translation]'); + }); + it("pluralizes using custom rules", function() { I18n.locale = "custom"; diff --git a/spec/js/translations.js b/spec/js/translations.js index fddfad32..874076d4 100644 --- a/spec/js/translations.js +++ b/spec/js/translations.js @@ -30,6 +30,12 @@ var DEBUG = false; , zero: "You have no messages" } + , sent: { + one: null + , other: null + , zero: null + } + , unread: { one: "You have 1 new message ({{unread}} unread)" , other: "You have {{count}} new messages ({{unread}} unread)" From 3554ec89c1e3c5dfc860d3cdc006463c38a8523d Mon Sep 17 00:00:00 2001 From: Jean Marc Delafont Date: Thu, 10 Nov 2016 11:24:13 +0100 Subject: [PATCH 280/466] Add fallback to pluralize --- app/assets/javascripts/i18n.js | 37 +++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index ec6635a9..194476dc 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -347,6 +347,8 @@ , locale , scopes , translations + , keys + , key ; scope = this.getFullScope(scope, options); @@ -360,16 +362,37 @@ continue; } - while (scopes.length) { - translations = translations[scopes.shift()]; + if (this.isSet(options.count)) { + while (scopes.length) { + translations = translations[scopes.shift()]; + + if (scopes.length == 0 && isObject(translations)) { + var hasAllMessages = true; + keys = Object.keys(translations); + while (keys.length) { + key = keys.shift(); + if (translations[key] === undefined || translations[key] === null) { + hasAllMessages = false; + break; + } + } + if (hasAllMessages) { + return translations; + } + } + } + } else { + while (scopes.length) { + translations = translations[scopes.shift()]; - if (translations === undefined || translations === null) { - break; + if (translations === undefined || translations === null) { + break; + } } - } - if (translations !== undefined && translations !== null) { - return translations; + if (translations !== undefined && translations !== null) { + return translations; + } } } From 9998a913b4be43631064672ffc53a4f1aaffe0cd Mon Sep 17 00:00:00 2001 From: Jean Marc Delafont Date: Thu, 10 Nov 2016 15:23:06 +0100 Subject: [PATCH 281/466] Add fallback to pluralize --- app/assets/javascripts/i18n.js | 84 ++++++++++++++++++++++------------ spec/js/pluralization.spec.js | 13 ++++++ 2 files changed, 67 insertions(+), 30 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 194476dc..061e3ebe 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -347,8 +347,6 @@ , locale , scopes , translations - , keys - , key ; scope = this.getFullScope(scope, options); @@ -361,37 +359,61 @@ if (!translations) { continue; } + while (scopes.length) { + translations = translations[scopes.shift()]; - if (this.isSet(options.count)) { - while (scopes.length) { - translations = translations[scopes.shift()]; - - if (scopes.length == 0 && isObject(translations)) { - var hasAllMessages = true; - keys = Object.keys(translations); - while (keys.length) { - key = keys.shift(); - if (translations[key] === undefined || translations[key] === null) { - hasAllMessages = false; - break; - } - } - if (hasAllMessages) { - return translations; - } - } + if (translations === undefined || translations === null) { + break; } - } else { - while (scopes.length) { - translations = translations[scopes.shift()]; + } - if (translations === undefined || translations === null) { - break; - } - } + if (translations !== undefined && translations !== null) { + return translations; + } + } + + if (this.isSet(options.defaultValue)) { + return options.defaultValue; + } + }; + + I18n.lookupForCount = function(scope, options) { + options = this.prepareOptions(options); + + var locales = this.locales.get(options.locale).slice() + , requestedLocale = locales[0] + , locale + , scopes + , translations + , keys + , key + ; + scope = this.getFullScope(scope, options); + + while (locales.length) { + locale = locales.shift(); + scopes = scope.split(this.defaultSeparator); + translations = this.translations[locale]; - if (translations !== undefined && translations !== null) { - return translations; + if (!translations) { + continue; + } + while (scopes.length) { + translations = translations[scopes.shift()]; + + if (scopes.length == 0 && isObject(translations)) { + var hasAtLeastOneMessage = false; + keys = Object.keys(translations); + while (keys.length) { + key = keys.shift(); + if (translations[key] !== undefined && translations[key] !== null) { + hasAtLeastOneMessage = true; + break; + } + } + if (hasAtLeastOneMessage) { + return translations; + } } } } @@ -399,6 +421,8 @@ if (this.isSet(options.defaultValue)) { return options.defaultValue; } + + return translations; }; // Rails changed the way the meridian is stored. @@ -553,7 +577,7 @@ options = this.prepareOptions(options); var translations, pluralizer, keys, key, message; - translations = this.lookup(scope, options); + translations = this.lookupForCount(scope, options); if (!translations) { return this.missingTranslation(scope, options); diff --git a/spec/js/pluralization.spec.js b/spec/js/pluralization.spec.js index 1fff6290..99021441 100644 --- a/spec/js/pluralization.spec.js +++ b/spec/js/pluralization.spec.js @@ -113,4 +113,17 @@ describe("Pluralization", function(){ expect(I18n.p(1, "inbox", options)).toEqual("You have 1 message"); expect(I18n.p(5, "inbox", options)).toEqual("You have 5 messages"); }); + + it("fallback to default locale when I18n.fallbacks is enabled", function() { + I18n.locale = "pt-BR"; + I18n.fallbacks = true; + I18n.translations["pt-BR"].inbox= { + one: null + , other: null + , zero: null + }; + expect(I18n.p(0, "inbox", { count: 0 })).toEqual("You have no messages"); + expect(I18n.p(1, "inbox", { count: 1 })).toEqual("You have 1 message"); + expect(I18n.p(5, "inbox", { count: 5 })).toEqual("You have 5 messages"); + }); }); From 7c02c223e3aacbb9ff280118c992be34c91dd9f4 Mon Sep 17 00:00:00 2001 From: Jean Marc Delafont Date: Mon, 14 Nov 2016 20:36:18 +0100 Subject: [PATCH 282/466] Fallback to pluralize rewriting to meet the requirements --- app/assets/javascripts/i18n.js | 82 ++++++++++++++++++---------------- spec/js/pluralization.spec.js | 68 ++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 38 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 061e3ebe..9f934086 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -377,16 +377,35 @@ } }; - I18n.lookupForCount = function(scope, options) { - options = this.prepareOptions(options); + // lookup pluralization rule key into translations + I18n.pluralizationLookupWithoutFallback = function(count, locale, translations) { + var pluralizer = this.pluralization.get(locale) + , pluralizerKeys = pluralizer(count) + , pluralizerKey + , message; + + if (isObject(translations)) { + while (pluralizerKeys.length) { + pluralizerKey = pluralizerKeys.shift(); + if (this.isSet(translations[pluralizerKey])) { + message = translations[pluralizerKey]; + break; + } + } + } + + return message; + }; + // Lookup dedicated to pluralization + I18n.pluralizationLookup = function(count, scope, options) { + options = this.prepareOptions(options); var locales = this.locales.get(options.locale).slice() , requestedLocale = locales[0] , locale , scopes , translations - , keys - , key + , message ; scope = this.getFullScope(scope, options); @@ -398,31 +417,30 @@ if (!translations) { continue; } + while (scopes.length) { translations = translations[scopes.shift()]; - if (scopes.length == 0 && isObject(translations)) { - var hasAtLeastOneMessage = false; - keys = Object.keys(translations); - while (keys.length) { - key = keys.shift(); - if (translations[key] !== undefined && translations[key] !== null) { - hasAtLeastOneMessage = true; - break; - } - } - if (hasAtLeastOneMessage) { - return translations; - } + message = this.pluralizationLookupWithoutFallback(count, locale, translations); } } + if (message != null && message != undefined) { + break; + } } - if (this.isSet(options.defaultValue)) { - return options.defaultValue; + if (message == null || message == undefined) { + if (this.isSet(options.defaultValue)) { + if (isObject(options.defaultValue)) { + message = this.pluralizationLookupWithoutFallback(count, options.locale, options.defaultValue); + } else { + message = options.defaultValue; + } + translations = options.defaultValue; + } } - return translations; + return { message: message, translations: translations }; }; // Rails changed the way the meridian is stored. @@ -575,32 +593,20 @@ // which will be retrieved from `options`. I18n.pluralize = function(count, scope, options) { options = this.prepareOptions(options); - var translations, pluralizer, keys, key, message; - - translations = this.lookupForCount(scope, options); + var pluralizer, message, result; - if (!translations) { + result = this.pluralizationLookup(count, scope, options); + if (result.translations == undefined || result.translations == null) { return this.missingTranslation(scope, options); } - pluralizer = this.pluralization.get(options.locale); - keys = pluralizer(count); - - while (keys.length) { - key = keys.shift(); - - if (this.isSet(translations[key])) { - message = translations[key]; - break; - } - } - options.count = String(count); - if (message != undefined) { - return this.interpolate(message, options); + if (result.message != undefined && result.message != null) { + return this.interpolate(result.message, options); } else { + pluralizer = this.pluralization.get(options.locale); return this.missingTranslation(scope + '.' + pluralizer(count)[0], options); } }; diff --git a/spec/js/pluralization.spec.js b/spec/js/pluralization.spec.js index 99021441..cd3c4b50 100644 --- a/spec/js/pluralization.spec.js +++ b/spec/js/pluralization.spec.js @@ -126,4 +126,72 @@ describe("Pluralization", function(){ expect(I18n.p(1, "inbox", { count: 1 })).toEqual("You have 1 message"); expect(I18n.p(5, "inbox", { count: 5 })).toEqual("You have 5 messages"); }); + + it("fallback to default locale when I18n.fallbacks is enabled", function() { + I18n.locale = "pt-BR"; + I18n.fallbacks = true; + I18n.translations["pt-BR"].inbox= { + one: "Você tem uma mensagem" + , other: null + , zero: "Você não tem nenhuma mensagem" + }; + expect(I18n.p(0, "inbox", { count: 0 })).toEqual("Você não tem nenhuma mensagem"); + expect(I18n.p(1, "inbox", { count: 1 })).toEqual("Você tem uma mensagem"); + expect(I18n.p(5, "inbox", { count: 5 })).toEqual('You have 5 messages'); + }); + + it("fallback to 'other' scope", function() { + I18n.locale = "pt-BR"; + I18n.fallbacks = true; + I18n.translations["pt-BR"].inbox= { + one: "Você tem uma mensagem" + , other: "Você tem {{count}} mensagens" + , zero: null + } + expect(I18n.p(0, "inbox", { count: 0 })).toEqual("Você tem 0 mensagens"); + expect(I18n.p(1, "inbox", { count: 1 })).toEqual("Você tem uma mensagem"); + expect(I18n.p(5, "inbox", { count: 5 })).toEqual("Você tem 5 mensagens"); + }); + + it("fallback to defaulValue when defaultValue is string", function() { + I18n.locale = "pt-BR"; + I18n.fallbacks = true; + I18n.translations["en"]["inbox"]["zero"] = null; + I18n.translations["en"]["inbox"]["one"] = null; + I18n.translations["en"]["inbox"]["other"] = null; + I18n.translations["pt-BR"].inbox= { + one: "Você tem uma mensagem" + , other: null + , zero: null + } + options = { + defaultValue: "default message" + }; + expect(I18n.p(0, "inbox", options)).toEqual("default message"); + expect(I18n.p(1, "inbox", options)).toEqual("Você tem uma mensagem"); + expect(I18n.p(5, "inbox", options)).toEqual("default message"); + }); + + it("fallback to defaulValue when defaultValue is an object", function() { + I18n.locale = "pt-BR"; + I18n.fallbacks = true; + I18n.translations["en"]["inbox"]["zero"] = null; + I18n.translations["en"]["inbox"]["one"] = null; + I18n.translations["en"]["inbox"]["other"] = null; + I18n.translations["pt-BR"].inbox= { + one: "Você tem uma mensagem" + , other: null + , zero: null + } + options = { + defaultValue: { + zero: "default message for no message" + , one: "default message for 1 message" + , other: "default message for {{count}} messages" + } + }; + expect(I18n.p(0, "inbox", options)).toEqual("default message for no message"); + expect(I18n.p(1, "inbox", options)).toEqual("Você tem uma mensagem"); + expect(I18n.p(5, "inbox", options)).toEqual("default message for 5 messages"); + }); }); From 2a6d9f8629e44d0a1279b09fbccc490abe523b9a Mon Sep 17 00:00:00 2001 From: Jean Marc Delafont Date: Tue, 22 Nov 2016 15:27:12 +0100 Subject: [PATCH 283/466] BugFix : I18n pluralization missing subtree en: mailbox: inbox: zero: ... one: .. other: .. and >> I18n.translations["pt-BR"]["mailbox"] undefined --- app/assets/javascripts/i18n.js | 5 ++++- spec/js/pluralization.spec.js | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 9f934086..22d00f26 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -420,7 +420,10 @@ while (scopes.length) { translations = translations[scopes.shift()]; - if (scopes.length == 0 && isObject(translations)) { + if (!isObject(translations)) { + break; + } + if (scopes.length == 0) { message = this.pluralizationLookupWithoutFallback(count, locale, translations); } } diff --git a/spec/js/pluralization.spec.js b/spec/js/pluralization.spec.js index cd3c4b50..eea49296 100644 --- a/spec/js/pluralization.spec.js +++ b/spec/js/pluralization.spec.js @@ -194,4 +194,18 @@ describe("Pluralization", function(){ expect(I18n.p(1, "inbox", options)).toEqual("Você tem uma mensagem"); expect(I18n.p(5, "inbox", options)).toEqual("default message for 5 messages"); }); + + it("fallback to default locale when I18n.fallbacks is enabled and no translations in sub scope", function() { + I18n.locale = "pt-BR"; + I18n.fallbacks = true; + I18n.translations["en"]["mailbox"] = { + inbox: I18n.translations["en"].inbox + } + + expect(I18n.translations["pt-BR"]["mailbox"]).toEqual(undefined); + expect(I18n.p(0, "mailbox.inbox", { count: 0 })).toEqual("You have no messages"); + expect(I18n.p(1, "mailbox.inbox", { count: 1 })).toEqual("You have 1 message"); + expect(I18n.p(5, "mailbox.inbox", { count: 5 })).toEqual("You have 5 messages"); + }); + }); From 6fa7737f965430d4c23a84ba3a184f934cc52ada Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 30 Nov 2016 10:30:10 +0800 Subject: [PATCH 284/466] ! Fix error raised in middleware cache cleaning in parallel test --- lib/i18n/js/middleware.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/i18n/js/middleware.rb b/lib/i18n/js/middleware.rb index 8bca9b07..6471c3e2 100644 --- a/lib/i18n/js/middleware.rb +++ b/lib/i18n/js/middleware.rb @@ -1,3 +1,5 @@ +require "fileutils" + module I18n module JS class Middleware @@ -32,7 +34,13 @@ def cache end def clear_cache - File.delete(cache_path) if File.exist?(cache_path) + # `File.delete` will raise error when "multiple worker" + # Are running at the same time, like in a parallel test + # + # `FileUtils.rm_f` is tested manually + # + # See https://github.com/fnando/i18n-js/issues/436 + FileUtils.rm_f(cache_path) if File.exist?(cache_path) end def save_cache(new_cache) From b54c8b9d2207c52abf489d39f7097438137331bc Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 7 Dec 2016 15:26:21 +0800 Subject: [PATCH 285/466] ~ Update change log for recent changes [ci skip] --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a872c2fd..b870d7da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). + ## [Unreleased] ### Added @@ -10,17 +11,24 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed +- [JS] Allow `defaultValue` to work in pluralization + (PR: https://github.com/fnando/i18n-js/pull/433) - [Ruby] Stop validating the fallback locales against `I18n.available_locales` This allows some locales to be used as fallback locales, but not to be generated in JS. (PR: https://github.com/fnando/i18n-js/pull/425) +- [Ruby] Remove dependency on gem `activesupport` ### Fixed - [JS] Stop converting numeric & boolean values into objects when merging objects with `I18n.extend` (PR: https://github.com/fnando/i18n-js/pull/420) +- [JS] Fix I18n pluralization fallback when tree is empty + (PR: https://github.com/fnando/i18n-js/pull/435) - [Ruby] Use old syntax to define lambda for compatibility with older Rubies (Issue: https://github.com/fnando/i18n-js/issues/419) +- [Ruby] Fix error raised in middleware cache cleaning in parallel test + (Issue: https://github.com/fnando/i18n-js/issues/436) ## [3.0.0.rc14] - 2016-08-29 From c35f26c168852e041e5892882924f796ad679004 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 7 Dec 2016 15:27:13 +0800 Subject: [PATCH 286/466] ^ Release 3.0.0.rc15 --- CHANGELOG.md | 18 +++++++++++++++++- lib/i18n/js/version.rb | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b870d7da..8d6ccf36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,21 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed +- Nothing + +### Fixed + +- Nothing + + +## [3.0.0.rc15] - 2016-12-07 + +### Added + +- Nothing + +### Changed + - [JS] Allow `defaultValue` to work in pluralization (PR: https://github.com/fnando/i18n-js/pull/433) - [Ruby] Stop validating the fallback locales against `I18n.available_locales` @@ -202,7 +217,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc14...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc15...HEAD +[3.0.0.rc15]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc14...v3.0.0.rc15 [3.0.0.rc14]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc13...v3.0.0.rc14 [3.0.0.rc13]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc12...v3.0.0.rc13 [3.0.0.rc12]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc11...v3.0.0.rc12 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index 0b9f0198..5f899d44 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -4,7 +4,7 @@ module Version MAJOR = 3 MINOR = 0 PATCH = 0 - STRING = "#{MAJOR}.#{MINOR}.#{PATCH}.rc14" + STRING = "#{MAJOR}.#{MINOR}.#{PATCH}.rc15" end end end From 21f40b79cc3728c17c826d2e63edce36782f3d9c Mon Sep 17 00:00:00 2001 From: Cantin Xu Date: Tue, 13 Dec 2016 14:17:34 +0800 Subject: [PATCH 287/466] Fix #437 make defaultValue works on plural translation --- app/assets/javascripts/i18n.js | 3 ++- spec/js/translate.spec.js | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 22d00f26..2d7f6375 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -525,6 +525,7 @@ I18n.translate = function(scope, options) { options = this.prepareOptions(options); + var copiedOptions = this.prepareOptions(options); var translationOptions = this.createTranslationOptions(scope, options); var translation; @@ -550,7 +551,7 @@ if (typeof(translation) === "string") { translation = this.interpolate(translation, options); } else if (isObject(translation) && this.isSet(options.count)) { - translation = this.pluralize(options.count, scope, options); + translation = this.pluralize(options.count, scope, copiedOptions); } return translation; diff --git a/spec/js/translate.spec.js b/spec/js/translate.spec.js index fc4d4afd..046c59ba 100644 --- a/spec/js/translate.spec.js +++ b/spec/js/translate.spec.js @@ -152,6 +152,11 @@ describe("Translate", function(){ expect(actual).toEqual("Warning!"); }); + it("uses default value for plural translation", function(){ + actual = I18n.t("message", {defaultValue: { one: '%{count} message', other: '%{count} messages'}, count: 1}); + expect(actual).toEqual("1 message"); + }); + it("uses default value for unknown locale", function(){ I18n.locale = "fr"; actual = I18n.t("warning", {defaultValue: "Warning!"}); From 0ca309c88e162c365d1718e5e8d5f11f0179114f Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 10 Jan 2017 10:14:42 +0800 Subject: [PATCH 288/466] ~ Add tips for using pluralisation & number formatting together [ci skip] --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 2952fcc2..b356ec66 100644 --- a/README.md +++ b/README.md @@ -603,6 +603,27 @@ The accepted formats are: Check out `spec/*.spec.js` files for more examples! +#### Using pluralization and number formatting together +Sometimes you might want to display translation with formatted number, like adding thousand delimiters to displayed number +You can do this: +```json +{ + "en": { + "point": { + "one": "1 Point", + "other": "{{formatted_number}} Points", + "zero": "0 Points" + } + } +} +``` +```js +var point_in_number = 1000; +I18n.t('point', { count: point_in_number, formatted_number: I18n.toNumber(point_in_number) }); +``` +Outout should be `1,000 points` + + ## Using multiple exported translation files on a page. This method is useful for very large apps where a single contained translations.js file is not desirable. Examples would be a global translations file and a more specific route translation file. From 5c5bdd933d99526f2057756dd5ba004ad95fab01 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 10 Jan 2017 16:35:10 +0800 Subject: [PATCH 289/466] ~ Fix typo [ci skip] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b356ec66..03c7fc9c 100644 --- a/README.md +++ b/README.md @@ -621,7 +621,7 @@ You can do this: var point_in_number = 1000; I18n.t('point', { count: point_in_number, formatted_number: I18n.toNumber(point_in_number) }); ``` -Outout should be `1,000 points` +Output should be `1,000 points` ## Using multiple exported translation files on a page. From 6f410d811d00d970a5e1d8945b10e7eb9d657085 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 13 Mar 2017 10:18:44 +0800 Subject: [PATCH 290/466] =?UTF-8?q?!=20Fix=20UMD=20pattern=20so=20the=20gl?= =?UTF-8?q?obal/root=20won=E2=80=99t=20be=20undefined=20(#446)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/assets/javascripts/i18n.js | 26 ++++++++++++--------- app/assets/javascripts/i18n/filtered.js.erb | 25 ++++++++++++-------- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 2d7f6375..e3ada2bb 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -12,19 +12,23 @@ // See tests for specific formatting like numbers and dates. // -;(function(factory) { - if (typeof module !== 'undefined' && module.exports) { - // Node/CommonJS - module.exports = factory(this); - } else if (typeof define === 'function' && define.amd) { - // AMD - var global=this; - define('i18n', function(){ return factory(global);}); +// Using UMD pattern from +// https://github.com/umdjs/umd#regular-module +// `returnExports.js` version +;(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define("i18n", function(){ return factory(root);}); + } else if (typeof module === 'object' && module.exports) { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like environments that support module.exports, + // like Node. + module.exports = factory(root); } else { - // Browser globals - this.I18n = factory(this); + // Browser globals (root is window) + root.I18n = factory(root); } -}(function(global) { +}(this, function(global) { "use strict"; // Use previously defined object if exists in current scope diff --git a/app/assets/javascripts/i18n/filtered.js.erb b/app/assets/javascripts/i18n/filtered.js.erb index 8a0cae62..5570bb4f 100644 --- a/app/assets/javascripts/i18n/filtered.js.erb +++ b/app/assets/javascripts/i18n/filtered.js.erb @@ -1,17 +1,22 @@ <%# encoding: utf-8 %> -;(function(factory) { - if (typeof module !== 'undefined' && module.exports) { - // Node/CommonJS - factory(require('i18n')); - } else if (typeof define === 'function' && define.amd) { - // AMD - define(['i18n'], factory); +// Using UMD pattern from +// https://github.com/umdjs/umd#regular-module +// `returnExports.js` version +;(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(["i18n"], factory); + } else if (typeof module === 'object' && module.exports) { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like environments that support module.exports, + // like Node. + factory(require("i18n")); } else { - // Browser globals - factory(this.I18n); + // Browser globals (root is window) + factory(root.I18n); } -}(function(I18n) { +}(this, function(I18n) { "use strict"; I18n.translations = <%= I18n::JS.filtered_translations.to_json %>; From 72b72a6a86149e325704b197054d490ff27f0a98 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 13 Mar 2017 10:29:09 +0800 Subject: [PATCH 291/466] * Install JS test framework with package.json --- .travis.yml | 5 +++-- package.json | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8abad201..a31c439d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,8 +12,9 @@ rvm: # We need to specify the version precisely - 2.3.0 - ruby-head -before_install: # Need to install something extra to test JS -- npm install jasmine-node@1.14.2 +before_install: +# Need to install something extra to test JS +- npm install gemfile: - gemfiles/i18n_0_6.gemfile - gemfiles/i18n_0_7.gemfile diff --git a/package.json b/package.json index 7b17daad..c1d813b7 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "i18n-js", "version": "0.0.0", "devDependencies": { - "jasmine-node": "*" + "jasmine-node": "^1.14.5" }, "main": "app/assets/javascripts/i18n.js", "scripts": { From 0bc1cba2b7c85aedf6fda6ccbef0af4dfce65d92 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 13 Mar 2017 10:29:36 +0800 Subject: [PATCH 292/466] * Test against MRI 2.3.3 & 2.4.0 --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a31c439d..6f75da59 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,8 @@ rvm: - 2.2 # Since the Travis Build Env does not recognize `2.3` alias yet # We need to specify the version precisely - - 2.3.0 + - 2.3.3 + - 2.4.0 - ruby-head before_install: # Need to install something extra to test JS From a4dfe54c1fa1246e50990970c5cd0d060c1a123a Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 13 Mar 2017 11:22:14 +0800 Subject: [PATCH 293/466] * Test against i18n 0.8.0 too --- Appraisals | 4 ++++ gemfiles/i18n_0_8.gemfile | 7 +++++++ 2 files changed, 11 insertions(+) create mode 100644 gemfiles/i18n_0_8.gemfile diff --git a/Appraisals b/Appraisals index a8cf0332..3f129437 100644 --- a/Appraisals +++ b/Appraisals @@ -6,3 +6,7 @@ end appraise "i18n_0_7" do gem "i18n", "~> 0.7.0" end + +appraise "i18n_0_8" do + gem "i18n", "~> 0.8.0" +end diff --git a/gemfiles/i18n_0_8.gemfile b/gemfiles/i18n_0_8.gemfile new file mode 100644 index 00000000..b600b23a --- /dev/null +++ b/gemfiles/i18n_0_8.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "i18n", "~> 0.8.0" + +gemspec :path => "../" From bb9c3f20acd9bdc98622fb1a666b0b9a46c51600 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 13 Mar 2017 13:06:01 +0800 Subject: [PATCH 294/466] * Forgot to put new gemfile in Travis config --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 6f75da59..0a9b8fec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,7 @@ before_install: gemfile: - gemfiles/i18n_0_6.gemfile - gemfiles/i18n_0_7.gemfile + - gemfiles/i18n_0_8.gemfile matrix: fast_finish: true allow_failures: From a6b73a23b6eff0037de31f981a054fbd3cf107a2 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 13 Mar 2017 13:09:31 +0800 Subject: [PATCH 295/466] * Stop testing for 2.0 (EOL) --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0a9b8fec..6a77fd8a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,9 +5,8 @@ language: ruby cache: - bundler rvm: - - 2.0 - - 2.1 - - 2.2 + - 2.1.10 + - 2.2.5 # Since the Travis Build Env does not recognize `2.3` alias yet # We need to specify the version precisely - 2.3.3 From 4df8623f27e7b072e6eb91afd7e5fff79c25160f Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 13 Mar 2017 13:12:34 +0800 Subject: [PATCH 296/466] * Enforce latest rake to be used --- i18n-js.gemspec | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n-js.gemspec b/i18n-js.gemspec index 143f0dc6..bc69171d 100644 --- a/i18n-js.gemspec +++ b/i18n-js.gemspec @@ -19,9 +19,10 @@ Gem::Specification.new do |s| s.require_paths = ["lib"] s.add_dependency "i18n", "~> 0.6", ">= 0.6.6" + s.add_development_dependency "appraisal", "~> 2.0" s.add_development_dependency "rspec", "~> 3.0" - s.add_development_dependency "rake", "~> 10.0" + s.add_development_dependency "rake", "~> 12.0" s.add_development_dependency "gem-release", ">= 0.7" s.required_ruby_version = ">= 1.9.3" From 2fde4ca9f82fd2e9d0973e51154803e0a3b5fc42 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 13 Mar 2017 13:13:13 +0800 Subject: [PATCH 297/466] * Drop support for ruby < 2.1 --- i18n-js.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n-js.gemspec b/i18n-js.gemspec index bc69171d..212575f3 100644 --- a/i18n-js.gemspec +++ b/i18n-js.gemspec @@ -25,5 +25,5 @@ Gem::Specification.new do |s| s.add_development_dependency "rake", "~> 12.0" s.add_development_dependency "gem-release", ">= 0.7" - s.required_ruby_version = ">= 1.9.3" + s.required_ruby_version = ">= 2.1.0" end From 7ed2d2900c844d70992b2f7222178867eaaca92a Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 13 Mar 2017 15:41:48 +0800 Subject: [PATCH 298/466] ^ Release 3.0.0.rc16 --- CHANGELOG.md | 15 ++++++++++++++- lib/i18n/js/version.rb | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d6ccf36..ab42011c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,18 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.0.0.rc16] - 2017-03-13 + +### Changed + +- [Ruby] Drop support for Ruby < `2.1.0` + +### Fixed + +- [JS] Make defaultValue works on plural translation +- [JS] Fix UMD pattern so the global/root won’t be undefined + + ## [3.0.0.rc15] - 2016-12-07 ### Added @@ -217,7 +229,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc15...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc16...HEAD +[3.0.0.rc16]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc15...v3.0.0.rc16 [3.0.0.rc15]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc14...v3.0.0.rc15 [3.0.0.rc14]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc13...v3.0.0.rc14 [3.0.0.rc13]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc12...v3.0.0.rc13 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index 5f899d44..9fcd38b6 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -4,7 +4,7 @@ module Version MAJOR = 3 MINOR = 0 PATCH = 0 - STRING = "#{MAJOR}.#{MINOR}.#{PATCH}.rc15" + STRING = "#{MAJOR}.#{MINOR}.#{PATCH}.rc16" end end end From f82b1ad0b3ed6e957b010882cf7dcf16a96a13f9 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 13 Mar 2017 15:49:49 +0800 Subject: [PATCH 299/466] ~ Update badges in README [ci skip] --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 03c7fc9c..2a05916f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,12 @@ # I18n.js +[![Gem Version](http://img.shields.io/gem/v/i18n-js.svg?style=flat-square)](http://badge.fury.io/rb/i18n-js) +[![License](https://img.shields.io/github/license/fnando/i18n-js.svg?style=flat-square)](http://badge.fury.io/rb/i18n-js) + [![Build Status](http://img.shields.io/travis/fnando/i18n-js.svg?style=flat-square)](https://travis-ci.org/fnando/i18n-js) +[![Dependency Status](http://img.shields.io/gemnasium/fnando/i18n-js.svg?style=flat-square)](https://gemnasium.com/fnando/i18n-js) [![Code Climate](http://img.shields.io/codeclimate/github/fnando/i18n-js.svg?style=flat-square)](https://codeclimate.com/github/fnando/i18n-js) + [![Gitter](https://img.shields.io/badge/gitter-join%20chat-1dce73.svg?style=flat-square)](https://gitter.im/fnando/i18n-js) It's a small library to provide the Rails I18n translations on the JavaScript. From 74ff0615e4266e7e613ce776d20af8d289d56aac Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 13 Mar 2017 15:52:54 +0800 Subject: [PATCH 300/466] ~ Fix license badge in README [ci skip] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2a05916f..fa9e11c9 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # I18n.js [![Gem Version](http://img.shields.io/gem/v/i18n-js.svg?style=flat-square)](http://badge.fury.io/rb/i18n-js) -[![License](https://img.shields.io/github/license/fnando/i18n-js.svg?style=flat-square)](http://badge.fury.io/rb/i18n-js) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Build Status](http://img.shields.io/travis/fnando/i18n-js.svg?style=flat-square)](https://travis-ci.org/fnando/i18n-js) [![Dependency Status](http://img.shields.io/gemnasium/fnando/i18n-js.svg?style=flat-square)](https://gemnasium.com/fnando/i18n-js) From 56e2d2ea743af8100028bd0eb8a0ccdf909e02f4 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 13 Mar 2017 15:54:37 +0800 Subject: [PATCH 301/466] ~ Flat style badge please [ci skip] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fa9e11c9..4b4229b7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # I18n.js [![Gem Version](http://img.shields.io/gem/v/i18n-js.svg?style=flat-square)](http://badge.fury.io/rb/i18n-js) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT) [![Build Status](http://img.shields.io/travis/fnando/i18n-js.svg?style=flat-square)](https://travis-ci.org/fnando/i18n-js) [![Dependency Status](http://img.shields.io/gemnasium/fnando/i18n-js.svg?style=flat-square)](https://gemnasium.com/fnando/i18n-js) From b8fb918c827a2d3bbe187372e47bbe1a7a6c4c19 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Sat, 1 Apr 2017 13:41:50 +0800 Subject: [PATCH 302/466] ^ Release 3.0.0 --- CHANGELOG.md | 14 +++++++++++++- README.md | 6 +----- i18n-js.gemspec | 2 +- lib/i18n/js/version.rb | 7 +------ 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab42011c..f5fa1096 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,17 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.0.0] - 2017-04-01 + +This is a fake official release, the *real* one will be `3.0.0.rc17` +And today is not April Fools' Day + +### Fixed + +- Ends the longest Release Candidate period among all ruby gems + (v3.0.0.rc1 released at 2012-05-10) + + ## [3.0.0.rc16] - 2017-03-13 ### Changed @@ -229,7 +240,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc16...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.0...HEAD +[3.0.0]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc16...v3.0.0 [3.0.0.rc16]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc15...v3.0.0.rc16 [3.0.0.rc15]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc14...v3.0.0.rc15 [3.0.0.rc14]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc13...v3.0.0.rc14 diff --git a/README.md b/README.md index 4b4229b7..0b217826 100644 --- a/README.md +++ b/README.md @@ -32,11 +32,7 @@ The `master` branch (including this README) is for latest `3.0.0.rc` instead of Add the gem to your Gemfile. ```ruby -source "https://rubygems.org" -gem "rails", "your_rails_version" -# You only need this RC version constraint during the development of `3.0.0`, once stable version is released you can remove `rc11` suffix -# `3.0.0.rc11` is the latest version of released RC version when this entry is changed, you might want to change it later -gem "i18n-js", ">= 3.0.0.rc11" +gem "i18n-js" ``` #### Rails app with [Asset Pipeline](http://guides.rubyonrails.org/asset_pipeline.html) diff --git a/i18n-js.gemspec b/i18n-js.gemspec index 212575f3..7571ff96 100644 --- a/i18n-js.gemspec +++ b/i18n-js.gemspec @@ -4,7 +4,7 @@ require "i18n/js/version" Gem::Specification.new do |s| s.name = "i18n-js" - s.version = I18n::JS::Version::STRING + s.version = I18n::JS::VERSION s.platform = Gem::Platform::RUBY s.authors = ["Nando Vieira"] s.email = ["fnando.vieira@gmail.com"] diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index 9fcd38b6..c90449ff 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -1,10 +1,5 @@ module I18n module JS - module Version - MAJOR = 3 - MINOR = 0 - PATCH = 0 - STRING = "#{MAJOR}.#{MINOR}.#{PATCH}.rc16" - end + VERSION = "3.0.0" end end From 2106dbc67aa59de408f7a58faf4b472029e06571 Mon Sep 17 00:00:00 2001 From: Jimmy Chao Date: Mon, 10 Apr 2017 21:20:05 -0400 Subject: [PATCH 303/466] Refactor/simplify initializer (#451) * Simplify initialize logic * Extract isSet to top level helper --- app/assets/javascripts/i18n.js | 90 ++++++++++------------------------ 1 file changed, 27 insertions(+), 63 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index e3ada2bb..de2f7f92 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -55,6 +55,11 @@ return type === 'function' || type === 'object' && !!obj; }; + // Check if value is different than undefined and null; + var isSet = function(value) { + return typeof(value) !== 'undefined' && value !== null; + }; + // Is a given value an array? // Borrowed from Underscore.js var isArray = function(val) { @@ -173,60 +178,21 @@ , missingTranslationPrefix: '' }; + // Set default locale. This locale will be used when fallback is enabled and + // the translation doesn't exist in a particular locale. I18n.reset = function() { - // Set default locale. This locale will be used when fallback is enabled and - // the translation doesn't exist in a particular locale. - this.defaultLocale = DEFAULT_OPTIONS.defaultLocale; - - // Set the current locale to `en`. - this.locale = DEFAULT_OPTIONS.locale; - - // Set the translation key separator. - this.defaultSeparator = DEFAULT_OPTIONS.defaultSeparator; - - // Set the placeholder format. Accepts `{{placeholder}}` and `%{placeholder}`. - this.placeholder = DEFAULT_OPTIONS.placeholder; - - // Set if engine should fallback to the default locale when a translation - // is missing. - this.fallbacks = DEFAULT_OPTIONS.fallbacks; - - // Set the default translation object. - this.translations = DEFAULT_OPTIONS.translations; - - // Set the default missing behaviour - this.missingBehaviour = DEFAULT_OPTIONS.missingBehaviour; - - // Set the default missing string prefix for guess behaviour - this.missingTranslationPrefix = DEFAULT_OPTIONS.missingTranslationPrefix; - + var key; + for (key in DEFAULT_OPTIONS) { + this[key] = DEFAULT_OPTIONS[key]; + } }; // Much like `reset`, but only assign options if not already assigned I18n.initializeOptions = function() { - if (typeof(this.defaultLocale) === "undefined" && this.defaultLocale !== null) - this.defaultLocale = DEFAULT_OPTIONS.defaultLocale; - - if (typeof(this.locale) === "undefined" && this.locale !== null) - this.locale = DEFAULT_OPTIONS.locale; - - if (typeof(this.defaultSeparator) === "undefined" && this.defaultSeparator !== null) - this.defaultSeparator = DEFAULT_OPTIONS.defaultSeparator; - - if (typeof(this.placeholder) === "undefined" && this.placeholder !== null) - this.placeholder = DEFAULT_OPTIONS.placeholder; - - if (typeof(this.fallbacks) === "undefined" && this.fallbacks !== null) - this.fallbacks = DEFAULT_OPTIONS.fallbacks; - - if (typeof(this.translations) === "undefined" && this.translations !== null) - this.translations = DEFAULT_OPTIONS.translations; - - if (typeof(this.missingBehaviour) === "undefined" && this.missingBehaviour !== null) - this.missingBehaviour = DEFAULT_OPTIONS.missingBehaviour; - - if (typeof(this.missingTranslationPrefix) === "undefined" && this.missingTranslationPrefix !== null) - this.missingTranslationPrefix = DEFAULT_OPTIONS.missingTranslationPrefix; + var key; + for (key in DEFAULT_OPTIONS) if (!isSet(this[key])) { + this[key] = DEFAULT_OPTIONS[key]; + } }; I18n.initializeOptions(); @@ -336,9 +302,7 @@ }; // Check if value is different than undefined and null; - I18n.isSet = function(value) { - return value !== undefined && value !== null; - }; + I18n.isSet = isSet; // Find and process the translation using the provided scope and options. // This is used internally by some functions and should not be used as an @@ -376,7 +340,7 @@ } } - if (this.isSet(options.defaultValue)) { + if (isSet(options.defaultValue)) { return options.defaultValue; } }; @@ -391,7 +355,7 @@ if (isObject(translations)) { while (pluralizerKeys.length) { pluralizerKey = pluralizerKeys.shift(); - if (this.isSet(translations[pluralizerKey])) { + if (isSet(translations[pluralizerKey])) { message = translations[pluralizerKey]; break; } @@ -437,7 +401,7 @@ } if (message == null || message == undefined) { - if (this.isSet(options.defaultValue)) { + if (isSet(options.defaultValue)) { if (isObject(options.defaultValue)) { message = this.pluralizationLookupWithoutFallback(count, options.locale, options.defaultValue); } else { @@ -492,7 +456,7 @@ continue; } - if (this.isSet(options[attr])) { + if (isSet(options[attr])) { continue; } @@ -511,13 +475,13 @@ // Defaults should be an array of hashes containing either // fallback scopes or messages - if (this.isSet(options.defaults)) { + if (isSet(options.defaults)) { translationOptions = translationOptions.concat(options.defaults); } // Maintain support for defaultValue. Since it is always a message // insert it in to the translation options as such. - if (this.isSet(options.defaultValue)) { + if (isSet(options.defaultValue)) { translationOptions.push({ message: options.defaultValue }); delete options.defaultValue; } @@ -537,9 +501,9 @@ // or message is found. var translationFound = translationOptions.some(function(translationOption) { - if (this.isSet(translationOption.scope)) { + if (isSet(translationOption.scope)) { translation = this.lookup(translationOption.scope, options); - } else if (this.isSet(translationOption.message)) { + } else if (isSet(translationOption.message)) { translation = translationOption.message; } @@ -554,7 +518,7 @@ if (typeof(translation) === "string") { translation = this.interpolate(translation, options); - } else if (isObject(translation) && this.isSet(options.count)) { + } else if (isObject(translation) && isSet(options.count)) { translation = this.pluralize(options.count, scope, copiedOptions); } @@ -581,7 +545,7 @@ placeholder = matches.shift(); name = placeholder.replace(this.placeholder, "$1"); - if (this.isSet(options[name])) { + if (isSet(options[name])) { value = options[name].toString().replace(/\$/gm, "_#$#_"); } else if (name in options) { value = this.nullPlaceholder(placeholder, message, options); @@ -985,7 +949,7 @@ options = this.prepareOptions(options); // Deal with the scope as an array. - if (scope.constructor === Array) { + if (isArray(scope)) { scope = scope.join(this.defaultSeparator); } From 3395177198c2f7c702405cd783a1bbf5f6856eb9 Mon Sep 17 00:00:00 2001 From: Jimmy Chao Date: Wed, 5 Apr 2017 01:42:50 -0400 Subject: [PATCH 304/466] Add lazy evaluation for default message, since default message is not used most of the time. --- app/assets/javascripts/i18n.js | 28 +++++++++++++++++++++------- spec/js/translate.spec.js | 11 +++++++++++ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index de2f7f92..82362044 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -52,7 +52,12 @@ // Borrowed from Underscore.js var isObject = function(obj) { var type = typeof obj; - return type === 'function' || type === 'object' && !!obj; + return type === 'function' || type === 'object' + }; + + var isFunction = function(func) { + var type = typeof func; + return type === 'function' }; // Check if value is different than undefined and null; @@ -100,6 +105,14 @@ return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)); } + var lazyEvaluate = function(message, scope) { + if (isFunction(message)) { + return message(scope); + } else { + return message; + } + } + var merge = function (dest, obj) { var key, value; for (key in obj) if (obj.hasOwnProperty(key)) { @@ -218,7 +231,7 @@ I18n.locales.get = function(locale) { var result = this[locale] || this[I18n.locale] || this["default"]; - if (typeof(result) === "function") { + if (isFunction(result)) { result = result(locale); } @@ -256,7 +269,7 @@ // Compute each locale with its country code. // So this will return an array containing both // `de-DE` and `de` locales. - locales.forEach(function(locale){ + locales.forEach(function(locale) { countryCode = locale.split("-")[0]; if (!~list.indexOf(locale)) { @@ -314,14 +327,15 @@ , requestedLocale = locales[0] , locale , scopes + , fullScope , translations ; - scope = this.getFullScope(scope, options); + fullScope = this.getFullScope(scope, options); while (locales.length) { locale = locales.shift(); - scopes = scope.split(this.defaultSeparator); + scopes = fullScope.split(this.defaultSeparator); translations = this.translations[locale]; if (!translations) { @@ -341,7 +355,7 @@ } if (isSet(options.defaultValue)) { - return options.defaultValue; + return lazyEvaluate(options.defaultValue, scope); } }; @@ -504,7 +518,7 @@ if (isSet(translationOption.scope)) { translation = this.lookup(translationOption.scope, options); } else if (isSet(translationOption.message)) { - translation = translationOption.message; + translation = lazyEvaluate(translationOption.message, scope); } if (translation !== undefined && translation !== null) { diff --git a/spec/js/translate.spec.js b/spec/js/translate.spec.js index 046c59ba..83ba5b1c 100644 --- a/spec/js/translate.spec.js +++ b/spec/js/translate.spec.js @@ -145,6 +145,17 @@ describe("Translate", function(){ actual = I18n.t("foo", options); expect(actual).toEqual("Hello all!"); }); + + it("uses default value with lazy evaluation", function () { + var options = { + defaults: [{scope: "bar"}] + , defaultValue: function(scope) { + return scope.toUpperCase(); + } + }; + actual = I18n.t("foo", options); + expect(actual).toEqual("FOO"); + }) }); it("uses default value for simple translation", function(){ From 83d5c5f43629801bd8d26f25ff0cb8cd25d15fde Mon Sep 17 00:00:00 2001 From: Jimmy Chao Date: Wed, 5 Apr 2017 01:29:19 -0400 Subject: [PATCH 305/466] Replace prepareOptions with single params to just set default object Bacause prepareOptions is not necessary for most of the cases except when we want to create a copy of options --- app/assets/javascripts/i18n.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 82362044..dcaec292 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -321,7 +321,7 @@ // This is used internally by some functions and should not be used as an // public API. I18n.lookup = function(scope, options) { - options = this.prepareOptions(options); + options = options || {} var locales = this.locales.get(options.locale).slice() , requestedLocale = locales[0] @@ -381,7 +381,7 @@ // Lookup dedicated to pluralization I18n.pluralizationLookup = function(count, scope, options) { - options = this.prepareOptions(options); + options = options || {} var locales = this.locales.get(options.locale).slice() , requestedLocale = locales[0] , locale @@ -505,7 +505,7 @@ // Translate the given scope with the provided options. I18n.translate = function(scope, options) { - options = this.prepareOptions(options); + options = options || {} var copiedOptions = this.prepareOptions(options); var translationOptions = this.createTranslationOptions(scope, options); @@ -541,7 +541,7 @@ // This function interpolates the all variables in the given message. I18n.interpolate = function(message, options) { - options = this.prepareOptions(options); + options = options || {} var matches = message.match(this.placeholder) , placeholder , value @@ -960,7 +960,7 @@ }; I18n.getFullScope = function(scope, options) { - options = this.prepareOptions(options); + options = options || {} // Deal with the scope as an array. if (isArray(scope)) { From 1c85c04b618f7b5570b2c0a5ddba3a1b89275d16 Mon Sep 17 00:00:00 2001 From: Jimmy Chao Date: Wed, 5 Apr 2017 01:33:24 -0400 Subject: [PATCH 306/466] Remove side effects in createTranslationOptions and pluralize --- app/assets/javascripts/i18n.js | 14 +++++++------- spec/js/translate.spec.js | 9 +++++++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index dcaec292..d6127a35 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -497,7 +497,6 @@ // insert it in to the translation options as such. if (isSet(options.defaultValue)) { translationOptions.push({ message: options.defaultValue }); - delete options.defaultValue; } return translationOptions; @@ -507,16 +506,19 @@ I18n.translate = function(scope, options) { options = options || {} - var copiedOptions = this.prepareOptions(options); var translationOptions = this.createTranslationOptions(scope, options); var translation; + + var optionsWithoutDefault = this.prepareOptions(options) + delete optionsWithoutDefault.defaultValue + // Iterate through the translation options until a translation // or message is found. var translationFound = translationOptions.some(function(translationOption) { if (isSet(translationOption.scope)) { - translation = this.lookup(translationOption.scope, options); + translation = this.lookup(translationOption.scope, optionsWithoutDefault); } else if (isSet(translationOption.message)) { translation = lazyEvaluate(translationOption.message, scope); } @@ -533,7 +535,7 @@ if (typeof(translation) === "string") { translation = this.interpolate(translation, options); } else if (isObject(translation) && isSet(options.count)) { - translation = this.pluralize(options.count, scope, copiedOptions); + translation = this.pluralize(options.count, scope, options); } return translation; @@ -578,7 +580,7 @@ // The pluralized translation may have other placeholders, // which will be retrieved from `options`. I18n.pluralize = function(count, scope, options) { - options = this.prepareOptions(options); + options = this.prepareOptions({count: String(count)}, options) var pluralizer, message, result; result = this.pluralizationLookup(count, scope, options); @@ -586,8 +588,6 @@ return this.missingTranslation(scope, options); } - options.count = String(count); - if (result.message != undefined && result.message != null) { return this.interpolate(result.message, options); } diff --git a/spec/js/translate.spec.js b/spec/js/translate.spec.js index 83ba5b1c..6b682bca 100644 --- a/spec/js/translate.spec.js +++ b/spec/js/translate.spec.js @@ -146,6 +146,15 @@ describe("Translate", function(){ expect(actual).toEqual("Hello all!"); }); + it("uses default scope over default value if default scope is found", function() { + var options = { + defaults: [{scope: "hello"}] + , defaultValue: "Hello all!" + }; + actual = I18n.t("foo", options); + expect(actual).toEqual("Hello World!"); + }) + it("uses default value with lazy evaluation", function () { var options = { defaults: [{scope: "bar"}] From 578555f57ece1cf226ef004118e60738502b859a Mon Sep 17 00:00:00 2001 From: artygus Date: Thu, 13 Apr 2017 16:20:47 +0100 Subject: [PATCH 307/466] Fix #303 Rails 3, doesn't seem to pickup "new" stuff --- lib/i18n/js/dependencies.rb | 4 +- lib/i18n/js/engine.rb | 73 ++++++++++---------------- spec/ruby/i18n/js/dependencies_spec.rb | 43 --------------- 3 files changed, 30 insertions(+), 90 deletions(-) delete mode 100644 spec/ruby/i18n/js/dependencies_spec.rb diff --git a/lib/i18n/js/dependencies.rb b/lib/i18n/js/dependencies.rb index 4048cf43..336ca0c7 100644 --- a/lib/i18n/js/dependencies.rb +++ b/lib/i18n/js/dependencies.rb @@ -16,8 +16,8 @@ def rails5? safe_gem_check("rails", "~> 5.0", ">= 5.0.0.beta1") && running_rails5? end - def sprockets_supports_register_preprocessor? - defined?(Sprockets) && Sprockets.respond_to?(:register_preprocessor) + def sprockets_rails_v2_plus? + safe_gem_check("sprockets-rails", ">= 2") end def rails? diff --git a/lib/i18n/js/engine.rb b/lib/i18n/js/engine.rb index 274c0605..da1e173e 100644 --- a/lib/i18n/js/engine.rb +++ b/lib/i18n/js/engine.rb @@ -9,56 +9,39 @@ class SprocketsExtension end class Engine < ::Rails::Engine - if JS::Dependencies.sprockets_supports_register_preprocessor? - # constant `Sprockets` should be available here after - # `.sprockets_supports_register_preprocessor?` called - sprockets_version = Gem::Version.new(Sprockets::VERSION).release - v2_only = Gem::Dependency.new("", " ~> 2") - v3_plus = Gem::Dependency.new("", " >= 3") + # See https://github.com/rails/sprockets/blob/master/guides/extending_sprockets.md#supporting-all-versions-of-sprockets-in-processors + # for reference of supporting multiple versions - # See https://github.com/rails/sprockets/blob/master/guides/extending_sprockets.md#supporting-all-versions-of-sprockets-in-processors - # for reference of supporting multiple versions + # `sprockets.environment` was used for 1.x of `sprockets-rails` + # https://github.com/rails/sprockets-rails/issues/227 + # + # References for current values: + # + # Here is where sprockets are attached with Rails. There is no 'sprockets.environment' mentioned. + # https://github.com/rails/sprockets-rails/blob/master/lib/sprockets/railtie.rb + # + # Finisher hook is the place which should be used as border. + # http://guides.rubyonrails.org/configuring.html#initializers + # + # For detail see Pull Request: + # https://github.com/fnando/i18n-js/pull/371 + initializer "i18n-js.register_preprocessor", after: :engines_blank_point, before: :finisher_hook do + # This must be called inside initializer block + # For details see comments for `using_asset_pipeline?` + next unless JS::Dependencies.using_asset_pipeline? - # `sprockets.environment` was used for 1.x of `sprockets-rails` - # https://github.com/rails/sprockets-rails/issues/227 + # From README of 2.x & 3.x of `sprockets-rails` + # It seems the `configure` block is preferred way to call `register_preprocessor` + # Not sure if this will break older versions of rails # - # References for current values: - # - # Here is where sprockets are attached with Rails. There is no 'sprockets.environment' mentioned. - # https://github.com/rails/sprockets-rails/blob/master/lib/sprockets/railtie.rb - # - # Finisher hook is the place which should be used as border. - # http://guides.rubyonrails.org/configuring.html#initializers - # - # For detail see Pull Request: - # https://github.com/fnando/i18n-js/pull/371 - # - # Not using -> for JRuby compatibility - # See https://github.com/fnando/i18n-js/issues/419 - initializer_args = case sprockets_version - when lambda {|v| v2_only.match?("", v) || v3_plus.match?("", v) } - { after: :engines_blank_point, before: :finisher_hook } - else - raise StandardError, "Sprockets version #{sprockets_version} is not supported" - end - - initializer "i18n-js.register_preprocessor", initializer_args do - # This must be called inside initializer block - # For details see comments for `using_asset_pipeline?` - next unless JS::Dependencies.using_asset_pipeline? - - # From README of 2.x & 3.x of `sprockets-rails` - # It seems the `configure` block is preferred way to call `register_preprocessor` - # Not sure if this will break older versions of rails - # - # https://github.com/rails/sprockets-rails/blob/v2.3.3/README.md - # https://github.com/rails/sprockets-rails/blob/v3.0.0/README.md + # https://github.com/rails/sprockets-rails/blob/v2.3.3/README.md + # https://github.com/rails/sprockets-rails/blob/v3.0.0/README.md + if JS::Dependencies.sprockets_rails_v2_plus? Rails.application.config.assets.configure do |config| - config.register_preprocessor( - "application/javascript", - ::I18n::JS::SprocketsExtension, - ) + config.register_preprocessor("application/javascript", ::I18n::JS::SprocketsExtension) end + elsif Rails.application.assets.respond_to?(:register_preprocessor) + Rails.application.assets.register_preprocessor("application/javascript", ::I18n::JS::SprocketsExtension) end end end diff --git a/spec/ruby/i18n/js/dependencies_spec.rb b/spec/ruby/i18n/js/dependencies_spec.rb deleted file mode 100644 index ced58a9e..00000000 --- a/spec/ruby/i18n/js/dependencies_spec.rb +++ /dev/null @@ -1,43 +0,0 @@ -require "spec_helper" - -describe I18n::JS::Dependencies, ".sprockets_supports_register_preprocessor?" do - - subject { described_class.sprockets_supports_register_preprocessor? } - - context 'when Sprockets is available to register preprocessors' do - let!(:sprockets_double) do - class_double('Sprockets').as_stubbed_const(register_processor: true).tap do |double| - allow(double).to receive(:respond_to?).with(:register_preprocessor).and_return(true) - end - end - - it { is_expected.to be_truthy } - it 'calls respond_to? with register_preprocessor on Sprockets' do - expect(sprockets_double).to receive(:respond_to?).with(:register_preprocessor).and_return(true) - subject - end - end - - context 'when Sprockets is NOT available to register preprocessors' do - let!(:sprockets_double) do - class_double('Sprockets').as_stubbed_const(register_processor: true).tap do |double| - allow(double).to receive(:respond_to?).with(:register_preprocessor).and_return(false) - end - end - - it { is_expected.to be_falsy } - it 'calls respond_to? with register_preprocessor on Sprockets' do - expect(sprockets_double).to receive(:respond_to?).with(:register_preprocessor).and_return(false) - subject - end - end - - context 'when Sprockets is missing' do - before do - hide_const('Sprockets') - expect { Sprockets }.to raise_error(NameError) - end - - it { is_expected.to be_falsy } - end -end From 490c384c97be1d8c1ea946e19d02d7063e14e60e Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 19 May 2017 09:24:41 +0800 Subject: [PATCH 308/466] * Update version to 3.0.0 in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c1d813b7..10f336ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "i18n-js", - "version": "0.0.0", + "version": "3.0.0", "devDependencies": { "jasmine-node": "^1.14.5" }, From aee20882caef01e35f3a2dd8ac5d2611f33b8604 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 19 May 2017 09:34:03 +0800 Subject: [PATCH 309/466] + Add file .npmignore --- .npmignore | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .npmignore diff --git a/.npmignore b/.npmignore new file mode 100644 index 00000000..f7169552 --- /dev/null +++ b/.npmignore @@ -0,0 +1,26 @@ +# https://docs.npmjs.com/misc/developers#keeping-files-out-of-your-package + +# tests +spec +coverage + +# build tools +.travis.yml + +# linters +.jscsrc +.jshintrc +.eslintrc* + +# editor settings +.idea +.editorconfig + +# Ruby code +gemfiles +lib +Gemfile* +*.gemspec +Rakefile +Appraisals + From 3b9def86983a681b30e02696463fec688aebaba0 Mon Sep 17 00:00:00 2001 From: Brian Tong Date: Tue, 13 Jun 2017 11:13:28 -0700 Subject: [PATCH 310/466] Update link to CLDR plural rules --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0b217826..febacdba 100644 --- a/README.md +++ b/README.md @@ -453,7 +453,7 @@ I18n.pluralization["ru"] = function (count) { }; ``` -You can find all rules on . +You can find all rules on . If you're using the same scope over and over again, you may use the `scope` option. From f4cdecf81dfee73daafbd63de2729af0af492609 Mon Sep 17 00:00:00 2001 From: Yauheni Dakuka Date: Fri, 16 Jun 2017 10:19:23 +0300 Subject: [PATCH 311/466] Update README.md (#459) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index febacdba..cf23b4ad 100644 --- a/README.md +++ b/README.md @@ -257,7 +257,7 @@ translations: ``` -#### Javscript Deep Merge (:js_extend option) +#### Javascript Deep Merge (:js_extend option) By default, the output file Javascript will call the `I18n.extend` method to ensure that newly loaded locale files are deep-merged with any locale data already in memory. To disable this either globally or per-file, From 2feb839bba8cb2a7706568429d4f7df1e5556342 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 20 Jul 2017 10:56:14 +0800 Subject: [PATCH 312/466] * Add spec for 3 part locale fallbacks --- spec/js/translate.spec.js | 17 ++++++++++++++++- spec/js/translations.js | 8 ++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/spec/js/translate.spec.js b/spec/js/translate.spec.js index 6b682bca..93bf6deb 100644 --- a/spec/js/translate.spec.js +++ b/spec/js/translate.spec.js @@ -65,7 +65,7 @@ describe("Translate", function(){ expect(I18n.t("hello", {locale: "pt-BR"})).toEqual("Olá Mundo!"); }); - it("fallbacks to the default locale when I18n.fallbackss is enabled", function(){ + it("fallbacks to the default locale when I18n.fallbacks is enabled", function(){ I18n.locale = "pt-BR"; I18n.fallbacks = true; expect(I18n.t("greetings.stranger")).toEqual("Hello stranger!"); @@ -83,6 +83,21 @@ describe("Translate", function(){ expect(I18n.t("hello")).toEqual("Hallo Welt!"); }); + describe("when a 3-part locale is used", function(){ + beforeEach(function(){ + I18n.locale = "zh-Hant-TW"; + I18n.fallbacks = true; + }); + + it("fallbacks to 2-part locale when absent", function(){ + expect(I18n.t("cat")).toEqual("貓"); + }); + + it("fallbacks to 1-part locale when 2-part missing requested translation", function(){ + expect(I18n.t("dog")).toEqual("狗"); + }); + }); + it("fallbacks using custom rules (function)", function(){ I18n.locale = "no"; I18n.fallbacks = true; diff --git a/spec/js/translations.js b/spec/js/translations.js index 874076d4..65636e10 100644 --- a/spec/js/translations.js +++ b/spec/js/translations.js @@ -130,6 +130,14 @@ var DEBUG = false; hello: "Hei Verden!" }; + Translations["zh-Hant"] = { + cat: "貓" + }; + + Translations["zh"] = { + dog: "狗" + }; + return Translations; }; From d383e593028ac3dddef0366b8e1f3c2e75f5040e Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 20 Jul 2017 10:47:04 +0800 Subject: [PATCH 313/466] * Implement 3 part locale fallbacks --- app/assets/javascripts/i18n.js | 80 ++++++++++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index d6127a35..4e07094d 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -246,8 +246,6 @@ I18n.locales["default"] = function(locale) { var locales = [] , list = [] - , countryCode - , count ; // Handle the inline locale option that can be provided to @@ -266,19 +264,85 @@ locales.push(I18n.defaultLocale); } + // Locale code format 1: + // According to RFC4646 (http://www.ietf.org/rfc/rfc4646.txt) + // language codes for Traditional Chinese should be `zh-Hant` + // + // But due to backward compatibility + // We use older version of IETF language tag + // @see http://www.w3.org/TR/html401/struct/dirlang.html + // @see http://en.wikipedia.org/wiki/IETF_language_tag + // + // Format: `language-code = primary-code ( "-" subcode )*` + // + // primary-code uses ISO639-1 + // @see http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes + // @see http://www.iso.org/iso/home/standards/language_codes.htm + // + // subcode uses ISO 3166-1 alpha-2 + // @see http://en.wikipedia.org/wiki/ISO_3166 + // @see http://www.iso.org/iso/country_codes.htm + // + // @note + // subcode can be in upper case or lower case + // defining it in upper case is a convention only + + + // Locale code format 2: + // Format: `code = primary-code ( "-" region-code )*` + // primary-code uses ISO 639-1 + // script-code uses ISO 15924 + // region-code uses ISO 3166-1 alpha-2 + // Example: zh-Hant-TW, en-HK, zh-Hant-CN + // + // It is similar to RFC4646 (or actually the same), + // but seems to be limited to language, script, region + // Compute each locale with its country code. - // So this will return an array containing both - // `de-DE` and `de` locales. + // So this will return an array containing + // `de-DE` and `de` + // or + // `zh-hans-tw`, `zh-hans`, `zh` + // locales. locales.forEach(function(locale) { - countryCode = locale.split("-")[0]; + var localeParts = locale.split("-"); + var firstFallback = null; + var secondFallback = null; + if (localeParts.length === 3) { + firstFallback = localeParts[0]; + secondFallback = [ + localeParts[0], + localeParts[1] + ].join("-"); + } + else if (localeParts.length === 2) { + firstFallback = localeParts[0]; + } - if (!~list.indexOf(locale)) { + if (list.indexOf(locale) === -1) { list.push(locale); } - if (I18n.fallbacks && countryCode && countryCode !== locale && !~list.indexOf(countryCode)) { - list.push(countryCode); + if (! I18n.fallbacks) { + return; } + + [ + firstFallback, + secondFallback + ].forEach(function(nullableFallbackLocale) { + // We don't want null values + if (typeof nullableFallbackLocale === "undefined") { return; } + if (nullableFallbackLocale === null) { return; } + // We don't want duplicate values + // + // Comparing with `locale` first is faster than + // checking whether value's presence in the list + if (nullableFallbackLocale === locale) { return; } + if (list.indexOf(nullableFallbackLocale) !== -1) { return; } + + list.push(nullableFallbackLocale); + }); }); // No locales set? English it is. From 343590d48665717ce4a7dd2f5f690fff91697942 Mon Sep 17 00:00:00 2001 From: Franz Liedke Date: Tue, 25 Jul 2017 11:59:17 +0200 Subject: [PATCH 314/466] Simplify Rails version checks There is no need to check for the rails gem to be installed. We are only concerned with whether Rails is available and don't care where it comes from (as there are other ways to install Rails). Fixes #466. --- CHANGELOG.md | 3 ++- lib/i18n/js/dependencies.rb | 36 ++++++++---------------------------- 2 files changed, 10 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5fa1096..070e5023 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- Nothing +- [Ruby] Relax Rails detection code to work with alternative installation methods + (PR: https://github.com/fnando/i18n-js/pull/467) ### Fixed diff --git a/lib/i18n/js/dependencies.rb b/lib/i18n/js/dependencies.rb index 336ca0c7..fc59552b 100644 --- a/lib/i18n/js/dependencies.rb +++ b/lib/i18n/js/dependencies.rb @@ -4,30 +4,14 @@ module JS # we need to specify pre-release version suffix in version constraint module Dependencies class << self - def rails3? - safe_gem_check("rails", "~> 3.0") && running_rails3? - end - - def rails4? - safe_gem_check("rails", "~> 4.0", ">= 4.0.0.beta1") && running_rails4? - end - - def rails5? - safe_gem_check("rails", "~> 5.0", ">= 5.0.0.beta1") && running_rails5? + def rails? + defined?(Rails) && Rails.respond_to?(:version) end def sprockets_rails_v2_plus? safe_gem_check("sprockets-rails", ">= 2") end - def rails? - rails_available? && running_rails? - end - - def rails_available? - safe_gem_check("rails", '>= 3.0.0.beta') - end - # This cannot be called at class definition time # Since not all libraries are loaded # @@ -47,20 +31,16 @@ def using_asset_pipeline? private - def running_rails3? - running_rails? && Rails.version.to_i == 3 - end - - def running_rails4? - running_rails? && Rails.version.to_i == 4 + def rails3? + rails? && Rails.version.to_i == 3 end - def running_rails5? - running_rails? && Rails.version.to_i == 5 + def rails4? + rails? && Rails.version.to_i == 4 end - def running_rails? - defined?(Rails) && Rails.respond_to?(:version) + def rails5? + rails? && Rails.version.to_i == 5 end def safe_gem_check(*args) From 5a28bc51e0925e11e3aeb552b2757379f6442e47 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 2 Aug 2017 15:33:40 +0800 Subject: [PATCH 315/466] ~ Add .editorconfig [ci skip] --- .editorconfig | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..7bd39f95 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,24 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# default configuration +[*] +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +# Unix-style newlines with a newline ending every file +end_of_line = lf + +# Set default charset +charset = utf-8 + +# Tab indentation (no size specified) +[Makefile] +indent_style = tab + +[*.{md,markdown}] +trim_trailing_whitespace = false From 58b93e9021de1761b3da8c34e250162527ecd0f8 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 2 Aug 2017 15:34:07 +0800 Subject: [PATCH 316/466] ~ Update CHANGELOG [ci skip] --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 070e5023..d69dd42e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,10 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- [Ruby] Relax Rails detection code to work with alternative installation methods +- [Ruby] Relax Rails detection code to work with alternative installation methods + (PR: https://github.com/fnando/i18n-js/pull/467) +- [JS] Fix fallback when "3-part" locale like `zh-Hant-TW` is used + It fallbacks to `zh` only before, now it fallbacks to `zh-Hant` (PR: https://github.com/fnando/i18n-js/pull/467) ### Fixed From a4f947fd65d80bd3e09971f21ab02e967e676742 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 2 Aug 2017 15:40:27 +0800 Subject: [PATCH 317/466] ^ Release 3.0.1 --- CHANGELOG.md | 20 ++++++++++++++------ lib/i18n/js/version.rb | 2 +- package.json | 2 +- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d69dd42e..e7e0bd9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,17 +11,24 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- [Ruby] Relax Rails detection code to work with alternative installation methods - (PR: https://github.com/fnando/i18n-js/pull/467) -- [JS] Fix fallback when "3-part" locale like `zh-Hant-TW` is used - It fallbacks to `zh` only before, now it fallbacks to `zh-Hant` - (PR: https://github.com/fnando/i18n-js/pull/467) +- Nothing ### Fixed - Nothing +## [3.0.1] - 2017-08-02 + +### Changed + +- [Ruby] Relax Rails detection code to work with alternative installation methods + (PR: https://github.com/fnando/i18n-js/pull/467) +- [JS] Fix fallback when "3-part" locale like `zh-Hant-TW` is used + It fallbacks to `zh` only before, now it fallbacks to `zh-Hant` + (PR: https://github.com/fnando/i18n-js/pull/465) + + ## [3.0.0] - 2017-04-01 This is a fake official release, the *real* one will be `3.0.0.rc17` @@ -244,7 +251,8 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.0...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.1...HEAD +[3.0.1]: https://github.com/fnando/i18n-js/compare/v3.0.0...v3.0.1 [3.0.0]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc16...v3.0.0 [3.0.0.rc16]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc15...v3.0.0.rc16 [3.0.0.rc15]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc14...v3.0.0.rc15 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index c90449ff..b312c20d 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -1,5 +1,5 @@ module I18n module JS - VERSION = "3.0.0" + VERSION = "3.0.1" end end diff --git a/package.json b/package.json index 10f336ab..17fe98ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "i18n-js", - "version": "3.0.0", + "version": "3.0.1", "devDependencies": { "jasmine-node": "^1.14.5" }, From ef1543a74ef82b49724ea3fa2c19fdb132b02f03 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 2 Aug 2017 15:48:46 +0800 Subject: [PATCH 318/466] * Update package.json before publishing to npm --- package.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/package.json b/package.json index 17fe98ef..6208bed9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,10 @@ { "name": "i18n-js", "version": "3.0.1", + "description": "A javascript library similar to Ruby on Rails i18n gem", + "author": "Nando Vieira", + "license": "MIT", + "keywords": ["i18n"], "devDependencies": { "jasmine-node": "^1.14.5" }, @@ -8,6 +12,10 @@ "scripts": { "test": "./node_modules/.bin/jasmine-node spec/js" }, + "homepage": "https://github.com/fnando/i18n-js", + "bugs": { + "url": "https://github.com/fnando/i18n-js/issues" + }, "repository": { "type": "git", "url": "https://github.com/fnando/i18n-js.git" From 5abadf6c22948972e0a5e447550272f635960f44 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 2 Aug 2017 15:53:06 +0800 Subject: [PATCH 319/466] ~ Update README about installation by NPM [ci skip] --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cf23b4ad..fe7a6eeb 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Features: - Lots more! :) ## Version Notice -The `master` branch (including this README) is for latest `3.0.0.rc` instead of `2.x`. +The `master` branch (including this README) is for latest `3.0.0` instead of `2.x`. ## Usage @@ -279,9 +279,15 @@ by hand or using your favorite programming language. More info below. #### Via NPM with webpack and CommonJS -Add the following line to your package.json dependencies (where version is the version you want - n.b. npm install requires it to be the gzipped tarball, see [npm install](https://www.npmjs.org/doc/cli/npm-install.html)) + +Add the following line to your package.json dependencies +where version is the version you want ```javascript -"i18n-js": "http://github.com/fnando/i18n-js/archive/v3.0.0.rc12.tar.gz" +"i18n-js": "{version_constraint}" + +// Or if you want unreleased version +// npm install requires it to be the gzipped tarball, see [npm install](https://www.npmjs.org/doc/cli/npm-install.html) +"i18n-js": "https://github.com/fnando/i18n-js/archive/{tag_name_or_branch_name_or_commit_sha}.tar.gz" ``` Run npm install then use via ```javascript From 53aa17ac727de13842f95af806945441f454df70 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 2 Aug 2017 16:12:31 +0800 Subject: [PATCH 320/466] * Ignore unrelated files before publishing npm package [ci skip] --- .npmignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.npmignore b/.npmignore index f7169552..6a00a86a 100644 --- a/.npmignore +++ b/.npmignore @@ -17,6 +17,7 @@ coverage .editorconfig # Ruby code +app/assets/javascripts/i18n/ gemfiles lib Gemfile* From ad2215cf1f7f4fa1041c7c3661f129cab11f5b9f Mon Sep 17 00:00:00 2001 From: Emmanuel Hoffmann Date: Mon, 28 Aug 2017 19:32:27 +0200 Subject: [PATCH 321/466] Fix code format in Readme --- README.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index fe7a6eeb..5129fa70 100644 --- a/README.md +++ b/README.md @@ -783,19 +783,20 @@ Due to the design of `sprockets`: This means that new locale files will not be detected, and so they will not trigger a i18n-js refresh. There are a few approaches to work around this: 1. You can force i18n-js to update its translations by completely clearing the assets cache. Use one of the following: + +```bash +$ rake assets:clobber +# Or, with older versions of Rails: +$ rake tmp:cache:clear +``` - ```bash - $ rake assets:clobber - # Or, with older versions of Rails: - $ rake tmp:cache:clear - ``` +These commands will remove *all* fingerprinted assets, and you will have to recompile them with - These commands will remove *all* fingerprinted assets, and you will have to recompile them with +```bash +$ rake assets:precompile +``` - ``` - $ rake assets:precompile - ``` - or similar commands. If you are precompiling assets on the target machine(s), cached pages may be broken by this, so they will need to be refreshed. +or similar commands. If you are precompiling assets on the target machine(s), cached pages may be broken by this, so they will need to be refreshed. 2. You can change something in a different locale file. From 5f8f1670b2be08f6d0274a343695e89f928008de Mon Sep 17 00:00:00 2001 From: Michael Pearson Date: Thu, 21 Sep 2017 17:35:02 +1000 Subject: [PATCH 322/466] Avoid writing files if we don't have to. Writing files triggers other actions in anything watching those files - eg rebuilding a webpack bundle. Normally this won't occur as i18n-js's caching will determine that nothing needs to be done, but it can add up quickly when running individual specs from the command line, where the cache is reset on every load. --- lib/i18n/js.rb | 3 +++ lib/i18n/js/segment.rb | 12 ++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/i18n/js.rb b/lib/i18n/js.rb index 4fa62ddf..d171e82c 100644 --- a/lib/i18n/js.rb +++ b/lib/i18n/js.rb @@ -219,6 +219,9 @@ def self.export_i18n_js FileUtils.mkdir_p(export_i18n_js_dir_path) i18n_js_path = File.expand_path('../../../app/assets/javascripts/i18n.js', __FILE__) + destination_path = File.expand_path("i18n.js", export_i18n_js_dir_path) + return if File.exist?(destination_path) && File.identical?(i18n_js_path, destination_path) + FileUtils.cp(i18n_js_path, export_i18n_js_dir_path) end diff --git a/lib/i18n/js/segment.rb b/lib/i18n/js/segment.rb index 4733338b..4ae6e3b5 100644 --- a/lib/i18n/js/segment.rb +++ b/lib/i18n/js/segment.rb @@ -40,11 +40,15 @@ def save! def write_file(_file = @file, _translations = @translations) FileUtils.mkdir_p File.dirname(_file) + contents = js_header + _translations.each do |locale, translations_for_locale| + contents << js_translations(locale, translations_for_locale) + end + + return if File.exist?(_file) && File.read(_file) == contents + File.open(_file, "w+") do |f| - f << js_header - _translations.each do |locale, translations_for_locale| - f << js_translations(locale, translations_for_locale) - end + f << contents end end From a88b206644a14b0de7bbb58e0ba1baab93737a8e Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 25 Sep 2017 17:13:27 +0800 Subject: [PATCH 323/466] ! Fix not comparing files content Caused by unclear review comment https://github.com/fnando/i18n-js/pull/473#pullrequestreview-64203591 --- lib/i18n/js.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/i18n/js.rb b/lib/i18n/js.rb index d171e82c..b14a178e 100644 --- a/lib/i18n/js.rb +++ b/lib/i18n/js.rb @@ -220,7 +220,7 @@ def self.export_i18n_js i18n_js_path = File.expand_path('../../../app/assets/javascripts/i18n.js', __FILE__) destination_path = File.expand_path("i18n.js", export_i18n_js_dir_path) - return if File.exist?(destination_path) && File.identical?(i18n_js_path, destination_path) + return if File.exist?(destination_path) && FileUtils.identical?(i18n_js_path, destination_path) FileUtils.cp(i18n_js_path, export_i18n_js_dir_path) end From dcf04257d2aa382ff0490f204bd5b452ac83f7a5 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 16 Oct 2017 09:35:10 +0800 Subject: [PATCH 324/466] * Run tests for I18n 0.9.x --- Appraisals | 4 ++++ gemfiles/i18n_0_9.gemfile | 7 +++++++ 2 files changed, 11 insertions(+) create mode 100644 gemfiles/i18n_0_9.gemfile diff --git a/Appraisals b/Appraisals index 3f129437..bee2080d 100644 --- a/Appraisals +++ b/Appraisals @@ -10,3 +10,7 @@ end appraise "i18n_0_8" do gem "i18n", "~> 0.8.0" end + +appraise "i18n_0_9" do + gem "i18n", "~> 0.9.0" +end diff --git a/gemfiles/i18n_0_9.gemfile b/gemfiles/i18n_0_9.gemfile new file mode 100644 index 00000000..ae05ce33 --- /dev/null +++ b/gemfiles/i18n_0_9.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "i18n", "~> 0.9.0" + +gemspec :path => "../" From 6edc1fbfdf17bd0b54f12c0d32d75e1a71fae65d Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 16 Oct 2017 09:36:12 +0800 Subject: [PATCH 325/466] * Update Travis config for ruby versions & gemfiles --- .travis.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6a77fd8a..9e5f6d15 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,11 +6,11 @@ cache: - bundler rvm: - 2.1.10 - - 2.2.5 + - 2.2.8 # Since the Travis Build Env does not recognize `2.3` alias yet # We need to specify the version precisely - - 2.3.3 - - 2.4.0 + - 2.3.5 + - 2.4.2 - ruby-head before_install: # Need to install something extra to test JS @@ -19,6 +19,7 @@ gemfile: - gemfiles/i18n_0_6.gemfile - gemfiles/i18n_0_7.gemfile - gemfiles/i18n_0_8.gemfile + - gemfiles/i18n_0_9.gemfile matrix: fast_finish: true allow_failures: From 51b9025ca0ae8b002f4ac7b18ce2d576c57c0b0d Mon Sep 17 00:00:00 2001 From: lidongjie Date: Thu, 26 Oct 2017 11:25:47 +0800 Subject: [PATCH 326/466] fix fallback bug --- app/assets/javascripts/i18n.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 4e07094d..1ed0dfac 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -309,11 +309,11 @@ var firstFallback = null; var secondFallback = null; if (localeParts.length === 3) { - firstFallback = localeParts[0]; - secondFallback = [ + firstFallback = [ localeParts[0], localeParts[1] ].join("-"); + secondFallback = localeParts[0]; } else if (localeParts.length === 2) { firstFallback = localeParts[0]; From 6a6ae57cbb85ff4f3cbd317a7ce47a7d90965557 Mon Sep 17 00:00:00 2001 From: lidongjie Date: Thu, 26 Oct 2017 13:55:33 +0800 Subject: [PATCH 327/466] Add a test case for 3 part locale fallbacks --- spec/js/translate.spec.js | 4 ++++ spec/js/translations.js | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/spec/js/translate.spec.js b/spec/js/translate.spec.js index 93bf6deb..81f1a83a 100644 --- a/spec/js/translate.spec.js +++ b/spec/js/translate.spec.js @@ -96,6 +96,10 @@ describe("Translate", function(){ it("fallbacks to 1-part locale when 2-part missing requested translation", function(){ expect(I18n.t("dog")).toEqual("狗"); }); + + it("fallbacks to 2-part for the first time", function(){ + expect(I18n.t("dragon")).toEqual("龍"); + }); }); it("fallbacks using custom rules (function)", function(){ diff --git a/spec/js/translations.js b/spec/js/translations.js index 65636e10..7517ff17 100644 --- a/spec/js/translations.js +++ b/spec/js/translations.js @@ -131,11 +131,13 @@ var DEBUG = false; }; Translations["zh-Hant"] = { - cat: "貓" + cat: "貓" + , dragon: "龍" }; Translations["zh"] = { - dog: "狗" + dog: "狗" + , dragon: "龙" }; return Translations; From df29752bf5622290241054661db126924c98e450 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 26 Oct 2017 15:51:21 +0800 Subject: [PATCH 328/466] ^ Release 3.0.2 --- CHANGELOG.md | 22 +++++++++++++++++++++- lib/i18n/js/version.rb | 2 +- package.json | 2 +- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7e0bd9c..b3de40ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,25 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.0.2] - 2017-10-26 + +### Added + +- Nothing + +### Changed + +- [Ruby] Avoid writing new file if a file with same content already exists + (PR: https://github.com/fnando/i18n-js/pull/473) +- [JS] Fix fallback when "3-part" locale like `zh-Hant-TW` is used + It was falling back to `zh` first instead of `zh-Hant` (see new test case added) + (PR: https://github.com/fnando/i18n-js/pull/475) + +### Fixed + +- Nothing + + ## [3.0.1] - 2017-08-02 ### Changed @@ -251,7 +270,8 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.1...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.2...HEAD +[3.0.2]: https://github.com/fnando/i18n-js/compare/v3.0.1...v3.0.2 [3.0.1]: https://github.com/fnando/i18n-js/compare/v3.0.0...v3.0.1 [3.0.0]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc16...v3.0.0 [3.0.0.rc16]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc15...v3.0.0.rc16 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index b312c20d..0840e3a5 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -1,5 +1,5 @@ module I18n module JS - VERSION = "3.0.1" + VERSION = "3.0.2" end end diff --git a/package.json b/package.json index 6208bed9..0643c9c9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "i18n-js", - "version": "3.0.1", + "version": "3.0.2", "description": "A javascript library similar to Ruby on Rails i18n gem", "author": "Nando Vieira", "license": "MIT", From c37eba5e337e4e000a9a8f2cbba7a81d59d0f53f Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 26 Oct 2017 15:53:08 +0800 Subject: [PATCH 329/466] ~ Remove useless entries in change log [ci skip] --- CHANGELOG.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3de40ea..9bf9d8e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,10 +20,6 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [3.0.2] - 2017-10-26 -### Added - -- Nothing - ### Changed - [Ruby] Avoid writing new file if a file with same content already exists @@ -32,10 +28,6 @@ This project adheres to [Semantic Versioning](http://semver.org/). It was falling back to `zh` first instead of `zh-Hant` (see new test case added) (PR: https://github.com/fnando/i18n-js/pull/475) -### Fixed - -- Nothing - ## [3.0.1] - 2017-08-02 From 38a488348200756cc1a1728b4aa822534f2decc8 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 14 Dec 2017 09:32:50 +0800 Subject: [PATCH 330/466] ~ Update instruction for Rails app without Asset Pipeline --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5129fa70..21e998d4 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Then get the JS files following the instructions below. <%# This is just an example, you can put `i18n.js` and `translations.js` anywhere you like %> <%# Unlike the Asset Pipeline example, you need to require both **in order** %> <%= javascript_include_tag "i18n" %> -<%= javascript_include_tag "translations" %> +<%= javascript_include_tag "translations", skip_pipeline: true %> ``` **There are two ways to get `translations.js`.** From 3a4b686aa2312394449fa7db228d0788d4f36c75 Mon Sep 17 00:00:00 2001 From: Leandro Lourenci Date: Fri, 29 Dec 2017 21:19:10 -0200 Subject: [PATCH 331/466] Fix extend when objects have array values --- app/assets/javascripts/i18n.js | 2 +- spec/js/extend.spec.js | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 1ed0dfac..b6f14687 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -117,7 +117,7 @@ var key, value; for (key in obj) if (obj.hasOwnProperty(key)) { value = obj[key]; - if (isString(value) || isNumber(value) || isBoolean(value)) { + if (isString(value) || isNumber(value) || isBoolean(value) || isArray(value)) { dest[key] = value; } else { if (dest[key] == null) dest[key] = {}; diff --git a/spec/js/extend.spec.js b/spec/js/extend.spec.js index beaa71da..f463c78d 100644 --- a/spec/js/extend.spec.js +++ b/spec/js/extend.spec.js @@ -82,4 +82,27 @@ describe("Extend", function () { expect(I18n.extend(obj1, obj2)).toEqual(expected); }); + + it("should merge array values", function() { + var obj1 = { + array1: [1, 2] + }, + obj2 = { + array2: [1, 2], + obj3: { + array3: [1, 2], + array4: [{obj4: 1}, 2] + } + }, + expected = { + array1: [1, 2], + array2: [1, 2], + obj3: { + array3: [1, 2], + array4: [{obj4: 1}, 2] + } + } + + expect(JSON.stringify(I18n.extend(obj1, obj2))).toEqual(JSON.stringify(expected)); + }); }); From 6f94d2928619de40450f6073671206dba5a9cbb4 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 2 Jan 2018 11:16:23 +0800 Subject: [PATCH 332/466] ^ Release 3.0.3 --- CHANGELOG.md | 11 ++++++++++- lib/i18n/js/version.rb | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bf9d8e1..bb511bb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.0.3] - 2018-01-02 + +### Fixed + +- [Ruby] Fix extend method when translations has array values + (PR: https://github.com/fnando/i18n-js/pull/487) + + ## [3.0.2] - 2017-10-26 ### Changed @@ -262,7 +270,8 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.2...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.3...HEAD +[3.0.3]: https://github.com/fnando/i18n-js/compare/v3.0.2...v3.0.3 [3.0.2]: https://github.com/fnando/i18n-js/compare/v3.0.1...v3.0.2 [3.0.1]: https://github.com/fnando/i18n-js/compare/v3.0.0...v3.0.1 [3.0.0]: https://github.com/fnando/i18n-js/compare/v3.0.0.rc16...v3.0.0 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index 0840e3a5..173aa19f 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -1,5 +1,5 @@ module I18n module JS - VERSION = "3.0.2" + VERSION = "3.0.3" end end From f7ec16a79433e637aabfec6b1b62899bd2707fb2 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 2 Jan 2018 14:07:10 +0800 Subject: [PATCH 333/466] * Test with MRI 2.5 --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9e5f6d15..cfa50734 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,8 +9,9 @@ rvm: - 2.2.8 # Since the Travis Build Env does not recognize `2.3` alias yet # We need to specify the version precisely - - 2.3.5 - - 2.4.2 + - 2.3.6 + - 2.4.3 + - 2.5.0 - ruby-head before_install: # Need to install something extra to test JS From 3b8b0e3cfc28ef752e14911e883b4784ce3491d2 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 2 Jan 2018 16:14:34 +0800 Subject: [PATCH 334/466] * Update rubygems to avoid issue --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index cfa50734..9fcee5eb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,9 @@ rvm: before_install: # Need to install something extra to test JS - npm install +# Bundler 1.16.1 and rubygems 2.7.3 got issue +# https://github.com/travis-ci/travis-ci/issues/8978#issuecomment-354036443 +- gem update --system gemfile: - gemfiles/i18n_0_6.gemfile - gemfiles/i18n_0_7.gemfile From 43fe15c4435869051b10f9ceca5e89149bd4f43f Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 3 Jan 2018 09:23:36 +0800 Subject: [PATCH 335/466] ^ Release 3.0.3 on npm [ci skip] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0643c9c9..7597f7e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "i18n-js", - "version": "3.0.2", + "version": "3.0.3", "description": "A javascript library similar to Ruby on Rails i18n gem", "author": "Nando Vieira", "license": "MIT", From 51a81d1edd5899b4665a2bc73c331224a8c03c8f Mon Sep 17 00:00:00 2001 From: Leandro Lourenci Date: Wed, 24 Jan 2018 17:32:58 -0200 Subject: [PATCH 336/466] Fix a bug when rails project has only Webpacker --- lib/i18n/js/dependencies.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/i18n/js/dependencies.rb b/lib/i18n/js/dependencies.rb index fc59552b..a81234e5 100644 --- a/lib/i18n/js/dependencies.rb +++ b/lib/i18n/js/dependencies.rb @@ -20,7 +20,7 @@ def using_asset_pipeline? assets_pipeline_available = (rails3? || rails4? || rails5?) && Rails.respond_to?(:application) && - Rails.application.respond_to?(:assets) + Rails.application.config.respond_to?(:assets) rails3_assets_enabled = rails3? && assets_pipeline_available && From d2970d1e35a6dbf99aa2ef47b378f965dd8d8280 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 26 Jan 2018 10:03:28 +0800 Subject: [PATCH 337/466] ^ Release 3.0.4 --- CHANGELOG.md | 11 ++++++++++- lib/i18n/js/version.rb | 4 +++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb511bb6..d501c6cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.0.4] - 2018-01-26 + +### Fixed + +- [Ruby] Fix `JS::Dependencies.using_asset_pipeline?` returning true when sprockets installed but disabled + (PR: https://github.com/fnando/i18n-js/pull/488) + + ## [3.0.3] - 2018-01-02 ### Fixed @@ -270,7 +278,8 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.3...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.4...HEAD +[3.0.3]: https://github.com/fnando/i18n-js/compare/v3.0.3...v3.0.4 [3.0.3]: https://github.com/fnando/i18n-js/compare/v3.0.2...v3.0.3 [3.0.2]: https://github.com/fnando/i18n-js/compare/v3.0.1...v3.0.2 [3.0.1]: https://github.com/fnando/i18n-js/compare/v3.0.0...v3.0.1 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index 173aa19f..36c98468 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + module I18n module JS - VERSION = "3.0.3" + VERSION = "3.0.4" end end From 8a13dcfc896687491ff4aba67b10932519467772 Mon Sep 17 00:00:00 2001 From: Dovi Winberger Date: Thu, 1 Feb 2018 08:55:21 +0200 Subject: [PATCH 338/466] making it clearer that creating translations.js is only for rails without Asset pipeline --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 21e998d4..f59cffc7 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Then get the JS files following the instructions below. <%= javascript_include_tag "translations", skip_pipeline: true %> ``` -**There are two ways to get `translations.js`.** +**There are two ways to get `translations.js` (For Rails app without [Asset Pipeline]).** 1. This `translations.js` file can be automatically generated by the `I18n::JS::Middleware`. Just add `config.middleware.use I18n::JS::Middleware` to your `config/application.rb` file. From 53cb0c2517c8b831d1d1a91bdb2e5e8fb8e1b335 Mon Sep 17 00:00:00 2001 From: Dovi Winberger Date: Thu, 1 Feb 2018 08:56:11 +0200 Subject: [PATCH 339/466] removed unneeded link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f59cffc7..da422c00 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Then get the JS files following the instructions below. <%= javascript_include_tag "translations", skip_pipeline: true %> ``` -**There are two ways to get `translations.js` (For Rails app without [Asset Pipeline]).** +**There are two ways to get `translations.js` (For Rails app without Asset Pipeline).** 1. This `translations.js` file can be automatically generated by the `I18n::JS::Middleware`. Just add `config.middleware.use I18n::JS::Middleware` to your `config/application.rb` file. From becaa235a5f69ed058236ee43d085f14d4c06240 Mon Sep 17 00:00:00 2001 From: Sage Ross Date: Wed, 21 Feb 2018 10:15:17 -0800 Subject: [PATCH 340/466] Allow i18n 1.x in gemspec The i18n gem is now at 1.0; the only breaking change is removal of support for Ruby 1.9. Resolves #491 --- i18n-js.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n-js.gemspec b/i18n-js.gemspec index 7571ff96..9c13fe2f 100644 --- a/i18n-js.gemspec +++ b/i18n-js.gemspec @@ -18,7 +18,7 @@ Gem::Specification.new do |s| s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] - s.add_dependency "i18n", "~> 0.6", ">= 0.6.6" + s.add_dependency "i18n", ">= 0.6.6", "< 2" s.add_development_dependency "appraisal", "~> 2.0" s.add_development_dependency "rspec", "~> 3.0" From 307dc10b6ec7c04448c1828b5a7620d0933fb2b8 Mon Sep 17 00:00:00 2001 From: Sage Ross Date: Wed, 21 Feb 2018 10:29:43 -0800 Subject: [PATCH 341/466] Add gemfile for i18n 1.0 --- Appraisals | 4 ++++ gemfiles/i18n_1_0.gemfile | 7 +++++++ 2 files changed, 11 insertions(+) create mode 100644 gemfiles/i18n_1_0.gemfile diff --git a/Appraisals b/Appraisals index bee2080d..b7ab376e 100644 --- a/Appraisals +++ b/Appraisals @@ -14,3 +14,7 @@ end appraise "i18n_0_9" do gem "i18n", "~> 0.9.0" end + +appraise "i18n_1_0" do + gem "i18n", "~> 1.0" +end diff --git a/gemfiles/i18n_1_0.gemfile b/gemfiles/i18n_1_0.gemfile new file mode 100644 index 00000000..aac418d4 --- /dev/null +++ b/gemfiles/i18n_1_0.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "i18n", "~> 1.0" + +gemspec :path => "../" From 8d4f1a5ae896b1d64b56910078f973b80bd8fa6c Mon Sep 17 00:00:00 2001 From: Sage Ross Date: Wed, 21 Feb 2018 10:36:39 -0800 Subject: [PATCH 342/466] Formatting updates from running npm and appraisal commands When I ran `npm install` and `bundle exec appraisal rake spec`, these benign changes were made automatically. --- gemfiles/i18n_0_6.gemfile | 2 +- gemfiles/i18n_0_7.gemfile | 2 +- gemfiles/i18n_0_8.gemfile | 2 +- gemfiles/i18n_0_9.gemfile | 2 +- gemfiles/i18n_1_0.gemfile | 2 +- package.json | 4 +++- 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/gemfiles/i18n_0_6.gemfile b/gemfiles/i18n_0_6.gemfile index 74563787..77bce784 100644 --- a/gemfiles/i18n_0_6.gemfile +++ b/gemfiles/i18n_0_6.gemfile @@ -4,4 +4,4 @@ source "https://rubygems.org" gem "i18n", "~> 0.6.0", ">= 0.6.6" -gemspec :path => "../" +gemspec path: "../" diff --git a/gemfiles/i18n_0_7.gemfile b/gemfiles/i18n_0_7.gemfile index 2d43217f..d1447838 100644 --- a/gemfiles/i18n_0_7.gemfile +++ b/gemfiles/i18n_0_7.gemfile @@ -4,4 +4,4 @@ source "https://rubygems.org" gem "i18n", "~> 0.7.0" -gemspec :path => "../" +gemspec path: "../" diff --git a/gemfiles/i18n_0_8.gemfile b/gemfiles/i18n_0_8.gemfile index b600b23a..eb4461c2 100644 --- a/gemfiles/i18n_0_8.gemfile +++ b/gemfiles/i18n_0_8.gemfile @@ -4,4 +4,4 @@ source "https://rubygems.org" gem "i18n", "~> 0.8.0" -gemspec :path => "../" +gemspec path: "../" diff --git a/gemfiles/i18n_0_9.gemfile b/gemfiles/i18n_0_9.gemfile index ae05ce33..771f77bb 100644 --- a/gemfiles/i18n_0_9.gemfile +++ b/gemfiles/i18n_0_9.gemfile @@ -4,4 +4,4 @@ source "https://rubygems.org" gem "i18n", "~> 0.9.0" -gemspec :path => "../" +gemspec path: "../" diff --git a/gemfiles/i18n_1_0.gemfile b/gemfiles/i18n_1_0.gemfile index aac418d4..dc06eef6 100644 --- a/gemfiles/i18n_1_0.gemfile +++ b/gemfiles/i18n_1_0.gemfile @@ -4,4 +4,4 @@ source "https://rubygems.org" gem "i18n", "~> 1.0" -gemspec :path => "../" +gemspec path: "../" diff --git a/package.json b/package.json index 7597f7e0..c64b2375 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,9 @@ "description": "A javascript library similar to Ruby on Rails i18n gem", "author": "Nando Vieira", "license": "MIT", - "keywords": ["i18n"], + "keywords": [ + "i18n" + ], "devDependencies": { "jasmine-node": "^1.14.5" }, From 236f4dac9bb8de9c1dd99aee7563bd9aaa1da358 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 26 Feb 2018 10:42:44 +0800 Subject: [PATCH 343/466] * Test against I18n 1.0.x on Travis --- .gitignore | 1 + .travis.yml | 1 + Appraisals | 2 +- gemfiles/i18n_1_0.gemfile | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 533e2d1f..adf5259b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules Gemfile.lock .idea/ gemfiles/*.gemfile.lock +gemfiles/.bundle diff --git a/.travis.yml b/.travis.yml index 9fcee5eb..15802a54 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,6 +24,7 @@ gemfile: - gemfiles/i18n_0_7.gemfile - gemfiles/i18n_0_8.gemfile - gemfiles/i18n_0_9.gemfile + - gemfiles/i18n_1_0.gemfile matrix: fast_finish: true allow_failures: diff --git a/Appraisals b/Appraisals index b7ab376e..cfef0ee9 100644 --- a/Appraisals +++ b/Appraisals @@ -16,5 +16,5 @@ appraise "i18n_0_9" do end appraise "i18n_1_0" do - gem "i18n", "~> 1.0" + gem "i18n", "~> 1.0.0" end diff --git a/gemfiles/i18n_1_0.gemfile b/gemfiles/i18n_1_0.gemfile index dc06eef6..0b6bd01d 100644 --- a/gemfiles/i18n_1_0.gemfile +++ b/gemfiles/i18n_1_0.gemfile @@ -2,6 +2,6 @@ source "https://rubygems.org" -gem "i18n", "~> 1.0" +gem "i18n", "~> 1.0.0" gemspec path: "../" From 01b9a35101c749b99f7a49996e520bf3964277d6 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 26 Feb 2018 11:42:57 +0800 Subject: [PATCH 344/466] ^ Release 3.0.5 --- CHANGELOG.md | 13 +++++++++++-- lib/i18n/js/version.rb | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d501c6cc..a4e440a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.0.5] - 2018-02-26 + +### Changed + +- [Ruby] Support `I18n` `1.0.x` + (PR: https://github.com/fnando/i18n-js/pull/492) + + ## [3.0.4] - 2018-01-26 ### Fixed @@ -278,8 +286,9 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.4...HEAD -[3.0.3]: https://github.com/fnando/i18n-js/compare/v3.0.3...v3.0.4 +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.5...HEAD +[3.0.5]: https://github.com/fnando/i18n-js/compare/v3.0.4...v3.0.5 +[3.0.4]: https://github.com/fnando/i18n-js/compare/v3.0.3...v3.0.4 [3.0.3]: https://github.com/fnando/i18n-js/compare/v3.0.2...v3.0.3 [3.0.2]: https://github.com/fnando/i18n-js/compare/v3.0.1...v3.0.2 [3.0.1]: https://github.com/fnando/i18n-js/compare/v3.0.0...v3.0.1 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index 36c98468..2ce75db0 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -2,6 +2,6 @@ module I18n module JS - VERSION = "3.0.4" + VERSION = "3.0.5" end end From 19c728f73c3c0f8d34097136f86562c1bd487d34 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 16 Apr 2018 14:50:43 +0800 Subject: [PATCH 345/466] * Add TravisBuddy notifications in GitHub comments --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 15802a54..2c7a54c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,3 +29,6 @@ matrix: fast_finish: true allow_failures: - rvm: ruby-head +notifications: + webhooks: https://www.travisbuddy.com/ + on_success: never From db3cdf82aaa33a0be558214d82e89a27204380a4 Mon Sep 17 00:00:00 2001 From: Maciej Bielecki Date: Tue, 29 May 2018 14:12:13 +0200 Subject: [PATCH 346/466] Add config file as a dependency of filtered translations --- lib/i18n/js/engine.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/i18n/js/engine.rb b/lib/i18n/js/engine.rb index da1e173e..4ba37095 100644 --- a/lib/i18n/js/engine.rb +++ b/lib/i18n/js/engine.rb @@ -60,6 +60,7 @@ def render(context, empty_hash_wtf) def self.run(filename, source, context) if context.logical_path == "i18n/filtered" ::I18n.load_path.each { |path| context.depend_on(File.expand_path(path)) } + context.depend_on(File.expand_path(I18n::JS.config_file_path)) end source From 4141ae3770d413f2f129e92c020f5ff2f5403ec3 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 30 May 2018 09:45:19 +0800 Subject: [PATCH 347/466] $ Rename a private method --- lib/i18n/js.rb | 7 ++++--- spec/ruby/i18n/js_spec.rb | 2 +- spec/spec_helper.rb | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/i18n/js.rb b/lib/i18n/js.rb index b14a178e..beea86d8 100644 --- a/lib/i18n/js.rb +++ b/lib/i18n/js.rb @@ -89,7 +89,7 @@ def self.filtered_translations end def self.translation_segments - if config? && config[:translations] + if config_file_exists? && config[:translations] configured_segments else [Segment.new("#{DEFAULT_EXPORT_DIR_PATH}/translations.js", translations)] @@ -99,7 +99,7 @@ def self.translation_segments # Load configuration file for partial exporting and # custom output directory def self.config - if config? + if config_file_exists? erb_result_from_yaml_file = ERB.new(File.read(config_file_path)).result Private::HashWithSymbolKeys.new( (::YAML.load(erb_result_from_yaml_file) || {}) @@ -109,8 +109,9 @@ def self.config end.freeze end + # @api private # Check if configuration file exist - def self.config? + def self.config_file_exists? File.file? config_file_path end diff --git a/spec/ruby/i18n/js_spec.rb b/spec/ruby/i18n/js_spec.rb index 60205d43..46ff3e2a 100644 --- a/spec/ruby/i18n/js_spec.rb +++ b/spec/ruby/i18n/js_spec.rb @@ -398,7 +398,7 @@ end it "sets empty hash as configuration when no file is found" do - I18n::JS.config?.should eql(false) + I18n::JS.config_file_exists?.should eql(false) I18n::JS.config.should eql({}) end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 14023925..1495d9cd 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -8,7 +8,7 @@ module Helpers def set_config(path) config_file_path = File.dirname(__FILE__) + "/fixtures/#{path}" allow(I18n::JS).to receive_messages( - :config? => true, + :config_file_exists? => true, :config_file_path => config_file_path, ) end From 7f91499d601f942fc9acbdd923022d8e0e5885b5 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 30 May 2018 09:57:37 +0800 Subject: [PATCH 348/466] ~ Add more code comments --- lib/i18n/js/engine.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/i18n/js/engine.rb b/lib/i18n/js/engine.rb index 4ba37095..e5d0971f 100644 --- a/lib/i18n/js/engine.rb +++ b/lib/i18n/js/engine.rb @@ -60,6 +60,12 @@ def render(context, empty_hash_wtf) def self.run(filename, source, context) if context.logical_path == "i18n/filtered" ::I18n.load_path.each { |path| context.depend_on(File.expand_path(path)) } + + # Absolute path is required or + # Sprockets assumes it's a logical path + # + # Also it's fine to depend on an absent file + # Sprocket will ignore paths of absent files context.depend_on(File.expand_path(I18n::JS.config_file_path)) end From c035e8bec1233022dca0048e81aa6527b34b4edc Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 30 May 2018 09:59:06 +0800 Subject: [PATCH 349/466] ^ Release 3.0.6 --- CHANGELOG.md | 11 ++++++++++- lib/i18n/js/version.rb | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4e440a2..d922e3a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.0.6] - 2018-05-30 + +### Fixed + +- [Ruby] Make JS `i18n/filtered` depends on i18n-js config too + (PR: https://github.com/fnando/i18n-js/pull/497) + + ## [3.0.5] - 2018-02-26 ### Changed @@ -286,7 +294,8 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.5...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.6...HEAD +[3.0.6]: https://github.com/fnando/i18n-js/compare/v3.0.5...v3.0.6 [3.0.5]: https://github.com/fnando/i18n-js/compare/v3.0.4...v3.0.5 [3.0.4]: https://github.com/fnando/i18n-js/compare/v3.0.3...v3.0.4 [3.0.3]: https://github.com/fnando/i18n-js/compare/v3.0.2...v3.0.3 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index 2ce75db0..28120161 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -2,6 +2,6 @@ module I18n module JS - VERSION = "3.0.5" + VERSION = "3.0.6" end end From 3d3dfd7ee9ab5941ec1da56310e3b5df7ca1e828 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 30 May 2018 17:10:44 +0800 Subject: [PATCH 350/466] ! Fix error raised when config file absent --- lib/i18n/js/engine.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/i18n/js/engine.rb b/lib/i18n/js/engine.rb index e5d0971f..4c0e201d 100644 --- a/lib/i18n/js/engine.rb +++ b/lib/i18n/js/engine.rb @@ -64,9 +64,11 @@ def self.run(filename, source, context) # Absolute path is required or # Sprockets assumes it's a logical path # - # Also it's fine to depend on an absent file - # Sprocket will ignore paths of absent files - context.depend_on(File.expand_path(I18n::JS.config_file_path)) + # Calling `depend on` with an absent file + # will invoke `resolve` and will throw an error in the end + if I18n::JS.config_file_exists? + context.depend_on(File.expand_path(I18n::JS.config_file_path)) + end end source From 5249d014df0f450440854b4f9aeafb60f22481ac Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 30 May 2018 17:12:16 +0800 Subject: [PATCH 351/466] ^ Release 3.0.7 --- CHANGELOG.md | 10 +++++++++- lib/i18n/js/version.rb | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d922e3a4..b2afe384 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.0.7] - 2018-05-30 + +### Fixed + +- [Ruby] Fix new bug occuring when config file is absent + + ## [3.0.6] - 2018-05-30 ### Fixed @@ -294,7 +301,8 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.6...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.7...HEAD +[3.0.7]: https://github.com/fnando/i18n-js/compare/v3.0.6...v3.0.7 [3.0.6]: https://github.com/fnando/i18n-js/compare/v3.0.5...v3.0.6 [3.0.5]: https://github.com/fnando/i18n-js/compare/v3.0.4...v3.0.5 [3.0.4]: https://github.com/fnando/i18n-js/compare/v3.0.3...v3.0.4 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index 28120161..9106b044 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -2,6 +2,6 @@ module I18n module JS - VERSION = "3.0.6" + VERSION = "3.0.7" end end From db6d797329456b8c682deae9512c7dc405b49a75 Mon Sep 17 00:00:00 2001 From: Chafik Date: Wed, 6 Jun 2018 10:56:40 +0200 Subject: [PATCH 352/466] Interpolate translation array (#498) * Interpolate translation array If the translation is an `Array`, the dynamic values were not interpolated. * Add unit test * Add shim for Array.map * Use ES5 syntax in map --- app/assets/javascripts/i18n.js | 4 ++ app/assets/javascripts/i18n/shims.js | 89 ++++++++++++++++++++++++++++ spec/js/translate.spec.js | 9 +++ spec/js/translations.js | 6 ++ 4 files changed, 108 insertions(+) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index b6f14687..87fb9a12 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -598,6 +598,10 @@ if (typeof(translation) === "string") { translation = this.interpolate(translation, options); + } else if (isArray(translation)) { + translation = translation.map(function(t) { + return this.interpolate(t, options); + }, this); } else if (isObject(translation) && isSet(options.count)) { translation = this.pluralize(options.count, scope, options); } diff --git a/app/assets/javascripts/i18n/shims.js b/app/assets/javascripts/i18n/shims.js index 98151b2b..05df41da 100644 --- a/app/assets/javascripts/i18n/shims.js +++ b/app/assets/javascripts/i18n/shims.js @@ -117,3 +117,92 @@ if (!Array.prototype.some) return false; }; } + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map +if (!Array.prototype.map) { + + Array.prototype.map = function(callback/*, thisArg*/) { + + var T, A, k; + + if (this == null) { + throw new TypeError('this is null or not defined'); + } + + // 1. Let O be the result of calling ToObject passing the |this| + // value as the argument. + var O = Object(this); + + // 2. Let lenValue be the result of calling the Get internal + // method of O with the argument "length". + // 3. Let len be ToUint32(lenValue). + var len = O.length >>> 0; + + // 4. If IsCallable(callback) is false, throw a TypeError exception. + // See: http://es5.github.com/#x9.11 + if (typeof callback !== 'function') { + throw new TypeError(callback + ' is not a function'); + } + + // 5. If thisArg was supplied, let T be thisArg; else let T be undefined. + if (arguments.length > 1) { + T = arguments[1]; + } + + // 6. Let A be a new array created as if by the expression new Array(len) + // where Array is the standard built-in constructor with that name and + // len is the value of len. + A = new Array(len); + + // 7. Let k be 0 + k = 0; + + // 8. Repeat, while k < len + while (k < len) { + + var kValue, mappedValue; + + // a. Let Pk be ToString(k). + // This is implicit for LHS operands of the in operator + // b. Let kPresent be the result of calling the HasProperty internal + // method of O with argument Pk. + // This step can be combined with c + // c. If kPresent is true, then + if (k in O) { + + // i. Let kValue be the result of calling the Get internal + // method of O with argument Pk. + kValue = O[k]; + + // ii. Let mappedValue be the result of calling the Call internal + // method of callback with T as the this value and argument + // list containing kValue, k, and O. + mappedValue = callback.call(T, kValue, k, O); + + // iii. Call the DefineOwnProperty internal method of A with arguments + // Pk, Property Descriptor + // { Value: mappedValue, + // Writable: true, + // Enumerable: true, + // Configurable: true }, + // and false. + + // In browsers that support Object.defineProperty, use the following: + // Object.defineProperty(A, k, { + // value: mappedValue, + // writable: true, + // enumerable: true, + // configurable: true + // }); + + // For best browser support, use the following: + A[k] = mappedValue; + } + // d. Increase k by 1. + k++; + } + + // 9. return A + return A; + }; +} diff --git a/spec/js/translate.spec.js b/spec/js/translate.spec.js index 81f1a83a..5ca25548 100644 --- a/spec/js/translate.spec.js +++ b/spec/js/translate.spec.js @@ -256,4 +256,13 @@ describe("Translate", function(){ it("accepts the scope as an array using a base scope", function(){ expect(I18n.t(["stranger"], {scope: "greetings"})).toEqual("Hello stranger!"); }); + + it("returns an array with values interpolated", function(){ + var options = {value: 314}; + expect(I18n.t("arrayWithParams", options)).toEqual([ + `An item with a param of ${options.value}`, + `Another item with a param of ${options.value}`, + `A last item with a param of ${options.value}`, + ]); + }); }); diff --git a/spec/js/translations.js b/spec/js/translations.js index 7517ff17..96e586fc 100644 --- a/spec/js/translations.js +++ b/spec/js/translations.js @@ -58,6 +58,12 @@ var DEBUG = false; } } } + + , arrayWithParams: [ + "An item with a param of {{value}}", + "Another item with a param of {{value}}", + "A last item with a param of {{value}}", + ] }; Translations["en-US"] = { From cb073a007d1b2bfec5ac503a83b602eea83d9990 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 6 Jun 2018 17:07:00 +0800 Subject: [PATCH 353/466] ^ Release 3.0.8 --- CHANGELOG.md | 11 ++++++++++- lib/i18n/js/version.rb | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2afe384..379c3fdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.0.8] - 2018-06-06 + +### Changed + +- [JS] Interpolate translation array too + (PR: https://github.com/fnando/i18n-js/pull/498) + + ## [3.0.7] - 2018-05-30 ### Fixed @@ -301,7 +309,8 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.7...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.8...HEAD +[3.0.8]: https://github.com/fnando/i18n-js/compare/v3.0.7...v3.0.8 [3.0.7]: https://github.com/fnando/i18n-js/compare/v3.0.6...v3.0.7 [3.0.6]: https://github.com/fnando/i18n-js/compare/v3.0.5...v3.0.6 [3.0.5]: https://github.com/fnando/i18n-js/compare/v3.0.4...v3.0.5 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index 9106b044..a2a71880 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -2,6 +2,6 @@ module I18n module JS - VERSION = "3.0.7" + VERSION = "3.0.8" end end From 9dd9e9ef1e19fd3ca052fffedbf93a5f79ff8852 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 6 Jun 2018 17:08:55 +0800 Subject: [PATCH 354/466] * Release 3.0.8 on npm too --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c64b2375..685c1cfa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "i18n-js", - "version": "3.0.3", + "version": "3.0.8", "description": "A javascript library similar to Ruby on Rails i18n gem", "author": "Nando Vieira", "license": "MIT", From a491da259d8713e8d3eda4103ca4dd8d4ac4eefc Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 21 Jun 2018 11:04:21 +0800 Subject: [PATCH 355/466] $ Use ES5 way to write spec --- spec/js/translate.spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/js/translate.spec.js b/spec/js/translate.spec.js index 5ca25548..c5663239 100644 --- a/spec/js/translate.spec.js +++ b/spec/js/translate.spec.js @@ -260,9 +260,9 @@ describe("Translate", function(){ it("returns an array with values interpolated", function(){ var options = {value: 314}; expect(I18n.t("arrayWithParams", options)).toEqual([ - `An item with a param of ${options.value}`, - `Another item with a param of ${options.value}`, - `A last item with a param of ${options.value}`, + "An item with a param of " + options.value, + "Another item with a param of " + options.value, + "A last item with a param of " + options.value ]); }); }); From a8cabcad66576307e2c78e4d8a076f6695a214ea Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 21 Jun 2018 11:22:05 +0800 Subject: [PATCH 356/466] $ Remove unused vars --- app/assets/javascripts/i18n.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 87fb9a12..5bf77444 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -388,7 +388,6 @@ options = options || {} var locales = this.locales.get(options.locale).slice() - , requestedLocale = locales[0] , locale , scopes , fullScope @@ -447,7 +446,6 @@ I18n.pluralizationLookup = function(count, scope, options) { options = options || {} var locales = this.locales.get(options.locale).slice() - , requestedLocale = locales[0] , locale , scopes , translations From d3ec4c2f9bd7d187157c0d30487afa85ae0db9dd Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 21 Jun 2018 11:23:58 +0800 Subject: [PATCH 357/466] ! Fix translation array interpolation for array with null --- app/assets/javascripts/i18n.js | 4 ++++ spec/js/translate.spec.js | 1 + spec/js/translations.js | 3 ++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 5bf77444..417a23c2 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -609,6 +609,10 @@ // This function interpolates the all variables in the given message. I18n.interpolate = function(message, options) { + if (message === null) { + return message; + } + options = options || {} var matches = message.match(this.placeholder) , placeholder diff --git a/spec/js/translate.spec.js b/spec/js/translate.spec.js index c5663239..9ad03757 100644 --- a/spec/js/translate.spec.js +++ b/spec/js/translate.spec.js @@ -260,6 +260,7 @@ describe("Translate", function(){ it("returns an array with values interpolated", function(){ var options = {value: 314}; expect(I18n.t("arrayWithParams", options)).toEqual([ + null, "An item with a param of " + options.value, "Another item with a param of " + options.value, "A last item with a param of " + options.value diff --git a/spec/js/translations.js b/spec/js/translations.js index 96e586fc..3e649337 100644 --- a/spec/js/translations.js +++ b/spec/js/translations.js @@ -60,9 +60,10 @@ var DEBUG = false; } , arrayWithParams: [ + null, "An item with a param of {{value}}", "Another item with a param of {{value}}", - "A last item with a param of {{value}}", + "A last item with a param of {{value}}" ] }; From 8e0c302f19445e72b81893301eab639f3453d4a6 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 21 Jun 2018 11:25:56 +0800 Subject: [PATCH 358/466] ^ Release 3.0.9 --- CHANGELOG.md | 10 +++++++++- lib/i18n/js/version.rb | 2 +- package.json | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 379c3fdc..11624323 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.0.9] - 2018-06-21 + +### Fixed + +- [JS] Fix translation array interpolation for array with null + + ## [3.0.8] - 2018-06-06 ### Changed @@ -309,7 +316,8 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.8...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.9...HEAD +[3.0.9]: https://github.com/fnando/i18n-js/compare/v3.0.8...v3.0.9 [3.0.8]: https://github.com/fnando/i18n-js/compare/v3.0.7...v3.0.8 [3.0.7]: https://github.com/fnando/i18n-js/compare/v3.0.6...v3.0.7 [3.0.6]: https://github.com/fnando/i18n-js/compare/v3.0.5...v3.0.6 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index a2a71880..0f55dd75 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -2,6 +2,6 @@ module I18n module JS - VERSION = "3.0.8" + VERSION = "3.0.9" end end diff --git a/package.json b/package.json index 685c1cfa..cf1f8d8a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "i18n-js", - "version": "3.0.8", + "version": "3.0.9", "description": "A javascript library similar to Ruby on Rails i18n gem", "author": "Nando Vieira", "license": "MIT", From b80a86ff0537cba84f8608bab34b225aaaf53fb1 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 21 Jun 2018 11:31:07 +0800 Subject: [PATCH 359/466] * Add a test case for translation key with null --- spec/js/translate.spec.js | 6 ++++++ spec/js/translations.js | 2 ++ 2 files changed, 8 insertions(+) diff --git a/spec/js/translate.spec.js b/spec/js/translate.spec.js index 9ad03757..2512e995 100644 --- a/spec/js/translate.spec.js +++ b/spec/js/translate.spec.js @@ -18,6 +18,12 @@ describe("Translate", function(){ expect(I18n.t("greetings")).toEqual(I18n.translations.en.greetings); }); + it("returns missing message translation for valid scope with null", function(){ + actual = I18n.t("null_key"); + expected = '[missing "en.null_key" translation]'; + expect(actual).toEqual(expected); + }); + it("returns missing message translation for invalid scope", function(){ actual = I18n.t("invalid.scope"); expected = '[missing "en.invalid.scope" translation]'; diff --git a/spec/js/translations.js b/spec/js/translations.js index 3e649337..3f2c21b5 100644 --- a/spec/js/translations.js +++ b/spec/js/translations.js @@ -65,6 +65,8 @@ var DEBUG = false; "Another item with a param of {{value}}", "A last item with a param of {{value}}" ] + + , null_key: null }; Translations["en-US"] = { From eb5baab5e4da0fd50317abf7eb6a8cd9e2a09990 Mon Sep 17 00:00:00 2001 From: "KISHIMOTO, Makoto" Date: Tue, 26 Jun 2018 15:59:30 +0900 Subject: [PATCH 360/466] Update i18n.js (#501) --- app/assets/javascripts/i18n.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 417a23c2..eed04b48 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -75,11 +75,11 @@ }; var isString = function(val) { - return typeof value == 'string' || Object.prototype.toString.call(val) === '[object String]'; + return typeof val === 'string' || Object.prototype.toString.call(val) === '[object String]'; }; var isNumber = function(val) { - return typeof val == 'number' || Object.prototype.toString.call(val) === '[object Number]'; + return typeof val === 'number' || Object.prototype.toString.call(val) === '[object Number]'; }; var isBoolean = function(val) { From 118c42270b3050a40aa280b16532d2d7a4cd1405 Mon Sep 17 00:00:00 2001 From: "KISHIMOTO, Makoto" Date: Wed, 27 Jun 2018 12:58:22 +0900 Subject: [PATCH 361/466] unnecessary empty statement removed (#502) --- app/assets/javascripts/i18n.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index eed04b48..a0f6d4f0 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -70,7 +70,7 @@ var isArray = function(val) { if (Array.isArray) { return Array.isArray(val); - }; + } return Object.prototype.toString.call(val) === '[object Array]'; }; From c8f6ff4edd726e219e4c6202d4c0de57ad0b7a70 Mon Sep 17 00:00:00 2001 From: will novak Date: Thu, 28 Jun 2018 12:07:23 -0400 Subject: [PATCH 362/466] Fix extend method when translations have null values --- app/assets/javascripts/i18n.js | 6 +++++- spec/js/extend.spec.js | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index a0f6d4f0..c1b9c37f 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -86,6 +86,10 @@ return val === true || val === false; }; + var isNull = function(val) { + return val === null; + }; + var decimalAdjust = function(type, value, exp) { // If the exp is undefined or zero... if (typeof exp === 'undefined' || +exp === 0) { @@ -117,7 +121,7 @@ var key, value; for (key in obj) if (obj.hasOwnProperty(key)) { value = obj[key]; - if (isString(value) || isNumber(value) || isBoolean(value) || isArray(value)) { + if (isString(value) || isNumber(value) || isBoolean(value) || isArray(value) || isNull(value)) { dest[key] = value; } else { if (dest[key] == null) dest[key] = {}; diff --git a/spec/js/extend.spec.js b/spec/js/extend.spec.js index f463c78d..df247cd7 100644 --- a/spec/js/extend.spec.js +++ b/spec/js/extend.spec.js @@ -60,7 +60,7 @@ describe("Extend", function () { expect(I18n.extend(obj1, obj2)).toEqual(expected); }); - it("should correctly merge string, numberic and boolean values", function() { + it("should correctly merge string, numberic, boolean, and null values", function() { var obj1 = { test1: { test2: false @@ -69,7 +69,8 @@ describe("Extend", function () { , obj2 = { test1: { test3: 23, - test4: 'abc' + test4: 'abc', + test5: null } } , expected = { @@ -77,6 +78,7 @@ describe("Extend", function () { test2: false , test3: 23 , test4: 'abc' + , test5: null } } From 6d7dd9b71bb4371a002f969c0fb0e97e470530df Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 29 Jun 2018 09:38:14 +0800 Subject: [PATCH 363/466] ^ Release 3.0.10 --- CHANGELOG.md | 13 ++++++++++++- lib/i18n/js/version.rb | 2 +- package.json | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11624323..8fdb56f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,16 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.0.10] - 2018-06-21 + +### Fixed + +- [JS] Fix extend method changing keys with `null` to empty objects + (PR: https://github.com/fnando/i18n-js/pull/503) +- [JS] Fix variable name in an internal method + (PR: https://github.com/fnando/i18n-js/pull/501) + + ## [3.0.9] - 2018-06-21 ### Fixed @@ -316,7 +326,8 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.9...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.10...HEAD +[3.0.10]: https://github.com/fnando/i18n-js/compare/v3.0.9...v3.0.10 [3.0.9]: https://github.com/fnando/i18n-js/compare/v3.0.8...v3.0.9 [3.0.8]: https://github.com/fnando/i18n-js/compare/v3.0.7...v3.0.8 [3.0.7]: https://github.com/fnando/i18n-js/compare/v3.0.6...v3.0.7 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index 0f55dd75..69ff3b68 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -2,6 +2,6 @@ module I18n module JS - VERSION = "3.0.9" + VERSION = "3.0.10" end end diff --git a/package.json b/package.json index cf1f8d8a..57c29ddc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "i18n-js", - "version": "3.0.9", + "version": "3.0.10", "description": "A javascript library similar to Ruby on Rails i18n gem", "author": "Nando Vieira", "license": "MIT", From 94dafc612ff633b47cdc01ad06ddb6fa5f06dfcf Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 29 Jun 2018 10:02:20 +0800 Subject: [PATCH 364/466] * Cache yarn & node_modules on Travis --- .travis.yml | 5 +- yarn.lock | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 yarn.lock diff --git a/.travis.yml b/.travis.yml index 2c7a54c7..a6e7f53b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,10 @@ sudo: false language: ruby cache: - - bundler + bundler: true + yarn: true + directories: + - node_modules rvm: - 2.1.10 - 2.2.8 diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..615c1af1 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,131 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +coffeescript@>=1.0.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/coffeescript/-/coffeescript-2.3.1.tgz#a25f69c251d25805c9842e57fc94bfc453ef6aed" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +gaze@~1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" + dependencies: + globule "^1.0.0" + +glob@~7.1.1: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globule@^1.0.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.1.tgz#5dffb1b191f22d20797a9369b49eab4e9839696d" + dependencies: + glob "~7.1.1" + lodash "~4.17.10" + minimatch "~3.0.2" + +growl@^1.10.2: + version "1.10.5" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +jasmine-growl-reporter@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/jasmine-growl-reporter/-/jasmine-growl-reporter-1.0.1.tgz#375306cef1fbf6357ad7913ca0358aa2285d6d39" + dependencies: + growl "^1.10.2" + +jasmine-node@^1.14.5: + version "1.15.0" + resolved "https://registry.yarnpkg.com/jasmine-node/-/jasmine-node-1.15.0.tgz#d5e9a92623c111f55e4b83ff2ab0407ddbc2a5b7" + dependencies: + coffeescript ">=1.0.1" + gaze "~1.1.2" + jasmine-growl-reporter "~1.0.1" + jasmine-reporters "~1.0.0" + mkdirp "~0.3.5" + requirejs ">=0.27.1" + underscore ">= 1.3.1" + walkdir ">= 0.0.1" + +jasmine-reporters@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/jasmine-reporters/-/jasmine-reporters-1.0.2.tgz#ab613ed5977dc7487e85b3c12f6a8ea8db2ade31" + dependencies: + mkdirp "~0.3.5" + +lodash@~4.17.10: + version "4.17.10" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" + +minimatch@^3.0.4, minimatch@~3.0.2: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" + +mkdirp@~0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +requirejs@>=0.27.1: + version "2.3.5" + resolved "https://registry.yarnpkg.com/requirejs/-/requirejs-2.3.5.tgz#617b9acbbcb336540ef4914d790323a8d4b861b0" + +"underscore@>= 1.3.1": + version "1.9.1" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961" + +"walkdir@>= 0.0.1": + version "0.0.12" + resolved "https://registry.yarnpkg.com/walkdir/-/walkdir-0.0.12.tgz#2f24f1ade64aab1e458591d4442c8868356e9281" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" From 34e70dbe6b5a11ab4b5ddbfac5d417f868203aa8 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 29 Jun 2018 10:04:42 +0800 Subject: [PATCH 365/466] * Use yarn install on Travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a6e7f53b..7c962f87 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ rvm: - ruby-head before_install: # Need to install something extra to test JS -- npm install +- yarn install # Bundler 1.16.1 and rubygems 2.7.3 got issue # https://github.com/travis-ci/travis-ci/issues/8978#issuecomment-354036443 - gem update --system From 397cc1a24f09ba7576fba5d7b5faab491c275cbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B7=B7=E6=B2=8CDM?= Date: Sun, 1 Jul 2018 23:02:45 +0800 Subject: [PATCH 366/466] The element of Array may not be "string" --- app/assets/javascripts/i18n.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index c1b9c37f..50ad0d23 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -602,7 +602,7 @@ translation = this.interpolate(translation, options); } else if (isArray(translation)) { translation = translation.map(function(t) { - return this.interpolate(t, options); + return (typeof(t) === "string" ? this.interpolate(t, options) : t); }, this); } else if (isObject(translation) && isSet(options.count)) { translation = this.pluralize(options.count, scope, options); From 683986ea450211a3c64a0b14ff9c868ae915bc2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B7=B7=E6=B2=8CDM?= Date: Thu, 5 Jul 2018 22:58:17 +0800 Subject: [PATCH 367/466] add test case --- spec/js/translate.spec.js | 4 +++- spec/js/translations.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/js/translate.spec.js b/spec/js/translate.spec.js index 2512e995..29cf4c47 100644 --- a/spec/js/translate.spec.js +++ b/spec/js/translate.spec.js @@ -269,7 +269,9 @@ describe("Translate", function(){ null, "An item with a param of " + options.value, "Another item with a param of " + options.value, - "A last item with a param of " + options.value + "A last item with a param of " + options.value, + ["An", "array", "of", "strings"], + {foo: "bar"} ]); }); }); diff --git a/spec/js/translations.js b/spec/js/translations.js index 3f2c21b5..3096ff9b 100644 --- a/spec/js/translations.js +++ b/spec/js/translations.js @@ -63,7 +63,9 @@ var DEBUG = false; null, "An item with a param of {{value}}", "Another item with a param of {{value}}", - "A last item with a param of {{value}}" + "A last item with a param of {{value}}", + ["An", "array", "of", "strings"], + {foo: "bar"} ] , null_key: null From 4070e7add941aaba6053b98a5150f8fb3c91622d Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 6 Jul 2018 13:35:54 +0800 Subject: [PATCH 368/466] ^ Release 3.0.11 --- CHANGELOG.md | 11 ++++++++++- lib/i18n/js/version.rb | 2 +- package.json | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fdb56f8..efed32f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.0.11] - 2018-07-06 + +### Fixed + +- [JS] Fix interpolation for array with non string/null elements + (PR: https://github.com/fnando/i18n-js/pull/505) + + ## [3.0.10] - 2018-06-21 ### Fixed @@ -326,7 +334,8 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.10...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.11...HEAD +[3.0.11]: https://github.com/fnando/i18n-js/compare/v3.0.10...v3.0.11 [3.0.10]: https://github.com/fnando/i18n-js/compare/v3.0.9...v3.0.10 [3.0.9]: https://github.com/fnando/i18n-js/compare/v3.0.8...v3.0.9 [3.0.8]: https://github.com/fnando/i18n-js/compare/v3.0.7...v3.0.8 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index 69ff3b68..671f1362 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -2,6 +2,6 @@ module I18n module JS - VERSION = "3.0.10" + VERSION = "3.0.11" end end diff --git a/package.json b/package.json index 57c29ddc..a346ea7c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "i18n-js", - "version": "3.0.10", + "version": "3.0.11", "description": "A javascript library similar to Ruby on Rails i18n gem", "author": "Nando Vieira", "license": "MIT", From 1674eb5ccbc107194340a0121067e5896a29e044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danilo=20B=C3=BCrger?= Date: Sat, 11 Aug 2018 18:41:29 +0200 Subject: [PATCH 369/466] Documented toTime --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index da422c00..67160ed9 100644 --- a/README.md +++ b/README.md @@ -576,14 +576,16 @@ I18n.translations["en"] = { I18n.l("date.formats.ordinal_day", "2009-09-18", { day: '18th' }); // Sep 18th ``` -If you prefer, you can use the `I18n.strftime` function to format dates. +If you prefer, you can use the `I18n.toTime` and `I18n.strftime` functions to format dates. ```javascript var date = new Date(); +I18n.toTime("date.formats.short", "2009-09-18"); +I18n.toTime("date.formats.short", date); I18n.strftime(date, "%d/%m/%Y"); ``` -The accepted formats are: +The accepted formats for `I18n.strftime` are: %a - The abbreviated weekday name (Sun) %A - The full weekday name (Sunday) From 8fd33e8dd908f40a482dcbacd008271ab035e4af Mon Sep 17 00:00:00 2001 From: Dagurmart <38987916+Dagurmart@users.noreply.github.com> Date: Wed, 3 Oct 2018 12:22:40 +0000 Subject: [PATCH 370/466] Clarify how interpolation works --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 67160ed9..956a7ad2 100644 --- a/README.md +++ b/README.md @@ -339,7 +339,7 @@ I18n.t("some.scoped.translation", {locale: "fr"}); You can also interpolate values: ```javascript -I18n.t("hello", {name: "John Doe"}); +I18n.t("greeting", {name: "John Doe"}); // greeting = "Hello %{name}" ``` You can set default values for missing scopes: ```javascript From 608d5526c6a0512cfe7d8685466d7717b768bafa Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 8 Oct 2018 10:49:25 +0800 Subject: [PATCH 371/466] ~ Add a new known issue to doc [ci skip] --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 956a7ad2..31bc0ed3 100644 --- a/README.md +++ b/README.md @@ -815,6 +815,13 @@ So ensure sprockets is loaded before this gem like moving entry of sprockets in **Note:** See issue [#404](https://github.com/fnando/i18n-js/issues/404) for more details and discussion of this issue. +### JS `I18n.toCurrency` & `I18n.toNumber` cannot handle large integers + +The above methods use `toFixed` and it only supports 53 bit integers. +Ref: http://2ality.com/2012/07/large-integers.html + +Feel free to find & discuss possible solution(s) at issue [#511](https://github.com/fnando/i18n-js/issues/511) + ## Maintainer From 6d821a3afad14820a94bd60c399308763316e7d0 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 8 Oct 2018 10:52:42 +0800 Subject: [PATCH 372/466] ~ Update example for interpolation [ci skip] --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 31bc0ed3..f51f9ac2 100644 --- a/README.md +++ b/README.md @@ -339,7 +339,12 @@ I18n.t("some.scoped.translation", {locale: "fr"}); You can also interpolate values: ```javascript -I18n.t("greeting", {name: "John Doe"}); // greeting = "Hello %{name}" +// You need the `translations` object setup first +I18n.translations["en"] = { + greeting: "Hello %{name}" +} + +I18n.t("greeting", {name: "John Doe"}); ``` You can set default values for missing scopes: ```javascript From 11796607c138f22452d035543c1530a6db5f5199 Mon Sep 17 00:00:00 2001 From: Enoch Riese Date: Sat, 13 Oct 2018 17:21:11 -0500 Subject: [PATCH 373/466] added code fix and test case to pluralize the correct scope when translation is found among defaults --- CHANGELOG.md | 62 +++++++++++++++++----------------- app/assets/javascripts/i18n.js | 6 ++-- spec/js/translate.spec.js | 7 ++++ 3 files changed, 42 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efed32f1..44bb8f85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed -- Nothing +- [JS] fix missing translation when pluralizing with default scopes ## [3.0.11] - 2018-07-06 @@ -70,7 +70,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- [Ruby] Support `I18n` `1.0.x` +- [Ruby] Support `I18n` `1.0.x` (PR: https://github.com/fnando/i18n-js/pull/492) @@ -78,7 +78,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed -- [Ruby] Fix `JS::Dependencies.using_asset_pipeline?` returning true when sprockets installed but disabled +- [Ruby] Fix `JS::Dependencies.using_asset_pipeline?` returning true when sprockets installed but disabled (PR: https://github.com/fnando/i18n-js/pull/488) @@ -86,7 +86,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed -- [Ruby] Fix extend method when translations has array values +- [Ruby] Fix extend method when translations has array values (PR: https://github.com/fnando/i18n-js/pull/487) @@ -94,10 +94,10 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- [Ruby] Avoid writing new file if a file with same content already exists +- [Ruby] Avoid writing new file if a file with same content already exists (PR: https://github.com/fnando/i18n-js/pull/473) -- [JS] Fix fallback when "3-part" locale like `zh-Hant-TW` is used - It was falling back to `zh` first instead of `zh-Hant` (see new test case added) +- [JS] Fix fallback when "3-part" locale like `zh-Hant-TW` is used + It was falling back to `zh` first instead of `zh-Hant` (see new test case added) (PR: https://github.com/fnando/i18n-js/pull/475) @@ -105,22 +105,22 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- [Ruby] Relax Rails detection code to work with alternative installation methods +- [Ruby] Relax Rails detection code to work with alternative installation methods (PR: https://github.com/fnando/i18n-js/pull/467) -- [JS] Fix fallback when "3-part" locale like `zh-Hant-TW` is used - It fallbacks to `zh` only before, now it fallbacks to `zh-Hant` +- [JS] Fix fallback when "3-part" locale like `zh-Hant-TW` is used + It fallbacks to `zh` only before, now it fallbacks to `zh-Hant` (PR: https://github.com/fnando/i18n-js/pull/465) ## [3.0.0] - 2017-04-01 -This is a fake official release, the *real* one will be `3.0.0.rc17` -And today is not April Fools' Day +This is a fake official release, the *real* one will be `3.0.0.rc17` +And today is not April Fools' Day ### Fixed -- Ends the longest Release Candidate period among all ruby gems - (v3.0.0.rc1 released at 2012-05-10) +- Ends the longest Release Candidate period among all ruby gems + (v3.0.0.rc1 released at 2012-05-10) ## [3.0.0.rc16] - 2017-03-13 @@ -143,24 +143,24 @@ And today is not April Fools' Day ### Changed -- [JS] Allow `defaultValue` to work in pluralization - (PR: https://github.com/fnando/i18n-js/pull/433) -- [Ruby] Stop validating the fallback locales against `I18n.available_locales` - This allows some locales to be used as fallback locales, but not to be generated in JS. - (PR: https://github.com/fnando/i18n-js/pull/425) -- [Ruby] Remove dependency on gem `activesupport` +- [JS] Allow `defaultValue` to work in pluralization + (PR: https://github.com/fnando/i18n-js/pull/433) +- [Ruby] Stop validating the fallback locales against `I18n.available_locales` + This allows some locales to be used as fallback locales, but not to be generated in JS. + (PR: https://github.com/fnando/i18n-js/pull/425) +- [Ruby] Remove dependency on gem `activesupport` ### Fixed -- [JS] Stop converting numeric & boolean values into objects - when merging objects with `I18n.extend` - (PR: https://github.com/fnando/i18n-js/pull/420) -- [JS] Fix I18n pluralization fallback when tree is empty - (PR: https://github.com/fnando/i18n-js/pull/435) -- [Ruby] Use old syntax to define lambda for compatibility with older Rubies - (Issue: https://github.com/fnando/i18n-js/issues/419) -- [Ruby] Fix error raised in middleware cache cleaning in parallel test - (Issue: https://github.com/fnando/i18n-js/issues/436) +- [JS] Stop converting numeric & boolean values into objects + when merging objects with `I18n.extend` + (PR: https://github.com/fnando/i18n-js/pull/420) +- [JS] Fix I18n pluralization fallback when tree is empty + (PR: https://github.com/fnando/i18n-js/pull/435) +- [Ruby] Use old syntax to define lambda for compatibility with older Rubies + (Issue: https://github.com/fnando/i18n-js/issues/419) +- [Ruby] Fix error raised in middleware cache cleaning in parallel test + (Issue: https://github.com/fnando/i18n-js/issues/436) ## [3.0.0.rc14] - 2016-08-29 @@ -168,8 +168,8 @@ And today is not April Fools' Day ### Changed - [JS] Method `I18n.extend()` behave as deep merging instead of shallow merging. (https://github.com/fnando/i18n-js/pull/416) -- [Ruby] Use object/class instead of block when registering Sprockets preprocessor (https://github.com/fnando/i18n-js/pull/418) - To ensure that your cache will expire properly based on locale file content after upgrading, +- [Ruby] Use object/class instead of block when registering Sprockets preprocessor (https://github.com/fnando/i18n-js/pull/418) + To ensure that your cache will expire properly based on locale file content after upgrading, you should run `rake assets:clobber` and/or other rake tasks that clear the asset cache once gem updated - [Ruby] Detect & support rails 5 (https://github.com/fnando/i18n-js/pull/413) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 50ad0d23..3cd4d10f 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -575,6 +575,7 @@ var translationOptions = this.createTranslationOptions(scope, options); var translation; + var usedScope = scope; var optionsWithoutDefault = this.prepareOptions(options) delete optionsWithoutDefault.defaultValue @@ -584,7 +585,8 @@ var translationFound = translationOptions.some(function(translationOption) { if (isSet(translationOption.scope)) { - translation = this.lookup(translationOption.scope, optionsWithoutDefault); + usedScope = translationOption.scope; + translation = this.lookup(usedScope, optionsWithoutDefault); } else if (isSet(translationOption.message)) { translation = lazyEvaluate(translationOption.message, scope); } @@ -605,7 +607,7 @@ return (typeof(t) === "string" ? this.interpolate(t, options) : t); }, this); } else if (isObject(translation) && isSet(options.count)) { - translation = this.pluralize(options.count, scope, options); + translation = this.pluralize(options.count, usedScope, options); } return translation; diff --git a/spec/js/translate.spec.js b/spec/js/translate.spec.js index 29cf4c47..a7148fc6 100644 --- a/spec/js/translate.spec.js +++ b/spec/js/translate.spec.js @@ -190,6 +190,13 @@ describe("Translate", function(){ actual = I18n.t("foo", options); expect(actual).toEqual("FOO"); }) + + it("pluralizes using the correct scope if translation is found within default scope", function() { + expect(I18n.translations["en"]["mailbox"]).toEqual(undefined); + actual = I18n.t("mailbox.inbox", {count: 1, defaults: [{scope: "inbox"}]}); + expected = I18n.t("inbox", {count: 1}) + expect(actual).toEqual(expected) + }) }); it("uses default value for simple translation", function(){ From 5ed79cd1ddaf1a18371c24705162cc2fe4630fd1 Mon Sep 17 00:00:00 2001 From: Enoch Riese Date: Sun, 14 Oct 2018 16:12:46 -0500 Subject: [PATCH 374/466] reverted whitespace in CHANGELOG --- CHANGELOG.md | 60 ++++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44bb8f85..c3c689fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,7 +70,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- [Ruby] Support `I18n` `1.0.x` +- [Ruby] Support `I18n` `1.0.x` (PR: https://github.com/fnando/i18n-js/pull/492) @@ -78,7 +78,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed -- [Ruby] Fix `JS::Dependencies.using_asset_pipeline?` returning true when sprockets installed but disabled +- [Ruby] Fix `JS::Dependencies.using_asset_pipeline?` returning true when sprockets installed but disabled (PR: https://github.com/fnando/i18n-js/pull/488) @@ -86,7 +86,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed -- [Ruby] Fix extend method when translations has array values +- [Ruby] Fix extend method when translations has array values (PR: https://github.com/fnando/i18n-js/pull/487) @@ -94,10 +94,10 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- [Ruby] Avoid writing new file if a file with same content already exists +- [Ruby] Avoid writing new file if a file with same content already exists (PR: https://github.com/fnando/i18n-js/pull/473) -- [JS] Fix fallback when "3-part" locale like `zh-Hant-TW` is used - It was falling back to `zh` first instead of `zh-Hant` (see new test case added) +- [JS] Fix fallback when "3-part" locale like `zh-Hant-TW` is used + It was falling back to `zh` first instead of `zh-Hant` (see new test case added) (PR: https://github.com/fnando/i18n-js/pull/475) @@ -105,22 +105,22 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- [Ruby] Relax Rails detection code to work with alternative installation methods +- [Ruby] Relax Rails detection code to work with alternative installation methods (PR: https://github.com/fnando/i18n-js/pull/467) -- [JS] Fix fallback when "3-part" locale like `zh-Hant-TW` is used - It fallbacks to `zh` only before, now it fallbacks to `zh-Hant` +- [JS] Fix fallback when "3-part" locale like `zh-Hant-TW` is used + It fallbacks to `zh` only before, now it fallbacks to `zh-Hant` (PR: https://github.com/fnando/i18n-js/pull/465) ## [3.0.0] - 2017-04-01 -This is a fake official release, the *real* one will be `3.0.0.rc17` -And today is not April Fools' Day +This is a fake official release, the *real* one will be `3.0.0.rc17` +And today is not April Fools' Day ### Fixed -- Ends the longest Release Candidate period among all ruby gems - (v3.0.0.rc1 released at 2012-05-10) +- Ends the longest Release Candidate period among all ruby gems + (v3.0.0.rc1 released at 2012-05-10) ## [3.0.0.rc16] - 2017-03-13 @@ -143,24 +143,24 @@ And today is not April Fools' Day ### Changed -- [JS] Allow `defaultValue` to work in pluralization - (PR: https://github.com/fnando/i18n-js/pull/433) -- [Ruby] Stop validating the fallback locales against `I18n.available_locales` - This allows some locales to be used as fallback locales, but not to be generated in JS. - (PR: https://github.com/fnando/i18n-js/pull/425) -- [Ruby] Remove dependency on gem `activesupport` +- [JS] Allow `defaultValue` to work in pluralization + (PR: https://github.com/fnando/i18n-js/pull/433) +- [Ruby] Stop validating the fallback locales against `I18n.available_locales` + This allows some locales to be used as fallback locales, but not to be generated in JS. + (PR: https://github.com/fnando/i18n-js/pull/425) +- [Ruby] Remove dependency on gem `activesupport` ### Fixed -- [JS] Stop converting numeric & boolean values into objects - when merging objects with `I18n.extend` - (PR: https://github.com/fnando/i18n-js/pull/420) -- [JS] Fix I18n pluralization fallback when tree is empty - (PR: https://github.com/fnando/i18n-js/pull/435) -- [Ruby] Use old syntax to define lambda for compatibility with older Rubies - (Issue: https://github.com/fnando/i18n-js/issues/419) -- [Ruby] Fix error raised in middleware cache cleaning in parallel test - (Issue: https://github.com/fnando/i18n-js/issues/436) +- [JS] Stop converting numeric & boolean values into objects + when merging objects with `I18n.extend` + (PR: https://github.com/fnando/i18n-js/pull/420) +- [JS] Fix I18n pluralization fallback when tree is empty + (PR: https://github.com/fnando/i18n-js/pull/435) +- [Ruby] Use old syntax to define lambda for compatibility with older Rubies + (Issue: https://github.com/fnando/i18n-js/issues/419) +- [Ruby] Fix error raised in middleware cache cleaning in parallel test + (Issue: https://github.com/fnando/i18n-js/issues/436) ## [3.0.0.rc14] - 2016-08-29 @@ -168,8 +168,8 @@ And today is not April Fools' Day ### Changed - [JS] Method `I18n.extend()` behave as deep merging instead of shallow merging. (https://github.com/fnando/i18n-js/pull/416) -- [Ruby] Use object/class instead of block when registering Sprockets preprocessor (https://github.com/fnando/i18n-js/pull/418) - To ensure that your cache will expire properly based on locale file content after upgrading, +- [Ruby] Use object/class instead of block when registering Sprockets preprocessor (https://github.com/fnando/i18n-js/pull/418) + To ensure that your cache will expire properly based on locale file content after upgrading, you should run `rake assets:clobber` and/or other rake tasks that clear the asset cache once gem updated - [Ruby] Detect & support rails 5 (https://github.com/fnando/i18n-js/pull/413) From 3503a84949d6f384a81863a26eb26cfa01bb400e Mon Sep 17 00:00:00 2001 From: Simon Bungartz Date: Fri, 26 Oct 2018 16:52:48 +0200 Subject: [PATCH 375/466] Add option to set different backend for I18n::JS to use. --- lib/i18n/js.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/i18n/js.rb b/lib/i18n/js.rb index beea86d8..1e4d5352 100644 --- a/lib/i18n/js.rb +++ b/lib/i18n/js.rb @@ -28,6 +28,15 @@ def self.config_file_path=(new_path) @config_file_path = new_path end + # Allow using a different backend than the one globally configured + def self.backend + @backend ||= I18n.backend + end + + def self.backend=(alternative_backend) + @backend = alternative_backend + end + # Export translations to JavaScript, considering settings # from configuration file def self.export @@ -161,7 +170,7 @@ def self.filter(translations, scopes) # Initialize and return translations def self.translations - ::I18n.backend.instance_eval do + self.backend.instance_eval do init_translations unless initialized? # When activesupport is absent, # the core extension (`#slice`) from `i18n` gem will be used instead From c60dc13cea86a33b262b34d680cb20ca4a1ecca7 Mon Sep 17 00:00:00 2001 From: Simon Bungartz Date: Fri, 26 Oct 2018 17:17:03 +0200 Subject: [PATCH 376/466] Use I18n::JS.backend for fallback module detection. --- lib/i18n/js/fallback_locales.rb | 2 +- spec/ruby/i18n/js/fallback_locales_spec.rb | 4 ++-- spec/ruby/i18n/js_spec.rb | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/i18n/js/fallback_locales.rb b/lib/i18n/js/fallback_locales.rb index 2b5eee58..79cf07c3 100644 --- a/lib/i18n/js/fallback_locales.rb +++ b/lib/i18n/js/fallback_locales.rb @@ -57,7 +57,7 @@ def default_fallbacks # # Maybe this should be fixed within I18n. def using_i18n_fallbacks_module? - I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks) + I18n::JS.backend.class.included_modules.include?(I18n::Backend::Fallbacks) end def ensure_valid_fallbacks_as_array! diff --git a/spec/ruby/i18n/js/fallback_locales_spec.rb b/spec/ruby/i18n/js/fallback_locales_spec.rb index e80ed9dd..612f79ca 100644 --- a/spec/ruby/i18n/js/fallback_locales_spec.rb +++ b/spec/ruby/i18n/js/fallback_locales_spec.rb @@ -55,10 +55,10 @@ let(:backend_with_fallbacks) { backend_class_with_fallbacks.new } before do - I18n.backend = backend_with_fallbacks + I18n::JS.backend = backend_with_fallbacks I18n.fallbacks[:fr] = [:de, :en] end - after { I18n.backend = I18n::Backend::Simple.new } + after { I18n::JS.backend = I18n::Backend::Simple.new } context "given true as fallbacks" do let(:fallbacks) { true } diff --git a/spec/ruby/i18n/js_spec.rb b/spec/ruby/i18n/js_spec.rb index 46ff3e2a..7458ccbb 100644 --- a/spec/ruby/i18n/js_spec.rb +++ b/spec/ruby/i18n/js_spec.rb @@ -313,10 +313,10 @@ let!(:old_backebad) { I18n.backend } before do - I18n.backend = backend_with_fallbacks + I18n::JS.backend = backend_with_fallbacks I18n.fallbacks[:fr] = [:de, :en] end - after { I18n.backend = old_backebad } + after { I18n::JS.backend = old_backebad } it "exports with defined locale as fallback when enabled" do set_config "js_file_per_locale_with_fallbacks_enabled.yml" From f2dbf5f1c2976998b094f5eeb6c407cdaf710f7c Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 1 Nov 2018 09:39:20 +0800 Subject: [PATCH 377/466] ^ Release 3.1.0 --- CHANGELOG.md | 18 ++++++++++++++++-- lib/i18n/js/version.rb | 2 +- package.json | 2 +- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3c689fa..d76003b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,20 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed -- [JS] fix missing translation when pluralizing with default scopes +- Nothing + + +## [3.1.0] - 2018-11-01 + +### Added + +- [Ruby] Add option to allow setting a different I18n backend + (PR: https://github.com/fnando/i18n-js/pull/519) + +### Fixed + +- [JS] Fix missing translation when pluralizing with default scopes + (PR: https://github.com/fnando/i18n-js/pull/516) ## [3.0.11] - 2018-07-06 @@ -334,7 +347,8 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.0.11...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.1.0...HEAD +[3.1.0]: https://github.com/fnando/i18n-js/compare/v3.0.11...v3.1.0 [3.0.11]: https://github.com/fnando/i18n-js/compare/v3.0.10...v3.0.11 [3.0.10]: https://github.com/fnando/i18n-js/compare/v3.0.9...v3.0.10 [3.0.9]: https://github.com/fnando/i18n-js/compare/v3.0.8...v3.0.9 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index 671f1362..093c5583 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -2,6 +2,6 @@ module I18n module JS - VERSION = "3.0.11" + VERSION = "3.1.0" end end diff --git a/package.json b/package.json index a346ea7c..71dd0514 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "i18n-js", - "version": "3.0.11", + "version": "3.1.0", "description": "A javascript library similar to Ruby on Rails i18n gem", "author": "Nando Vieira", "license": "MIT", From e9efb9ae561c797f3167eddff89b0e14fc0d9450 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 1 Nov 2018 09:43:19 +0800 Subject: [PATCH 378/466] ~ Fix recent change log entries format [ci skip] --- CHANGELOG.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d76003b6..07b38a79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,12 +22,12 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added -- [Ruby] Add option to allow setting a different I18n backend +- [Ruby] Add option to allow setting a different I18n backend (PR: https://github.com/fnando/i18n-js/pull/519) ### Fixed -- [JS] Fix missing translation when pluralizing with default scopes +- [JS] Fix missing translation when pluralizing with default scopes (PR: https://github.com/fnando/i18n-js/pull/516) @@ -35,7 +35,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed -- [JS] Fix interpolation for array with non string/null elements +- [JS] Fix interpolation for array with non string/null elements (PR: https://github.com/fnando/i18n-js/pull/505) @@ -43,9 +43,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed -- [JS] Fix extend method changing keys with `null` to empty objects +- [JS] Fix extend method changing keys with `null` to empty objects (PR: https://github.com/fnando/i18n-js/pull/503) -- [JS] Fix variable name in an internal method +- [JS] Fix variable name in an internal method (PR: https://github.com/fnando/i18n-js/pull/501) @@ -60,7 +60,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- [JS] Interpolate translation array too +- [JS] Interpolate translation array too (PR: https://github.com/fnando/i18n-js/pull/498) @@ -75,7 +75,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed -- [Ruby] Make JS `i18n/filtered` depends on i18n-js config too +- [Ruby] Make JS `i18n/filtered` depends on i18n-js config too (PR: https://github.com/fnando/i18n-js/pull/497) From e757cfb0836f33adc16b9d2ca17411bf9343c38e Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 1 Nov 2018 09:45:14 +0800 Subject: [PATCH 379/466] * Use back minor MRI versions in Travis config Too lazy to update them --- .travis.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7c962f87..d0e08334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,13 +8,11 @@ cache: directories: - node_modules rvm: - - 2.1.10 - - 2.2.8 - # Since the Travis Build Env does not recognize `2.3` alias yet - # We need to specify the version precisely - - 2.3.6 - - 2.4.3 - - 2.5.0 + - 2.1 + - 2.2 + - 2.3 + - 2.4 + - 2.5 - ruby-head before_install: # Need to install something extra to test JS From 290816a1f3cda84d7bbceab11339ccd3efc50b1b Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 1 Nov 2018 09:48:55 +0800 Subject: [PATCH 380/466] * Add coveralls --- i18n-js.gemspec | 1 + spec/spec_helper.rb | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/i18n-js.gemspec b/i18n-js.gemspec index 9c13fe2f..704adbe6 100644 --- a/i18n-js.gemspec +++ b/i18n-js.gemspec @@ -24,6 +24,7 @@ Gem::Specification.new do |s| s.add_development_dependency "rspec", "~> 3.0" s.add_development_dependency "rake", "~> 12.0" s.add_development_dependency "gem-release", ">= 0.7" + s.add_development_dependency "coveralls", ">= 0.7" s.required_ruby_version = ">= 2.1.0" end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1495d9cd..cc6be7e2 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,6 +3,13 @@ require "i18n/js" +require "rspec" + +if ENV["TRAVIS"] + require "coveralls" + Coveralls.wear! +end + module Helpers # Set the configuration as the current one def set_config(path) From a7f599b53b20728b890749fcf4bdc68d935e004a Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 1 Nov 2018 13:47:38 +0800 Subject: [PATCH 381/466] ~ Update badges in README [ci skip] --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index f51f9ac2..260fdeee 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,7 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT) [![Build Status](http://img.shields.io/travis/fnando/i18n-js.svg?style=flat-square)](https://travis-ci.org/fnando/i18n-js) -[![Dependency Status](http://img.shields.io/gemnasium/fnando/i18n-js.svg?style=flat-square)](https://gemnasium.com/fnando/i18n-js) -[![Code Climate](http://img.shields.io/codeclimate/github/fnando/i18n-js.svg?style=flat-square)](https://codeclimate.com/github/fnando/i18n-js) +[![Coverage Status](http://img.shields.io/coveralls/fnando/i18n-js.svg?style=flat-square)](https://coveralls.io/r/fnando/i18n-js) [![Gitter](https://img.shields.io/badge/gitter-join%20chat-1dce73.svg?style=flat-square)](https://gitter.im/fnando/i18n-js) From 5fbae6b39e13fc0440c618d71825f6d365a591b2 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 1 Nov 2018 14:00:30 +0800 Subject: [PATCH 382/466] ! Fix coveralls usage --- spec/spec_helper.rb | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index cc6be7e2..293baf88 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,20 @@ +require 'rubygems' +require 'bundler' + +begin + require 'simplecov' + SimpleCov.start do + add_filter 'spec' + end +rescue LoadError + # SimpleCov ain't available - continue +end + +if ENV["TRAVIS"] + require "coveralls" + Coveralls.wear!("rails") +end + require "i18n" require "json" @@ -5,11 +22,6 @@ require "rspec" -if ENV["TRAVIS"] - require "coveralls" - Coveralls.wear! -end - module Helpers # Set the configuration as the current one def set_config(path) From 89baf6060187b8d96fdbb6e43826a0eb49f01f5a Mon Sep 17 00:00:00 2001 From: Simon Bungartz Date: Wed, 7 Nov 2018 16:59:14 +0100 Subject: [PATCH 383/466] Add custom backend instructions to README --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 260fdeee..42a6ad82 100644 --- a/README.md +++ b/README.md @@ -826,6 +826,24 @@ Ref: http://2ality.com/2012/07/large-integers.html Feel free to find & discuss possible solution(s) at issue [#511](https://github.com/fnando/i18n-js/issues/511) +### Cannot be used with different backends + +If you set `I18n.backend` to something other than the default `Simple` backend, you will likely get an exception like this: + +``` +Undefined method 'initialized?' for +``` + +For now, i18n-js is only compatible with the `Simple` backend. +If you need a more sophisticated backend for your rails application, like `I18n::Backend::ActiveRecord`, you can setup i18n-js to get translations from a separate `Simple` backend, by adding the following in an initializer: + +```ruby +I18n::JS.backend = I18n::Backend::Simple.new +``` + +This means however, that only translations from your static locale files will be present in JavaScript. + +See issue [#428](https://github.com/fnando/i18n-js/issues/428) for more details and discussion of this issue. ## Maintainer From 81dbd819008f7d52cee48fae205bb5cbd9e5535e Mon Sep 17 00:00:00 2001 From: Simon Bungartz Date: Thu, 8 Nov 2018 09:42:46 +0100 Subject: [PATCH 384/466] Update README with information on reloading with I18n::JS.backend != I18n.backend --- README.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 42a6ad82..89dac452 100644 --- a/README.md +++ b/README.md @@ -826,7 +826,7 @@ Ref: http://2ality.com/2012/07/large-integers.html Feel free to find & discuss possible solution(s) at issue [#511](https://github.com/fnando/i18n-js/issues/511) -### Cannot be used with different backends +### Only works with `Simple` backend If you set `I18n.backend` to something other than the default `Simple` backend, you will likely get an exception like this: @@ -838,11 +838,33 @@ For now, i18n-js is only compatible with the `Simple` backend. If you need a more sophisticated backend for your rails application, like `I18n::Backend::ActiveRecord`, you can setup i18n-js to get translations from a separate `Simple` backend, by adding the following in an initializer: ```ruby -I18n::JS.backend = I18n::Backend::Simple.new +I18n::JS.backend = I18n.backend +I18n.backend = I18n::Backend::Chain.new(, I18n.backend) ``` +This will use your backend with the default `Simple` backend as fallback, while i18n-js only sees and uses the simple backend. This means however, that only translations from your static locale files will be present in JavaScript. +If you do cannot use a `Chain`-Backend for some reason, you can also set + +```ruby +I18n::JS.backend = I18n::Backend::Simple.new +I18n.backend = +``` + +However, the automatic reloading of translations in developement will not work in this case. +This is because Rails calls `I18n.reload!` for each request in development, but `reload!` will not be called on `I18n::JS.backend`, since it is a different object. +One option would be to patch `I18n.reload!` in an initializer: + +```ruby +module I18n + def self.reload! + I18n::JS.backend.reload! + super + end +end +``` + See issue [#428](https://github.com/fnando/i18n-js/issues/428) for more details and discussion of this issue. ## Maintainer From c4b0aafa58f36fd3a61c06cd00cd4bf43bb982aa Mon Sep 17 00:00:00 2001 From: Leon Strachan Date: Mon, 12 Nov 2018 20:17:37 +0100 Subject: [PATCH 385/466] Add json only feature for file exports/nodejs imports --- lib/i18n/js.rb | 8 ++++++++ lib/i18n/js/errors.rb | 9 +++++++++ lib/i18n/js/segment.rb | 17 +++++++++++++--- spec/fixtures/json_only.yml | 8 ++++++++ spec/ruby/i18n/js/segment_spec.rb | 32 +++++++++++++++++++++++++++++++ spec/ruby/i18n/js_spec.rb | 20 +++++++++++++++++-- 6 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 lib/i18n/js/errors.rb create mode 100644 spec/fixtures/json_only.yml diff --git a/lib/i18n/js.rb b/lib/i18n/js.rb index 1e4d5352..f398670f 100644 --- a/lib/i18n/js.rb +++ b/lib/i18n/js.rb @@ -187,6 +187,13 @@ def self.use_fallbacks? fallbacks != false end + def self.json_only + config.fetch(:json_only) do + # default value + false + end + end + def self.fallbacks config.fetch(:fallbacks) do # default value @@ -215,6 +222,7 @@ def self.extract_segment_options(options) segment_options = Private::HashWithSymbolKeys.new({ js_extend: js_extend, sort_translation_keys: sort_translation_keys?, + json_only: json_only }).freeze segment_options.merge(options.slice(*Segment::OPTIONS)) end diff --git a/lib/i18n/js/errors.rb b/lib/i18n/js/errors.rb new file mode 100644 index 00000000..7fc44598 --- /dev/null +++ b/lib/i18n/js/errors.rb @@ -0,0 +1,9 @@ +module I18n + module JS + class JsonOnlyLocaleRequiredError < StandardError + def message + 'The json_only option requires %{locale} in the file name.' + end + end + end +end diff --git a/lib/i18n/js/segment.rb b/lib/i18n/js/segment.rb index 4ae6e3b5..6c671f6f 100644 --- a/lib/i18n/js/segment.rb +++ b/lib/i18n/js/segment.rb @@ -1,11 +1,12 @@ require "i18n/js/private/hash_with_symbol_keys" +require "i18n/js/errors" module I18n module JS # Class which enscapulates a translations hash and outputs a single JSON translation file class Segment - OPTIONS = [:namespace, :pretty_print, :js_extend, :sort_translation_keys].freeze + OPTIONS = [:namespace, :pretty_print, :js_extend, :sort_translation_keys, :json_only].freeze LOCALE_INTERPOLATOR = /%\{locale\}/ attr_reader *([:file, :translations] | OPTIONS) @@ -23,6 +24,7 @@ def initialize(file, translations, options = {}) @pretty_print = !!options[:pretty_print] @js_extend = options.key?(:js_extend) ? !!options[:js_extend] : true @sort_translation_keys = options.key?(:sort_translation_keys) ? !!options[:sort_translation_keys] : true + @json_only = options.key?(:json_only) ? !!options[:json_only] : false end # Saves JSON file containing translations @@ -32,6 +34,9 @@ def save! write_file(file_for_locale(locale), @translations.slice(locale)) end else + if @json_only + raise I18n::JS::JsonOnlyLocaleRequiredError + end write_file end end @@ -53,7 +58,11 @@ def write_file(_file = @file, _translations = @translations) end def js_header - %(#{@namespace}.translations || (#{@namespace}.translations = {});\n) + if @json_only + '' + else + %(#{@namespace}.translations || (#{@namespace}.translations = {});\n) + end end def js_translations(locale, translations) @@ -63,7 +72,9 @@ def js_translations(locale, translations) end def js_translations_line(locale, translations) - if @js_extend + if @json_only + %({"#{locale}":#{translations}}) + elsif @js_extend %(#{@namespace}.translations["#{locale}"] = I18n.extend((#{@namespace}.translations["#{locale}"] || {}), #{translations});\n) else %(#{@namespace}.translations["#{locale}"] = #{translations};\n) diff --git a/spec/fixtures/json_only.yml b/spec/fixtures/json_only.yml new file mode 100644 index 00000000..0cd0ab1d --- /dev/null +++ b/spec/fixtures/json_only.yml @@ -0,0 +1,8 @@ +fallbacks: false +json_only: true + +translations: + - file: "tmp/i18n-js/json_only.%{locale}.js" + only: + - "*.date.formats.*" + - "*.number.currency.*" diff --git a/spec/ruby/i18n/js/segment_spec.rb b/spec/ruby/i18n/js/segment_spec.rb index 7baa09d6..3bc4b346 100644 --- a/spec/ruby/i18n/js/segment_spec.rb +++ b/spec/ruby/i18n/js/segment_spec.rb @@ -6,10 +6,12 @@ let(:translations){ { en: { "test" => "Test" }, fr: { "test" => "Test2" } } } let(:namespace) { "MyNamespace" } let(:pretty_print){ nil } + let(:json_only) { nil } let(:js_extend) { nil } let(:sort_translation_keys){ nil } let(:options) { { namespace: namespace, pretty_print: pretty_print, + json_only: json_only, js_extend: js_extend, sort_translation_keys: sort_translation_keys }.delete_if{|k,v| v.nil?} } subject { I18n::JS::Segment.new(file, translations, options) } @@ -59,6 +61,36 @@ end end + describe "#saving!" do + before { allow(I18n::JS).to receive(:export_i18n_js_dir_path).and_return(temp_path) } + + context "when json_only is true" do + let(:file){ "tmp/i18n-js/%{locale}.js" } + let(:json_only){ true } + it 'should output the keys as sorted' do + subject.save! + file_should_exist "en.js" + file_should_exist "fr.js" + + File.open(File.join(temp_path, "en.js")){|f| f.read}.should eql( + %Q({"en":{"test":"Test"}}) + ) + + File.open(File.join(temp_path, "fr.js")){|f| f.read}.should eql( + %Q({"fr":{"test":"Test2"}}) + ) + end + end + + context "when json_only is true without locale" do + let(:file){ "tmp/i18n-js/segment.js" } + let(:json_only){ true } + it 'should output the keys as sorted' do + expect { subject.save! }.to raise_error(I18n::JS::JsonOnlyLocaleRequiredError) + end + end + end + describe "#save!" do before { allow(I18n::JS).to receive(:export_i18n_js_dir_path).and_return(temp_path) } before { subject.save! } diff --git a/spec/ruby/i18n/js_spec.rb b/spec/ruby/i18n/js_spec.rb index 7458ccbb..1536b77c 100644 --- a/spec/ruby/i18n/js_spec.rb +++ b/spec/ruby/i18n/js_spec.rb @@ -30,14 +30,14 @@ it "exports messages using custom output path" do set_config "custom_path.yml" - I18n::JS::Segment.should_receive(:new).with("tmp/i18n-js/all.js", translations, {js_extend: true, sort_translation_keys: true}).and_call_original + I18n::JS::Segment.should_receive(:new).with("tmp/i18n-js/all.js", translations, {js_extend: true, sort_translation_keys: true, json_only: false}).and_call_original I18n::JS::Segment.any_instance.should_receive(:save!).with(no_args) I18n::JS.export end it "sets default scope to * when not specified" do set_config "no_scope.yml" - I18n::JS::Segment.should_receive(:new).with("tmp/i18n-js/no_scope.js", translations, {js_extend: true, sort_translation_keys: true}).and_call_original + I18n::JS::Segment.should_receive(:new).with("tmp/i18n-js/no_scope.js", translations, {js_extend: true, sort_translation_keys: true, json_only: false}).and_call_original I18n::JS::Segment.any_instance.should_receive(:save!).with(no_args) I18n::JS.export end @@ -109,6 +109,22 @@ ) end + it "exports as json only" do + set_config "json_only.yml" + I18n::JS.export + en_output = File.read(File.join(I18n::JS.export_i18n_js_dir_path, "json_only.en.js")) + fr_output = File.read(File.join(I18n::JS.export_i18n_js_dir_path, "json_only.fr.js")) + expect(en_output).to eq (< Date: Fri, 16 Nov 2018 15:41:16 +0800 Subject: [PATCH 386/466] * Update version requirement of I18n for CVE-2014-10077 --- i18n-js.gemspec | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/i18n-js.gemspec b/i18n-js.gemspec index 704adbe6..ad4dff6a 100644 --- a/i18n-js.gemspec +++ b/i18n-js.gemspec @@ -18,7 +18,9 @@ Gem::Specification.new do |s| s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] - s.add_dependency "i18n", ">= 0.6.6", "< 2" + # CVE-2014-10077 which is fixed for >= 0.8.0 + # https://nvd.nist.gov/vuln/detail/CVE-2014-10077 + s.add_dependency "i18n", ">= 0.8.0", "< 2" s.add_development_dependency "appraisal", "~> 2.0" s.add_development_dependency "rspec", "~> 3.0" From dc7caf963e4e416130392d8732759e15da9389e3 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 16 Nov 2018 15:45:13 +0800 Subject: [PATCH 387/466] * Test against i18n 1.1 and not < 0.8 --- .travis.yml | 3 +-- Appraisals | 12 ++++-------- gemfiles/i18n_0_6.gemfile | 7 ------- gemfiles/{i18n_0_7.gemfile => i18n_1_1.gemfile} | 2 +- 4 files changed, 6 insertions(+), 18 deletions(-) delete mode 100644 gemfiles/i18n_0_6.gemfile rename gemfiles/{i18n_0_7.gemfile => i18n_1_1.gemfile} (80%) diff --git a/.travis.yml b/.travis.yml index d0e08334..fc9d078e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,11 +21,10 @@ before_install: # https://github.com/travis-ci/travis-ci/issues/8978#issuecomment-354036443 - gem update --system gemfile: - - gemfiles/i18n_0_6.gemfile - - gemfiles/i18n_0_7.gemfile - gemfiles/i18n_0_8.gemfile - gemfiles/i18n_0_9.gemfile - gemfiles/i18n_1_0.gemfile + - gemfiles/i18n_1_1.gemfile matrix: fast_finish: true allow_failures: diff --git a/Appraisals b/Appraisals index cfef0ee9..6eda51f8 100644 --- a/Appraisals +++ b/Appraisals @@ -1,12 +1,4 @@ -appraise "i18n_0_6" do - gem "i18n", "~> 0.6.0", ">= 0.6.6" -end - -appraise "i18n_0_7" do - gem "i18n", "~> 0.7.0" -end - appraise "i18n_0_8" do gem "i18n", "~> 0.8.0" end @@ -18,3 +10,7 @@ end appraise "i18n_1_0" do gem "i18n", "~> 1.0.0" end + +appraise "i18n_1_1" do + gem "i18n", "~> 1.1.0" +end diff --git a/gemfiles/i18n_0_6.gemfile b/gemfiles/i18n_0_6.gemfile deleted file mode 100644 index 77bce784..00000000 --- a/gemfiles/i18n_0_6.gemfile +++ /dev/null @@ -1,7 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "i18n", "~> 0.6.0", ">= 0.6.6" - -gemspec path: "../" diff --git a/gemfiles/i18n_0_7.gemfile b/gemfiles/i18n_1_1.gemfile similarity index 80% rename from gemfiles/i18n_0_7.gemfile rename to gemfiles/i18n_1_1.gemfile index d1447838..e63fb87e 100644 --- a/gemfiles/i18n_0_7.gemfile +++ b/gemfiles/i18n_1_1.gemfile @@ -2,6 +2,6 @@ source "https://rubygems.org" -gem "i18n", "~> 0.7.0" +gem "i18n", "~> 1.1.0" gemspec path: "../" From e85502cbc83d5c0dd4fd37af24fff19f56ab9a2b Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 16 Nov 2018 15:45:25 +0800 Subject: [PATCH 388/466] * Ignore coverage files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index adf5259b..d8c97433 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,6 @@ pkg node_modules Gemfile.lock .idea/ +coverage/ gemfiles/*.gemfile.lock gemfiles/.bundle From 11b6f22b58c78dec05bd710ba0dc3b66d30e87f6 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 16 Nov 2018 16:04:28 +0800 Subject: [PATCH 389/466] ^ Release 3.2.0 No JS change --- CHANGELOG.md | 17 +++++++++++++++-- lib/i18n/js/version.rb | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07b38a79..c6aa62f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,18 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.2.0] - 2018-11-16 + +### Added + +- [Ruby] Add option `json_only` to generate translations in JSON + (PR: https://github.com/fnando/i18n-js/pull/524) + +### Changed + +- [Ruby] Requires `i18n` to be `>= 0.8.0` for CVE-2014-10077 + + ## [3.1.0] - 2018-11-01 ### Added @@ -347,8 +359,9 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.1.0...HEAD -[3.1.0]: https://github.com/fnando/i18n-js/compare/v3.0.11...v3.1.0 +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.2.0...HEAD +[3.2.0]: https://github.com/fnando/i18n-js/compare/v3.1.0...v3.2.0 +[3.1.0]: https://github.com/fnando/i18n-js/compare/v3.0.11...v3.1.0 [3.0.11]: https://github.com/fnando/i18n-js/compare/v3.0.10...v3.0.11 [3.0.10]: https://github.com/fnando/i18n-js/compare/v3.0.9...v3.0.10 [3.0.9]: https://github.com/fnando/i18n-js/compare/v3.0.8...v3.0.9 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index 093c5583..8d196deb 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -2,6 +2,6 @@ module I18n module JS - VERSION = "3.1.0" + VERSION = "3.2.0" end end From 35c54e4e6fe9df2f6b95da18f47f9e24314728a9 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 11 Dec 2018 17:42:15 +0800 Subject: [PATCH 390/466] * Test against i18n 1.2.x --- Appraisals | 4 ++++ gemfiles/i18n_1_2.gemfile | 7 +++++++ 2 files changed, 11 insertions(+) create mode 100644 gemfiles/i18n_1_2.gemfile diff --git a/Appraisals b/Appraisals index 6eda51f8..08ec0635 100644 --- a/Appraisals +++ b/Appraisals @@ -14,3 +14,7 @@ end appraise "i18n_1_1" do gem "i18n", "~> 1.1.0" end + +appraise "i18n_1_2" do + gem "i18n", "~> 1.2.0" +end diff --git a/gemfiles/i18n_1_2.gemfile b/gemfiles/i18n_1_2.gemfile new file mode 100644 index 00000000..836ee44e --- /dev/null +++ b/gemfiles/i18n_1_2.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "i18n", "~> 1.2.0" + +gemspec path: "../" From 0d626d86b35e922b8a0449780911aefa379fe82d Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 21 Dec 2018 09:22:03 +0800 Subject: [PATCH 391/466] * Test against i18n 1.2 & 1.3 on Travis --- .travis.yml | 2 ++ Appraisals | 4 ++++ gemfiles/i18n_1_3.gemfile | 7 +++++++ 3 files changed, 13 insertions(+) create mode 100644 gemfiles/i18n_1_3.gemfile diff --git a/.travis.yml b/.travis.yml index fc9d078e..e3560007 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,8 @@ gemfile: - gemfiles/i18n_0_9.gemfile - gemfiles/i18n_1_0.gemfile - gemfiles/i18n_1_1.gemfile + - gemfiles/i18n_1_2.gemfile + - gemfiles/i18n_1_3.gemfile matrix: fast_finish: true allow_failures: diff --git a/Appraisals b/Appraisals index 08ec0635..3e20a398 100644 --- a/Appraisals +++ b/Appraisals @@ -18,3 +18,7 @@ end appraise "i18n_1_2" do gem "i18n", "~> 1.2.0" end + +appraise "i18n_1_3" do + gem "i18n", "~> 1.3.0" +end diff --git a/gemfiles/i18n_1_3.gemfile b/gemfiles/i18n_1_3.gemfile new file mode 100644 index 00000000..a01f6d1d --- /dev/null +++ b/gemfiles/i18n_1_3.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "i18n", "~> 1.3.0" + +gemspec path: "../" From 361361d6b71262e08a4da12ae81eda723692abf1 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 21 Dec 2018 09:34:42 +0800 Subject: [PATCH 392/466] * Stop testing ruby 2.1 & 2.2 since unable to run `gem update --system` --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index e3560007..dee23e85 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,8 +8,6 @@ cache: directories: - node_modules rvm: - - 2.1 - - 2.2 - 2.3 - 2.4 - 2.5 From 57c1d83f4d9229ca0f396818eefb69540d78d285 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 21 Dec 2018 16:45:15 +0800 Subject: [PATCH 393/466] $ Stop escaping char in Regexp unnecessarily --- app/assets/javascripts/i18n.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 3cd4d10f..d9b7b8de 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -645,7 +645,7 @@ value = this.missingPlaceholder(placeholder, message, options); } - regex = new RegExp(placeholder.replace(/\{/gm, "\\{").replace(/\}/gm, "\\}")); + regex = new RegExp(placeholder.replace(/{/gm, "\\{").replace(/}/gm, "\\}")); message = message.replace(regex, value); } From 7acc9a66dd7b53475ccd488feded0d25f986329e Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 21 Dec 2018 16:49:13 +0800 Subject: [PATCH 394/466] * Use strict value comparison in JS and refactor a bit - Remove unneeded semicolon - Remove duplicate var statement --- app/assets/javascripts/i18n.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index d9b7b8de..6ba17cbe 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -471,16 +471,16 @@ if (!isObject(translations)) { break; } - if (scopes.length == 0) { + if (scopes.length === 0) { message = this.pluralizationLookupWithoutFallback(count, locale, translations); } } - if (message != null && message != undefined) { + if (typeof message !== "undefined" && message !== null) { break; } } - if (message == null || message == undefined) { + if (typeof message === "undefined" || message === null) { if (isSet(options.defaultValue)) { if (isObject(options.defaultValue)) { message = this.pluralizationLookupWithoutFallback(count, options.locale, options.defaultValue); @@ -631,8 +631,6 @@ return message; } - var value; - while (matches.length) { placeholder = matches.shift(); name = placeholder.replace(this.placeholder, "$1"); @@ -660,11 +658,11 @@ var pluralizer, message, result; result = this.pluralizationLookup(count, scope, options); - if (result.translations == undefined || result.translations == null) { + if (typeof result.translations === "undefined" || result.translations == null) { return this.missingTranslation(scope, options); } - if (result.message != undefined && result.message != null) { + if (typeof result.message !== "undefined" && result.message != null) { return this.interpolate(result.message, options); } else { @@ -676,7 +674,7 @@ // Return a missing translation message for the given parameters. I18n.missingTranslation = function(scope, options) { //guess intended string - if(this.missingBehaviour == 'guess'){ + if(this.missingBehaviour === 'guess'){ //get only the last portion of the scope var s = scope.split('.').slice(-1)[0]; //replace underscore with space && camelcase with space and lowercase letter @@ -838,7 +836,7 @@ // we have a date, so just return it. if (typeof(date) == "object") { return date; - }; + } matches = date.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2})([\.,]\d{1,3})?)?(Z|\+00:?00)?/); From c1f658e12f5ae110b91b6de44a0aab9da7a09689 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 21 Dec 2018 16:51:49 +0800 Subject: [PATCH 395/466] * Add missing semicolons after some statements, remove unused var --- app/assets/javascripts/i18n.js | 16 ++++++++-------- spec/js/translations.js | 1 - 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 6ba17cbe..648460b8 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -107,7 +107,7 @@ // Shift back value = value.toString().split('e'); return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)); - } + }; var lazyEvaluate = function(message, scope) { if (isFunction(message)) { @@ -115,7 +115,7 @@ } else { return message; } - } + }; var merge = function (dest, obj) { var key, value; @@ -389,7 +389,7 @@ // This is used internally by some functions and should not be used as an // public API. I18n.lookup = function(scope, options) { - options = options || {} + options = options || {}; var locales = this.locales.get(options.locale).slice() , locale @@ -448,7 +448,7 @@ // Lookup dedicated to pluralization I18n.pluralizationLookup = function(count, scope, options) { - options = options || {} + options = options || {}; var locales = this.locales.get(options.locale).slice() , locale , scopes @@ -570,7 +570,7 @@ // Translate the given scope with the provided options. I18n.translate = function(scope, options) { - options = options || {} + options = options || {}; var translationOptions = this.createTranslationOptions(scope, options); @@ -619,7 +619,7 @@ return message; } - options = options || {} + options = options || {}; var matches = message.match(this.placeholder) , placeholder , value @@ -655,7 +655,7 @@ // which will be retrieved from `options`. I18n.pluralize = function(count, scope, options) { options = this.prepareOptions({count: String(count)}, options) - var pluralizer, message, result; + var pluralizer, result; result = this.pluralizationLookup(count, scope, options); if (typeof result.translations === "undefined" || result.translations == null) { @@ -1034,7 +1034,7 @@ }; I18n.getFullScope = function(scope, options) { - options = options || {} + options = options || {}; // Deal with the scope as an array. if (isArray(scope)) { diff --git a/spec/js/translations.js b/spec/js/translations.js index 3096ff9b..d7a1be62 100644 --- a/spec/js/translations.js +++ b/spec/js/translations.js @@ -1,4 +1,3 @@ -var DEBUG = false; ;(function(){ var generator = function() { From 01272ae545c1b77300352154ff6ec3bb3c8fc512 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 2 Jan 2019 10:56:57 +0800 Subject: [PATCH 396/466] * Test against ruby 2.6 & I18n 1.4 --- .travis.yml | 2 ++ Appraisals | 4 ++++ gemfiles/i18n_1_4.gemfile | 7 +++++++ 3 files changed, 13 insertions(+) create mode 100644 gemfiles/i18n_1_4.gemfile diff --git a/.travis.yml b/.travis.yml index dee23e85..b71aecb0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ rvm: - 2.3 - 2.4 - 2.5 + - 2.6 - ruby-head before_install: # Need to install something extra to test JS @@ -25,6 +26,7 @@ gemfile: - gemfiles/i18n_1_1.gemfile - gemfiles/i18n_1_2.gemfile - gemfiles/i18n_1_3.gemfile + - gemfiles/i18n_1_4.gemfile matrix: fast_finish: true allow_failures: diff --git a/Appraisals b/Appraisals index 3e20a398..2cc05f59 100644 --- a/Appraisals +++ b/Appraisals @@ -22,3 +22,7 @@ end appraise "i18n_1_3" do gem "i18n", "~> 1.3.0" end + +appraise "i18n_1_4" do + gem "i18n", "~> 1.4.0" +end diff --git a/gemfiles/i18n_1_4.gemfile b/gemfiles/i18n_1_4.gemfile new file mode 100644 index 00000000..2ee82d80 --- /dev/null +++ b/gemfiles/i18n_1_4.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "i18n", "~> 1.4.0" + +gemspec path: "../" From f8ecc908e7b40222715c19a796c99b364e8319e8 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 7 Jan 2019 09:13:35 +0800 Subject: [PATCH 397/466] * Test against I18n 1.5 --- .travis.yml | 1 + Appraisals | 4 ++++ gemfiles/i18n_1_5.gemfile | 7 +++++++ 3 files changed, 12 insertions(+) create mode 100644 gemfiles/i18n_1_5.gemfile diff --git a/.travis.yml b/.travis.yml index b71aecb0..979ff2aa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,6 +27,7 @@ gemfile: - gemfiles/i18n_1_2.gemfile - gemfiles/i18n_1_3.gemfile - gemfiles/i18n_1_4.gemfile + - gemfiles/i18n_1_5.gemfile matrix: fast_finish: true allow_failures: diff --git a/Appraisals b/Appraisals index 2cc05f59..e48a9a9c 100644 --- a/Appraisals +++ b/Appraisals @@ -26,3 +26,7 @@ end appraise "i18n_1_4" do gem "i18n", "~> 1.4.0" end + +appraise "i18n_1_5" do + gem "i18n", "~> 1.5.1" +end diff --git a/gemfiles/i18n_1_5.gemfile b/gemfiles/i18n_1_5.gemfile new file mode 100644 index 00000000..f33f1985 --- /dev/null +++ b/gemfiles/i18n_1_5.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "i18n", "~> 1.5.1" + +gemspec path: "../" From b74036b1ba57b89d98271889559fe1004e587bb4 Mon Sep 17 00:00:00 2001 From: Johnny Shields Date: Thu, 17 Jan 2019 20:03:28 +0900 Subject: [PATCH 398/466] Loosen gem dependency of i18n gem This prevents using this gem on Rails 3. The CVE itself has suitable workarounds and users themselves should be responsible for managing these. It's not the job of a secondary gem like I18n JS to protect users from it. --- i18n-js.gemspec | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/i18n-js.gemspec b/i18n-js.gemspec index ad4dff6a..d22ea5c1 100644 --- a/i18n-js.gemspec +++ b/i18n-js.gemspec @@ -18,9 +18,7 @@ Gem::Specification.new do |s| s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] - # CVE-2014-10077 which is fixed for >= 0.8.0 - # https://nvd.nist.gov/vuln/detail/CVE-2014-10077 - s.add_dependency "i18n", ">= 0.8.0", "< 2" + s.add_dependency "i18n" s.add_development_dependency "appraisal", "~> 2.0" s.add_development_dependency "rspec", "~> 3.0" From c38051276bf9c3c40cd897230b2196e94d24b232 Mon Sep 17 00:00:00 2001 From: johnnyshields Date: Thu, 17 Jan 2019 20:19:42 +0900 Subject: [PATCH 399/466] Version/test change --- .travis.yml | 4 ++++ CHANGELOG.md | 2 +- gemfiles/i18n_0_6.gemfile | 7 +++++++ gemfiles/i18n_0_7.gemfile | 7 +++++++ 4 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 gemfiles/i18n_0_6.gemfile create mode 100644 gemfiles/i18n_0_7.gemfile diff --git a/.travis.yml b/.travis.yml index 979ff2aa..809c4266 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,8 @@ cache: directories: - node_modules rvm: + - 2.1 + - 2.2 - 2.3 - 2.4 - 2.5 @@ -20,6 +22,8 @@ before_install: # https://github.com/travis-ci/travis-ci/issues/8978#issuecomment-354036443 - gem update --system gemfile: + - gemfiles/i18n_0_6.gemfile + - gemfiles/i18n_0_7.gemfile - gemfiles/i18n_0_8.gemfile - gemfiles/i18n_0_9.gemfile - gemfiles/i18n_1_0.gemfile diff --git a/CHANGELOG.md b/CHANGELOG.md index c6aa62f0..6e7b8077 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed -- Nothing +- [Ruby] Rollback `i18n` gem version requirement `>= 0.8.0` ## [3.2.0] - 2018-11-16 diff --git a/gemfiles/i18n_0_6.gemfile b/gemfiles/i18n_0_6.gemfile new file mode 100644 index 00000000..118efcce --- /dev/null +++ b/gemfiles/i18n_0_6.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "i18n", "~> 0.6.0" + +gemspec path: "../" diff --git a/gemfiles/i18n_0_7.gemfile b/gemfiles/i18n_0_7.gemfile new file mode 100644 index 00000000..d1447838 --- /dev/null +++ b/gemfiles/i18n_0_7.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "i18n", "~> 0.7.0" + +gemspec path: "../" From 5e53f02277f2928be3f1cb4d8ee2251f2979b62d Mon Sep 17 00:00:00 2001 From: johnnyshields Date: Thu, 17 Jan 2019 22:56:14 +0900 Subject: [PATCH 400/466] Remove line causing failure --- .travis.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 809c4266..5dd6c88e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,11 +16,8 @@ rvm: - 2.6 - ruby-head before_install: -# Need to install something extra to test JS +# Needed to test JS - yarn install -# Bundler 1.16.1 and rubygems 2.7.3 got issue -# https://github.com/travis-ci/travis-ci/issues/8978#issuecomment-354036443 -- gem update --system gemfile: - gemfiles/i18n_0_6.gemfile - gemfiles/i18n_0_7.gemfile From 606b444522a147062e1a02054df071723fd6ad02 Mon Sep 17 00:00:00 2001 From: Johnny Shields Date: Fri, 18 Jan 2019 12:40:55 +0900 Subject: [PATCH 401/466] Update i18n-js.gemspec --- i18n-js.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n-js.gemspec b/i18n-js.gemspec index d22ea5c1..fefe0c65 100644 --- a/i18n-js.gemspec +++ b/i18n-js.gemspec @@ -18,7 +18,7 @@ Gem::Specification.new do |s| s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] - s.add_dependency "i18n" + s.add_dependency "i18n", ">= 0.6.6" s.add_development_dependency "appraisal", "~> 2.0" s.add_development_dependency "rspec", "~> 3.0" From 43af7497d95b7bb3bae323f170f1f0c2f21b1b65 Mon Sep 17 00:00:00 2001 From: Johnny Shields Date: Fri, 18 Jan 2019 12:41:43 +0900 Subject: [PATCH 402/466] Update .travis.yml --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5dd6c88e..d22132e6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,8 +8,6 @@ cache: directories: - node_modules rvm: - - 2.1 - - 2.2 - 2.3 - 2.4 - 2.5 From 621b48a89a1a12ce3feb9e7ba11d4749be348244 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 18 Jan 2019 12:01:27 +0800 Subject: [PATCH 403/466] * Switch to expect syntax in spec files --- spec/ruby/i18n/js/segment_spec.rb | 32 +++---- spec/ruby/i18n/js/utils_spec.rb | 28 +++--- spec/ruby/i18n/js_spec.rb | 142 +++++++++++++++--------------- spec/spec_helper.rb | 4 +- 4 files changed, 103 insertions(+), 103 deletions(-) diff --git a/spec/ruby/i18n/js/segment_spec.rb b/spec/ruby/i18n/js/segment_spec.rb index 3bc4b346..292a1a42 100644 --- a/spec/ruby/i18n/js/segment_spec.rb +++ b/spec/ruby/i18n/js/segment_spec.rb @@ -19,22 +19,22 @@ describe ".new" do it "should persist the file path variable" do - subject.file.should eql("tmp/i18n-js/segment.js") + expect(subject.file).to eql("tmp/i18n-js/segment.js") end it "should persist the translations variable" do - subject.translations.should eql(translations) + expect(subject.translations).to eql(translations) end it "should persist the namespace variable" do - subject.namespace.should eql("MyNamespace") + expect(subject.namespace).to eql("MyNamespace") end context "when namespace is nil" do let(:namespace){ nil } it "should default namespace to `I18n`" do - subject.namespace.should eql("I18n") + expect(subject.namespace).to eql("I18n") end end @@ -42,13 +42,13 @@ subject { I18n::JS::Segment.new(file, translations) } it "should default namespace to `I18n`" do - subject.namespace.should eql("I18n") + expect(subject.namespace).to eql("I18n") end end context "when pretty_print is nil" do it "should set pretty_print to false" do - subject.pretty_print.should be false + expect(subject.pretty_print).to be false end end @@ -56,7 +56,7 @@ let(:pretty_print){ 1 } it "should set pretty_print to true" do - subject.pretty_print.should be true + expect(subject.pretty_print).to be true end end end @@ -72,11 +72,11 @@ file_should_exist "en.js" file_should_exist "fr.js" - File.open(File.join(temp_path, "en.js")){|f| f.read}.should eql( + expect(File.open(File.join(temp_path, "en.js")){|f| f.read}).to eql( %Q({"en":{"test":"Test"}}) ) - File.open(File.join(temp_path, "fr.js")){|f| f.read}.should eql( + expect(File.open(File.join(temp_path, "fr.js")){|f| f.read}).to eql( %Q({"fr":{"test":"Test2"}}) ) end @@ -99,7 +99,7 @@ it "should write the file" do file_should_exist "segment.js" - File.open(File.join(temp_path, "segment.js")){|f| f.read}.should eql <<-EOF + expect(File.open(File.join(temp_path, "segment.js")){|f| f.read}).to eql <<-EOF MyNamespace.translations || (MyNamespace.translations = {}); MyNamespace.translations["en"] = I18n.extend((MyNamespace.translations["en"] || {}), {"test":"Test"}); MyNamespace.translations["fr"] = I18n.extend((MyNamespace.translations["fr"] || {}), {"test":"Test2"}); @@ -114,12 +114,12 @@ file_should_exist "en.js" file_should_exist "fr.js" - File.open(File.join(temp_path, "en.js")){|f| f.read}.should eql <<-EOF + expect(File.open(File.join(temp_path, "en.js")){|f| f.read}).to eql <<-EOF MyNamespace.translations || (MyNamespace.translations = {}); MyNamespace.translations["en"] = I18n.extend((MyNamespace.translations["en"] || {}), {"test":"Test"}); EOF - File.open(File.join(temp_path, "fr.js")){|f| f.read}.should eql <<-EOF + expect(File.open(File.join(temp_path, "fr.js")){|f| f.read}).to eql <<-EOF MyNamespace.translations || (MyNamespace.translations = {}); MyNamespace.translations["fr"] = I18n.extend((MyNamespace.translations["fr"] || {}), {"test":"Test2"}); EOF @@ -134,7 +134,7 @@ it 'should output the keys as sorted' do file_should_exist "segment.js" - File.open(File.join(temp_path, "segment.js")){|f| f.read}.should eql <<-EOF + expect(File.open(File.join(temp_path, "segment.js")){|f| f.read}).to eql <<-EOF MyNamespace.translations || (MyNamespace.translations = {}); MyNamespace.translations["en"] = I18n.extend((MyNamespace.translations["en"] || {}), {"a":"Test","b":"Test"}); EOF @@ -149,7 +149,7 @@ it 'should output the keys as sorted' do file_should_exist "segment.js" - File.open(File.join(temp_path, "segment.js")){|f| f.read}.should eql <<-EOF + expect(File.open(File.join(temp_path, "segment.js")){|f| f.read}).to eql <<-EOF MyNamespace.translations || (MyNamespace.translations = {}); MyNamespace.translations["en"] = {"a":"Test","b":"Test"}; EOF @@ -164,7 +164,7 @@ it 'should output the keys as sorted' do file_should_exist "segment.js" - File.open(File.join(temp_path, "segment.js")){|f| f.read}.should eql <<-EOF + expect(File.open(File.join(temp_path, "segment.js")){|f| f.read}).to eql <<-EOF MyNamespace.translations || (MyNamespace.translations = {}); MyNamespace.translations["en"] = I18n.extend((MyNamespace.translations["en"] || {}), {"a":"Test","b":"Test"}); EOF @@ -179,7 +179,7 @@ it 'should output the keys as sorted' do file_should_exist "segment.js" - File.open(File.join(temp_path, "segment.js")){|f| f.read}.should eql <<-EOF + expect(File.open(File.join(temp_path, "segment.js")){|f| f.read}).to eql <<-EOF MyNamespace.translations || (MyNamespace.translations = {}); MyNamespace.translations["en"] = I18n.extend((MyNamespace.translations["en"] || {}), {"b":"Test","a":"Test"}); EOF diff --git a/spec/ruby/i18n/js/utils_spec.rb b/spec/ruby/i18n/js/utils_spec.rb index d55e3eec..6e3c87ed 100644 --- a/spec/ruby/i18n/js/utils_spec.rb +++ b/spec/ruby/i18n/js/utils_spec.rb @@ -28,14 +28,14 @@ target = {:a => {:b => 1}} result = described_class.deep_merge(target, {:a => {:c => 2}}) - result[:a].should eql({:b => 1, :c => 2}) + expect(result[:a]).to eql({:b => 1, :c => 2}) end it "performs a banged deep merge" do target = {:a => {:b => 1}} described_class.deep_merge!(target, {:a => {:c => 2}}) - target[:a].should eql({:b => 1, :c => 2}) + expect(target[:a]).to eql({:b => 1, :c => 2}) end end @@ -45,7 +45,7 @@ result = described_class.deep_reject(hash) { |k, v| k == :b } - result.should eql({:a => {}}) + expect(result).to eql({:a => {}}) end it "performs a deep keys rejection prunning the whole tree if necessary" do @@ -53,7 +53,7 @@ result = described_class.deep_reject(hash) { |k, v| k == :b } - result.should eql({:a => {}}) + expect(result).to eql({:a => {}}) end @@ -62,8 +62,8 @@ result = described_class.deep_reject(hash) { |k, v| k == :b } - result.should eql({:a => {:c => 2}}) - hash.should eql({:a => {:b => 1, :c => 2}}) + expect(result).to eql({:a => {:c => 2}}) + expect(hash).to eql({:a => {:b => 1, :c => 2}}) end end @@ -73,7 +73,7 @@ it "performs a deep keys sort without changing the original hash" do should eql({:y => 3, :z => {:a => 2, :b => 1}}) - unsorted_hash.should eql({:z => {:b => 1, :a => 2}, :y => 3}) + expect(unsorted_hash).to eql({:z => {:b => 1, :a => 2}, :y => 3}) end # Idea from gem `rails_admin` @@ -91,16 +91,16 @@ describe ".scopes_match?" do it "performs a comparison of literal scopes" do - described_class.scopes_match?([:a, :b], [:a, :b, :c]).should_not eql true - described_class.scopes_match?([:a, :b, :c], [:a, :b, :c]).should eql true - described_class.scopes_match?([:a, :b, :c], [:a, :b, :d]).should_not eql true + expect(described_class.scopes_match?([:a, :b], [:a, :b, :c])).to_not eql true + expect(described_class.scopes_match?([:a, :b, :c], [:a, :b, :c])).to eql true + expect(described_class.scopes_match?([:a, :b, :c], [:a, :b, :d])).to_not eql true end it "performs a comparison of wildcard scopes" do - described_class.scopes_match?([:a, '*'], [:a, :b, :c]).should_not eql true - described_class.scopes_match?([:a, '*', :c], [:a, :b, :c]).should eql true - described_class.scopes_match?([:a, :b, :c], [:a, '*', :c]).should eql true - described_class.scopes_match?([:a, :b, :c], [:a, '*', '*']).should eql true + expect(described_class.scopes_match?([:a, '*'], [:a, :b, :c])).to_not eql true + expect(described_class.scopes_match?([:a, '*', :c], [:a, :b, :c])).to eql true + expect(described_class.scopes_match?([:a, :b, :c], [:a, '*', :c])).to eql true + expect(described_class.scopes_match?([:a, :b, :c], [:a, '*', '*'])).to eql true end end end diff --git a/spec/ruby/i18n/js_spec.rb b/spec/ruby/i18n/js_spec.rb index 1536b77c..ac05d5c8 100644 --- a/spec/ruby/i18n/js_spec.rb +++ b/spec/ruby/i18n/js_spec.rb @@ -30,15 +30,15 @@ it "exports messages using custom output path" do set_config "custom_path.yml" - I18n::JS::Segment.should_receive(:new).with("tmp/i18n-js/all.js", translations, {js_extend: true, sort_translation_keys: true, json_only: false}).and_call_original - I18n::JS::Segment.any_instance.should_receive(:save!).with(no_args) + allow(I18n::JS::Segment).to receive(:new).with("tmp/i18n-js/all.js", translations, {js_extend: true, sort_translation_keys: true, json_only: false}).and_call_original + allow_any_instance_of(I18n::JS::Segment).to receive(:save!).with(no_args) I18n::JS.export end it "sets default scope to * when not specified" do set_config "no_scope.yml" - I18n::JS::Segment.should_receive(:new).with("tmp/i18n-js/no_scope.js", translations, {js_extend: true, sort_translation_keys: true, json_only: false}).and_call_original - I18n::JS::Segment.any_instance.should_receive(:save!).with(no_args) + allow(I18n::JS::Segment).to receive(:new).with("tmp/i18n-js/no_scope.js", translations, {js_extend: true, sort_translation_keys: true, json_only: false}).and_call_original + allow_any_instance_of(I18n::JS::Segment).to receive(:save!).with(no_args) I18n::JS.export end @@ -91,7 +91,7 @@ set_config "multiple_conditions_per_locale.yml" result = I18n::JS.translation_segments - result.map(&:file).should eql(["tmp/i18n-js/bits.%{locale}.js"]) + expect(result.map(&:file)).to eql(["tmp/i18n-js/bits.%{locale}.js"]) result.map(&:save!) @@ -142,40 +142,40 @@ context "filters" do it "filters translations using scope *.date.formats" do result = I18n::JS.filter(translations, "*.date.formats") - result[:en][:date].keys.should eql([:formats]) - result[:fr][:date].keys.should eql([:formats]) + expect(result[:en][:date].keys).to eql([:formats]) + expect(result[:fr][:date].keys).to eql([:formats]) end it "filters translations using scope [*.date.formats, *.number.currency.format]" do result = I18n::JS.scoped_translations(["*.date.formats", "*.number.currency.format"]) - result[:en].keys.collect(&:to_s).sort.should eql(%w[ date number ]) - result[:fr].keys.collect(&:to_s).sort.should eql(%w[ date number ]) + expect(result[:en].keys.collect(&:to_s).sort).to eql(%w[ date number ]) + expect(result[:fr].keys.collect(&:to_s).sort).to eql(%w[ date number ]) end it "filters translations using multi-star scope" do result = I18n::JS.scoped_translations("*.*.formats") - result[:en].keys.collect(&:to_s).sort.should eql(%w[ date time ]) - result[:fr].keys.collect(&:to_s).sort.should eql(%w[ date time ]) + expect(result[:en].keys.collect(&:to_s).sort).to eql(%w[ date time ]) + expect(result[:fr].keys.collect(&:to_s).sort).to eql(%w[ date time ]) - result[:en][:date].keys.should eql([:formats]) - result[:en][:time].keys.should eql([:formats]) + expect(result[:en][:date].keys).to eql([:formats]) + expect(result[:en][:time].keys).to eql([:formats]) - result[:fr][:date].keys.should eql([:formats]) - result[:fr][:time].keys.should eql([:formats]) + expect(result[:fr][:date].keys).to eql([:formats]) + expect(result[:fr][:time].keys).to eql([:formats]) end it "filters translations using alternated stars" do result = I18n::JS.scoped_translations("*.admin.*.title") - result[:en][:admin].keys.collect(&:to_s).sort.should eql(%w[ edit show ]) - result[:fr][:admin].keys.collect(&:to_s).sort.should eql(%w[ edit show ]) + expect(result[:en][:admin].keys.collect(&:to_s).sort).to eql(%w[ edit show ]) + expect(result[:fr][:admin].keys.collect(&:to_s).sort).to eql(%w[ edit show ]) - result[:en][:admin][:show][:title].should eql("Show") - result[:fr][:admin][:show][:title].should eql("Visualiser") + expect(result[:en][:admin][:show][:title]).to eql("Show") + expect(result[:fr][:admin][:show][:title]).to eql("Visualiser") - result[:en][:admin][:edit][:title].should eql("Edit") - result[:fr][:admin][:edit][:title].should eql("Editer") + expect(result[:en][:admin][:edit][:title]).to eql("Edit") + expect(result[:fr][:admin][:edit][:title]).to eql("Editer") end describe ".filtered_translations" do @@ -227,50 +227,50 @@ it "does not include scopes listed in the exceptions list" do result = I18n::JS.scoped_translations("*", ['de.*', '*.admin', '*.*.currency']) - result[:de].should be_empty + expect(result[:de]).to be_empty - result[:en][:admin].should be_nil - result[:fr][:admin].should be_nil - result[:ja][:admin].should be_nil + expect(result[:en][:admin]).to be_nil + expect(result[:fr][:admin]).to be_nil + expect(result[:ja][:admin]).to be_nil - result[:en][:number][:currency].should be_nil - result[:fr][:number][:currency].should be_nil + expect(result[:en][:number][:currency]).to be_nil + expect(result[:fr][:number][:currency]).to be_nil end it "does not include scopes listed in the exceptions list and respects the 'only' option" do result = I18n::JS.scoped_translations("fr.*", ['*.admin', '*.*.currency']) - result[:en].should be_nil - result[:de].should be_nil - result[:ja].should be_nil + expect(result[:en]).to be_nil + expect(result[:de]).to be_nil + expect(result[:ja]).to be_nil - result[:fr][:admin].should be_nil - result[:fr][:number][:currency].should be_nil + expect(result[:fr][:admin]).to be_nil + expect(result[:fr][:number][:currency]).to be_nil - result[:fr][:time][:am].should be_a(String) + expect(result[:fr][:time][:am]).to be_a(String) end it "does exclude absolute scopes listed in the exceptions list" do result = I18n::JS.scoped_translations("*", ['de', 'en.admin', 'fr.number.currency']) - result[:de].should be_nil + expect(result[:de]).to be_nil - result[:en].should be_a(Hash) - result[:en][:admin].should be_nil + expect(result[:en]).to be_a(Hash) + expect(result[:en][:admin]).to be_nil - result[:fr][:number].should be_a(Hash) - result[:fr][:number][:currency].should be_nil + expect(result[:fr][:number]).to be_a(Hash) + expect(result[:fr][:number][:currency]).to be_nil end it "does not exclude non-absolute scopes listed in the exceptions list" do result = I18n::JS.scoped_translations("*", ['admin', 'currency']) - result[:en][:admin].should be_a(Hash) - result[:fr][:admin].should be_a(Hash) - result[:ja][:admin].should be_a(Hash) + expect(result[:en][:admin]).to be_a(Hash) + expect(result[:fr][:admin]).to be_a(Hash) + expect(result[:ja][:admin]).to be_a(Hash) - result[:en][:number][:currency].should be_a(Hash) - result[:fr][:number][:currency].should be_a(Hash) + expect(result[:en][:number][:currency]).to be_a(Hash) + expect(result[:fr][:number][:currency]).to be_a(Hash) end end @@ -281,30 +281,30 @@ it "exports without fallback when disabled" do set_config "js_file_per_locale_without_fallbacks.yml" - subject[:fr][:fallback_test].should eql(nil) - subject[:fr][:null_test].should eql(nil) - subject[:de][:null_test].should eql(nil) + expect(subject[:fr][:fallback_test]).to eql(nil) + expect(subject[:fr][:null_test]).to eql(nil) + expect(subject[:de][:null_test]).to eql(nil) end it "exports with default_locale as fallback when enabled" do set_config "js_file_per_locale_with_fallbacks_enabled.yml" - subject[:fr][:fallback_test].should eql("Success") - subject[:fr][:null_test].should eql("fallback for null") - subject[:de][:null_test].should eql("fallback for null") + expect(subject[:fr][:fallback_test]).to eql("Success") + expect(subject[:fr][:null_test]).to eql("fallback for null") + expect(subject[:de][:null_test]).to eql("fallback for null") end it "exports with default_locale as fallback when enabled with :default_locale" do set_config "js_file_per_locale_with_fallbacks_as_default_locale_symbol.yml" - subject[:fr][:fallback_test].should eql("Success") - subject[:fr][:null_test].should eql("fallback for null") - subject[:de][:null_test].should eql("fallback for null") + expect(subject[:fr][:fallback_test]).to eql("Success") + expect(subject[:fr][:null_test]).to eql("fallback for null") + expect(subject[:de][:null_test]).to eql("fallback for null") end it "exports with given locale as fallback" do set_config "js_file_per_locale_with_fallbacks_as_locale.yml" - subject[:fr][:fallback_test].should eql("Erfolg") - subject[:fr][:null_test].should eql(nil) - subject[:de][:null_test].should eql(nil) + expect(subject[:fr][:fallback_test]).to eql("Erfolg") + expect(subject[:fr][:null_test]).to eql(nil) + expect(subject[:de][:null_test]).to eql(nil) end context "when given locale is in `I18n.available_locales` but its translation is missing" do @@ -336,17 +336,17 @@ it "exports with defined locale as fallback when enabled" do set_config "js_file_per_locale_with_fallbacks_enabled.yml" - subject[:fr][:fallback_test].should eql("Erfolg") + expect(subject[:fr][:fallback_test]).to eql("Erfolg") end it "exports with defined locale as fallback when enabled with :default_locale" do set_config "js_file_per_locale_with_fallbacks_as_default_locale_symbol.yml" - subject[:fr][:fallback_test].should eql("Success") + expect(subject[:fr][:fallback_test]).to eql("Success") end it "exports with Fallbacks as Hash" do set_config "js_file_per_locale_with_fallbacks_as_hash.yml" - subject[:fr][:fallback_test].should eql("Erfolg") + expect(subject[:fr][:fallback_test]).to eql("Erfolg") end end end @@ -389,9 +389,9 @@ it "should allow all locales" do result = I18n::JS.scoped_translations("*.admin.*.title") - result[:en][:admin][:show][:title].should eql("Show") - result[:fr][:admin][:show][:title].should eql("Visualiser") - result[:ja][:admin][:show][:title].should eql("Ignore me") + expect(result[:en][:admin][:show][:title]).to eql("Show") + expect(result[:fr][:admin][:show][:title]).to eql("Visualiser") + expect(result[:ja][:admin][:show][:title]).to eql("Ignore me") end end @@ -401,28 +401,28 @@ it "should ignore non-valid locales" do result = I18n::JS.scoped_translations("*.admin.*.title") - result[:en][:admin][:show][:title].should eql("Show") - result[:fr][:admin][:show][:title].should eql("Visualiser") - result.keys.include?(:ja).should eql(false) + expect(result[:en][:admin][:show][:title]).to eql("Show") + expect(result[:fr][:admin][:show][:title]).to eql("Visualiser") + expect(result.keys.include?(:ja)).to eql(false) end end end context "general" do it "sets export directory" do - I18n::JS::DEFAULT_EXPORT_DIR_PATH.should eql("public/javascripts") + expect(I18n::JS::DEFAULT_EXPORT_DIR_PATH).to eql("public/javascripts") end it "sets empty hash as configuration when no file is found" do - I18n::JS.config_file_exists?.should eql(false) - I18n::JS.config.should eql({}) + expect(I18n::JS.config_file_exists?).to eql(false) + expect(I18n::JS.config).to eql({}) end it "executes erb in config file" do set_config "erb.yml" config_entry = I18n::JS.config[:translations].first - config_entry["only"].should eq("*.date.formats") + expect(config_entry["only"]).to eq("*.date.formats") end end @@ -434,7 +434,7 @@ allow(FileUtils).to receive(:mkdir_p).and_call_original allow(FileUtils).to receive(:cp).and_call_original - described_class.stub(:export_i18n_js_dir_path).and_return(export_i18n_js_dir_path) + allow(described_class).to receive(:export_i18n_js_dir_path).and_return(export_i18n_js_dir_path) I18n::JS.export_i18n_js end @@ -448,7 +448,7 @@ expect(FileUtils).to have_received(:cp).once end it "exports the file" do - File.should be_file(File.join(I18n::JS.export_i18n_js_dir_path, "i18n.js")) + expect(File).to be_file(File.join(I18n::JS.export_i18n_js_dir_path, "i18n.js")) end end @@ -484,7 +484,7 @@ expect(FileUtils).to have_received(:cp).once end it "exports the file" do - File.should be_file(File.join(config_export_path, "i18n.js")) + expect(File).to be_file(File.join(config_export_path, "i18n.js")) end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 293baf88..cd896430 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -70,10 +70,10 @@ def self.included(base) # Remove deprecation warnings config.expect_with :rspec do |c| - c.syntax = [:should, :expect] + c.syntax = [:expect] end config.mock_with :rspec do |c| - c.syntax = [:should, :expect] + c.syntax = [:expect] end end From c1b1165fc72846d23fad574476c6e39ae2318e84 Mon Sep 17 00:00:00 2001 From: Johnny Shields Date: Tue, 22 Jan 2019 18:15:12 +0900 Subject: [PATCH 404/466] Fix merging of plural keys (#472) * Fix merging of plural keys * Update utils.rb * Update utils.rb * Fix changelog * Add/fix specs * Add better explanatory comment. * Attempt at fixing specs on Travis * Fix spec --- CHANGELOG.md | 1 + lib/i18n/js/utils.rb | 19 ++++++++++++++++--- spec/fixtures/locales.yml | 7 +++++++ spec/fixtures/merge_plurals.yml | 6 ++++++ spec/ruby/i18n/js_spec.rb | 26 +++++++++++++++++++++++--- 5 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 spec/fixtures/merge_plurals.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e7b8077..bed3ceff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - [Ruby] Rollback `i18n` gem version requirement `>= 0.8.0` +- [Ruby] Fix merging of plural keys across locales. ## [3.2.0] - 2018-11-16 diff --git a/lib/i18n/js/utils.rb b/lib/i18n/js/utils.rb index 02db4764..e66ae864 100644 --- a/lib/i18n/js/utils.rb +++ b/lib/i18n/js/utils.rb @@ -1,10 +1,23 @@ module I18n module JS module Utils - # deep_merge by Stefan Rusterholz, see . - # The last result is modified to treat `nil` as missing key + PLURAL_KEYS = %i[zero one two few many other].freeze + + # Based on deep_merge by Stefan Rusterholz, see . + # This method is used to handle I18n fallbacks. Given two equivalent path nodes in two locale trees: + # 1. If the node in the current locale appears to be an I18n pluralization (:one, :other, etc.), + # use the node as-is without merging. This prevents mixing locales with different pluralization schemes. + # 2. Else if both nodes are Hashes, combine (merge) the key-value pairs of the two nodes into one, + # prioritizing the current locale. + # 3. Else if either node is nil, use the other node. MERGER = proc do |_key, v1, v2| - Hash === v1 && Hash === v2 ? v1.merge(v2, &MERGER) : (v2.nil? ? v1 : v2) + if Hash === v2 && (v2.keys - PLURAL_KEYS).empty? + v2 + elsif Hash === v1 && Hash === v2 + v1.merge(v2, &MERGER) + else + v2.nil? ? v1 : v2 + end end HASH_NIL_VALUE_CLEANER_PROC = proc do |k, v| diff --git a/spec/fixtures/locales.yml b/spec/fixtures/locales.yml index a00d60b6..16269f69 100755 --- a/spec/fixtures/locales.yml +++ b/spec/fixtures/locales.yml @@ -37,6 +37,9 @@ en: foo: "Foo" fallback_test: "Success" null_test: "fallback for null" + merge_plurals: + one: Apple + other: Apples de: fallback_test: "Erfolg" @@ -81,6 +84,10 @@ fr: note: "plus de détails" edit: title: "Editer" + merge_plurals: + zero: Pomme + one: Pomme + other: Pommes ja: admin: diff --git a/spec/fixtures/merge_plurals.yml b/spec/fixtures/merge_plurals.yml new file mode 100644 index 00000000..12acc67d --- /dev/null +++ b/spec/fixtures/merge_plurals.yml @@ -0,0 +1,6 @@ +fallbacks: false + +translations: + - file: "tmp/i18n-js/merge_plurals.js" + only: + - "*.merge_plurals" diff --git a/spec/ruby/i18n/js_spec.rb b/spec/ruby/i18n/js_spec.rb index ac05d5c8..33e83291 100644 --- a/spec/ruby/i18n/js_spec.rb +++ b/spec/ruby/i18n/js_spec.rb @@ -595,11 +595,10 @@ end it "exports with the keys sorted" do - expect(subject).to eq(< Date: Tue, 22 Jan 2019 18:20:08 +0900 Subject: [PATCH 405/466] json_only option should allow multiple locales in one file (#531) * - `json_only` option should allow multiple locales. - Refactor segment writer code to use formatter classes. This can be extended in the future if needed to support other formats easily. * RSpecs: should --> expect --- CHANGELOG.md | 3 +- lib/i18n/js/errors.rb | 9 ------ lib/i18n/js/formatters/base.rb | 23 +++++++++++++ lib/i18n/js/formatters/js.rb | 31 ++++++++++++++++++ lib/i18n/js/formatters/json.rb | 13 ++++++++ lib/i18n/js/segment.rb | 54 +++++++++---------------------- spec/fixtures/json_only.yml | 10 ++++++ spec/ruby/i18n/js/segment_spec.rb | 42 ++++++++++++++++++++---- 8 files changed, 130 insertions(+), 55 deletions(-) delete mode 100644 lib/i18n/js/errors.rb create mode 100644 lib/i18n/js/formatters/base.rb create mode 100644 lib/i18n/js/formatters/js.rb create mode 100644 lib/i18n/js/formatters/json.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index bed3ceff..e84bd1d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- Nothing +- [Ruby] `json_only` option should allow multiple locales. +- [Ruby] Simplified and cleaned code related to JS/JSON formatting. ### Fixed diff --git a/lib/i18n/js/errors.rb b/lib/i18n/js/errors.rb deleted file mode 100644 index 7fc44598..00000000 --- a/lib/i18n/js/errors.rb +++ /dev/null @@ -1,9 +0,0 @@ -module I18n - module JS - class JsonOnlyLocaleRequiredError < StandardError - def message - 'The json_only option requires %{locale} in the file name.' - end - end - end -end diff --git a/lib/i18n/js/formatters/base.rb b/lib/i18n/js/formatters/base.rb new file mode 100644 index 00000000..4a6c4b69 --- /dev/null +++ b/lib/i18n/js/formatters/base.rb @@ -0,0 +1,23 @@ +module I18n + module JS + module Formatters + class Base + def initialize(js_extend: false, namespace: nil, pretty_print: false) + @js_extend = js_extend + @namespace = namespace + @pretty_print = pretty_print + end + + protected + + def format_json(translations) + if @pretty_print + ::JSON.pretty_generate(translations) + else + translations.to_json + end + end + end + end + end +end diff --git a/lib/i18n/js/formatters/js.rb b/lib/i18n/js/formatters/js.rb new file mode 100644 index 00000000..d03cabdc --- /dev/null +++ b/lib/i18n/js/formatters/js.rb @@ -0,0 +1,31 @@ +require "i18n/js/formatters/base" + +module I18n + module JS + module Formatters + class JS < Base + def format(translations) + contents = header + translations.each do |locale, translations_for_locale| + contents << line(locale, format_json(translations_for_locale)) + end + contents + end + + protected + + def header + %(#{@namespace}.translations || (#{@namespace}.translations = {});\n) + end + + def line(locale, translations) + if @js_extend + %(#{@namespace}.translations["#{locale}"] = I18n.extend((#{@namespace}.translations["#{locale}"] || {}), #{translations});\n) + else + %(#{@namespace}.translations["#{locale}"] = #{translations};\n) + end + end + end + end + end +end diff --git a/lib/i18n/js/formatters/json.rb b/lib/i18n/js/formatters/json.rb new file mode 100644 index 00000000..a0eb91fd --- /dev/null +++ b/lib/i18n/js/formatters/json.rb @@ -0,0 +1,13 @@ +require "i18n/js/formatters/base" + +module I18n + module JS + module Formatters + class JSON < Base + def format(translations) + format_json(translations) + end + end + end + end +end diff --git a/lib/i18n/js/segment.rb b/lib/i18n/js/segment.rb index 6c671f6f..84a1471a 100644 --- a/lib/i18n/js/segment.rb +++ b/lib/i18n/js/segment.rb @@ -1,5 +1,6 @@ require "i18n/js/private/hash_with_symbol_keys" -require "i18n/js/errors" +require "i18n/js/formatters/js" +require "i18n/js/formatters/json" module I18n module JS @@ -34,21 +35,20 @@ def save! write_file(file_for_locale(locale), @translations.slice(locale)) end else - if @json_only - raise I18n::JS::JsonOnlyLocaleRequiredError - end write_file end end protected + def file_for_locale(locale) + @file.gsub(LOCALE_INTERPOLATOR, locale.to_s) + end + def write_file(_file = @file, _translations = @translations) FileUtils.mkdir_p File.dirname(_file) - contents = js_header - _translations.each do |locale, translations_for_locale| - contents << js_translations(locale, translations_for_locale) - end + _translations = Utils.deep_key_sort(_translations) if @sort_translation_keys + contents = formatter.format(_translations) return if File.exist?(_file) && File.read(_file) == contents @@ -57,42 +57,18 @@ def write_file(_file = @file, _translations = @translations) end end - def js_header + def formatter if @json_only - '' + Formatters::JSON.new(**formatter_options) else - %(#{@namespace}.translations || (#{@namespace}.translations = {});\n) + Formatters::JS.new(**formatter_options) end end - def js_translations(locale, translations) - translations = Utils.deep_key_sort(translations) if @sort_translation_keys - translations = print_json(translations) - js_translations_line(locale, translations) - end - - def js_translations_line(locale, translations) - if @json_only - %({"#{locale}":#{translations}}) - elsif @js_extend - %(#{@namespace}.translations["#{locale}"] = I18n.extend((#{@namespace}.translations["#{locale}"] || {}), #{translations});\n) - else - %(#{@namespace}.translations["#{locale}"] = #{translations};\n) - end - end - - # Outputs pretty or ugly JSON depending on :pretty_print option - def print_json(translations) - if @pretty_print - JSON.pretty_generate(translations) - else - translations.to_json - end - end - - # interpolates filename - def file_for_locale(locale) - @file.gsub(LOCALE_INTERPOLATOR, locale.to_s) + def formatter_options + { js_extend: @js_extend, + namespace: @namespace, + pretty_print: @pretty_print } end end end diff --git a/spec/fixtures/json_only.yml b/spec/fixtures/json_only.yml index 0cd0ab1d..7327136f 100644 --- a/spec/fixtures/json_only.yml +++ b/spec/fixtures/json_only.yml @@ -6,3 +6,13 @@ translations: only: - "*.date.formats.*" - "*.number.currency.*" + + - file: "tmp/i18n-js/json_only_multi.js" + only: + - "*.date.formats.*" + - "*.number.currency.*" + + - file: "tmp/i18n-js/json_only_multi_pretty.js" + only: + - "*.date.formats.*" + - "*.number.currency.*" diff --git a/spec/ruby/i18n/js/segment_spec.rb b/spec/ruby/i18n/js/segment_spec.rb index 292a1a42..9ed47c64 100644 --- a/spec/ruby/i18n/js/segment_spec.rb +++ b/spec/ruby/i18n/js/segment_spec.rb @@ -64,19 +64,20 @@ describe "#saving!" do before { allow(I18n::JS).to receive(:export_i18n_js_dir_path).and_return(temp_path) } - context "when json_only is true" do + context "when json_only is true with locale" do let(:file){ "tmp/i18n-js/%{locale}.js" } let(:json_only){ true } - it 'should output the keys as sorted' do + + it 'should output JSON files per locale' do subject.save! file_should_exist "en.js" file_should_exist "fr.js" - expect(File.open(File.join(temp_path, "en.js")){|f| f.read}).to eql( + expect(File.read(File.join(temp_path, "en.js"))).to eql( %Q({"en":{"test":"Test"}}) ) - expect(File.open(File.join(temp_path, "fr.js")){|f| f.read}).to eql( + expect(File.read(File.join(temp_path, "fr.js"))).to eql( %Q({"fr":{"test":"Test2"}}) ) end @@ -85,8 +86,37 @@ context "when json_only is true without locale" do let(:file){ "tmp/i18n-js/segment.js" } let(:json_only){ true } - it 'should output the keys as sorted' do - expect { subject.save! }.to raise_error(I18n::JS::JsonOnlyLocaleRequiredError) + + it 'should output one JSON file for all locales' do + subject.save! + file_should_exist "segment.js" + + expect(File.read(File.join(temp_path, "segment.js"))).to eql( + %Q({"en":{"test":"Test"},"fr":{"test":"Test2"}}) + ) + end + end + + context "when json_only and pretty print are true" do + let(:file){ "tmp/i18n-js/segment.js" } + let(:json_only){ true } + let(:pretty_print){ true } + + it 'should output one JSON file for all locales' do + subject.save! + file_should_exist "segment.js" + + expect(File.read(File.join(temp_path, "segment.js"))).to eql <<-EOS +{ + "en": { + "test": "Test" + }, + "fr": { + "test": "Test2" + } +} +EOS +.chomp end end end From 931c534f45006363652e5ec0b5e5120a1c108126 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 22 Jan 2019 17:29:59 +0800 Subject: [PATCH 406/466] ^ Release 3.2.1 --- CHANGELOG.md | 27 ++++++++++++++++++++++----- lib/i18n/js/version.rb | 2 +- package.json | 2 +- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e84bd1d6..8cd7c57d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,13 +11,29 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- [Ruby] `json_only` option should allow multiple locales. -- [Ruby] Simplified and cleaned code related to JS/JSON formatting. +- Nothing + +### Fixed + +- Nothing + + +## [3.2.1] - 2019-01-22 + +### Changed + +- [Ruby] `json_only` option should allow multiple locales. + (PR: https://github.com/fnando/i18n-js/pull/531) +- [Ruby] Simplified and cleaned code related to JS/JSON formatting. + (PR: https://github.com/fnando/i18n-js/pull/531) +- [JS] Use strict value comparison ### Fixed -- [Ruby] Rollback `i18n` gem version requirement `>= 0.8.0` -- [Ruby] Fix merging of plural keys across locales. +- [Ruby] Relax `i18n` version requirement back to `>= 0.6.6` + (PR: https://github.com/fnando/i18n-js/pull/530) +- [Ruby] Fix merging of plural keys across locales. + (PR: https://github.com/fnando/i18n-js/pull/472) ## [3.2.0] - 2018-11-16 @@ -361,7 +377,8 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.2.0...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.2.1...HEAD +[3.2.1]: https://github.com/fnando/i18n-js/compare/v3.2.0...v3.2.1 [3.2.0]: https://github.com/fnando/i18n-js/compare/v3.1.0...v3.2.0 [3.1.0]: https://github.com/fnando/i18n-js/compare/v3.0.11...v3.1.0 [3.0.11]: https://github.com/fnando/i18n-js/compare/v3.0.10...v3.0.11 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index 8d196deb..df4892c2 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -2,6 +2,6 @@ module I18n module JS - VERSION = "3.2.0" + VERSION = "3.2.1" end end diff --git a/package.json b/package.json index 71dd0514..716d2e80 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "i18n-js", - "version": "3.1.0", + "version": "3.2.1", "description": "A javascript library similar to Ruby on Rails i18n gem", "author": "Nando Vieira", "license": "MIT", From 869d1689ed788ff50121de492db354652971c23d Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 8 May 2019 17:32:07 +0800 Subject: [PATCH 407/466] ! Return invalid date/time input values (null & undefined) as-is Instead of raising error Issue reported in https://github.com/fnando/i18n-js/issues/535 --- app/assets/javascripts/i18n.js | 20 +++++++++++++++----- spec/js/localization.spec.js | 8 +++++++- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 648460b8..80e569ac 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -615,7 +615,7 @@ // This function interpolates the all variables in the given message. I18n.interpolate = function(message, options) { - if (message === null) { + if (message == null) { return message; } @@ -833,8 +833,12 @@ // I18n.parseDate = function(date) { var matches, convertedDate, fraction; + // A date input of `null` or `undefined` will be returned as-is + if (date == null) { + return date; + } // we have a date, so just return it. - if (typeof(date) == "object") { + if (typeof(date) === "object") { return date; } @@ -980,12 +984,18 @@ , format = this.lookup(scope) ; - if (date.toString().match(/invalid/i)) { - return date.toString(); + // A date input of `null` or `undefined` will be returned as-is + if (date == null) { + return date; + } + + var date_string = date.toString() + if (date_string.match(/invalid/i)) { + return date_string; } if (!format) { - return date.toString(); + return date_string; } return this.strftime(date, format); diff --git a/spec/js/localization.spec.js b/spec/js/localization.spec.js index 115909de..3cd15b33 100644 --- a/spec/js/localization.spec.js +++ b/spec/js/localization.spec.js @@ -34,6 +34,12 @@ describe("Localization", function(){ expect(I18n.l("time.formats.long", "2009-11-29 15:07:59")).toEqual("Domingo, 29 de Novembro de 2009, 15:07 h"); }); + it("return 'Invalid Date' or original value for invalid input", function(){ + expect(I18n.l("time.formats.default", "")).toEqual("Invalid Date"); + expect(I18n.l("time.formats.default", null)).toEqual(null); + expect(I18n.l("time.formats.default", undefined)).toEqual(undefined); + }); + it("localizes date/time strings with placeholders", function(){ I18n.locale = "pt-BR"; @@ -45,4 +51,4 @@ describe("Localization", function(){ I18n.locale = "pt-BR"; expect(I18n.l("percentage", 123.45)).toEqual("123,45%"); }); -}); \ No newline at end of file +}); From 02ab0b096754b355eb7f381c340fd9743492e384 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 8 May 2019 17:32:17 +0800 Subject: [PATCH 408/466] * Test against i18n 1.6.x --- .travis.yml | 1 + Appraisals | 4 ++++ gemfiles/i18n_1_6.gemfile | 7 +++++++ 3 files changed, 12 insertions(+) create mode 100644 gemfiles/i18n_1_6.gemfile diff --git a/.travis.yml b/.travis.yml index d22132e6..6edef22c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,6 +27,7 @@ gemfile: - gemfiles/i18n_1_3.gemfile - gemfiles/i18n_1_4.gemfile - gemfiles/i18n_1_5.gemfile + - gemfiles/i18n_1_6.gemfile matrix: fast_finish: true allow_failures: diff --git a/Appraisals b/Appraisals index e48a9a9c..5dbacac2 100644 --- a/Appraisals +++ b/Appraisals @@ -30,3 +30,7 @@ end appraise "i18n_1_5" do gem "i18n", "~> 1.5.1" end + +appraise "i18n_1_6" do + gem "i18n", "~> 1.6.0" +end diff --git a/gemfiles/i18n_1_6.gemfile b/gemfiles/i18n_1_6.gemfile new file mode 100644 index 00000000..d1b3a7d1 --- /dev/null +++ b/gemfiles/i18n_1_6.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "i18n", "~> 1.6.0" + +gemspec path: "../" From 5c73e33b19936cffe5458807f3ab9f3fcb1d6c74 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 9 May 2019 10:07:18 +0800 Subject: [PATCH 409/466] ^ Release 3.2.2 --- CHANGELOG.md | 11 ++++++++++- lib/i18n/js/version.rb | 2 +- package.json | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cd7c57d..922e3761 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.2.2] - 2019-05-09 + +### Fixed + +- [JS] Return invalid date/time input values (null & undefined) as-is + (Commit: https://github.com/fnando/i18n-js/commit/869d1689ed788ff50121de492db354652971c23d) + + ## [3.2.1] - 2019-01-22 ### Changed @@ -377,7 +385,8 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.2.1...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.2.2...HEAD +[3.2.2]: https://github.com/fnando/i18n-js/compare/v3.2.1...v3.2.2 [3.2.1]: https://github.com/fnando/i18n-js/compare/v3.2.0...v3.2.1 [3.2.0]: https://github.com/fnando/i18n-js/compare/v3.1.0...v3.2.0 [3.1.0]: https://github.com/fnando/i18n-js/compare/v3.0.11...v3.1.0 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index df4892c2..67d5de66 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -2,6 +2,6 @@ module I18n module JS - VERSION = "3.2.1" + VERSION = "3.2.2" end end diff --git a/package.json b/package.json index 716d2e80..7afdfd0a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "i18n-js", - "version": "3.2.1", + "version": "3.2.2", "description": "A javascript library similar to Ruby on Rails i18n gem", "author": "Nando Vieira", "license": "MIT", From 8568fdde72c98c3af73feb4a5842caa66e118eff Mon Sep 17 00:00:00 2001 From: Michael Hoy Date: Thu, 23 May 2019 14:41:39 -0400 Subject: [PATCH 410/466] allow rails 6 fix method name add rails6? to asset_pipeline_available --- lib/i18n/js/dependencies.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/i18n/js/dependencies.rb b/lib/i18n/js/dependencies.rb index a81234e5..009a6c62 100644 --- a/lib/i18n/js/dependencies.rb +++ b/lib/i18n/js/dependencies.rb @@ -18,7 +18,7 @@ def sprockets_rails_v2_plus? # Call this in an initializer def using_asset_pipeline? assets_pipeline_available = - (rails3? || rails4? || rails5?) && + (rails3? || rails4? || rails5? || rails6?) && Rails.respond_to?(:application) && Rails.application.config.respond_to?(:assets) rails3_assets_enabled = @@ -26,7 +26,7 @@ def using_asset_pipeline? assets_pipeline_available && Rails.application.config.assets.enabled != false - assets_pipeline_available && (rails4? || rails5? || rails3_assets_enabled) + assets_pipeline_available && (rails4? || rails5? || rails6? || rails3_assets_enabled) end private @@ -43,6 +43,10 @@ def rails5? rails? && Rails.version.to_i == 5 end + def rails6? + rails? && Rails.version.to_i == 6 + end + def safe_gem_check(*args) if Gem::Specification.respond_to?(:find_by_name) Gem::Specification.find_by_name(*args) From fac2e34df40069bc2bad4e36f11a95b142b93b6f Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 24 May 2019 10:04:30 +0800 Subject: [PATCH 411/466] ^ Release 3.2.3 --- CHANGELOG.md | 11 ++++++++++- lib/i18n/js/version.rb | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 922e3761..93686538 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.2.3] - 2019-05-24 + +### Changed + +- Allow rails 6 to be used with this gem + (PR: https://github.com/fnando/i18n-js/pull/536) + + ## [3.2.2] - 2019-05-09 ### Fixed @@ -385,7 +393,8 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.2.2...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.2.3...HEAD +[3.2.3]: https://github.com/fnando/i18n-js/compare/v3.2.2...v3.2.3 [3.2.2]: https://github.com/fnando/i18n-js/compare/v3.2.1...v3.2.2 [3.2.1]: https://github.com/fnando/i18n-js/compare/v3.2.0...v3.2.1 [3.2.0]: https://github.com/fnando/i18n-js/compare/v3.1.0...v3.2.0 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index 67d5de66..6fdedf94 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -2,6 +2,6 @@ module I18n module JS - VERSION = "3.2.2" + VERSION = "3.2.3" end end From dcd371d2398f56db36b25d50f0e6df0787337e55 Mon Sep 17 00:00:00 2001 From: Kaitlin Jaffe Date: Tue, 4 Jun 2019 15:03:55 -0700 Subject: [PATCH 412/466] added support for , , and strftime formats --- CHANGELOG.md | 2 +- README.md | 45 ++++++++++++++++--------------- app/assets/javascripts/i18n.js | 49 ++++++++++++++++++---------------- spec/js/dates.spec.js | 10 +++++++ 4 files changed, 60 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93686538..f31b4d4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added -- Nothing +- Support for `%P`, `%Z`, and `%l` strftime formats to match Ruby strftime ### Changed diff --git a/README.md b/README.md index 89dac452..25b75745 100644 --- a/README.md +++ b/README.md @@ -591,28 +591,29 @@ I18n.strftime(date, "%d/%m/%Y"); The accepted formats for `I18n.strftime` are: - %a - The abbreviated weekday name (Sun) - %A - The full weekday name (Sunday) - %b - The abbreviated month name (Jan) - %B - The full month name (January) - %d - Day of the month (01..31) - %-d - Day of the month (1..31) - %H - Hour of the day, 24-hour clock (00..23) - %-H - Hour of the day, 24-hour clock (0..23) - %I - Hour of the day, 12-hour clock (01..12) - %-I - Hour of the day, 12-hour clock (1..12) - %m - Month of the year (01..12) - %-m - Month of the year (1..12) - %M - Minute of the hour (00..59) - %-M - Minute of the hour (0..59) - %p - Meridian indicator (AM or PM) - %S - Second of the minute (00..60) - %-S - Second of the minute (0..60) - %w - Day of the week (Sunday is 0, 0..6) - %y - Year without a century (00..99) - %-y - Year without a century (0..99) - %Y - Year with century - %z - Timezone offset (+0545) + %a - The abbreviated weekday name (Sun) + %A - The full weekday name (Sunday) + %b - The abbreviated month name (Jan) + %B - The full month name (January) + %c - The preferred local date and time representation + %d - Day of the month (01..31) + %-d - Day of the month (1..31) + %H - Hour of the day, 24-hour clock (00..23) + %-H - Hour of the day, 24-hour clock (0..23) + %I - Hour of the day, 12-hour clock (01..12) + %-I/%l - Hour of the day, 12-hour clock (1..12) + %m - Month of the year (01..12) + %-m - Month of the year (1..12) + %M - Minute of the hour (00..59) + %-M - Minute of the hour (0..59) + %p/%P - Meridian indicator (AM or PM) + %S - Second of the minute (00..60) + %-S - Second of the minute (0..60) + %w - Day of the week (Sunday is 0, 0..6) + %y - Year without a century (00..99) + %-y - Year without a century (0..99) + %Y - Year with century + %z/%Z - Timezone offset (+0545) Check out `spec/*.spec.js` files for more examples! diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 80e569ac..1a436782 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -889,29 +889,29 @@ // // The accepted formats are: // - // %a - The abbreviated weekday name (Sun) - // %A - The full weekday name (Sunday) - // %b - The abbreviated month name (Jan) - // %B - The full month name (January) - // %c - The preferred local date and time representation - // %d - Day of the month (01..31) - // %-d - Day of the month (1..31) - // %H - Hour of the day, 24-hour clock (00..23) - // %-H - Hour of the day, 24-hour clock (0..23) - // %I - Hour of the day, 12-hour clock (01..12) - // %-I - Hour of the day, 12-hour clock (1..12) - // %m - Month of the year (01..12) - // %-m - Month of the year (1..12) - // %M - Minute of the hour (00..59) - // %-M - Minute of the hour (0..59) - // %p - Meridian indicator (AM or PM) - // %S - Second of the minute (00..60) - // %-S - Second of the minute (0..60) - // %w - Day of the week (Sunday is 0, 0..6) - // %y - Year without a century (00..99) - // %-y - Year without a century (0..99) - // %Y - Year with century - // %z - Timezone offset (+0545) + // %a - The abbreviated weekday name (Sun) + // %A - The full weekday name (Sunday) + // %b - The abbreviated month name (Jan) + // %B - The full month name (January) + // %c - The preferred local date and time representation + // %d - Day of the month (01..31) + // %-d - Day of the month (1..31) + // %H - Hour of the day, 24-hour clock (00..23) + // %-H - Hour of the day, 24-hour clock (0..23) + // %I - Hour of the day, 12-hour clock (01..12) + // %-I/%l - Hour of the day, 12-hour clock (1..12) + // %m - Month of the year (01..12) + // %-m - Month of the year (1..12) + // %M - Minute of the hour (00..59) + // %-M - Minute of the hour (0..59) + // %p/%P - Meridian indicator (AM or PM) + // %S - Second of the minute (00..60) + // %-S - Second of the minute (0..60) + // %w - Day of the week (Sunday is 0, 0..6) + // %y - Year without a century (00..99) + // %-y - Year without a century (0..99) + // %Y - Year with century + // %z/%Z - Timezone offset (+0545) // I18n.strftime = function(date, format) { var options = this.lookup("date") @@ -962,11 +962,13 @@ format = format.replace("%-H", hour); format = format.replace("%I", padding(hour12)); format = format.replace("%-I", hour12); + format = format.replace("%l", hour12); format = format.replace("%m", padding(month)); format = format.replace("%-m", month); format = format.replace("%M", padding(mins)); format = format.replace("%-M", mins); format = format.replace("%p", meridianOptions[meridian]); + format = format.replace("%P", meridianOptions[meridian].toLowerCase()); format = format.replace("%S", padding(secs)); format = format.replace("%-S", secs); format = format.replace("%w", weekDay); @@ -974,6 +976,7 @@ format = format.replace("%-y", padding(year).replace(/^0+/, "")); format = format.replace("%Y", year); format = format.replace("%z", timezoneoffset); + format = format.replace("%Z", timezoneoffset); return format; }; diff --git a/spec/js/dates.spec.js b/spec/js/dates.spec.js index 88be3f86..f57d9a12 100644 --- a/spec/js/dates.spec.js +++ b/spec/js/dates.spec.js @@ -135,6 +135,7 @@ describe("Dates", function(){ // 12-hour without padding expect(I18n.strftime(date, "%-I")).toEqual("7"); + expect(I18n.strftime(date, "%l")).toEqual("7"); // minutes without padding expect(I18n.strftime(date, "%-M")).toEqual("8"); @@ -188,7 +189,9 @@ describe("Dates", function(){ spyOn(date, "getTimezoneOffset").andReturn(345); expect(I18n.strftime(date, "%z")).toMatch(/^(\+|-)[\d]{4}$/); + expect(I18n.strftime(date, "%Z")).toMatch(/^(\+|-)[\d]{4}$/); expect(I18n.strftime(date, "%z")).toEqual("-0545"); + expect(I18n.strftime(date, "%Z")).toEqual("-0545"); }); it("formats date with positive time zone", function(){ @@ -198,22 +201,27 @@ describe("Dates", function(){ spyOn(date, "getTimezoneOffset").andReturn(-345); expect(I18n.strftime(date, "%z")).toMatch(/^(\+|-)[\d]{4}$/); + expect(I18n.strftime(date, "%Z")).toMatch(/^(\+|-)[\d]{4}$/); expect(I18n.strftime(date, "%z")).toEqual("+0545"); + expect(I18n.strftime(date, "%Z")).toEqual("+0545"); }); it("formats date with custom meridian", function(){ I18n.locale = "en-US"; var date = new Date(2009, 3, 26, 19, 35, 44); expect(I18n.strftime(date, "%p")).toEqual("pm"); + expect(I18n.strftime(date, "%P")).toEqual("pm"); }); it("formats date with meridian boundaries", function(){ I18n.locale = "en-US"; var date = new Date(2009, 3, 26, 0, 35, 44); expect(I18n.strftime(date, "%p")).toEqual("am"); + expect(I18n.strftime(date, "%P")).toEqual("am"); date = new Date(2009, 3, 26, 12, 35, 44); expect(I18n.strftime(date, "%p")).toEqual("pm"); + expect(I18n.strftime(date, "%P")).toEqual("pm"); }); it("formats date using 12-hours format", function(){ @@ -251,9 +259,11 @@ describe("Dates", function(){ var date = new Date(2009, 3, 26, 19, 35, 44); expect(I18n.strftime(date, "%p")).toEqual("de:PM"); + expect(I18n.strftime(date, "%P")).toEqual("de:pm"); date = new Date(2009, 3, 26, 7, 35, 44); expect(I18n.strftime(date, "%p")).toEqual("de:AM"); + expect(I18n.strftime(date, "%P")).toEqual("de:am"); }); it("fails to format invalid date", function(){ From d35dd5ed41a836153eac8c1fb2ea69256b6eca23 Mon Sep 17 00:00:00 2001 From: Kaitlin Jaffe Date: Tue, 4 Jun 2019 15:06:41 -0700 Subject: [PATCH 413/466] updated docs for P format --- README.md | 3 ++- app/assets/javascripts/i18n.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 25b75745..25ed0230 100644 --- a/README.md +++ b/README.md @@ -606,7 +606,8 @@ The accepted formats for `I18n.strftime` are: %-m - Month of the year (1..12) %M - Minute of the hour (00..59) %-M - Minute of the hour (0..59) - %p/%P - Meridian indicator (AM or PM) + %p - Meridian indicator (AM or PM) + %P - Meridian indicator (am or pm) %S - Second of the minute (00..60) %-S - Second of the minute (0..60) %w - Day of the week (Sunday is 0, 0..6) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 1a436782..aef5e67b 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -904,7 +904,8 @@ // %-m - Month of the year (1..12) // %M - Minute of the hour (00..59) // %-M - Minute of the hour (0..59) - // %p/%P - Meridian indicator (AM or PM) + // %p - Meridian indicator (AM or PM) + // %P - Meridian indicator (am or pm) // %S - Second of the minute (00..60) // %-S - Second of the minute (0..60) // %w - Day of the week (Sunday is 0, 0..6) From 4b2ff1fbc059032e664e31147fe6a2e91d373dda Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 6 Jun 2019 14:20:24 +0800 Subject: [PATCH 414/466] ^ Release 3.3.0 --- CHANGELOG.md | 12 ++++++++++-- lib/i18n/js/version.rb | 2 +- package.json | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f31b4d4f..c7059bc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added -- Support for `%P`, `%Z`, and `%l` strftime formats to match Ruby strftime +- Nothing ### Changed @@ -18,6 +18,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.3.0] - 2019-06-06 + +### Added + +- Support for `%P`, `%Z`, and `%l` strftime formats to match Ruby strftime + + ## [3.2.3] - 2019-05-24 ### Changed @@ -393,7 +400,8 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.2.3...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.3.0...HEAD +[3.3.0]: https://github.com/fnando/i18n-js/compare/v3.2.3...v3.3.0 [3.2.3]: https://github.com/fnando/i18n-js/compare/v3.2.2...v3.2.3 [3.2.2]: https://github.com/fnando/i18n-js/compare/v3.2.1...v3.2.2 [3.2.1]: https://github.com/fnando/i18n-js/compare/v3.2.0...v3.2.1 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index 6fdedf94..d34da4ea 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -2,6 +2,6 @@ module I18n module JS - VERSION = "3.2.3" + VERSION = "3.3.0" end end diff --git a/package.json b/package.json index 7afdfd0a..5f9957a1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "i18n-js", - "version": "3.2.2", + "version": "3.3.0", "description": "A javascript library similar to Ruby on Rails i18n gem", "author": "Nando Vieira", "license": "MIT", From fc83dfff7af3ea250973f91b918f3967ebb64b88 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 6 Jun 2019 14:25:08 +0800 Subject: [PATCH 415/466] ~ Add badge for NPM package [ci skip] --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 25ed0230..0a906013 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # I18n.js [![Gem Version](http://img.shields.io/gem/v/i18n-js.svg?style=flat-square)](http://badge.fury.io/rb/i18n-js) +[![npm](https://img.shields.io/npm/v/i18n-js.svg?style=flat-square)](https://www.npmjs.com/package/i18n-js) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT) [![Build Status](http://img.shields.io/travis/fnando/i18n-js.svg?style=flat-square)](https://travis-ci.org/fnando/i18n-js) From 1136345dfdc0303f53b5017d0eb89ab11c3019c8 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 6 Jun 2019 14:31:38 +0800 Subject: [PATCH 416/466] ~ Specify the badge source below badges [ci skip] --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 0a906013..3a545327 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ [![Gitter](https://img.shields.io/badge/gitter-join%20chat-1dce73.svg?style=flat-square)](https://gitter.im/fnando/i18n-js) +The above badges are generated by https://shields.io/ + It's a small library to provide the Rails I18n translations on the JavaScript. Features: From f37d6ce85e142f38aa6c0d98de83d59c77c85101 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 6 Jun 2019 14:32:31 +0800 Subject: [PATCH 417/466] ~ Forgot to use backquote syntax [ci skip] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a545327..ba14c92a 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![Gitter](https://img.shields.io/badge/gitter-join%20chat-1dce73.svg?style=flat-square)](https://gitter.im/fnando/i18n-js) -The above badges are generated by https://shields.io/ +> The above badges are generated by https://shields.io/ It's a small library to provide the Rails I18n translations on the JavaScript. From 9b89dde929d0a0ec337726e672330081f61a396e Mon Sep 17 00:00:00 2001 From: mcamara Date: Thu, 30 May 2019 23:55:11 +0300 Subject: [PATCH 418/466] Added ability to import another js library in an es6 way --- lib/i18n/js/formatters/base.rb | 3 ++- lib/i18n/js/formatters/js.rb | 3 ++- lib/i18n/js/segment.rb | 6 ++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/i18n/js/formatters/base.rb b/lib/i18n/js/formatters/base.rb index 4a6c4b69..45a0b3a4 100644 --- a/lib/i18n/js/formatters/base.rb +++ b/lib/i18n/js/formatters/base.rb @@ -2,10 +2,11 @@ module I18n module JS module Formatters class Base - def initialize(js_extend: false, namespace: nil, pretty_print: false) + def initialize(js_extend: false, namespace: nil, pretty_print: false, import: nil) @js_extend = js_extend @namespace = namespace @pretty_print = pretty_print + @import = import end protected diff --git a/lib/i18n/js/formatters/js.rb b/lib/i18n/js/formatters/js.rb index d03cabdc..f1e05f12 100644 --- a/lib/i18n/js/formatters/js.rb +++ b/lib/i18n/js/formatters/js.rb @@ -15,7 +15,8 @@ def format(translations) protected def header - %(#{@namespace}.translations || (#{@namespace}.translations = {});\n) + text = @import ? %(import I18n from '#{@import}';\n) : '' + text + %(#{@namespace}.translations || (#{@namespace}.translations = {});\n) end def line(locale, translations) diff --git a/lib/i18n/js/segment.rb b/lib/i18n/js/segment.rb index 84a1471a..28cd2334 100644 --- a/lib/i18n/js/segment.rb +++ b/lib/i18n/js/segment.rb @@ -7,7 +7,7 @@ module JS # Class which enscapulates a translations hash and outputs a single JSON translation file class Segment - OPTIONS = [:namespace, :pretty_print, :js_extend, :sort_translation_keys, :json_only].freeze + OPTIONS = [:namespace, :pretty_print, :js_extend, :import, :sort_translation_keys, :json_only].freeze LOCALE_INTERPOLATOR = /%\{locale\}/ attr_reader *([:file, :translations] | OPTIONS) @@ -24,6 +24,7 @@ def initialize(file, translations, options = {}) @namespace = options[:namespace] || 'I18n' @pretty_print = !!options[:pretty_print] @js_extend = options.key?(:js_extend) ? !!options[:js_extend] : true + @import = options.key?(:import) ? options[:import] : nil @sort_translation_keys = options.key?(:sort_translation_keys) ? !!options[:sort_translation_keys] : true @json_only = options.key?(:json_only) ? !!options[:json_only] : false end @@ -68,7 +69,8 @@ def formatter def formatter_options { js_extend: @js_extend, namespace: @namespace, - pretty_print: @pretty_print } + pretty_print: @pretty_print, + import: @import } end end end From ffd39fa53358b65177716bfcf5f41954b5b36c11 Mon Sep 17 00:00:00 2001 From: mcamara Date: Mon, 16 Sep 2019 19:46:45 +0200 Subject: [PATCH 419/466] Added tests and updated readme --- README.md | 32 +++++++++++++++---- ...ith_namespace_import_and_pretty_print.yml} | 1 + spec/ruby/i18n/js_spec.rb | 5 +-- 3 files changed, 29 insertions(+), 9 deletions(-) rename spec/fixtures/{js_file_with_namespace_and_pretty_print.yml => js_file_with_namespace_import_and_pretty_print.yml} (81%) diff --git a/README.md b/README.md index ba14c92a..2505ea9d 100644 --- a/README.md +++ b/README.md @@ -247,6 +247,25 @@ MyNamespace.translations || (MyNamespace.translations = {}); MyNamespace.translations["en"] = { ... } ``` +### Import I18n library in the translations file + +Setting the `import: 'i18n-js'` option will add `import I18n from 'i18n-js';`. The name of the package to import can be changed. +This can be useful to use this gem with the [i18n-js](https://www.npmjs.com/package/i18n-js) npm package, which is quite useful to use it with webpack. + +For example: + +```yaml +translations: +- file: "public/javascripts/i18n/translations.js" + import: 'i18n-js' +``` + +will create: + +``` +import I18n from 'i18n-js'; +I18n.translations || (I18n.translations = {}); +``` #### Pretty Print @@ -282,8 +301,7 @@ by hand or using your favorite programming language. More info below. #### Via NPM with webpack and CommonJS -Add the following line to your package.json dependencies -where version is the version you want +Add the following line to your package.json dependencies where version is the version you want ```javascript "i18n-js": "{version_constraint}" @@ -622,8 +640,8 @@ The accepted formats for `I18n.strftime` are: Check out `spec/*.spec.js` files for more examples! #### Using pluralization and number formatting together -Sometimes you might want to display translation with formatted number, like adding thousand delimiters to displayed number -You can do this: +Sometimes you might want to display translation with formatted number, like adding thousand delimiters to displayed number +You can do this: ```json { "en": { @@ -794,7 +812,7 @@ Due to the design of `sprockets`: This means that new locale files will not be detected, and so they will not trigger a i18n-js refresh. There are a few approaches to work around this: 1. You can force i18n-js to update its translations by completely clearing the assets cache. Use one of the following: - + ```bash $ rake assets:clobber # Or, with older versions of Rails: @@ -818,7 +836,7 @@ or similar commands. If you are precompiling assets on the target machine(s), c ### Translations in JS are not updated when Sprockets not loaded before this gem The "rails engine" declaration will try to detect existence of "sprockets" before adding the initailizer -If sprockets is loaded after this gem, the preprocessor for +If sprockets is loaded after this gem, the preprocessor for making JS translations file cache to depend on content of locale files will not be hooked. So ensure sprockets is loaded before this gem like moving entry of sprockets in Gemfile or adding "require" statements for sprockets somewhere. @@ -826,7 +844,7 @@ So ensure sprockets is loaded before this gem like moving entry of sprockets in ### JS `I18n.toCurrency` & `I18n.toNumber` cannot handle large integers -The above methods use `toFixed` and it only supports 53 bit integers. +The above methods use `toFixed` and it only supports 53 bit integers. Ref: http://2ality.com/2012/07/large-integers.html Feel free to find & discuss possible solution(s) at issue [#511](https://github.com/fnando/i18n-js/issues/511) diff --git a/spec/fixtures/js_file_with_namespace_and_pretty_print.yml b/spec/fixtures/js_file_with_namespace_import_and_pretty_print.yml similarity index 81% rename from spec/fixtures/js_file_with_namespace_and_pretty_print.yml rename to spec/fixtures/js_file_with_namespace_import_and_pretty_print.yml index 56fe52bc..73db85af 100644 --- a/spec/fixtures/js_file_with_namespace_and_pretty_print.yml +++ b/spec/fixtures/js_file_with_namespace_import_and_pretty_print.yml @@ -5,3 +5,4 @@ translations: only: '*' namespace: "Foo" pretty_print: true + import: 'random-library' diff --git a/spec/ruby/i18n/js_spec.rb b/spec/ruby/i18n/js_spec.rb index 33e83291..c83c8d85 100644 --- a/spec/ruby/i18n/js_spec.rb +++ b/spec/ruby/i18n/js_spec.rb @@ -351,11 +351,11 @@ end end - context "namespace and pretty_print options" do + context "namespace, import and pretty_print options" do before do stub_const('I18n::JS::DEFAULT_EXPORT_DIR_PATH', temp_path) - set_config "js_file_with_namespace_and_pretty_print.yml" + set_config "js_file_with_namespace_import_and_pretty_print.yml" end it "exports with defined locale as fallback when enabled" do @@ -364,6 +364,7 @@ output = File.read(File.join(I18n::JS.export_i18n_js_dir_path, "en.js")) expect(output).to match(/^#{ < Date: Wed, 9 Oct 2019 10:49:08 +0200 Subject: [PATCH 420/466] Changed import to prefix --- README.md | 23 +++++++++++-------- lib/i18n/js/formatters/base.rb | 4 ++-- lib/i18n/js/formatters/js.rb | 2 +- lib/i18n/js/segment.rb | 6 ++--- ...ith_namespace_prefix_and_pretty_print.yml} | 2 +- spec/ruby/i18n/js_spec.rb | 6 ++--- 6 files changed, 23 insertions(+), 20 deletions(-) rename spec/fixtures/{js_file_with_namespace_import_and_pretty_print.yml => js_file_with_namespace_prefix_and_pretty_print.yml} (70%) diff --git a/README.md b/README.md index 2505ea9d..b4bba2ae 100644 --- a/README.md +++ b/README.md @@ -247,17 +247,19 @@ MyNamespace.translations || (MyNamespace.translations = {}); MyNamespace.translations["en"] = { ... } ``` -### Import I18n library in the translations file -Setting the `import: 'i18n-js'` option will add `import I18n from 'i18n-js';`. The name of the package to import can be changed. +### Adding a line at the beggining of the translations file (useful for imports) + +Setting the `prefix: "import I18n from 'i18n-js';\n"` option will add the line at the beggining of the resultant translation file. This can be useful to use this gem with the [i18n-js](https://www.npmjs.com/package/i18n-js) npm package, which is quite useful to use it with webpack. +The user should provide the semi-colon and the newline character if needed. For example: ```yaml translations: - file: "public/javascripts/i18n/translations.js" - import: 'i18n-js' + prefix: "import I18n from 'i18n-js';\n" ``` will create: @@ -265,7 +267,7 @@ will create: ``` import I18n from 'i18n-js'; I18n.translations || (I18n.translations = {}); -``` + #### Pretty Print @@ -301,7 +303,8 @@ by hand or using your favorite programming language. More info below. #### Via NPM with webpack and CommonJS -Add the following line to your package.json dependencies where version is the version you want +Add the following line to your package.json dependencies +where version is the version you want ```javascript "i18n-js": "{version_constraint}" @@ -640,8 +643,8 @@ The accepted formats for `I18n.strftime` are: Check out `spec/*.spec.js` files for more examples! #### Using pluralization and number formatting together -Sometimes you might want to display translation with formatted number, like adding thousand delimiters to displayed number -You can do this: +Sometimes you might want to display translation with formatted number, like adding thousand delimiters to displayed number +You can do this: ```json { "en": { @@ -812,7 +815,7 @@ Due to the design of `sprockets`: This means that new locale files will not be detected, and so they will not trigger a i18n-js refresh. There are a few approaches to work around this: 1. You can force i18n-js to update its translations by completely clearing the assets cache. Use one of the following: - + ```bash $ rake assets:clobber # Or, with older versions of Rails: @@ -836,7 +839,7 @@ or similar commands. If you are precompiling assets on the target machine(s), c ### Translations in JS are not updated when Sprockets not loaded before this gem The "rails engine" declaration will try to detect existence of "sprockets" before adding the initailizer -If sprockets is loaded after this gem, the preprocessor for +If sprockets is loaded after this gem, the preprocessor for making JS translations file cache to depend on content of locale files will not be hooked. So ensure sprockets is loaded before this gem like moving entry of sprockets in Gemfile or adding "require" statements for sprockets somewhere. @@ -844,7 +847,7 @@ So ensure sprockets is loaded before this gem like moving entry of sprockets in ### JS `I18n.toCurrency` & `I18n.toNumber` cannot handle large integers -The above methods use `toFixed` and it only supports 53 bit integers. +The above methods use `toFixed` and it only supports 53 bit integers. Ref: http://2ality.com/2012/07/large-integers.html Feel free to find & discuss possible solution(s) at issue [#511](https://github.com/fnando/i18n-js/issues/511) diff --git a/lib/i18n/js/formatters/base.rb b/lib/i18n/js/formatters/base.rb index 45a0b3a4..9738208f 100644 --- a/lib/i18n/js/formatters/base.rb +++ b/lib/i18n/js/formatters/base.rb @@ -2,11 +2,11 @@ module I18n module JS module Formatters class Base - def initialize(js_extend: false, namespace: nil, pretty_print: false, import: nil) + def initialize(js_extend: false, namespace: nil, pretty_print: false, prefix: nil) @js_extend = js_extend @namespace = namespace @pretty_print = pretty_print - @import = import + @prefix = prefix end protected diff --git a/lib/i18n/js/formatters/js.rb b/lib/i18n/js/formatters/js.rb index f1e05f12..db0d9428 100644 --- a/lib/i18n/js/formatters/js.rb +++ b/lib/i18n/js/formatters/js.rb @@ -15,7 +15,7 @@ def format(translations) protected def header - text = @import ? %(import I18n from '#{@import}';\n) : '' + text = @prefix || '' text + %(#{@namespace}.translations || (#{@namespace}.translations = {});\n) end diff --git a/lib/i18n/js/segment.rb b/lib/i18n/js/segment.rb index 28cd2334..5b00e350 100644 --- a/lib/i18n/js/segment.rb +++ b/lib/i18n/js/segment.rb @@ -7,7 +7,7 @@ module JS # Class which enscapulates a translations hash and outputs a single JSON translation file class Segment - OPTIONS = [:namespace, :pretty_print, :js_extend, :import, :sort_translation_keys, :json_only].freeze + OPTIONS = [:namespace, :pretty_print, :js_extend, :prefix, :sort_translation_keys, :json_only].freeze LOCALE_INTERPOLATOR = /%\{locale\}/ attr_reader *([:file, :translations] | OPTIONS) @@ -24,7 +24,7 @@ def initialize(file, translations, options = {}) @namespace = options[:namespace] || 'I18n' @pretty_print = !!options[:pretty_print] @js_extend = options.key?(:js_extend) ? !!options[:js_extend] : true - @import = options.key?(:import) ? options[:import] : nil + @prefix = options.key?(:prefix) ? options[:prefix] : nil @sort_translation_keys = options.key?(:sort_translation_keys) ? !!options[:sort_translation_keys] : true @json_only = options.key?(:json_only) ? !!options[:json_only] : false end @@ -70,7 +70,7 @@ def formatter_options { js_extend: @js_extend, namespace: @namespace, pretty_print: @pretty_print, - import: @import } + prefix: @prefix } end end end diff --git a/spec/fixtures/js_file_with_namespace_import_and_pretty_print.yml b/spec/fixtures/js_file_with_namespace_prefix_and_pretty_print.yml similarity index 70% rename from spec/fixtures/js_file_with_namespace_import_and_pretty_print.yml rename to spec/fixtures/js_file_with_namespace_prefix_and_pretty_print.yml index 73db85af..b5e1da16 100644 --- a/spec/fixtures/js_file_with_namespace_import_and_pretty_print.yml +++ b/spec/fixtures/js_file_with_namespace_prefix_and_pretty_print.yml @@ -5,4 +5,4 @@ translations: only: '*' namespace: "Foo" pretty_print: true - import: 'random-library' + prefix: "import random from 'random-library';\n" diff --git a/spec/ruby/i18n/js_spec.rb b/spec/ruby/i18n/js_spec.rb index c83c8d85..40572cd0 100644 --- a/spec/ruby/i18n/js_spec.rb +++ b/spec/ruby/i18n/js_spec.rb @@ -351,11 +351,11 @@ end end - context "namespace, import and pretty_print options" do + context "namespace, prefix and pretty_print options" do before do stub_const('I18n::JS::DEFAULT_EXPORT_DIR_PATH', temp_path) - set_config "js_file_with_namespace_import_and_pretty_print.yml" + set_config "js_file_with_namespace_prefix_and_pretty_print.yml" end it "exports with defined locale as fallback when enabled" do @@ -364,7 +364,7 @@ output = File.read(File.join(I18n::JS.export_i18n_js_dir_path, "en.js")) expect(output).to match(/^#{ < Date: Thu, 10 Oct 2019 15:19:02 +0800 Subject: [PATCH 421/466] * Test againest I18n 1.7 --- .travis.yml | 1 + Appraisals | 4 ++++ gemfiles/i18n_1_7.gemfile | 7 +++++++ 3 files changed, 12 insertions(+) create mode 100644 gemfiles/i18n_1_7.gemfile diff --git a/.travis.yml b/.travis.yml index 6edef22c..b0d79322 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,7 @@ gemfile: - gemfiles/i18n_1_4.gemfile - gemfiles/i18n_1_5.gemfile - gemfiles/i18n_1_6.gemfile + - gemfiles/i18n_1_7.gemfile matrix: fast_finish: true allow_failures: diff --git a/Appraisals b/Appraisals index 5dbacac2..193f2e4f 100644 --- a/Appraisals +++ b/Appraisals @@ -34,3 +34,7 @@ end appraise "i18n_1_6" do gem "i18n", "~> 1.6.0" end + +appraise "i18n_1_7" do + gem "i18n", "~> 1.7.0" +end diff --git a/gemfiles/i18n_1_7.gemfile b/gemfiles/i18n_1_7.gemfile new file mode 100644 index 00000000..576d9e32 --- /dev/null +++ b/gemfiles/i18n_1_7.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "i18n", "~> 1.7.0" + +gemspec path: "../" From 6dbfa2590e53132b193c1b19a36a93d962491bbb Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 15 Oct 2019 10:16:10 +0800 Subject: [PATCH 422/466] ^ Release 3.4.0 --- CHANGELOG.md | 11 ++++++++++- lib/i18n/js/version.rb | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7059bc1..b7dace0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.4.0] - 2019-10-15 + +### Added + +- Allow `prefix` to be added to generated translations files + (PR: https://github.com/fnando/i18n-js/pull/549) + + ## [3.3.0] - 2019-06-06 ### Added @@ -400,7 +408,8 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.3.0...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.4.0...HEAD +[3.4.0]: https://github.com/fnando/i18n-js/compare/v3.3.0...v3.4.0 [3.3.0]: https://github.com/fnando/i18n-js/compare/v3.2.3...v3.3.0 [3.2.3]: https://github.com/fnando/i18n-js/compare/v3.2.2...v3.2.3 [3.2.2]: https://github.com/fnando/i18n-js/compare/v3.2.1...v3.2.2 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index d34da4ea..19b50713 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -2,6 +2,6 @@ module I18n module JS - VERSION = "3.3.0" + VERSION = "3.4.0" end end From afb4be4669b30a8e15219bae867d07e3cf8d2428 Mon Sep 17 00:00:00 2001 From: Alex Ghiculescu Date: Tue, 15 Oct 2019 10:58:33 -0500 Subject: [PATCH 423/466] Fix merging of plural keys to work with fallbacks that aren't overridden https://github.com/fnando/i18n-js/pull/472 added a regression when using fallbacks, this PR fixes it. --- lib/i18n/js/utils.rb | 6 ++--- spec/fixtures/locales.yml | 8 ++++++ .../merge_plurals_with_no_overrides.yml | 4 +++ spec/ruby/i18n/js_spec.rb | 26 ++++++++++++++++++- 4 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 spec/fixtures/merge_plurals_with_no_overrides.yml diff --git a/lib/i18n/js/utils.rb b/lib/i18n/js/utils.rb index e66ae864..40be13d0 100644 --- a/lib/i18n/js/utils.rb +++ b/lib/i18n/js/utils.rb @@ -6,13 +6,13 @@ module Utils # Based on deep_merge by Stefan Rusterholz, see . # This method is used to handle I18n fallbacks. Given two equivalent path nodes in two locale trees: # 1. If the node in the current locale appears to be an I18n pluralization (:one, :other, etc.), - # use the node as-is without merging. This prevents mixing locales with different pluralization schemes. - # 2. Else if both nodes are Hashes, combine (merge) the key-value pairs of the two nodes into one, + # use the node, but merge in any missing/non-nil keys from the fallback (default) locale. + # 2. Else if both nodes are Hashes, combine (merge) the key-value pairs of the two nodes into one, # prioritizing the current locale. # 3. Else if either node is nil, use the other node. MERGER = proc do |_key, v1, v2| if Hash === v2 && (v2.keys - PLURAL_KEYS).empty? - v2 + v2.merge(v1, &MERGER) elsif Hash === v1 && Hash === v2 v1.merge(v2, &MERGER) else diff --git a/spec/fixtures/locales.yml b/spec/fixtures/locales.yml index 16269f69..d2f51b53 100755 --- a/spec/fixtures/locales.yml +++ b/spec/fixtures/locales.yml @@ -40,6 +40,10 @@ en: merge_plurals: one: Apple other: Apples + merge_plurals_with_no_overrides: + zero: "No Apple" + one: Apple + other: Apples de: fallback_test: "Erfolg" @@ -88,6 +92,10 @@ fr: zero: Pomme one: Pomme other: Pommes + merge_plurals_with_no_overrides: + zero: + one: + other: ja: admin: diff --git a/spec/fixtures/merge_plurals_with_no_overrides.yml b/spec/fixtures/merge_plurals_with_no_overrides.yml new file mode 100644 index 00000000..c32468d2 --- /dev/null +++ b/spec/fixtures/merge_plurals_with_no_overrides.yml @@ -0,0 +1,4 @@ +translations: + - file: "tmp/i18n-js/merge_plurals_with_no_overrides.js" + only: + - "*.merge_plurals_with_no_overrides" diff --git a/spec/ruby/i18n/js_spec.rb b/spec/ruby/i18n/js_spec.rb index 40572cd0..fb825bd1 100644 --- a/spec/ruby/i18n/js_spec.rb +++ b/spec/ruby/i18n/js_spec.rb @@ -598,7 +598,7 @@ it "exports with the keys sorted" do expect(subject).to eq < Date: Tue, 15 Oct 2019 11:49:13 -0500 Subject: [PATCH 424/466] better handling of locales where the pluralization keys differ from the default --- lib/i18n/js/utils.rb | 7 +++++-- spec/fixtures/locales.yml | 9 +++++++++ spec/ruby/i18n/js_spec.rb | 2 ++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/i18n/js/utils.rb b/lib/i18n/js/utils.rb index 40be13d0..b6f5dd24 100644 --- a/lib/i18n/js/utils.rb +++ b/lib/i18n/js/utils.rb @@ -10,13 +10,16 @@ module Utils # 2. Else if both nodes are Hashes, combine (merge) the key-value pairs of the two nodes into one, # prioritizing the current locale. # 3. Else if either node is nil, use the other node. + PLURAL_MERGER = proc do |_key, v1, v2| + v1 || v2 + end MERGER = proc do |_key, v1, v2| if Hash === v2 && (v2.keys - PLURAL_KEYS).empty? - v2.merge(v1, &MERGER) + v2.merge(v1, &PLURAL_MERGER).slice(*v2.keys) elsif Hash === v1 && Hash === v2 v1.merge(v2, &MERGER) else - v2.nil? ? v1 : v2 + v2 || v1 end end diff --git a/spec/fixtures/locales.yml b/spec/fixtures/locales.yml index d2f51b53..e7efdf68 100755 --- a/spec/fixtures/locales.yml +++ b/spec/fixtures/locales.yml @@ -45,6 +45,13 @@ en: one: Apple other: Apples +ru: + merge_plurals_with_no_overrides: + one: кот + few: кошек + many: кошка + other: кошек + de: fallback_test: "Erfolg" null_test: ~ @@ -92,6 +99,8 @@ fr: zero: Pomme one: Pomme other: Pommes + +en-US: merge_plurals_with_no_overrides: zero: one: diff --git a/spec/ruby/i18n/js_spec.rb b/spec/ruby/i18n/js_spec.rb index fb825bd1..b4d3bdcb 100644 --- a/spec/ruby/i18n/js_spec.rb +++ b/spec/ruby/i18n/js_spec.rb @@ -680,8 +680,10 @@ I18n.translations || (I18n.translations = {}); I18n.translations[\"de\"] = I18n.extend((I18n.translations[\"de\"] || {}), {\"merge_plurals_with_no_overrides\":{\"one\":\"Apple\",\"other\":\"Apples\",\"zero\":\"No Apple\"}}); I18n.translations[\"en\"] = I18n.extend((I18n.translations[\"en\"] || {}), {\"merge_plurals_with_no_overrides\":{\"one\":\"Apple\",\"other\":\"Apples\",\"zero\":\"No Apple\"}}); +I18n.translations[\"en-US\"] = I18n.extend((I18n.translations[\"en-US\"] || {}), {\"merge_plurals_with_no_overrides\":{\"one\":\"Apple\",\"other\":\"Apples\",\"zero\":\"No Apple\"}}); I18n.translations[\"fr\"] = I18n.extend((I18n.translations[\"fr\"] || {}), {\"merge_plurals_with_no_overrides\":{\"one\":\"Apple\",\"other\":\"Apples\",\"zero\":\"No Apple\"}}); I18n.translations[\"ja\"] = I18n.extend((I18n.translations[\"ja\"] || {}), {\"merge_plurals_with_no_overrides\":{\"one\":\"Apple\",\"other\":\"Apples\",\"zero\":\"No Apple\"}}); +I18n.translations[\"ru\"] = I18n.extend((I18n.translations[\"ru\"] || {}), {\"merge_plurals_with_no_overrides\":{\"few\":\"кошек\",\"many\":\"кошка\",\"one\":\"кот\",\"other\":\"кошек\"}}); EOS end end From 1cbf1468d8ee8795bb45eddc75f46c102ed6eb56 Mon Sep 17 00:00:00 2001 From: Alex Ghiculescu Date: Tue, 15 Oct 2019 12:22:59 -0500 Subject: [PATCH 425/466] Hash#slice was only added in Ruby 2.5 --- lib/i18n/js/utils.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/i18n/js/utils.rb b/lib/i18n/js/utils.rb index b6f5dd24..07b9429e 100644 --- a/lib/i18n/js/utils.rb +++ b/lib/i18n/js/utils.rb @@ -15,7 +15,7 @@ module Utils end MERGER = proc do |_key, v1, v2| if Hash === v2 && (v2.keys - PLURAL_KEYS).empty? - v2.merge(v1, &PLURAL_MERGER).slice(*v2.keys) + slice(v2.merge(v1, &PLURAL_MERGER), v2.keys) elsif Hash === v1 && Hash === v2 v1.merge(v2, &MERGER) else @@ -27,6 +27,14 @@ module Utils v.kind_of?(Hash) ? (v.delete_if(&HASH_NIL_VALUE_CLEANER_PROC); false) : v.nil? end + def self.slice(hash, keys) + if hash.respond_to?(:slice) # ruby 2.5 onwards + hash.slice(*keys) + else + hash.select {|key, _| keys.include?(key)} + end + end + def self.strip_keys_with_nil_values(hash) hash.dup.delete_if(&HASH_NIL_VALUE_CLEANER_PROC) end From dd6135c1f2d9070adba0441e7f5f60bde5ee5e76 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 16 Oct 2019 09:23:43 +0800 Subject: [PATCH 426/466] * Turn off noisy TravisBuddy [ci skip] --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b0d79322..64835870 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,6 +33,3 @@ matrix: fast_finish: true allow_failures: - rvm: ruby-head -notifications: - webhooks: https://www.travisbuddy.com/ - on_success: never From 64d8c5a050e51078648d3b9f6bbd18b1aaa5b886 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 16 Oct 2019 09:24:21 +0800 Subject: [PATCH 427/466] ~ Fix typo in README [ci skip] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b4bba2ae..f3a09267 100644 --- a/README.md +++ b/README.md @@ -248,7 +248,7 @@ MyNamespace.translations["en"] = { ... } ``` -### Adding a line at the beggining of the translations file (useful for imports) +### Adding a line at the beginning of the translations file (useful for imports) Setting the `prefix: "import I18n from 'i18n-js';\n"` option will add the line at the beggining of the resultant translation file. This can be useful to use this gem with the [i18n-js](https://www.npmjs.com/package/i18n-js) npm package, which is quite useful to use it with webpack. From d209e3362c4c05ca137c11ab63cc503a2f61062b Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 16 Oct 2019 09:29:35 +0800 Subject: [PATCH 428/466] * Turn on TravisBuddy again with less noisy mode [ci skip] --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 64835870..129bddaa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,3 +33,5 @@ matrix: fast_finish: true allow_failures: - rvm: ruby-head +notifications: + webhooks: https://www.travisbuddy.com/?insertMode=update From 0c5dc6e413ed8b45a3cb7535c8b66dace45010d8 Mon Sep 17 00:00:00 2001 From: Alex Ghiculescu Date: Thu, 17 Oct 2019 10:02:27 -0500 Subject: [PATCH 429/466] + test --- spec/fixtures/locales.yml | 8 +++++ .../merge_plurals_with_partial_overrides.yml | 4 +++ spec/ruby/i18n/js_spec.rb | 29 ++++++++++++++++++- 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/merge_plurals_with_partial_overrides.yml diff --git a/spec/fixtures/locales.yml b/spec/fixtures/locales.yml index e7efdf68..ec565f87 100755 --- a/spec/fixtures/locales.yml +++ b/spec/fixtures/locales.yml @@ -44,6 +44,9 @@ en: zero: "No Apple" one: Apple other: Apples + merge_plurals_with_partial_overrides: + one: Cat + other: Cats ru: merge_plurals_with_no_overrides: @@ -105,6 +108,11 @@ en-US: zero: one: other: + merge_plurals_with_partial_overrides: + one: Cat + few: + many: + other: ja: admin: diff --git a/spec/fixtures/merge_plurals_with_partial_overrides.yml b/spec/fixtures/merge_plurals_with_partial_overrides.yml new file mode 100644 index 00000000..60aa495d --- /dev/null +++ b/spec/fixtures/merge_plurals_with_partial_overrides.yml @@ -0,0 +1,4 @@ +translations: + - file: "tmp/i18n-js/merge_plurals_with_partial_overrides.js" + only: + - "*.merge_plurals_with_partial_overrides" diff --git a/spec/ruby/i18n/js_spec.rb b/spec/ruby/i18n/js_spec.rb index b4d3bdcb..ed550c20 100644 --- a/spec/ruby/i18n/js_spec.rb +++ b/spec/ruby/i18n/js_spec.rb @@ -598,7 +598,7 @@ it "exports with the keys sorted" do expect(subject).to eq < Date: Fri, 1 Nov 2019 11:38:50 +0800 Subject: [PATCH 430/466] ^ Release 3.4.1 --- CHANGELOG.md | 18 ++++++++++++++---- lib/i18n/js/version.rb | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7dace0e..cee3b6e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,11 +18,19 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.4.1] - 2019-11-01 + +### Fixed + +- [Ruby] Fix merging of plural keys to work with fallbacks that aren't overridden + (PR: https://github.com/fnando/i18n-js/pull/551) + + ## [3.4.0] - 2019-10-15 ### Added -- Allow `prefix` to be added to generated translations files +- [Ruby] Allow `prefix` to be added to generated translations files (PR: https://github.com/fnando/i18n-js/pull/549) @@ -30,14 +38,15 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added -- Support for `%P`, `%Z`, and `%l` strftime formats to match Ruby strftime +- [JS] Support for `%P`, `%Z`, and `%l` strftime formats to match Ruby strftime + (PR: https://github.com/fnando/i18n-js/pull/537) ## [3.2.3] - 2019-05-24 ### Changed -- Allow rails 6 to be used with this gem +- [Ruby] Allow rails 6 to be used with this gem (PR: https://github.com/fnando/i18n-js/pull/536) @@ -408,7 +417,8 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.4.0...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.4.1...HEAD +[3.4.1]: https://github.com/fnando/i18n-js/compare/v3.4.0...v3.4.1 [3.4.0]: https://github.com/fnando/i18n-js/compare/v3.3.0...v3.4.0 [3.3.0]: https://github.com/fnando/i18n-js/compare/v3.2.3...v3.3.0 [3.2.3]: https://github.com/fnando/i18n-js/compare/v3.2.2...v3.2.3 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index 19b50713..062adf8a 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -2,6 +2,6 @@ module I18n module JS - VERSION = "3.4.0" + VERSION = "3.4.1" end end From 41dd9c98e2723b59e17d49a4f1f8c39c1aff334c Mon Sep 17 00:00:00 2001 From: Jochen Lutz Date: Fri, 8 Nov 2019 09:36:25 +0100 Subject: [PATCH 431/466] added strftime format %k --- CHANGELOG.md | 2 +- README.md | 2 +- app/assets/javascripts/i18n.js | 3 ++- spec/js/dates.spec.js | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cee3b6e9..758b2f1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added -- Nothing +- Support for `%k` strftime format to match Ruby strftime ### Changed diff --git a/README.md b/README.md index f3a09267..51c2fdbb 100644 --- a/README.md +++ b/README.md @@ -623,7 +623,7 @@ The accepted formats for `I18n.strftime` are: %d - Day of the month (01..31) %-d - Day of the month (1..31) %H - Hour of the day, 24-hour clock (00..23) - %-H - Hour of the day, 24-hour clock (0..23) + %-H/%k - Hour of the day, 24-hour clock (0..23) %I - Hour of the day, 12-hour clock (01..12) %-I/%l - Hour of the day, 12-hour clock (1..12) %m - Month of the year (01..12) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index aef5e67b..3c821bad 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -897,7 +897,7 @@ // %d - Day of the month (01..31) // %-d - Day of the month (1..31) // %H - Hour of the day, 24-hour clock (00..23) - // %-H - Hour of the day, 24-hour clock (0..23) + // %-H/%k - Hour of the day, 24-hour clock (0..23) // %I - Hour of the day, 12-hour clock (01..12) // %-I/%l - Hour of the day, 12-hour clock (1..12) // %m - Month of the year (01..12) @@ -961,6 +961,7 @@ format = format.replace("%-d", day); format = format.replace("%H", padding(hour)); format = format.replace("%-H", hour); + format = format.replace("%k", hour); format = format.replace("%I", padding(hour12)); format = format.replace("%-I", hour12); format = format.replace("%l", hour12); diff --git a/spec/js/dates.spec.js b/spec/js/dates.spec.js index f57d9a12..a918d7ff 100644 --- a/spec/js/dates.spec.js +++ b/spec/js/dates.spec.js @@ -132,6 +132,7 @@ describe("Dates", function(){ // 24-hour without padding expect(I18n.strftime(date, "%-H")).toEqual("7"); + expect(I18n.strftime(date, "%k")).toEqual("7"); // 12-hour without padding expect(I18n.strftime(date, "%-I")).toEqual("7"); From faf66a3caaf79d740a26fa0ecad37c0b4bbf5a97 Mon Sep 17 00:00:00 2001 From: Alex Ghiculescu Date: Fri, 8 Nov 2019 09:41:14 -0600 Subject: [PATCH 432/466] Fix regression in 3.4.1 Better handling of keys that are a string in some locales, and a hash in another. --- lib/i18n/js/utils.rb | 10 ++++++---- spec/fixtures/locales.yml | 13 +++++++++++++ spec/fixtures/millions.yml | 4 ++++ spec/ruby/i18n/js_spec.rb | 32 +++++++++++++++++++++++++++++++- 4 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 spec/fixtures/millions.yml diff --git a/lib/i18n/js/utils.rb b/lib/i18n/js/utils.rb index 07b9429e..1f7dfb9f 100644 --- a/lib/i18n/js/utils.rb +++ b/lib/i18n/js/utils.rb @@ -14,10 +14,12 @@ module Utils v1 || v2 end MERGER = proc do |_key, v1, v2| - if Hash === v2 && (v2.keys - PLURAL_KEYS).empty? - slice(v2.merge(v1, &PLURAL_MERGER), v2.keys) - elsif Hash === v1 && Hash === v2 - v1.merge(v2, &MERGER) + if Hash === v1 && Hash === v2 + if (v2.keys - PLURAL_KEYS).empty? + slice(v2.merge(v1, &PLURAL_MERGER), v2.keys) + else + v1.merge(v2, &MERGER) + end else v2 || v1 end diff --git a/spec/fixtures/locales.yml b/spec/fixtures/locales.yml index ec565f87..1ac14f9b 100755 --- a/spec/fixtures/locales.yml +++ b/spec/fixtures/locales.yml @@ -1,5 +1,9 @@ en: number: + human: + decimal_units: + units: + million: Million format: separator: "." delimiter: "," @@ -48,6 +52,15 @@ en: one: Cat other: Cats +es: + number: + human: + decimal_units: + units: + million: + one: millón + other: millones + ru: merge_plurals_with_no_overrides: one: кот diff --git a/spec/fixtures/millions.yml b/spec/fixtures/millions.yml new file mode 100644 index 00000000..e19afc6c --- /dev/null +++ b/spec/fixtures/millions.yml @@ -0,0 +1,4 @@ +translations: + - file: "tmp/i18n-js/millions.js" + only: + - "*.number.human.decimal_units.units" diff --git a/spec/ruby/i18n/js_spec.rb b/spec/ruby/i18n/js_spec.rb index ed550c20..59f2404e 100644 --- a/spec/ruby/i18n/js_spec.rb +++ b/spec/ruby/i18n/js_spec.rb @@ -598,7 +598,7 @@ it "exports with the keys sorted" do expect(subject).to eq < Date: Mon, 11 Nov 2019 15:31:28 +0800 Subject: [PATCH 433/466] ^ Release 3.4.2 --- CHANGELOG.md | 11 ++++++++++- lib/i18n/js/version.rb | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cee3b6e9..17066737 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.4.2] - 2019-11-11 + +### Fixed + +- [Ruby] Fix regression introduced in PR #551 + (PR: https://github.com/fnando/i18n-js/pull/555) + + ## [3.4.1] - 2019-11-01 ### Fixed @@ -417,7 +425,8 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.4.1...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.4.2...HEAD +[3.4.1]: https://github.com/fnando/i18n-js/compare/v3.4.1...v3.4.2 [3.4.1]: https://github.com/fnando/i18n-js/compare/v3.4.0...v3.4.1 [3.4.0]: https://github.com/fnando/i18n-js/compare/v3.3.0...v3.4.0 [3.3.0]: https://github.com/fnando/i18n-js/compare/v3.2.3...v3.3.0 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index 062adf8a..12ea2f67 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -2,6 +2,6 @@ module I18n module JS - VERSION = "3.4.1" + VERSION = "3.4.2" end end From b82dacefb01ad69c79762b547e73af699a1ef373 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 12 Nov 2019 10:55:44 +0800 Subject: [PATCH 434/466] ^ Release 3.5.0 --- CHANGELOG.md | 15 ++++++++++++--- lib/i18n/js/version.rb | 2 +- package.json | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f5bfd31..ba709e1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added -- Support for `%k` strftime format to match Ruby strftime +- Nothing ### Changed @@ -18,6 +18,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.5.0] - 2019-11-12 + +### Added + +- [JS] Support for `%k` strftime format to match Ruby strftime + (PR: https://github.com/fnando/i18n-js/pull/554) + + ## [3.4.2] - 2019-11-11 ### Fixed @@ -425,8 +433,9 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.4.2...HEAD -[3.4.1]: https://github.com/fnando/i18n-js/compare/v3.4.1...v3.4.2 +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.5.0...HEAD +[3.5.0]: https://github.com/fnando/i18n-js/compare/v3.4.2...v3.5.0 +[3.4.2]: https://github.com/fnando/i18n-js/compare/v3.4.1...v3.4.2 [3.4.1]: https://github.com/fnando/i18n-js/compare/v3.4.0...v3.4.1 [3.4.0]: https://github.com/fnando/i18n-js/compare/v3.3.0...v3.4.0 [3.3.0]: https://github.com/fnando/i18n-js/compare/v3.2.3...v3.3.0 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index 12ea2f67..d406c7f2 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -2,6 +2,6 @@ module I18n module JS - VERSION = "3.4.2" + VERSION = "3.5.0" end end diff --git a/package.json b/package.json index 5f9957a1..e2209808 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "i18n-js", - "version": "3.3.0", + "version": "3.5.0", "description": "A javascript library similar to Ruby on Rails i18n gem", "author": "Nando Vieira", "license": "MIT", From 7c48a0303ba2c4ab82eedb11ae659377c3028d8b Mon Sep 17 00:00:00 2001 From: bisubus Date: Fri, 20 Dec 2019 16:18:31 +0300 Subject: [PATCH 435/466] Add Function.prototype.bind shim --- app/assets/javascripts/i18n/shims.js | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/app/assets/javascripts/i18n/shims.js b/app/assets/javascripts/i18n/shims.js index 05df41da..a219870c 100644 --- a/app/assets/javascripts/i18n/shims.js +++ b/app/assets/javascripts/i18n/shims.js @@ -206,3 +206,35 @@ if (!Array.prototype.map) { return A; }; } + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind +if (!Function.prototype.bind) (function(){ + var ArrayPrototypeSlice = Array.prototype.slice; + Function.prototype.bind = function(otherThis) { + if (typeof this !== 'function') { + // closest thing possible to the ECMAScript 5 + // internal IsCallable function + throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); + } + + var baseArgs= ArrayPrototypeSlice .call(arguments, 1), + baseArgsLength = baseArgs.length, + fToBind = this, + fNOP = function() {}, + fBound = function() { + baseArgs.length = baseArgsLength; // reset to default base arguments + baseArgs.push.apply(baseArgs, arguments); + return fToBind.apply( + fNOP.prototype.isPrototypeOf(this) ? this : otherThis, baseArgs + ); + }; + + if (this.prototype) { + // Function.prototype doesn't have a prototype property + fNOP.prototype = this.prototype; + } + fBound.prototype = new fNOP(); + + return fBound; + }; +})(); From 37ce00f6dd66a4d9174df464e2c6d050ffb978bb Mon Sep 17 00:00:00 2001 From: bisubus Date: Fri, 20 Dec 2019 16:27:37 +0300 Subject: [PATCH 436/466] Bind I18n shortcut functions Allows to use shortcut functions like t('foo') without I18n context --- app/assets/javascripts/i18n.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 3c821bad..0adb3e9f 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -1084,9 +1084,9 @@ }; // Set aliases, so we can save some typing. - I18n.t = I18n.translate; - I18n.l = I18n.localize; - I18n.p = I18n.pluralize; + I18n.t = I18n.translate.bind(I18n); + I18n.l = I18n.localize.bind(I18n); + I18n.p = I18n.pluralize.bind(I18n); return I18n; })); From f759fc760ed37020fb162a31736ccb3261aa16fe Mon Sep 17 00:00:00 2001 From: bisubus Date: Fri, 20 Dec 2019 16:44:53 +0300 Subject: [PATCH 437/466] Update translate.spec.js --- spec/js/translate.spec.js | 102 +++++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 46 deletions(-) diff --git a/spec/js/translate.spec.js b/spec/js/translate.spec.js index a7148fc6..30f297e8 100644 --- a/spec/js/translate.spec.js +++ b/spec/js/translate.spec.js @@ -10,35 +10,45 @@ describe("Translate", function(){ I18n.translations = Translations(); }); + it("sets bound alias", function() { + expect(I18n.t).toEqual(jasmine.any(Function)); + expect(I18n.t).not.toBe(I18n.translate); + }); + it("returns translation for single scope", function(){ - expect(I18n.t("hello")).toEqual("Hello World!"); + expect(I18n.translate("hello")).toEqual("Hello World!"); + }); + + it("returns translation with 't' shortcut", function(){ + var t = I18n.t; + expect(t("hello")).toEqual("Hello World!"); }); it("returns translation as object", function(){ - expect(I18n.t("greetings")).toEqual(I18n.translations.en.greetings); + expect(I18n.translate("greetings")).toEqual(I18n.translations.en.greetings); }); it("returns missing message translation for valid scope with null", function(){ - actual = I18n.t("null_key"); + actual = I18n.translate("null_key"); expected = '[missing "en.null_key" translation]'; expect(actual).toEqual(expected); }); it("returns missing message translation for invalid scope", function(){ - actual = I18n.t("invalid.scope"); + actual = I18n.translate("invalid.scope"); expected = '[missing "en.invalid.scope" translation]'; expect(actual).toEqual(expected); }); it("returns missing message translation with provided locale for invalid scope", function(){ - actual = I18n.t("invalid.scope", { locale: "ja" }); + actual = I18n.translate("invalid.scope", { locale: "ja" }); expected = '[missing "ja.invalid.scope" translation]'; expect(actual).toEqual(expected); }); it("returns guessed translation if missingBehaviour is set to guess", function(){ I18n.missingBehaviour = 'guess' - actual = I18n.t("invalid.thisIsAutomaticallyGeneratedTranslation"); + actual = I18n.translate("invalid.thisIsAutomaticallyGeneratedTranslation"); expected = 'this is automatically generated translation'; expect(actual).toEqual(expected); }); @@ -46,47 +56,47 @@ describe("Translate", function(){ it("returns guessed translation with prefix if missingBehaviour is set to guess and prefix is also provided", function(){ I18n.missingBehaviour = 'guess' I18n.missingTranslationPrefix = 'EE: ' - actual = I18n.t("invalid.thisIsAutomaticallyGeneratedTranslation"); + actual = I18n.translate("invalid.thisIsAutomaticallyGeneratedTranslation"); expected = 'EE: this is automatically generated translation'; expect(actual).toEqual(expected); }); it("returns missing message translation for valid scope with scope", function(){ - actual = I18n.t("monster", {scope: "greetings"}); + actual = I18n.translate("monster", {scope: "greetings"}); expected = '[missing "en.greetings.monster" translation]'; expect(actual).toEqual(expected); }); it("returns translation for single scope on a custom locale", function(){ I18n.locale = "pt-BR"; - expect(I18n.t("hello")).toEqual("Olá Mundo!"); + expect(I18n.translate("hello")).toEqual("Olá Mundo!"); }); it("returns translation for multiple scopes", function(){ - expect(I18n.t("greetings.stranger")).toEqual("Hello stranger!"); + expect(I18n.translate("greetings.stranger")).toEqual("Hello stranger!"); }); it("returns translation with default locale option", function(){ - expect(I18n.t("hello", {locale: "en"})).toEqual("Hello World!"); - expect(I18n.t("hello", {locale: "pt-BR"})).toEqual("Olá Mundo!"); + expect(I18n.translate("hello", {locale: "en"})).toEqual("Hello World!"); + expect(I18n.translate("hello", {locale: "pt-BR"})).toEqual("Olá Mundo!"); }); it("fallbacks to the default locale when I18n.fallbacks is enabled", function(){ I18n.locale = "pt-BR"; I18n.fallbacks = true; - expect(I18n.t("greetings.stranger")).toEqual("Hello stranger!"); + expect(I18n.translate("greetings.stranger")).toEqual("Hello stranger!"); }); it("fallbacks to default locale when providing an unknown locale", function(){ I18n.locale = "fr"; I18n.fallbacks = true; - expect(I18n.t("greetings.stranger")).toEqual("Hello stranger!"); + expect(I18n.translate("greetings.stranger")).toEqual("Hello stranger!"); }); it("fallbacks to less specific locale", function(){ I18n.locale = "de-DE"; I18n.fallbacks = true; - expect(I18n.t("hello")).toEqual("Hallo Welt!"); + expect(I18n.translate("hello")).toEqual("Hallo Welt!"); }); describe("when a 3-part locale is used", function(){ @@ -96,15 +106,15 @@ describe("Translate", function(){ }); it("fallbacks to 2-part locale when absent", function(){ - expect(I18n.t("cat")).toEqual("貓"); + expect(I18n.translate("cat")).toEqual("貓"); }); it("fallbacks to 1-part locale when 2-part missing requested translation", function(){ - expect(I18n.t("dog")).toEqual("狗"); + expect(I18n.translate("dog")).toEqual("狗"); }); it("fallbacks to 2-part for the first time", function(){ - expect(I18n.t("dragon")).toEqual("龍"); + expect(I18n.translate("dragon")).toEqual("龍"); }); }); @@ -115,7 +125,7 @@ describe("Translate", function(){ return ["nb"]; }; - expect(I18n.t("hello")).toEqual("Hei Verden!"); + expect(I18n.translate("hello")).toEqual("Hei Verden!"); }); it("fallbacks using custom rules (array)", function() { @@ -123,7 +133,7 @@ describe("Translate", function(){ I18n.fallbacks = true; I18n.locales["no"] = ["no", "nb"]; - expect(I18n.t("hello")).toEqual("Hei Verden!"); + expect(I18n.translate("hello")).toEqual("Hei Verden!"); }); it("fallbacks using custom rules (string)", function() { @@ -131,25 +141,25 @@ describe("Translate", function(){ I18n.fallbacks = true; I18n.locales["no"] = "nb"; - expect(I18n.t("hello")).toEqual("Hei Verden!"); + expect(I18n.translate("hello")).toEqual("Hei Verden!"); }); describe("when provided default values", function() { it("uses scope provided in defaults if scope doesn't exist", function() { - actual = I18n.t("Hello!", {defaults: [{scope: "greetings.stranger"}]}); + actual = I18n.translate("Hello!", {defaults: [{scope: "greetings.stranger"}]}); expect(actual).toEqual("Hello stranger!"); }); it("continues to fallback until a scope is found", function() { var defaults = [{scope: "foo"}, {scope: "hello"}]; - actual = I18n.t("foo", {defaults: defaults}); + actual = I18n.translate("foo", {defaults: defaults}); expect(actual).toEqual("Hello World!"); }); it("uses message if specified as a default", function() { var defaults = [{message: "Hello all!"}]; - actual = I18n.t("foo", {defaults: defaults}); + actual = I18n.translate("foo", {defaults: defaults}); expect(actual).toEqual("Hello all!"); }); @@ -158,7 +168,7 @@ describe("Translate", function(){ {scope: "bar"} , {message: "Hello all!"} , {scope: "hello"}]; - actual = I18n.t("foo", {defaults: defaults}); + actual = I18n.translate("foo", {defaults: defaults}); expect(actual).toEqual("Hello all!"); }); @@ -167,7 +177,7 @@ describe("Translate", function(){ defaults: [{scope: "bar"}] , defaultValue: "Hello all!" }; - actual = I18n.t("foo", options); + actual = I18n.translate("foo", options); expect(actual).toEqual("Hello all!"); }); @@ -176,7 +186,7 @@ describe("Translate", function(){ defaults: [{scope: "hello"}] , defaultValue: "Hello all!" }; - actual = I18n.t("foo", options); + actual = I18n.translate("foo", options); expect(actual).toEqual("Hello World!"); }) @@ -187,36 +197,36 @@ describe("Translate", function(){ return scope.toUpperCase(); } }; - actual = I18n.t("foo", options); + actual = I18n.translate("foo", options); expect(actual).toEqual("FOO"); }) it("pluralizes using the correct scope if translation is found within default scope", function() { expect(I18n.translations["en"]["mailbox"]).toEqual(undefined); - actual = I18n.t("mailbox.inbox", {count: 1, defaults: [{scope: "inbox"}]}); - expected = I18n.t("inbox", {count: 1}) + actual = I18n.translate("mailbox.inbox", {count: 1, defaults: [{scope: "inbox"}]}); + expected = I18n.translate("inbox", {count: 1}) expect(actual).toEqual(expected) }) }); it("uses default value for simple translation", function(){ - actual = I18n.t("warning", {defaultValue: "Warning!"}); + actual = I18n.translate("warning", {defaultValue: "Warning!"}); expect(actual).toEqual("Warning!"); }); it("uses default value for plural translation", function(){ - actual = I18n.t("message", {defaultValue: { one: '%{count} message', other: '%{count} messages'}, count: 1}); + actual = I18n.translate("message", {defaultValue: { one: '%{count} message', other: '%{count} messages'}, count: 1}); expect(actual).toEqual("1 message"); }); it("uses default value for unknown locale", function(){ I18n.locale = "fr"; - actual = I18n.t("warning", {defaultValue: "Warning!"}); + actual = I18n.translate("warning", {defaultValue: "Warning!"}); expect(actual).toEqual("Warning!"); }); it("uses default value with interpolation", function(){ - actual = I18n.t( + actual = I18n.translate( "alert", {defaultValue: "Attention! {{message}}", message: "You're out of quota!"} ); @@ -225,33 +235,33 @@ describe("Translate", function(){ }); it("ignores default value when scope exists", function(){ - actual = I18n.t("hello", {defaultValue: "What's up?"}); + actual = I18n.translate("hello", {defaultValue: "What's up?"}); expect(actual).toEqual("Hello World!"); }); it("returns translation for custom scope separator", function(){ I18n.defaultSeparator = "•"; - actual = I18n.t("greetings•stranger"); + actual = I18n.translate("greetings•stranger"); expect(actual).toEqual("Hello stranger!"); }); it("returns boolean values", function() { - expect(I18n.t("booleans.yes")).toEqual(true); - expect(I18n.t("booleans.no")).toEqual(false); + expect(I18n.translate("booleans.yes")).toEqual(true); + expect(I18n.translate("booleans.no")).toEqual(false); }); it("escapes $ when doing substitution (IE)", function(){ I18n.locale = "en"; - expect(I18n.t("paid", {price: "$0"})).toEqual("You were paid $0"); - expect(I18n.t("paid", {price: "$0.12"})).toEqual("You were paid $0.12"); - expect(I18n.t("paid", {price: "$1.35"})).toEqual("You were paid $1.35"); + expect(I18n.translate("paid", {price: "$0"})).toEqual("You were paid $0"); + expect(I18n.translate("paid", {price: "$0.12"})).toEqual("You were paid $0.12"); + expect(I18n.translate("paid", {price: "$1.35"})).toEqual("You were paid $1.35"); }); it("replaces all occurrences of escaped $", function(){ I18n.locale = "en"; - expect(I18n.t("paid_with_vat", { + expect(I18n.translate("paid_with_vat", { price: "$0.12", vat: "$0.02"} )).toEqual("You were paid $0.12 (incl. VAT $0.02)"); @@ -259,20 +269,20 @@ describe("Translate", function(){ it("sets default scope", function(){ var options = {scope: "greetings"}; - expect(I18n.t("stranger", options)).toEqual("Hello stranger!"); + expect(I18n.translate("stranger", options)).toEqual("Hello stranger!"); }); it("accepts the scope as an array", function(){ - expect(I18n.t(["greetings", "stranger"])).toEqual("Hello stranger!"); + expect(I18n.translate(["greetings", "stranger"])).toEqual("Hello stranger!"); }); it("accepts the scope as an array using a base scope", function(){ - expect(I18n.t(["stranger"], {scope: "greetings"})).toEqual("Hello stranger!"); + expect(I18n.translate(["stranger"], {scope: "greetings"})).toEqual("Hello stranger!"); }); it("returns an array with values interpolated", function(){ var options = {value: 314}; - expect(I18n.t("arrayWithParams", options)).toEqual([ + expect(I18n.translate("arrayWithParams", options)).toEqual([ null, "An item with a param of " + options.value, "Another item with a param of " + options.value, From 9df47a39bc0795e1dd71eee0d66fc823d35716fa Mon Sep 17 00:00:00 2001 From: bisubus Date: Fri, 20 Dec 2019 16:46:19 +0300 Subject: [PATCH 438/466] Update localization.spec.js --- spec/js/localization.spec.js | 38 +++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/spec/js/localization.spec.js b/spec/js/localization.spec.js index 3cd15b33..caed4396 100644 --- a/spec/js/localization.spec.js +++ b/spec/js/localization.spec.js @@ -10,45 +10,55 @@ describe("Localization", function(){ I18n.translations = Translations(); }); + it("sets bound alias", function() { + expect(I18n.l).toEqual(jasmine.any(Function)); + expect(I18n.l).not.toBe(I18n.localize); + }); + it("localizes number", function(){ - expect(I18n.l("number", 1234567)).toEqual("1,234,567.000"); + expect(I18n.localize("number", 1234567)).toEqual("1,234,567.000"); + }); + + it("localizes number with 'l' shortcut", function(){ + var l = I18n.l; + expect(l("number", 1234567)).toEqual("1,234,567.000"); }); it("localizes currency", function(){ - expect(I18n.l("currency", 1234567)).toEqual("$1,234,567.00"); + expect(I18n.localize("currency", 1234567)).toEqual("$1,234,567.00"); }); it("localizes date strings", function(){ I18n.locale = "pt-BR"; - expect(I18n.l("date.formats.default", "2009-11-29")).toEqual("29/11/2009"); - expect(I18n.l("date.formats.short", "2009-01-07")).toEqual("07 de Janeiro"); - expect(I18n.l("date.formats.long", "2009-01-07")).toEqual("07 de Janeiro de 2009"); + expect(I18n.localize("date.formats.default", "2009-11-29")).toEqual("29/11/2009"); + expect(I18n.localize("date.formats.short", "2009-01-07")).toEqual("07 de Janeiro"); + expect(I18n.localize("date.formats.long", "2009-01-07")).toEqual("07 de Janeiro de 2009"); }); it("localizes time strings", function(){ I18n.locale = "pt-BR"; - expect(I18n.l("time.formats.default", "2009-11-29 15:07:59")).toEqual("Domingo, 29 de Novembro de 2009, 15:07 h"); - expect(I18n.l("time.formats.short", "2009-01-07 09:12:35")).toEqual("07/01, 09:12 h"); - expect(I18n.l("time.formats.long", "2009-11-29 15:07:59")).toEqual("Domingo, 29 de Novembro de 2009, 15:07 h"); + expect(I18n.localize("time.formats.default", "2009-11-29 15:07:59")).toEqual("Domingo, 29 de Novembro de 2009, 15:07 h"); + expect(I18n.localize("time.formats.short", "2009-01-07 09:12:35")).toEqual("07/01, 09:12 h"); + expect(I18n.localize("time.formats.long", "2009-11-29 15:07:59")).toEqual("Domingo, 29 de Novembro de 2009, 15:07 h"); }); it("return 'Invalid Date' or original value for invalid input", function(){ - expect(I18n.l("time.formats.default", "")).toEqual("Invalid Date"); - expect(I18n.l("time.formats.default", null)).toEqual(null); - expect(I18n.l("time.formats.default", undefined)).toEqual(undefined); + expect(I18n.localize("time.formats.default", "")).toEqual("Invalid Date"); + expect(I18n.localize("time.formats.default", null)).toEqual(null); + expect(I18n.localize("time.formats.default", undefined)).toEqual(undefined); }); it("localizes date/time strings with placeholders", function(){ I18n.locale = "pt-BR"; - expect(I18n.l("date.formats.short_with_placeholders", "2009-01-07", { p1: "!", p2: "?" })).toEqual("07 de Janeiro ! ?"); - expect(I18n.l("time.formats.short_with_placeholders", "2009-01-07 09:12:35", { p1: "!" })).toEqual("07/01, 09:12 h !"); + expect(I18n.localize("date.formats.short_with_placeholders", "2009-01-07", { p1: "!", p2: "?" })).toEqual("07 de Janeiro ! ?"); + expect(I18n.localize("time.formats.short_with_placeholders", "2009-01-07 09:12:35", { p1: "!" })).toEqual("07/01, 09:12 h !"); }); it("localizes percentage", function(){ I18n.locale = "pt-BR"; - expect(I18n.l("percentage", 123.45)).toEqual("123,45%"); + expect(I18n.localize("percentage", 123.45)).toEqual("123,45%"); }); }); From 275dbbc943f3b4085d90cf4002c7895d7f893062 Mon Sep 17 00:00:00 2001 From: bisubus Date: Fri, 20 Dec 2019 16:47:43 +0300 Subject: [PATCH 439/466] Update pluralization.spec.js --- spec/js/pluralization.spec.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/spec/js/pluralization.spec.js b/spec/js/pluralization.spec.js index eea49296..d47d53cc 100644 --- a/spec/js/pluralization.spec.js +++ b/spec/js/pluralization.spec.js @@ -10,8 +10,9 @@ describe("Pluralization", function(){ I18n.translations = Translations(); }); - it("sets alias", function() { - expect(I18n.p).toEqual(I18n.pluralize); + it("sets bound alias", function() { + expect(I18n.p).toEqual(jasmine.any(Function)); + expect(I18n.p).not.toBe(I18n.pluralize); }); it("pluralizes scope", function(){ @@ -20,6 +21,13 @@ describe("Pluralization", function(){ expect(I18n.p(5, "inbox")).toEqual("You have 5 messages"); }); + it("pluralizes scope with 'p' shortcut", function(){ + var p = I18n.p; + expect(p(0, "inbox")).toEqual("You have no messages"); + expect(p(1, "inbox")).toEqual("You have 1 message"); + expect(p(5, "inbox")).toEqual("You have 5 messages"); + }); + it("pluralizes using the 'other' scope", function(){ I18n.translations["en"]["inbox"]["zero"] = null; expect(I18n.p(0, "inbox")).toEqual("You have 0 messages"); From 253595068cb66962627b3794d275dcd36a56b92c Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Sat, 21 Dec 2019 17:03:22 +0800 Subject: [PATCH 440/466] ^ Release 3.5.1 --- CHANGELOG.md | 11 ++++++++++- lib/i18n/js/version.rb | 2 +- package.json | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba709e1e..8faeea88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.5.1] - 2019-12-21 + +### Changed + +- [JS] Bound shortcut functions + (PR: https://github.com/fnando/i18n-js/pull/560) + + ## [3.5.0] - 2019-11-12 ### Added @@ -433,7 +441,8 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.5.0...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.5.1...HEAD +[3.5.1]: https://github.com/fnando/i18n-js/compare/v3.5.0...v3.5.1 [3.5.0]: https://github.com/fnando/i18n-js/compare/v3.4.2...v3.5.0 [3.4.2]: https://github.com/fnando/i18n-js/compare/v3.4.1...v3.4.2 [3.4.1]: https://github.com/fnando/i18n-js/compare/v3.4.0...v3.4.1 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index d406c7f2..ab689352 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -2,6 +2,6 @@ module I18n module JS - VERSION = "3.5.0" + VERSION = "3.5.1" end end diff --git a/package.json b/package.json index e2209808..80751494 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "i18n-js", - "version": "3.5.0", + "version": "3.5.1", "description": "A javascript library similar to Ruby on Rails i18n gem", "author": "Nando Vieira", "license": "MIT", From 37da124dd2215dd3ce8f220bca31452692b64866 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 27 Dec 2019 16:45:40 +0800 Subject: [PATCH 441/466] * Test against MRI 2.7 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 129bddaa..08f97543 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ rvm: - 2.4 - 2.5 - 2.6 + - 2.7 - ruby-head before_install: # Needed to test JS From 5505008f34b7ab7c418c996d0e820d188f51f2d6 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 14 Jan 2020 10:06:19 +0800 Subject: [PATCH 442/466] * Test against I18n 1.8.x --- .travis.yml | 1 + Appraisals | 4 ++++ gemfiles/i18n_1_8.gemfile | 7 +++++++ 3 files changed, 12 insertions(+) create mode 100644 gemfiles/i18n_1_8.gemfile diff --git a/.travis.yml b/.travis.yml index 08f97543..a3899507 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,6 +30,7 @@ gemfile: - gemfiles/i18n_1_5.gemfile - gemfiles/i18n_1_6.gemfile - gemfiles/i18n_1_7.gemfile + - gemfiles/i18n_1_8.gemfile matrix: fast_finish: true allow_failures: diff --git a/Appraisals b/Appraisals index 193f2e4f..5161e304 100644 --- a/Appraisals +++ b/Appraisals @@ -38,3 +38,7 @@ end appraise "i18n_1_7" do gem "i18n", "~> 1.7.0" end + +appraise "i18n_1_8" do + gem "i18n", "~> 1.8.0" +end diff --git a/gemfiles/i18n_1_8.gemfile b/gemfiles/i18n_1_8.gemfile new file mode 100644 index 00000000..1d5e4cf8 --- /dev/null +++ b/gemfiles/i18n_1_8.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "i18n", "~> 1.8.0" + +gemspec path: "../" From dba4d31dad3c21c8f37378450a3921182110d80e Mon Sep 17 00:00:00 2001 From: EddieOne Date: Wed, 12 Feb 2020 20:25:05 -0700 Subject: [PATCH 443/466] Add suffix option https://github.com/fnando/i18n-js/issues/520 --- README.md | 2 +- lib/i18n/js/formatters/base.rb | 3 +- lib/i18n/js/formatters/js.rb | 6 +- lib/i18n/js/segment.rb | 7 +- package-lock.json | 192 ++++++++++++++++++ package.json | 2 +- ...with_namespace_prefix_and_pretty_print.yml | 1 + spec/ruby/i18n/js_spec.rb | 3 +- 8 files changed, 209 insertions(+), 7 deletions(-) create mode 100644 package-lock.json diff --git a/README.md b/README.md index 51c2fdbb..895eb460 100644 --- a/README.md +++ b/README.md @@ -252,7 +252,7 @@ MyNamespace.translations["en"] = { ... } Setting the `prefix: "import I18n from 'i18n-js';\n"` option will add the line at the beggining of the resultant translation file. This can be useful to use this gem with the [i18n-js](https://www.npmjs.com/package/i18n-js) npm package, which is quite useful to use it with webpack. -The user should provide the semi-colon and the newline character if needed. +The user should provide the semi-colon and the newline character if needed. Note, the suffix option may also be used to wrap translations files. For example: diff --git a/lib/i18n/js/formatters/base.rb b/lib/i18n/js/formatters/base.rb index 9738208f..c76c2976 100644 --- a/lib/i18n/js/formatters/base.rb +++ b/lib/i18n/js/formatters/base.rb @@ -2,11 +2,12 @@ module I18n module JS module Formatters class Base - def initialize(js_extend: false, namespace: nil, pretty_print: false, prefix: nil) + def initialize(js_extend: false, namespace: nil, pretty_print: false, prefix: nil, suffix: nil) @js_extend = js_extend @namespace = namespace @pretty_print = pretty_print @prefix = prefix + @suffix = suffix end protected diff --git a/lib/i18n/js/formatters/js.rb b/lib/i18n/js/formatters/js.rb index db0d9428..14d09974 100644 --- a/lib/i18n/js/formatters/js.rb +++ b/lib/i18n/js/formatters/js.rb @@ -9,7 +9,7 @@ def format(translations) translations.each do |locale, translations_for_locale| contents << line(locale, format_json(translations_for_locale)) end - contents + contents << footer end protected @@ -19,6 +19,10 @@ def header text + %(#{@namespace}.translations || (#{@namespace}.translations = {});\n) end + def footer + @suffix || '' + end + def line(locale, translations) if @js_extend %(#{@namespace}.translations["#{locale}"] = I18n.extend((#{@namespace}.translations["#{locale}"] || {}), #{translations});\n) diff --git a/lib/i18n/js/segment.rb b/lib/i18n/js/segment.rb index 5b00e350..64544b23 100644 --- a/lib/i18n/js/segment.rb +++ b/lib/i18n/js/segment.rb @@ -7,7 +7,7 @@ module JS # Class which enscapulates a translations hash and outputs a single JSON translation file class Segment - OPTIONS = [:namespace, :pretty_print, :js_extend, :prefix, :sort_translation_keys, :json_only].freeze + OPTIONS = [:namespace, :pretty_print, :js_extend, :prefix, :suffix, :sort_translation_keys, :json_only].freeze LOCALE_INTERPOLATOR = /%\{locale\}/ attr_reader *([:file, :translations] | OPTIONS) @@ -25,6 +25,7 @@ def initialize(file, translations, options = {}) @pretty_print = !!options[:pretty_print] @js_extend = options.key?(:js_extend) ? !!options[:js_extend] : true @prefix = options.key?(:prefix) ? options[:prefix] : nil + @suffix = options.key?(:suffix) ? options[:suffix] : nil @sort_translation_keys = options.key?(:sort_translation_keys) ? !!options[:sort_translation_keys] : true @json_only = options.key?(:json_only) ? !!options[:json_only] : false end @@ -70,7 +71,9 @@ def formatter_options { js_extend: @js_extend, namespace: @namespace, pretty_print: @pretty_print, - prefix: @prefix } + prefix: @prefix, + suffix: @suffix + } end end end diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..4cec13f8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,192 @@ +{ + "name": "i18n-js", + "version": "3.5.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "coffeescript": { + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.12.7.tgz", + "integrity": "sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "gaze": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "dev": true, + "requires": { + "globule": "1.3.1" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.4", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "globule": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.1.tgz", + "integrity": "sha512-OVyWOHgw29yosRHCHo7NncwR1hW5ew0W/UrvtwvjefVJeQ26q4/8r8FmPsSF1hJ93IgWkyv16pCTz6WblMzm/g==", + "dev": true, + "requires": { + "glob": "7.1.6", + "lodash": "4.17.15", + "minimatch": "3.0.4" + } + }, + "growl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", + "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "jasmine-growl-reporter": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/jasmine-growl-reporter/-/jasmine-growl-reporter-0.2.1.tgz", + "integrity": "sha1-1fCje5L2qD/VxkgrgJSVyQqLVf4=", + "dev": true, + "requires": { + "growl": "1.7.0" + } + }, + "jasmine-node": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/jasmine-node/-/jasmine-node-1.16.2.tgz", + "integrity": "sha512-A4AA2WaikuE7s/NYQCqfYsyCczEgObLgNH7IxRQ2SBshLBZg7vUEiiGX4GPbveW5f06nYmXYlzY4UjnZjXjV9g==", + "dev": true, + "requires": { + "coffeescript": "1.12.7", + "gaze": "1.1.3", + "jasmine-growl-reporter": "0.2.1", + "jasmine-reporters": "1.0.2", + "mkdirp": "0.3.5", + "requirejs": "2.3.6", + "underscore": "1.9.2", + "walkdir": "0.0.12" + } + }, + "jasmine-reporters": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/jasmine-reporters/-/jasmine-reporters-1.0.2.tgz", + "integrity": "sha1-q2E+1Zd9x0h+hbPBL2qOqNsq3jE=", + "dev": true, + "requires": { + "mkdirp": "0.3.5" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "mkdirp": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "requirejs": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz", + "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==", + "dev": true + }, + "underscore": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.2.tgz", + "integrity": "sha512-D39qtimx0c1fI3ya1Lnhk3E9nONswSKhnffBI0gME9C99fYOkNi04xs8K6pePLhvl1frbDemkaBQ5ikWllR2HQ==", + "dev": true + }, + "walkdir": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.12.tgz", + "integrity": "sha512-HFhaD4mMWPzFSqhpyDG48KDdrjfn409YQuVW7ckZYhW4sE87mYtWifdB/+73RA7+p4s4K18n5Jfx1kHthE1gBw==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } +} diff --git a/package.json b/package.json index 80751494..30fd285b 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "i18n" ], "devDependencies": { - "jasmine-node": "^1.14.5" + "jasmine-node": "^1.16.2" }, "main": "app/assets/javascripts/i18n.js", "scripts": { diff --git a/spec/fixtures/js_file_with_namespace_prefix_and_pretty_print.yml b/spec/fixtures/js_file_with_namespace_prefix_and_pretty_print.yml index b5e1da16..871fa002 100644 --- a/spec/fixtures/js_file_with_namespace_prefix_and_pretty_print.yml +++ b/spec/fixtures/js_file_with_namespace_prefix_and_pretty_print.yml @@ -6,3 +6,4 @@ translations: namespace: "Foo" pretty_print: true prefix: "import random from 'random-library';\n" + suffix: "//test" diff --git a/spec/ruby/i18n/js_spec.rb b/spec/ruby/i18n/js_spec.rb index 59f2404e..136f21fe 100644 --- a/spec/ruby/i18n/js_spec.rb +++ b/spec/ruby/i18n/js_spec.rb @@ -351,7 +351,7 @@ end end - context "namespace, prefix and pretty_print options" do + context "namespace, prefix, suffix, and pretty_print options" do before do stub_const('I18n::JS::DEFAULT_EXPORT_DIR_PATH', temp_path) @@ -379,6 +379,7 @@ "foo": "Foo", "fallback_test": "Success" }; +//test EOS }$/) end From 7bca360c1a3d282a945febc65a6367ca49edc0cd Mon Sep 17 00:00:00 2001 From: EddieOne Date: Thu, 13 Feb 2020 15:38:02 -0700 Subject: [PATCH 444/466] Updates based on feedback --- lib/i18n/js/formatters/js.rb | 6 +- package-lock.json | 192 ----------------------------------- package.json | 2 +- 3 files changed, 2 insertions(+), 198 deletions(-) delete mode 100644 package-lock.json diff --git a/lib/i18n/js/formatters/js.rb b/lib/i18n/js/formatters/js.rb index 14d09974..cd2e28c9 100644 --- a/lib/i18n/js/formatters/js.rb +++ b/lib/i18n/js/formatters/js.rb @@ -9,7 +9,7 @@ def format(translations) translations.each do |locale, translations_for_locale| contents << line(locale, format_json(translations_for_locale)) end - contents << footer + contents << (@suffix || '') end protected @@ -19,10 +19,6 @@ def header text + %(#{@namespace}.translations || (#{@namespace}.translations = {});\n) end - def footer - @suffix || '' - end - def line(locale, translations) if @js_extend %(#{@namespace}.translations["#{locale}"] = I18n.extend((#{@namespace}.translations["#{locale}"] || {}), #{translations});\n) diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 4cec13f8..00000000 --- a/package-lock.json +++ /dev/null @@ -1,192 +0,0 @@ -{ - "name": "i18n-js", - "version": "3.5.1", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "coffeescript": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.12.7.tgz", - "integrity": "sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "gaze": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", - "dev": true, - "requires": { - "globule": "1.3.1" - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.4", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "globule": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.1.tgz", - "integrity": "sha512-OVyWOHgw29yosRHCHo7NncwR1hW5ew0W/UrvtwvjefVJeQ26q4/8r8FmPsSF1hJ93IgWkyv16pCTz6WblMzm/g==", - "dev": true, - "requires": { - "glob": "7.1.6", - "lodash": "4.17.15", - "minimatch": "3.0.4" - } - }, - "growl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", - "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "jasmine-growl-reporter": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/jasmine-growl-reporter/-/jasmine-growl-reporter-0.2.1.tgz", - "integrity": "sha1-1fCje5L2qD/VxkgrgJSVyQqLVf4=", - "dev": true, - "requires": { - "growl": "1.7.0" - } - }, - "jasmine-node": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/jasmine-node/-/jasmine-node-1.16.2.tgz", - "integrity": "sha512-A4AA2WaikuE7s/NYQCqfYsyCczEgObLgNH7IxRQ2SBshLBZg7vUEiiGX4GPbveW5f06nYmXYlzY4UjnZjXjV9g==", - "dev": true, - "requires": { - "coffeescript": "1.12.7", - "gaze": "1.1.3", - "jasmine-growl-reporter": "0.2.1", - "jasmine-reporters": "1.0.2", - "mkdirp": "0.3.5", - "requirejs": "2.3.6", - "underscore": "1.9.2", - "walkdir": "0.0.12" - } - }, - "jasmine-reporters": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/jasmine-reporters/-/jasmine-reporters-1.0.2.tgz", - "integrity": "sha1-q2E+1Zd9x0h+hbPBL2qOqNsq3jE=", - "dev": true, - "requires": { - "mkdirp": "0.3.5" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "1.1.11" - } - }, - "mkdirp": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", - "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "requirejs": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz", - "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==", - "dev": true - }, - "underscore": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.2.tgz", - "integrity": "sha512-D39qtimx0c1fI3ya1Lnhk3E9nONswSKhnffBI0gME9C99fYOkNi04xs8K6pePLhvl1frbDemkaBQ5ikWllR2HQ==", - "dev": true - }, - "walkdir": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.12.tgz", - "integrity": "sha512-HFhaD4mMWPzFSqhpyDG48KDdrjfn409YQuVW7ckZYhW4sE87mYtWifdB/+73RA7+p4s4K18n5Jfx1kHthE1gBw==", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - } - } -} diff --git a/package.json b/package.json index 30fd285b..80751494 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "i18n" ], "devDependencies": { - "jasmine-node": "^1.16.2" + "jasmine-node": "^1.14.5" }, "main": "app/assets/javascripts/i18n.js", "scripts": { From b4d139652d3231111628d902b81e20de74a1a198 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 14 Feb 2020 10:34:54 +0800 Subject: [PATCH 445/466] ~ Update README for suffix option Also fix the formatting (missing close code fence) [ci skip] --- README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 895eb460..94e82b09 100644 --- a/README.md +++ b/README.md @@ -248,11 +248,11 @@ MyNamespace.translations["en"] = { ... } ``` -### Adding a line at the beginning of the translations file (useful for imports) +### Adding prefix & suffix to the translations file(s) -Setting the `prefix: "import I18n from 'i18n-js';\n"` option will add the line at the beggining of the resultant translation file. +Setting the `prefix: "import I18n from 'i18n-js';\n"` option will add the line at the beginning of the resultant translation file. This can be useful to use this gem with the [i18n-js](https://www.npmjs.com/package/i18n-js) npm package, which is quite useful to use it with webpack. -The user should provide the semi-colon and the newline character if needed. Note, the suffix option may also be used to wrap translations files. +The user should provide the semi-colon and the newline character if needed. For example: @@ -267,6 +267,11 @@ will create: ``` import I18n from 'i18n-js'; I18n.translations || (I18n.translations = {}); +``` + + +`suffix` option is added in https://github.com/fnando/i18n-js/pull/561. +It's similar to `prefix` so won't explain it in details. #### Pretty Print From 0c45eeaa7118d9d371bd33f83c942087731cab93 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 14 Feb 2020 11:07:28 +0800 Subject: [PATCH 446/466] ^ Release 3.6.0 --- CHANGELOG.md | 11 ++++++++++- lib/i18n/js/version.rb | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8faeea88..ce10533c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.6.0] - 2020-02-14 + +### Added + +- [Ruby] Allow `suffix` to be added to generated translations files + (PR: https://github.com/fnando/i18n-js/pull/561) + + ## [3.5.1] - 2019-12-21 ### Changed @@ -441,7 +449,8 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.5.1...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.6.0...HEAD +[3.6.0]: https://github.com/fnando/i18n-js/compare/v3.5.1...v3.6.0 [3.5.1]: https://github.com/fnando/i18n-js/compare/v3.5.0...v3.5.1 [3.5.0]: https://github.com/fnando/i18n-js/compare/v3.4.2...v3.5.0 [3.4.2]: https://github.com/fnando/i18n-js/compare/v3.4.1...v3.4.2 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index ab689352..6c9967a8 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -2,6 +2,6 @@ module I18n module JS - VERSION = "3.5.1" + VERSION = "3.6.0" end end From 9c458efc89b9b65a663342b6bd152f5500b0ba92 Mon Sep 17 00:00:00 2001 From: RWOverdijk Date: Thu, 28 May 2020 11:38:41 +0200 Subject: [PATCH 447/466] allow options for localization and add spec --- app/assets/javascripts/i18n.js | 26 +++++++++++++------------- spec/js/localization.spec.js | 14 ++++++++++++++ spec/js/translations.js | 11 ++++++++++- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index 0adb3e9f..b6c8a386 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -779,8 +779,8 @@ I18n.toCurrency = function(number, options) { options = this.prepareOptions( options - , this.lookup("number.currency.format") - , this.lookup("number.format") + , this.lookup("number.currency.format", options) + , this.lookup("number.format", options) , CURRENCY_FORMAT ); @@ -799,17 +799,17 @@ switch (scope) { case "currency": - return this.toCurrency(value); + return this.toCurrency(value, options); case "number": - scope = this.lookup("number.format"); + scope = this.lookup("number.format", options); return this.toNumber(value, scope); case "percentage": - return this.toPercentage(value); + return this.toPercentage(value, options); default: var localizedValue; if (scope.match(/^(date|time)/)) { - localizedValue = this.toTime(scope, value); + localizedValue = this.toTime(scope, value, options); } else { localizedValue = value.toString(); } @@ -914,8 +914,8 @@ // %Y - Year with century // %z/%Z - Timezone offset (+0545) // - I18n.strftime = function(date, format) { - var options = this.lookup("date") + I18n.strftime = function(date, format, options) { + var options = this.lookup("date", options) , meridianOptions = I18n.meridian() ; @@ -984,9 +984,9 @@ }; // Convert the given dateString into a formatted date. - I18n.toTime = function(scope, dateString) { + I18n.toTime = function(scope, dateString, options) { var date = this.parseDate(dateString) - , format = this.lookup(scope) + , format = this.lookup(scope, options) ; // A date input of `null` or `undefined` will be returned as-is @@ -1003,15 +1003,15 @@ return date_string; } - return this.strftime(date, format); + return this.strftime(date, format, options); }; // Convert a number into a formatted percentage value. I18n.toPercentage = function(number, options) { options = this.prepareOptions( options - , this.lookup("number.percentage.format") - , this.lookup("number.format") + , this.lookup("number.percentage.format", options) + , this.lookup("number.format", options) , PERCENTAGE_FORMAT ); diff --git a/spec/js/localization.spec.js b/spec/js/localization.spec.js index caed4396..df08f94f 100644 --- a/spec/js/localization.spec.js +++ b/spec/js/localization.spec.js @@ -36,6 +36,20 @@ describe("Localization", function(){ expect(I18n.localize("date.formats.long", "2009-01-07")).toEqual("07 de Janeiro de 2009"); }); + it("localizes strings with locale from options", function(){ + I18n.locale = "en"; + + expect(I18n.localize("date.formats.default", "2009-11-29", { locale: "pt-BR" })).toEqual("29/11/2009"); + expect(I18n.localize("date.formats.short", "2009-01-07", { locale: "pt-BR" })).toEqual("07 de Janeiro"); + expect(I18n.localize("date.formats.long", "2009-01-07", { locale: "pt-BR" })).toEqual("07 de Janeiro de 2009"); + expect(I18n.localize("time.formats.default", "2009-11-29 15:07:59", { locale: "pt-BR" })).toEqual("Domingo, 29 de Novembro de 2009, 15:07 h"); + expect(I18n.localize("time.formats.short", "2009-01-07 09:12:35", { locale: "pt-BR" })).toEqual("07/01, 09:12 h"); + expect(I18n.localize("time.formats.long", "2009-11-29 15:07:59", { locale: "pt-BR" })).toEqual("Domingo, 29 de Novembro de 2009, 15:07 h"); + expect(I18n.localize("number", 1234567, { locale: "pt-BR" })).toEqual("1,234,567.000"); + expect(I18n.localize("currency", 1234567, { locale: "pt-BR" })).toEqual("R$ 1.234.567,00"); + expect(I18n.localize("percentage", 123.45, { locale: "pt-BR" })).toEqual("123,45%"); + }); + it("localizes time strings", function(){ I18n.locale = "pt-BR"; diff --git a/spec/js/translations.js b/spec/js/translations.js index d7a1be62..df2ea0f5 100644 --- a/spec/js/translations.js +++ b/spec/js/translations.js @@ -90,7 +90,16 @@ hello: "Olá Mundo!" , number: { - percentage: { + currency: { + format: { + delimiter: ".", + format: "%u %n", + precision: 2, + separator: ",", + unit: "R$" + } + } + , percentage: { format: { delimiter: "" , separator: "," From ec63f77fc4df9845045b0125fd7d50e73776e578 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 29 May 2020 13:37:53 +0800 Subject: [PATCH 448/466] ^ Release 3.7.0 --- CHANGELOG.md | 11 ++++++++++- lib/i18n/js/version.rb | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce10533c..e96a0e9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.7.0] - 2020-05-29 + +### Added + +- [JS] Allow options to be passed in when calling `I18n.localize`/`I18n.l` + (PR: https://github.com/fnando/i18n-js/pull/570) + + ## [3.6.0] - 2020-02-14 ### Added @@ -449,7 +457,8 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.6.0...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.7.0...HEAD +[3.6.0]: https://github.com/fnando/i18n-js/compare/v3.6.0...v3.7.0 [3.6.0]: https://github.com/fnando/i18n-js/compare/v3.5.1...v3.6.0 [3.5.1]: https://github.com/fnando/i18n-js/compare/v3.5.0...v3.5.1 [3.5.0]: https://github.com/fnando/i18n-js/compare/v3.4.2...v3.5.0 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index 6c9967a8..03a6678d 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -2,6 +2,6 @@ module I18n module JS - VERSION = "3.6.0" + VERSION = "3.7.0" end end From b572f10cb682f0cc7fed93384bbe6ebc85212bfd Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 29 May 2020 13:40:42 +0800 Subject: [PATCH 449/466] ! Update version number in package.json To publish new version for npm package --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 80751494..c785f548 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "i18n-js", - "version": "3.5.1", + "version": "3.7.0", "description": "A javascript library similar to Ruby on Rails i18n gem", "author": "Nando Vieira", "license": "MIT", From e05ea822e64afd7026185f9438985a3e8ab1b1c9 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 8 Jun 2020 11:25:53 +0800 Subject: [PATCH 450/466] * Update min version for dev dependency appraisal --- i18n-js.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n-js.gemspec b/i18n-js.gemspec index fefe0c65..214ebdb8 100644 --- a/i18n-js.gemspec +++ b/i18n-js.gemspec @@ -20,7 +20,7 @@ Gem::Specification.new do |s| s.add_dependency "i18n", ">= 0.6.6" - s.add_development_dependency "appraisal", "~> 2.0" + s.add_development_dependency "appraisal", "~> 2.3" s.add_development_dependency "rspec", "~> 3.0" s.add_development_dependency "rake", "~> 12.0" s.add_development_dependency "gem-release", ">= 0.7" From 12bb04835d3976aae612ca59c7cabdd7cea5ae20 Mon Sep 17 00:00:00 2001 From: Ismail Chukmaev Date: Fri, 26 Jun 2020 12:56:59 +0400 Subject: [PATCH 451/466] Fixed: now all underscores replaces to spaces --- app/assets/javascripts/i18n.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index b6c8a386..ef51ae8a 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -679,7 +679,7 @@ var s = scope.split('.').slice(-1)[0]; //replace underscore with space && camelcase with space and lowercase letter return (this.missingTranslationPrefix.length > 0 ? this.missingTranslationPrefix : '') + - s.replace('_',' ').replace(/([a-z])([A-Z])/g, + s.replace(/_/g,' ').replace(/([a-z])([A-Z])/g, function(match, p1, p2) {return p1 + ' ' + p2.toLowerCase()} ); } From 20feaa73f9dfb43d848eadd4c05934bdb5b06cd0 Mon Sep 17 00:00:00 2001 From: Ismail Chukmaev Date: Mon, 29 Jun 2020 11:22:01 +0400 Subject: [PATCH 452/466] Updated translate spec --- spec/js/translate.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/js/translate.spec.js b/spec/js/translate.spec.js index 30f297e8..d3939516 100644 --- a/spec/js/translate.spec.js +++ b/spec/js/translate.spec.js @@ -48,7 +48,7 @@ describe("Translate", function(){ it("returns guessed translation if missingBehaviour is set to guess", function(){ I18n.missingBehaviour = 'guess' - actual = I18n.translate("invalid.thisIsAutomaticallyGeneratedTranslation"); + actual = I18n.translate("invalid.this_Is_AutomaticallyGeneratedTranslation"); expected = 'this is automatically generated translation'; expect(actual).toEqual(expected); }); From 22b9564846339ce4bd986dae77adcbbea9fade0f Mon Sep 17 00:00:00 2001 From: Ismail Chukmaev Date: Mon, 29 Jun 2020 12:57:54 +0400 Subject: [PATCH 453/466] Changed spec --- spec/js/translate.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/js/translate.spec.js b/spec/js/translate.spec.js index d3939516..dd9f55a4 100644 --- a/spec/js/translate.spec.js +++ b/spec/js/translate.spec.js @@ -48,7 +48,7 @@ describe("Translate", function(){ it("returns guessed translation if missingBehaviour is set to guess", function(){ I18n.missingBehaviour = 'guess' - actual = I18n.translate("invalid.this_Is_AutomaticallyGeneratedTranslation"); + actual = I18n.translate("invalid.this_is_automatically_generated_translation"); expected = 'this is automatically generated translation'; expect(actual).toEqual(expected); }); From d7e15c49a40e4b821621f0b637b876f30c3c5a1d Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 30 Jun 2020 09:57:54 +0800 Subject: [PATCH 454/466] * Add back old test case for missing behaviour `guess` so both old & new cases are tested --- spec/js/translate.spec.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/spec/js/translate.spec.js b/spec/js/translate.spec.js index dd9f55a4..6e23ae06 100644 --- a/spec/js/translate.spec.js +++ b/spec/js/translate.spec.js @@ -48,9 +48,14 @@ describe("Translate", function(){ it("returns guessed translation if missingBehaviour is set to guess", function(){ I18n.missingBehaviour = 'guess' - actual = I18n.translate("invalid.this_is_automatically_generated_translation"); - expected = 'this is automatically generated translation'; - expect(actual).toEqual(expected); + + var actual_1 = I18n.translate("invalid.thisIsAutomaticallyGeneratedTranslation"); + var expected_1 = 'this is automatically generated translation'; + expect(actual_1).toEqual(expected_1); + + var actual_2 = I18n.translate("invalid.this_is_automatically_generated_translation"); + var expected_2 = 'this is automatically generated translation'; + expect(actual_2).toEqual(expected_2); }); it("returns guessed translation with prefix if missingBehaviour is set to guess and prefix is also provided", function(){ From 5e3600017eca17ae9ae1369d03c5f55b3e46229c Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 30 Jun 2020 10:00:59 +0800 Subject: [PATCH 455/466] ^ Release 3.7.1 --- CHANGELOG.md | 13 +++++++++++-- lib/i18n/js/version.rb | 2 +- package.json | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e96a0e9f..54d60f81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.7.1] - 2020-06-30 + +### Fixed + +- [JS] For translation missing behaviour `guess`, replace all underscores to spaces properly + (PR: https://github.com/fnando/i18n-js/pull/574) + + ## [3.7.0] - 2020-05-29 ### Added @@ -457,8 +465,9 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.7.0...HEAD -[3.6.0]: https://github.com/fnando/i18n-js/compare/v3.6.0...v3.7.0 +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.7.1...HEAD +[3.7.1]: https://github.com/fnando/i18n-js/compare/v3.7.0...v3.7.1 +[3.7.0]: https://github.com/fnando/i18n-js/compare/v3.6.0...v3.7.0 [3.6.0]: https://github.com/fnando/i18n-js/compare/v3.5.1...v3.6.0 [3.5.1]: https://github.com/fnando/i18n-js/compare/v3.5.0...v3.5.1 [3.5.0]: https://github.com/fnando/i18n-js/compare/v3.4.2...v3.5.0 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index 03a6678d..0aef60e2 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -2,6 +2,6 @@ module I18n module JS - VERSION = "3.7.0" + VERSION = "3.7.1" end end diff --git a/package.json b/package.json index c785f548..1966d4a7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "i18n-js", - "version": "3.7.0", + "version": "3.7.1", "description": "A javascript library similar to Ruby on Rails i18n gem", "author": "Nando Vieira", "license": "MIT", From a5cb4f254f5eb515afcb1420a9213617d26a32f1 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 17 Jul 2019 10:04:13 +0800 Subject: [PATCH 456/466] * Add support for option `separator` (key/scope separator) to translate methods --- app/assets/javascripts/i18n.js | 10 +++++----- spec/js/translate.spec.js | 5 +++++ spec/js/translations.js | 6 +++++- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index ef51ae8a..b8f8517b 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -402,7 +402,7 @@ while (locales.length) { locale = locales.shift(); - scopes = fullScope.split(this.defaultSeparator); + scopes = fullScope.split(options.separator || this.defaultSeparator); translations = this.translations[locale]; if (!translations) { @@ -459,7 +459,7 @@ while (locales.length) { locale = locales.shift(); - scopes = scope.split(this.defaultSeparator); + scopes = scope.split(options.separator || this.defaultSeparator); translations = this.translations[locale]; if (!translations) { @@ -685,7 +685,7 @@ var localeForTranslation = (options != null && options.locale != null) ? options.locale : this.currentLocale(); var fullScope = this.getFullScope(scope, options); - var fullScopeWithLocale = [localeForTranslation, fullScope].join(this.defaultSeparator); + var fullScopeWithLocale = [localeForTranslation, fullScope].join(options.separator || this.defaultSeparator); return '[missing "' + fullScopeWithLocale + '" translation]'; }; @@ -1053,7 +1053,7 @@ // Deal with the scope as an array. if (isArray(scope)) { - scope = scope.join(this.defaultSeparator); + scope = scope.join(options.separator || this.defaultSeparator); } // Deal with the scope option provided through the second argument. @@ -1061,7 +1061,7 @@ // I18n.t('hello', {scope: 'greetings'}); // if (options.scope) { - scope = [options.scope, scope].join(this.defaultSeparator); + scope = [options.scope, scope].join(options.separator || this.defaultSeparator); } return scope; diff --git a/spec/js/translate.spec.js b/spec/js/translate.spec.js index 6e23ae06..33d964ab 100644 --- a/spec/js/translate.spec.js +++ b/spec/js/translate.spec.js @@ -296,4 +296,9 @@ describe("Translate", function(){ {foo: "bar"} ]); }); + + + it("returns value with key containing dot but different separator specified", function() { + expect(I18n.t(["A implies B means something."], {scope: "sentences_with_dots", separator: "|"})).toEqual("A implies B means that when A is true, B must be true."); + }); }); diff --git a/spec/js/translations.js b/spec/js/translations.js index df2ea0f5..df05b498 100644 --- a/spec/js/translations.js +++ b/spec/js/translations.js @@ -67,7 +67,11 @@ {foo: "bar"} ] - , null_key: null + , null_key: null, + + sentences_with_dots: { + "A implies B means something.": "A implies B means that when A is true, B must be true." + } }; Translations["en-US"] = { From 39cfc1585bf1f4fc561a5ed2d9040cae03deb5d8 Mon Sep 17 00:00:00 2001 From: Mark Lodato Date: Tue, 15 Sep 2020 12:47:15 -0400 Subject: [PATCH 457/466] Add note for using yarn if using webpacker --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 94e82b09..bab1afe4 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,16 @@ Then get the JS files following the instructions below. then only the specified locales will be exported. Current version of `i18n.js` will also be exported to avoid version mismatching by downloading. +#### Rails with [webpacker](https://github.com/rails/webpacker) + +If you're using `webpacker`, you may need to add the dependencies to your client with: + +``` +yarn add i18n-js +``` + +For more details, see [this gist](https://gist.github.com/bazzel/ecdff4718962e57c2d5569cf01d332fe). + #### Export Configuration (For translations) Exported translation files generated by `I18n::JS::Middleware` or `rake i18n:js:export` can be customized with config file `config/i18n-js.yml` From dbab13a443adb68425a32bb53a822265b5e863d2 Mon Sep 17 00:00:00 2001 From: Mark Lodato Date: Wed, 16 Sep 2020 08:15:22 -0400 Subject: [PATCH 458/466] Move webpacker section to installation section --- README.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index bab1afe4..e5403f21 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,18 @@ Add the gem to your Gemfile. gem "i18n-js" ``` +#### Rails with [webpacker](https://github.com/rails/webpacker) + +If you're using `webpacker`, you may need to add the dependencies to your client with: + +``` +yarn add i18n-js +# or, if you're using npm, +npm install i18n-js +``` + +For more details, see [this gist](https://gist.github.com/bazzel/ecdff4718962e57c2d5569cf01d332fe). + #### Rails app with [Asset Pipeline](http://guides.rubyonrails.org/asset_pipeline.html) If you're using the [asset pipeline](http://guides.rubyonrails.org/asset_pipeline.html), @@ -79,16 +91,6 @@ Then get the JS files following the instructions below. then only the specified locales will be exported. Current version of `i18n.js` will also be exported to avoid version mismatching by downloading. -#### Rails with [webpacker](https://github.com/rails/webpacker) - -If you're using `webpacker`, you may need to add the dependencies to your client with: - -``` -yarn add i18n-js -``` - -For more details, see [this gist](https://gist.github.com/bazzel/ecdff4718962e57c2d5569cf01d332fe). - #### Export Configuration (For translations) Exported translation files generated by `I18n::JS::Middleware` or `rake i18n:js:export` can be customized with config file `config/i18n-js.yml` From e373c4b4be2dc75c7509b4993b63aa2cf3504488 Mon Sep 17 00:00:00 2001 From: scumah Date: Wed, 30 Sep 2020 17:52:37 +0200 Subject: [PATCH 459/466] Allow scoped calls to toHumanSize() --- app/assets/javascripts/i18n.js | 7 +++++-- spec/js/numbers.spec.js | 4 ++++ spec/js/translations.js | 12 ++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js index b8f8517b..890192d5 100644 --- a/app/assets/javascripts/i18n.js +++ b/app/assets/javascripts/i18n.js @@ -1025,6 +1025,7 @@ , iterations = 0 , unit , precision + , fullScope ; while (size >= kb && iterations < 4) { @@ -1033,10 +1034,12 @@ } if (iterations === 0) { - unit = this.t("number.human.storage_units.units.byte", {count: size}); + fullScope = this.getFullScope("number.human.storage_units.units.byte", options); + unit = this.t(fullScope, {count: size}); precision = 0; } else { - unit = this.t("number.human.storage_units.units." + SIZE_UNITS[iterations]); + fullScope = this.getFullScope("number.human.storage_units.units." + SIZE_UNITS[iterations], options); + unit = this.t(fullScope); precision = (size - Math.floor(size) === 0) ? 0 : 1; } diff --git a/spec/js/numbers.spec.js b/spec/js/numbers.spec.js index 22f30ed3..8fcc4544 100644 --- a/spec/js/numbers.spec.js +++ b/spec/js/numbers.spec.js @@ -150,6 +150,10 @@ describe("Numbers", function(){ expect(I18n.toHumanSize(1024 * 1.6, {precision: 0})).toEqual("2KB"); }); + it("returns number as human size using custom scope", function(){ + expect(I18n.toHumanSize(1024 * 1024, {scope: "extended"})).toEqual("1Megabyte"); + }); + it("formats numbers with strip insignificant zero", function() { options = {separator: ".", delimiter: ",", strip_insignificant_zeros: true}; diff --git a/spec/js/translations.js b/spec/js/translations.js index df05b498..55ea2fb1 100644 --- a/spec/js/translations.js +++ b/spec/js/translations.js @@ -58,6 +58,18 @@ } } + , extended: { + number: { + human: { + storage_units: { + units: { + "mb": "Megabyte" + } + } + } + } + } + , arrayWithParams: [ null, "An item with a param of {{value}}", From 5214b1e5971a32af2a9cea7d4a5d8c3a3e47e721 Mon Sep 17 00:00:00 2001 From: scumah Date: Wed, 14 Oct 2020 13:51:09 +0200 Subject: [PATCH 460/466] Update README - Document scope option in toHumanSize - Fix typos - Update plural rules broken link - Remove white spaces [ci skip] --- README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e5403f21..596f4041 100644 --- a/README.md +++ b/README.md @@ -337,7 +337,7 @@ var i18n = require("i18n-js"); ### Setting up -You **don't** need to set up a thing. The default settings will work just okay. But if you want to split translations into several files or specify specific contexts, you can follow the rest of this setting up section. +You **don't** need to set up a thing. The default settings will work just okay. But if you want to split translations into several files or specify contexts, you can follow the rest of this setting up section. Set your locale is easy as ```javascript @@ -405,7 +405,7 @@ I18n.t("some.missing.scope", {defaults: [{scope: "some.existing.scope"}]}); I18n.t("some.missing.scope", {defaults: [{message: "Some message"}]}); ``` -Default values must be provided as an array of hashs where the key is the +Default values must be provided as an array of hashes where the key is the type of translation desired, a `scope` or a `message`. The translation returned will be either the first scope recognized, or the first message defined. @@ -504,7 +504,7 @@ I18n.pluralization["ru"] = function (count) { }; ``` -You can find all rules on . +You can find all rules on . If you're using the same scope over and over again, you may use the `scope` option. @@ -586,6 +586,7 @@ The `toHumanSize` function accepts the following options: - `delimiter`: defaults to `""` - `strip_insignificant_zeros`: defaults to `false` - `format`: defaults to `%n%u` +- `scope`: defaults to `""` @@ -740,7 +741,7 @@ This method is useful for very large apps where a single contained translations. To use this with require.js we are only going to change a few things from above. -1. In your `config/i18n-js.yml` we need to add a better location for the i18n to be exported. You want to use this location so that it can be properly precompiled by r.js. +1. In your `config/i18n-js.yml` we need to add a better location for the i18n to be exported to. You want to use this location so that it can be properly precompiled by r.js. ```yaml export_i18n_js: "app/assets/javascript/nls" @@ -784,7 +785,7 @@ To use this with require.js we are only going to change a few things from above. // ... }); ``` -4. (optional) As an additional configuration we can make a task to be run before the requirejs optimizer. This will allow any automated scripts that run the requirejs optimizer to export the strings before we run r.js +4. (optional) As an additional configuration we can make a task to be run before the requirejs optimizer. This will allow any automated scripts that run the requirejs optimizer to export the strings before we run r.js. ```rake # lib/tasks/i18n.rake @@ -845,7 +846,7 @@ These commands will remove *all* fingerprinted assets, and you will have to reco $ rake assets:precompile ``` -or similar commands. If you are precompiling assets on the target machine(s), cached pages may be broken by this, so they will need to be refreshed. +or similar commands. If you are precompiling assets on the target machine(s), cached pages may be broken by this, so they will need to be refreshed. 2. You can change something in a different locale file. @@ -856,9 +857,9 @@ or similar commands. If you are precompiling assets on the target machine(s), c ### Translations in JS are not updated when Sprockets not loaded before this gem The "rails engine" declaration will try to detect existence of "sprockets" before adding the initailizer -If sprockets is loaded after this gem, the preprocessor for +If sprockets is loaded after this gem, the preprocessor for making JS translations file cache to depend on content of locale files will not be hooked. -So ensure sprockets is loaded before this gem like moving entry of sprockets in Gemfile or adding "require" statements for sprockets somewhere. +So ensure sprockets is loaded before this gem by moving the entry of sprockets in the Gemfile or adding "require" statements for sprockets somewhere. **Note:** See issue [#404](https://github.com/fnando/i18n-js/issues/404) for more details and discussion of this issue. From eb8cb0960f282db988a41962fa63a2f634182980 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 15 Oct 2020 10:16:18 +0800 Subject: [PATCH 461/466] ^ Release 3.8.0 --- CHANGELOG.md | 11 ++++++++++- lib/i18n/js/version.rb | 2 +- package.json | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54d60f81..e9297455 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Nothing +## [3.8.0] - 2020-10-15 + +### Added + +- [JS] Add option `scope` for `toHumanSize()` + (PR: https://github.com/fnando/i18n-js/pull/583) + + ## [3.7.1] - 2020-06-30 ### Fixed @@ -465,7 +473,8 @@ And today is not April Fools' Day -[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.7.1...HEAD +[Unreleased]: https://github.com/fnando/i18n-js/compare/v3.8.0...HEAD +[3.8.0]: https://github.com/fnando/i18n-js/compare/v3.7.1...v3.8.0 [3.7.1]: https://github.com/fnando/i18n-js/compare/v3.7.0...v3.7.1 [3.7.0]: https://github.com/fnando/i18n-js/compare/v3.6.0...v3.7.0 [3.6.0]: https://github.com/fnando/i18n-js/compare/v3.5.1...v3.6.0 diff --git a/lib/i18n/js/version.rb b/lib/i18n/js/version.rb index 0aef60e2..35e183a6 100644 --- a/lib/i18n/js/version.rb +++ b/lib/i18n/js/version.rb @@ -2,6 +2,6 @@ module I18n module JS - VERSION = "3.7.1" + VERSION = "3.8.0" end end diff --git a/package.json b/package.json index 1966d4a7..d5cdf6f8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "i18n-js", - "version": "3.7.1", + "version": "3.8.0", "description": "A javascript library similar to Ruby on Rails i18n gem", "author": "Nando Vieira", "license": "MIT", From b2c041d6c26d89cbcb09b508d08da8e2a71ddd14 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 16 Nov 2020 10:20:18 +0800 Subject: [PATCH 462/466] Use GitHub Actions instead of Travis CI (#586) --- .github/workflows/tests.yaml | 111 +++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 .github/workflows/tests.yaml diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 00000000..274b66d7 --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,111 @@ +name: Tests + +on: + pull_request: + branches: + - master + paths-ignore: + - 'README.md' + push: + branches: + - master + paths-ignore: + - 'README.md' + +jobs: + ruby_unit_tests: + name: Ruby Unit Tests + if: "contains(github.event.commits[0].message, '[ci skip]') == false" + strategy: + fail-fast: false + matrix: + os: + - ubuntu + ruby: + - 2.4 + - 2.5 + - 2.6 + - 2.7 + gemfile: + - gemfiles/i18n_0_6.gemfile + - gemfiles/i18n_0_7.gemfile + - gemfiles/i18n_0_8.gemfile + - gemfiles/i18n_0_9.gemfile + - gemfiles/i18n_1_0.gemfile + - gemfiles/i18n_1_1.gemfile + - gemfiles/i18n_1_2.gemfile + - gemfiles/i18n_1_3.gemfile + - gemfiles/i18n_1_4.gemfile + - gemfiles/i18n_1_5.gemfile + - gemfiles/i18n_1_6.gemfile + - gemfiles/i18n_1_7.gemfile + - gemfiles/i18n_1_8.gemfile + allow_failures: + - false + include: + - os: ubuntu + ruby: ruby-head + gemfile: gemfiles/i18n_1_8.gemfile + allow_failures: true + env: + BUNDLE_GEMFILE: "${{ matrix.gemfile }}" + BUNDLE_PATH: "./vendor/bundle" + ALLOW_FAILURES: "${{ matrix.allow_failures }}" + runs-on: ${{ matrix.os }}-latest + continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'debug' }} + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + - uses: actions/cache@v2 + with: + path: gemfiles/vendor/bundle + key: ${{ runner.os }}-gems-${{ matrix.ruby }}-${{ matrix.gemfile }}-${{ github.ref }}-${{ github.sha }}-v2 + restore-keys: | + ${{ runner.os }}-gems--${{ matrix.ruby }}-${{ matrix.gemfile }}-${{ github.ref }}- + ${{ runner.os }}-gems--${{ matrix.ruby }}-${{ matrix.gemfile }}- + ${{ runner.os }}-gems--${{ matrix.ruby }}- + - name: Bundle Install + run: | + bundle install --jobs 4 --retry 3 + - name: Test + run: bundle exec rake spec:ruby || $ALLOW_FAILURES + + js_unit_tests: + name: JS Unit Tests + if: "contains(github.event.commits[0].message, '[ci skip]') == false" + strategy: + fail-fast: false + matrix: + os: + - ubuntu + node: + - 10 + - 12 + - 14 + runs-on: ${{ matrix.os }}-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup node + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node }} + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + - uses: actions/cache@v2 + id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ github.ref }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-yarn-${{ github.ref }}- + ${{ runner.os }}-yarn- + - name: Install JS Dependencies + run: yarn install + - name: Test + run: npm test From ce7cc4ecbaa77e00beca53b301cb710c9c16edd0 Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Sun, 15 Nov 2020 18:39:18 -0800 Subject: [PATCH 463/466] Ditch the rest of Travis CI files and update readme. (#588) --- .travis.yml | 39 --- README.md | 785 +++++++++++++++++++++++++++++----------------------- i18njs.png | Bin 0 -> 10010 bytes 3 files changed, 442 insertions(+), 382 deletions(-) delete mode 100644 .travis.yml create mode 100644 i18njs.png diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a3899507..00000000 --- a/.travis.yml +++ /dev/null @@ -1,39 +0,0 @@ -# Send builds to container-based infrastructure -# http://docs.travis-ci.com/user/workers/container-based-infrastructure/ -sudo: false -language: ruby -cache: - bundler: true - yarn: true - directories: - - node_modules -rvm: - - 2.3 - - 2.4 - - 2.5 - - 2.6 - - 2.7 - - ruby-head -before_install: -# Needed to test JS -- yarn install -gemfile: - - gemfiles/i18n_0_6.gemfile - - gemfiles/i18n_0_7.gemfile - - gemfiles/i18n_0_8.gemfile - - gemfiles/i18n_0_9.gemfile - - gemfiles/i18n_1_0.gemfile - - gemfiles/i18n_1_1.gemfile - - gemfiles/i18n_1_2.gemfile - - gemfiles/i18n_1_3.gemfile - - gemfiles/i18n_1_4.gemfile - - gemfiles/i18n_1_5.gemfile - - gemfiles/i18n_1_6.gemfile - - gemfiles/i18n_1_7.gemfile - - gemfiles/i18n_1_8.gemfile -matrix: - fast_finish: true - allow_failures: - - rvm: ruby-head -notifications: - webhooks: https://www.travisbuddy.com/?insertMode=update diff --git a/README.md b/README.md index 596f4041..e5709326 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,22 @@ -# I18n.js - -[![Gem Version](http://img.shields.io/gem/v/i18n-js.svg?style=flat-square)](http://badge.fury.io/rb/i18n-js) -[![npm](https://img.shields.io/npm/v/i18n-js.svg?style=flat-square)](https://www.npmjs.com/package/i18n-js) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT) - -[![Build Status](http://img.shields.io/travis/fnando/i18n-js.svg?style=flat-square)](https://travis-ci.org/fnando/i18n-js) -[![Coverage Status](http://img.shields.io/coveralls/fnando/i18n-js.svg?style=flat-square)](https://coveralls.io/r/fnando/i18n-js) - -[![Gitter](https://img.shields.io/badge/gitter-join%20chat-1dce73.svg?style=flat-square)](https://gitter.im/fnando/i18n-js) - -> The above badges are generated by https://shields.io/ - -It's a small library to provide the Rails I18n translations on the JavaScript. +

+ i18n.js +

+ +

+ It's a small library to provide the Rails I18n translations on the JavaScript. +

+ +

+ Tests + Gem Version + npm + License: MIT + Build Status + Coverage Status + Gitter +

+ +--- Features: @@ -23,8 +28,9 @@ Features: - Lots more! :) ## Version Notice -The `master` branch (including this README) is for latest `3.0.0` instead of `2.x`. +The `master` branch (including this README) is for latest `3.0.0` instead of +`2.x`. ## Usage @@ -33,13 +39,15 @@ The `master` branch (including this README) is for latest `3.0.0` instead of `2. #### Rails app Add the gem to your Gemfile. + ```ruby gem "i18n-js" ``` #### Rails with [webpacker](https://github.com/rails/webpacker) -If you're using `webpacker`, you may need to add the dependencies to your client with: +If you're using `webpacker`, you may need to add the dependencies to your client +with: ``` yarn add i18n-js @@ -47,12 +55,14 @@ yarn add i18n-js npm install i18n-js ``` -For more details, see [this gist](https://gist.github.com/bazzel/ecdff4718962e57c2d5569cf01d332fe). +For more details, see +[this gist](https://gist.github.com/bazzel/ecdff4718962e57c2d5569cf01d332fe). #### Rails app with [Asset Pipeline](http://guides.rubyonrails.org/asset_pipeline.html) -If you're using the [asset pipeline](http://guides.rubyonrails.org/asset_pipeline.html), -then you must add the following line to your `app/assets/javascripts/application.js`. +If you're using the +[asset pipeline](http://guides.rubyonrails.org/asset_pipeline.html), then you +must add the following line to your `app/assets/javascripts/application.js`. ```javascript // @@ -68,9 +78,9 @@ then you must add the following line to your `app/assets/javascripts/application #### Rails app without [Asset Pipeline](http://guides.rubyonrails.org/asset_pipeline.html) +First, put this in your `application.html` (layout file). Then get the JS files +following the instructions below. -First, put this in your `application.html` (layout file). -Then get the JS files following the instructions below. ```erb <%# This is just an example, you can put `i18n.js` and `translations.js` anywhere you like %> <%# Unlike the Asset Pipeline example, you need to require both **in order** %> @@ -78,55 +88,66 @@ Then get the JS files following the instructions below. <%= javascript_include_tag "translations", skip_pipeline: true %> ``` -**There are two ways to get `translations.js` (For Rails app without Asset Pipeline).** - -1. This `translations.js` file can be automatically generated by the `I18n::JS::Middleware`. - Just add `config.middleware.use I18n::JS::Middleware` to your `config/application.rb` file. -2. If you can't or prefer not to generate this file, - you can move the middleware line to your `config/environments/development.rb` file - and run `rake i18n:js:export` before deploying. - This will export all translation files, including the custom scopes - you may have defined on `config/i18n-js.yml`. - If `I18n.available_locales` is set (e.g. in your Rails `config/application.rb` file) - then only the specified locales will be exported. - Current version of `i18n.js` will also be exported to avoid version mismatching by downloading. +**There are two ways to get `translations.js` (For Rails app without Asset +Pipeline).** + +1. This `translations.js` file can be automatically generated by the + `I18n::JS::Middleware`. Just add `config.middleware.use I18n::JS::Middleware` + to your `config/application.rb` file. +2. If you can't or prefer not to generate this file, you can move the middleware + line to your `config/environments/development.rb` file and run + `rake i18n:js:export` before deploying. This will export all translation + files, including the custom scopes you may have defined on + `config/i18n-js.yml`. If `I18n.available_locales` is set (e.g. in your Rails + `config/application.rb` file) then only the specified locales will be + exported. Current version of `i18n.js` will also be exported to avoid version + mismatching by downloading. #### Export Configuration (For translations) -Exported translation files generated by `I18n::JS::Middleware` or `rake i18n:js:export` can be customized with config file `config/i18n-js.yml` -(use `rails generate i18n:js:config` to create it). -You can even get more files generated to different folders and with different translations to best suit your needs. -The config file also affects developers using Asset Pipeline to require translations. -Except the option `file`, since all translations are required by adding `//= require i18n/translations`. +Exported translation files generated by `I18n::JS::Middleware` or +`rake i18n:js:export` can be customized with config file `config/i18n-js.yml` +(use `rails generate i18n:js:config` to create it). You can even get more files +generated to different folders and with different translations to best suit your +needs. The config file also affects developers using Asset Pipeline to require +translations. Except the option `file`, since all translations are required by +adding `//= require i18n/translations`. Examples: + ```yaml translations: -- file: 'public/javascripts/path-to-your-messages-file.js' - only: '*.date.formats' -- file: 'public/javascripts/path-to-your-second-file.js' - only: ['*.activerecord', '*.admin.*.title'] + - file: "public/javascripts/path-to-your-messages-file.js" + only: "*.date.formats" + - file: "public/javascripts/path-to-your-second-file.js" + only: ["*.activerecord", "*.admin.*.title"] ``` -If `only` is omitted all the translations will be saved. Also, make sure you add that initial `*`; it specifies that all languages will be exported. If you want to export only one language, you can do something like this: +If `only` is omitted all the translations will be saved. Also, make sure you add +that initial `*`; it specifies that all languages will be exported. If you want +to export only one language, you can do something like this: + ```yaml translations: -- file: 'public/javascripts/en.js' - only: 'en.*' -- file: 'public/javascripts/pt-BR.js' - only: 'pt-BR.*' + - file: "public/javascripts/en.js" + only: "en.*" + - file: "public/javascripts/pt-BR.js" + only: "pt-BR.*" ``` -Optionally, you can auto generate a translation file per available locale if you specify the `%{locale}` placeholder. +Optionally, you can auto generate a translation file per available locale if you +specify the `%{locale}` placeholder. + ```yaml translations: -- file: "public/javascripts/i18n/%{locale}.js" - only: '*' -- file: "public/javascripts/frontend/i18n/%{locale}.js" - only: ['*.frontend', '*.users.*'] + - file: "public/javascripts/i18n/%{locale}.js" + only: "*" + - file: "public/javascripts/frontend/i18n/%{locale}.js" + only: ["*.frontend", "*.users.*"] ``` You can also include ERB in your config file. + ```yaml translations: <% Widgets.each do |widget| %> @@ -135,39 +156,37 @@ translations: <% end %> ``` -You are able to exclude certain phrases or whole groups of phrases by -specifying the YAML key(s) in the `except` configuration option. The outputted -JS translations file (exported or generated by the middleware) will omit any -keys listed in `except` configuration param: +You are able to exclude certain phrases or whole groups of phrases by specifying +the YAML key(s) in the `except` configuration option. The outputted JS +translations file (exported or generated by the middleware) will omit any keys +listed in `except` configuration param: ```yaml translations: - - except: ['*.active_admin', '*.ransack', '*.activerecord.errors'] + - except: ["*.active_admin", "*.ransack", "*.activerecord.errors"] ``` - #### Export Configuration (For other things) -- `I18n::JS.config_file_path` - Expected Type: `String` - Default: `config/i18n-js.yml` - Behaviour: Try to read the config file from that location +- `I18n::JS.config_file_path` Expected Type: `String` Default: + `config/i18n-js.yml` Behaviour: Try to read the config file from that location -- `I18n::JS.export_i18n_js_dir_path` - Expected Type: `String` - Default: `public/javascripts` - Behaviour: - - Any `String`: considered as a relative path for a folder to `Rails.root` and export `i18n.js` to that folder for `rake i18n:js:export` +- `I18n::JS.export_i18n_js_dir_path` Expected Type: `String` Default: + `public/javascripts` Behaviour: + + - Any `String`: considered as a relative path for a folder to `Rails.root` and + export `i18n.js` to that folder for `rake i18n:js:export` - Any non-`String` (`nil`, `false`, `:none`, etc): Disable `i18n.js` exporting -- `I18n::JS.sort_translation_keys` - Expected Type: `Boolean` - Default: `true` +- `I18n::JS.sort_translation_keys` Expected Type: `Boolean` Default: `true` Behaviour: - - Sets whether or not to deep sort all translation keys in order to generate identical output for the same translations + + - Sets whether or not to deep sort all translation keys in order to generate + identical output for the same translations - Set to true to ensure identical asset fingerprints for the asset pipeline -- You may also set `export_i18n_js` and `sort_translation_keys` in your config file, e.g.: +- You may also set `export_i18n_js` and `sort_translation_keys` in your config + file, e.g.: ```yaml export_i18n_js: false @@ -180,21 +199,24 @@ translations: - ... ``` -To find more examples on how to use the configuration file please refer to the tests. +To find more examples on how to use the configuration file please refer to the +tests. #### Fallbacks -If you specify the `fallbacks` option, you will be able to fill missing translations with those inside fallback locale(s). -Default value is `true`. +If you specify the `fallbacks` option, you will be able to fill missing +translations with those inside fallback locale(s). Default value is `true`. Examples: + ```yaml fallbacks: true translations: -- file: "public/javascripts/i18n/%{locale}.js" - only: '*' + - file: "public/javascripts/i18n/%{locale}.js" + only: "*" ``` + This will enable merging fallbacks into each file. (set to `false` to disable). If you use `I18n` with fallbacks, the fallbacks defined there will be used. Otherwise `I18n.default_locale` will be used. @@ -203,9 +225,10 @@ Otherwise `I18n.default_locale` will be used. fallbacks: :de translations: -- file: "public/javascripts/i18n/%{locale}.js" - only: '*' + - file: "public/javascripts/i18n/%{locale}.js" + only: "*" ``` + Here, the specified locale `:de` will be used as fallback for all locales. ```yaml @@ -214,42 +237,49 @@ fallbacks: de: "en" translations: -- file: "public/javascripts/i18n/%{locale}.js" - only: '*' + - file: "public/javascripts/i18n/%{locale}.js" + only: "*" ``` -Fallbacks defined will be used, if not defined (e.g. `:pl`) `I18n.fallbacks` or `I18n.default_locale` will be used. + +Fallbacks defined will be used, if not defined (e.g. `:pl`) `I18n.fallbacks` or +`I18n.default_locale` will be used. ```yaml fallbacks: :default_locale translations: -- file: "public/javascripts/i18n/%{locale}.js" - only: '*' + - file: "public/javascripts/i18n/%{locale}.js" + only: "*" ``` -Setting the option to `:default_locale` will enforce the fallback to use the `I18n.default_locale`, ignoring `I18n.fallbacks`. + +Setting the option to `:default_locale` will enforce the fallback to use the +`I18n.default_locale`, ignoring `I18n.fallbacks`. Examples: + ```yaml fallbacks: false translations: -- file: "public/javascripts/i18n/%{locale}.js" - only: '*' + - file: "public/javascripts/i18n/%{locale}.js" + only: "*" ``` -You must disable this feature by setting the option to `false`. -To find more examples on how to use the configuration file please refer to the tests. +You must disable this feature by setting the option to `false`. +To find more examples on how to use the configuration file please refer to the +tests. #### Namespace -Setting the `namespace` option will change the namespace of the output Javascript file to something other than `I18n`. -This can be useful in no-conflict scenarios. Example: +Setting the `namespace` option will change the namespace of the output +Javascript file to something other than `I18n`. This can be useful in +no-conflict scenarios. Example: ```yaml translations: -- file: "public/javascripts/i18n/translations.js" - namespace: "MyNamespace" + - file: "public/javascripts/i18n/translations.js" + namespace: "MyNamespace" ``` will create: @@ -259,19 +289,20 @@ MyNamespace.translations || (MyNamespace.translations = {}); MyNamespace.translations["en"] = { ... } ``` - ### Adding prefix & suffix to the translations file(s) -Setting the `prefix: "import I18n from 'i18n-js';\n"` option will add the line at the beginning of the resultant translation file. -This can be useful to use this gem with the [i18n-js](https://www.npmjs.com/package/i18n-js) npm package, which is quite useful to use it with webpack. -The user should provide the semi-colon and the newline character if needed. +Setting the `prefix: "import I18n from 'i18n-js';\n"` option will add the line +at the beginning of the resultant translation file. This can be useful to use +this gem with the [i18n-js](https://www.npmjs.com/package/i18n-js) npm package, +which is quite useful to use it with webpack. The user should provide the +semi-colon and the newline character if needed. For example: ```yaml translations: -- file: "public/javascripts/i18n/translations.js" - prefix: "import I18n from 'i18n-js';\n" + - file: "public/javascripts/i18n/translations.js" + prefix: "import I18n from 'i18n-js';\n" ``` will create: @@ -281,47 +312,44 @@ import I18n from 'i18n-js'; I18n.translations || (I18n.translations = {}); ``` - `suffix` option is added in https://github.com/fnando/i18n-js/pull/561. It's similar to `prefix` so won't explain it in details. - #### Pretty Print -Set the `pretty_print` option if you would like whitespace and indentation in your output file (default: false) +Set the `pretty_print` option if you would like whitespace and indentation in +your output file (default: false) ```yaml translations: -- file: "public/javascripts/i18n/translations.js" - pretty_print: true + - file: "public/javascripts/i18n/translations.js" + pretty_print: true ``` - #### Javascript Deep Merge (:js_extend option) -By default, the output file Javascript will call the `I18n.extend` method to ensure that newly loaded locale -files are deep-merged with any locale data already in memory. To disable this either globally or per-file, -set the `js_extend` option to false +By default, the output file Javascript will call the `I18n.extend` method to +ensure that newly loaded locale files are deep-merged with any locale data +already in memory. To disable this either globally or per-file, set the +`js_extend` option to false ```yaml -js_extend: false # this will disable Javascript I18n.extend globally +js_extend: false # this will disable Javascript I18n.extend globally translations: -- file: "public/javascripts/i18n/translations.js" - js_extend: false # this will disable Javascript I18n.extend for this file + - file: "public/javascripts/i18n/translations.js" + js_extend: false # this will disable Javascript I18n.extend for this file ``` - #### Vanilla JavaScript -Just add the `i18n.js` file to your page. You'll have to build the translations object -by hand or using your favorite programming language. More info below. - +Just add the `i18n.js` file to your page. You'll have to build the translations +object by hand or using your favorite programming language. More info below. #### Via NPM with webpack and CommonJS +Add the following line to your package.json dependencies where version is the +version you want: -Add the following line to your package.json dependencies -where version is the version you want ```javascript "i18n-js": "{version_constraint}" @@ -329,17 +357,21 @@ where version is the version you want // npm install requires it to be the gzipped tarball, see [npm install](https://www.npmjs.org/doc/cli/npm-install.html) "i18n-js": "https://github.com/fnando/i18n-js/archive/{tag_name_or_branch_name_or_commit_sha}.tar.gz" ``` + Run npm install then use via + ```javascript var i18n = require("i18n-js"); ``` - ### Setting up -You **don't** need to set up a thing. The default settings will work just okay. But if you want to split translations into several files or specify contexts, you can follow the rest of this setting up section. +You **don't** need to set up a thing. The default settings will work just okay. +But if you want to split translations into several files or specify contexts, +you can follow the rest of this setting up section. Set your locale is easy as + ```javascript I18n.defaultLocale = "pt-BR"; I18n.locale = "pt-BR"; @@ -347,9 +379,11 @@ I18n.currentLocale(); // pt-BR ``` -**NOTE:** You can now apply your configuration **before I18n** is loaded like this: +**NOTE:** You can now apply your configuration **before I18n** is loaded like +this: + ```javascript -I18n = {} // You must define this object in top namespace, which should be `window` +I18n = {}; // You must define this object in top namespace, which should be `window` I18n.defaultLocale = "pt-BR"; I18n.locale = "pt-BR"; @@ -359,7 +393,8 @@ I18n.currentLocale(); // pt-BR ``` -In practice, you'll have something like the following in your `application.html.erb`: +In practice, you'll have something like the following in your +`application.html.erb`: ```erb ``` -By default missing translations will first be looked for in less -specific versions of the requested locale and if that fails by taking -them from your `I18n.defaultLocale`. +By default missing translations will first be looked for in less specific +versions of the requested locale and if that fails by taking them from your +`I18n.defaultLocale`. ```javascript // if I18n.defaultLocale = "en" and translation doesn't exist @@ -431,24 +468,26 @@ them from your `I18n.defaultLocale`. I18n.t("some.missing.scope"); ``` -Custom fallback rules can also be specified for a particular language. There -are three different ways of doing it so: +Custom fallback rules can also be specified for a particular language. There are +three different ways of doing it so: ```javascript I18n.locales.no = ["nb", "en"]; I18n.locales.no = "nb"; -I18n.locales.no = function(locale){ return ["nb"]; }; +I18n.locales.no = function (locale) { + return ["nb"]; +}; ``` By default a missing translation will be displayed as [missing "name of scope" translation] -While you are developing or if you do not want to provide a translation -in the default language you can set +While you are developing or if you do not want to provide a translation in the +default language you can set ```javascript -I18n.missingBehaviour='guess'; +I18n.missingBehaviour = "guess"; ``` this will take the last section of your scope and guess the intended value. @@ -458,28 +497,34 @@ Camel case becomes lower cased text and underscores are replaced with space becomes "what is your favorite Christmas present" -In order to still detect untranslated strings, you can -i18n.missingTranslationPrefix to something like: +In order to still detect untranslated strings, you can set +`i18n.missingTranslationPrefix` to something like: + ```javascript -I18n.missingTranslationPrefix = 'EE: '; +I18n.missingTranslationPrefix = "EE: "; ``` And result will be: + ```javascript -"EE: what is your favorite Christmas present" +"EE: what is your favorite Christmas present"; + ``` This will help you doing automated tests against your localisation assets. Some people prefer returning `null` for missing translation: + ```javascript -I18n.missingTranslation = function () { return undefined; }; +I18n.missingTranslation = function () { + return undefined; +}; ``` Pluralization is possible as well and by default provides English rules: ```javascript -I18n.t("inbox.counting", {count: 10}); // You have 10 messages +I18n.t("inbox.counting", { count: 10 }); // You have 10 messages ``` The sample above expects the following translation: @@ -495,21 +540,34 @@ en: **NOTE:** Rails I18n recognizes the `zero` option. -If you need special rules just define them for your language, for example Russian, just add a new pluralizer: +If you need special rules just define them for your language, for example +Russian, just add a new pluralizer: ```javascript I18n.pluralization["ru"] = function (count) { - var key = count % 10 == 1 && count % 100 != 11 ? "one" : [2, 3, 4].indexOf(count % 10) >= 0 && [12, 13, 14].indexOf(count % 100) < 0 ? "few" : count % 10 == 0 || [5, 6, 7, 8, 9].indexOf(count % 10) >= 0 || [11, 12, 13, 14].indexOf(count % 100) >= 0 ? "many" : "other"; + var key = + count % 10 == 1 && count % 100 != 11 + ? "one" + : [2, 3, 4].indexOf(count % 10) >= 0 && + [12, 13, 14].indexOf(count % 100) < 0 + ? "few" + : count % 10 == 0 || + [5, 6, 7, 8, 9].indexOf(count % 10) >= 0 || + [11, 12, 13, 14].indexOf(count % 100) >= 0 + ? "many" + : "other"; return [key]; }; ``` -You can find all rules on . +You can find all rules on +. -If you're using the same scope over and over again, you may use the `scope` option. +If you're using the same scope over and over again, you may use the `scope` +option. ```javascript -var options = {scope: "activerecord.attributes.user"}; +var options = { scope: "activerecord.attributes.user" }; I18n.t("name", options); I18n.t("email", options); @@ -538,14 +596,13 @@ I18n.l("percentage", 123.45); // 123.450% ``` -To have more control over number formatting, you can use the -`I18n.toNumber`, `I18n.toPercentage`, `I18n.toCurrency` and `I18n.toHumanSize` -functions. +To have more control over number formatting, you can use the `I18n.toNumber`, +`I18n.toPercentage`, `I18n.toCurrency` and `I18n.toHumanSize` functions. ```javascript -I18n.toNumber(1000); // 1,000.000 -I18n.toCurrency(1000); // $1,000.00 -I18n.toPercentage(100); // 100.000% +I18n.toNumber(1000); // 1,000.000 +I18n.toCurrency(1000); // $1,000.00 +I18n.toPercentage(100); // 100.000% ``` The `toNumber` and `toPercentage` functions accept the following options: @@ -558,9 +615,9 @@ The `toNumber` and `toPercentage` functions accept the following options: See some number formatting examples: ```javascript -I18n.toNumber(1000, {precision: 0}); // 1,000 -I18n.toNumber(1000, {delimiter: ".", separator: ","}); // 1.000,000 -I18n.toNumber(1000, {delimiter: ".", precision: 0}); // 1.000 +I18n.toNumber(1000, { precision: 0 }); // 1,000 +I18n.toNumber(1000, { delimiter: ".", separator: "," }); // 1.000,000 +I18n.toNumber(1000, { delimiter: ".", precision: 0 }); // 1.000 ``` The `toCurrency` function accepts the following options: @@ -576,7 +633,7 @@ The `toCurrency` function accepts the following options: You can provide only the options you want to override: ```javascript -I18n.toCurrency(1000, {precision: 0}); // $1,000 +I18n.toCurrency(1000, { precision: 0 }); // $1,000 ``` The `toHumanSize` function accepts the following options: @@ -595,18 +652,17 @@ I18n.toHumanSize(1234); // 1KB I18n.toHumanSize(1234 * 1024); // 1MB ``` - #### Date formatting ```javascript // accepted formats -I18n.l("date.formats.short", "2009-09-18"); // yyyy-mm-dd -I18n.l("time.formats.short", "2009-09-18 23:12:43"); // yyyy-mm-dd hh:mm:ss -I18n.l("time.formats.short", "2009-11-09T18:10:34"); // JSON format with local Timezone (part of ISO-8601) +I18n.l("date.formats.short", "2009-09-18"); // yyyy-mm-dd +I18n.l("time.formats.short", "2009-09-18 23:12:43"); // yyyy-mm-dd hh:mm:ss +I18n.l("time.formats.short", "2009-11-09T18:10:34"); // JSON format with local Timezone (part of ISO-8601) I18n.l("time.formats.short", "2009-11-09T18:10:34Z"); // JSON format in UTC (part of ISO-8601) -I18n.l("date.formats.short", 1251862029000); // Epoch time -I18n.l("date.formats.short", "09/18/2009"); // mm/dd/yyyy -I18n.l("date.formats.short", (new Date())); // Date object +I18n.l("date.formats.short", 1251862029000); // Epoch time +I18n.l("date.formats.short", "09/18/2009"); // mm/dd/yyyy +I18n.l("date.formats.short", new Date()); // Date object ``` You can also add placeholders to the date format: @@ -615,14 +671,16 @@ You can also add placeholders to the date format: I18n.translations["en"] = { date: { formats: { - ordinal_day: "%B %{day}" - } - } -} -I18n.l("date.formats.ordinal_day", "2009-09-18", { day: '18th' }); // Sep 18th + ordinal_day: "%B %{day}", + }, + }, +}; + +I18n.l("date.formats.ordinal_day", "2009-09-18", { day: "18th" }); // Sep 18th ``` -If you prefer, you can use the `I18n.toTime` and `I18n.strftime` functions to format dates. +If you prefer, you can use the `I18n.toTime` and `I18n.strftime` functions to +format dates. ```javascript var date = new Date(); @@ -661,8 +719,10 @@ The accepted formats for `I18n.strftime` are: Check out `spec/*.spec.js` files for more examples! #### Using pluralization and number formatting together -Sometimes you might want to display translation with formatted number, like adding thousand delimiters to displayed number -You can do this: + +Sometimes you might want to display translation with formatted number, like +adding thousand delimiters to displayed number You can do this: + ```json { "en": { @@ -674,153 +734,176 @@ You can do this: } } ``` + ```js var point_in_number = 1000; -I18n.t('point', { count: point_in_number, formatted_number: I18n.toNumber(point_in_number) }); +I18n.t("point", { + count: point_in_number, + formatted_number: I18n.toNumber(point_in_number), +}); ``` -Output should be `1,000 points` +Output should be `1,000 points` ## Using multiple exported translation files on a page. -This method is useful for very large apps where a single contained translations.js file is not desirable. Examples would be a global translations file and a more specific route translation file. + +This method is useful for very large apps where a single contained +translations.js file is not desirable. Examples would be a global translations +file and a more specific route translation file. ### Rails without asset pipeline -1. Setup your `config/i18n-js.yml` to have multiple files and try to minimize any overlap. - - ```yaml - sort_translation_keys: true - fallbacks: false - - translations: - + file: "app/assets/javascript/nls/welcome.js" - only: - + '*.welcome.*' - - + file: "app/assets/javascript/nls/albums.js" - only: - + '*.albums.*' - - + file: "app/assets/javascript/nls/global.js" - only: - + '*' - # Exempt any routes specific translations from being - # included in the global translation file - except: - + '*.welcome.*' - + '*.albums.*' - ``` - When `rake i18n:js:export` is executed it will create 3 translations files that can be loaded via the `javascript_include_tag` - -2. Add the `javascript_include_tag` to your layout and to any route specific files that will require it. - ```ruby - # views/layouts/application.html.erb - <%= javascript_include_tag( - "i18n" - "nls/global" - ) %> - ``` - and in the route specific - - ```ruby - # views/welcome/index.html.erb - <%= javascript_include_tag( - "nls/welcome" - ) %> - ``` + +1. Setup your `config/i18n-js.yml` to have multiple files and try to minimize + any overlap. + +```yaml +sort_translation_keys: true +fallbacks: false + +translations: + + file: "app/assets/javascript/nls/welcome.js" + only: + + '*.welcome.*' + + + file: "app/assets/javascript/nls/albums.js" + only: + + '*.albums.*' + + + file: "app/assets/javascript/nls/global.js" + only: + + '*' + # Exempt any routes specific translations from being + # included in the global translation file + except: + + '*.welcome.*' + + '*.albums.*' +``` + +When `rake i18n:js:export` is executed it will create 3 translations files that +can be loaded via the `javascript_include_tag` + +2. Add the `javascript_include_tag` to your layout and to any route specific + files that will require it. + +```ruby + # views/layouts/application.html.erb + <%= javascript_include_tag( + "i18n" + "nls/global" + ) %> +``` + +and in the route specific + +```ruby + # views/welcome/index.html.erb + <%= javascript_include_tag( + "nls/welcome" + ) %> +``` 3. Make sure that you add these files to your `config/application.rb` - ```ruby - config.assets.precompile += %w( - i18n - nls/* - ) - ``` +```ruby + config.assets.precompile += %w( + i18n + nls/* + ) +``` ### Using require.js / r.js To use this with require.js we are only going to change a few things from above. -1. In your `config/i18n-js.yml` we need to add a better location for the i18n to be exported to. You want to use this location so that it can be properly precompiled by r.js. - - ```yaml - export_i18n_js: "app/assets/javascript/nls" - ``` - -2. In your `config/require.yml` we need to add a map, shim all the translations, and include them into the appropriate modules - - ```yaml - # In your maps add (if you do not have this you will need to add it) - map: - '*': - i18n: 'nls/i18n' - - # In your shims - shims: - nls/welcome: - deps: - + i18n - - nls/global: - deps: - + i18n - - # Finally in your modules - modules: - + name: 'application' - include: - + i18n - + 'nls/global' - - + name: 'welcome' - exclude: - + application - include: - + 'nls/welcome' - ``` -3. When `rake assets:precompile` is executed it will optimize the translations into the correct modules so they are loaded with their assigned module, and loading them with requirejs is as simple as requiring any other shim. - - ```javascript - define(['welcome/other_asset','nls/welcome'], function (otherAsset){ - // ... - }); - ``` -4. (optional) As an additional configuration we can make a task to be run before the requirejs optimizer. This will allow any automated scripts that run the requirejs optimizer to export the strings before we run r.js. - - ```rake - # lib/tasks/i18n.rake - Rake::Task[:'i18n:js:export'].prerequisites.clear - task :'i18n:js:export' => :'i18n:js:before_export' - task :'requirejs:precompile:external' => :'i18n:js:export' - - namespace :i18n do - namespace :js do - task :before_export => :'assets:environment' do - I18n.load_path += Dir[Rails.root.join('config', 'locales', '*.{yml,rb}')] - I18n.backend.load_translations - end +1. In your `config/i18n-js.yml` we need to add a better location for the i18n to + be exported to. You want to use this location so that it can be properly + precompiled by r.js. + +```yaml +export_i18n_js: "app/assets/javascript/nls" +``` + +2. In your `config/require.yml` we need to add a map, shim all the translations, + and include them into the appropriate modules + +```yaml +# In your maps add (if you do not have this you will need to add it) +map: + '*': + i18n: 'nls/i18n' + +# In your shims +shims: + nls/welcome: + deps: + + i18n + + nls/global: + deps: + + i18n + +# Finally in your modules +modules: + + name: 'application' + include: + + i18n + + 'nls/global' + + + name: 'welcome' + exclude: + + application + include: + + 'nls/welcome' +``` + +3. When `rake assets:precompile` is executed it will optimize the translations + into the correct modules so they are loaded with their assigned module, and + loading them with requirejs is as simple as requiring any other shim. + +```javascript +define(["welcome/other_asset", "nls/welcome"], function (otherAsset) { + // ... +}); +``` + +4. (optional) As an additional configuration we can make a task to be run before + the requirejs optimizer. This will allow any automated scripts that run the + requirejs optimizer to export the strings before we run r.js. + +```rake +# lib/tasks/i18n.rake +Rake::Task[:'i18n:js:export'].prerequisites.clear +task :'i18n:js:export' => :'i18n:js:before_export' +task :'requirejs:precompile:external' => :'i18n:js:export' + +namespace :i18n do + namespace :js do + task :before_export => :'assets:environment' do + I18n.load_path += Dir[Rails.root.join('config', 'locales', '*.{yml,rb}')] + I18n.backend.load_translations end end - ``` +end +``` ## Using I18n.js with other languages (Python, PHP, ...) -The JavaScript library is language agnostic; so you can use it with PHP, Python, [your favorite language here]. -The only requirement is that you need to set the `translations` attribute like following: +The JavaScript library is language agnostic; so you can use it with PHP, Python, +[your favorite language here]. The only requirement is that you need to set the +`translations` attribute like following: ```javascript I18n.translations = {}; I18n.translations["en"] = { - message: "Some special message for you" -} + message: "Some special message for you", +}; I18n.translations["pt-BR"] = { - message: "Uma mensagem especial para você" -} + message: "Uma mensagem especial para você", +}; ``` - ## Known Issues ### Missing translations in precompiled file(s) after adding any new locale file @@ -828,66 +911,80 @@ I18n.translations["pt-BR"] = { Due to the design of `sprockets`: - `depend_on` only takes file paths, not directory paths -- registered `preprocessors` are only run when the fingerprint of any asset file, including `.erb` files, is changed +- registered `preprocessors` are only run when the fingerprint of any asset + file, including `.erb` files, is changed + +This means that new locale files will not be detected, and so they will not +trigger a i18n-js refresh. There are a few approaches to work around this: -This means that new locale files will not be detected, and so they will not trigger a i18n-js refresh. There are a few approaches to work around this: +1. You can force i18n-js to update its translations by completely clearing the + assets cache. Use one of the following: -1. You can force i18n-js to update its translations by completely clearing the assets cache. Use one of the following: - ```bash $ rake assets:clobber # Or, with older versions of Rails: $ rake tmp:cache:clear ``` -These commands will remove *all* fingerprinted assets, and you will have to recompile them with +These commands will remove _all_ fingerprinted assets, and you will have to +recompile them with ```bash $ rake assets:precompile ``` -or similar commands. If you are precompiling assets on the target machine(s), cached pages may be broken by this, so they will need to be refreshed. +or similar commands. If you are precompiling assets on the target machine(s), +cached pages may be broken by this, so they will need to be refreshed. 2. You can change something in a different locale file. 3. Finally, you can change `config.assets.version`. -**Note:** See issue [#213](https://github.com/fnando/i18n-js/issues/213) for more details and discussion of this issue. +**Note:** See issue [#213](https://github.com/fnando/i18n-js/issues/213) for +more details and discussion of this issue. ### Translations in JS are not updated when Sprockets not loaded before this gem -The "rails engine" declaration will try to detect existence of "sprockets" before adding the initailizer -If sprockets is loaded after this gem, the preprocessor for -making JS translations file cache to depend on content of locale files will not be hooked. -So ensure sprockets is loaded before this gem by moving the entry of sprockets in the Gemfile or adding "require" statements for sprockets somewhere. +The "rails engine" declaration will try to detect existence of "sprockets" +before adding the initailizer If sprockets is loaded after this gem, the +preprocessor for making JS translations file cache to depend on content of +locale files will not be hooked. So ensure sprockets is loaded before this gem +by moving the entry of sprockets in the Gemfile or adding "require" statements +for sprockets somewhere. -**Note:** See issue [#404](https://github.com/fnando/i18n-js/issues/404) for more details and discussion of this issue. +**Note:** See issue [#404](https://github.com/fnando/i18n-js/issues/404) for +more details and discussion of this issue. ### JS `I18n.toCurrency` & `I18n.toNumber` cannot handle large integers -The above methods use `toFixed` and it only supports 53 bit integers. -Ref: http://2ality.com/2012/07/large-integers.html +The above methods use `toFixed` and it only supports 53 bit integers. Ref: +http://2ality.com/2012/07/large-integers.html -Feel free to find & discuss possible solution(s) at issue [#511](https://github.com/fnando/i18n-js/issues/511) +Feel free to find & discuss possible solution(s) at issue +[#511](https://github.com/fnando/i18n-js/issues/511) ### Only works with `Simple` backend -If you set `I18n.backend` to something other than the default `Simple` backend, you will likely get an exception like this: +If you set `I18n.backend` to something other than the default `Simple` backend, +you will likely get an exception like this: ``` Undefined method 'initialized?' for ``` -For now, i18n-js is only compatible with the `Simple` backend. -If you need a more sophisticated backend for your rails application, like `I18n::Backend::ActiveRecord`, you can setup i18n-js to get translations from a separate `Simple` backend, by adding the following in an initializer: +For now, i18n-js is only compatible with the `Simple` backend. If you need a +more sophisticated backend for your rails application, like +`I18n::Backend::ActiveRecord`, you can setup i18n-js to get translations from a +separate `Simple` backend, by adding the following in an initializer: ```ruby I18n::JS.backend = I18n.backend I18n.backend = I18n::Backend::Chain.new(, I18n.backend) ``` -This will use your backend with the default `Simple` backend as fallback, while i18n-js only sees and uses the simple backend. -This means however, that only translations from your static locale files will be present in JavaScript. +This will use your backend with the default `Simple` backend as fallback, while +i18n-js only sees and uses the simple backend. This means however, that only +translations from your static locale files will be present in JavaScript. If you do cannot use a `Chain`-Backend for some reason, you can also set @@ -896,9 +993,11 @@ I18n::JS.backend = I18n::Backend::Simple.new I18n.backend = ``` -However, the automatic reloading of translations in developement will not work in this case. -This is because Rails calls `I18n.reload!` for each request in development, but `reload!` will not be called on `I18n::JS.backend`, since it is a different object. -One option would be to patch `I18n.reload!` in an initializer: +However, the automatic reloading of translations in developement will not work +in this case. This is because Rails calls `I18n.reload!` for each request in +development, but `reload!` will not be called on `I18n::JS.backend`, since it is +a different object. One option would be to patch `I18n.reload!` in an +initializer: ```ruby module I18n @@ -909,11 +1008,12 @@ module I18n end ``` -See issue [#428](https://github.com/fnando/i18n-js/issues/428) for more details and discussion of this issue. +See issue [#428](https://github.com/fnando/i18n-js/issues/428) for more details +and discussion of this issue. ## Maintainer -- Nando Vieira - +- Nando Vieira - ## Contributing @@ -921,13 +1021,14 @@ Once you've made your great commits: 1. [Fork](http://help.github.com/forking/) I18n.js 2. Create a branch with a clear name -3. Make your changes (Please also add/change spec, README and CHANGELOG if applicable) +3. Make your changes (Please also add/change spec, README and CHANGELOG if + applicable) 4. Push changes to the created branch 5. [Create an Pull Request](http://github.com/fnando/i18n-js/pulls) 6. That's it! -Please respect the indentation rules and code style. -And use 2 spaces, not tabs. And don't touch the versioning thing. +Please respect the indentation rules and code style. And use 2 spaces, not tabs. +And don't touch the versioning thing. ## Running tests @@ -949,21 +1050,19 @@ You can run both Ruby and JavaScript specs with `rake spec`. (The MIT License) -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -'Software'), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the 'Software'), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/i18njs.png b/i18njs.png new file mode 100644 index 0000000000000000000000000000000000000000..d322733cef8c6dd0a0731ef049462fb6a9809e80 GIT binary patch literal 10010 zcmYj%cT`i&^EXY3bU~^#MT(%5OBDzm0Rz$n>FoxP9z^NVJ5m&KDT2}jRFEK|fCvIo zZW6jQX=3Pz6p`v1pWpY7_ne%~&d%)2XJ&TK&Tf)yVtj+~EYDdoGBQR3eH}A0GD?Wl zAETutwcfss(qv>5CniV>T@op#WO7BvMoit3)DcrQ`PY&@8YWH}e@UdYfitPy&c6D8 zga70HCo=(hl1Rz_CQ52p{g0(;a#h1zT+L!W*!q7X*Z--NvbPsgGZj-a)6u^Bf8(pk zYD@P~VyY&)@g8%5meU^SL7PyL-OXgg&y?%L`&L`&^5Y~E8YXkTPNa7JfyuCSbva6JD+B1(%>h;aZe_^- z2r=$6${2A%FNNId(#h@A&f9ttK{g4`fMv{xlmCE8 zT(|z?9^>R5!%PpQzhWw8qSsBgvm}2#y0#jtz8s~y6sfnBF0&G&@zuqf7;%FbZh$v` zxS1lq7Oy&J9kOuGY(B{R`vc=~H?N;bQSxHw%_&b8QlRG07O+V?&NTK%D6;=n!i?XY)mV+$fZM}%K_m8oV@`fQ z!ftl!KE|2Geb&sMM_W#!+&7YxN9=>9y_`EWF^eI{Uzw6$^dF78d3?Wrt4BYx-zd5F zX6m%J!}3GT3D5&?5rsF8`4O%^;t;TvCcB<+y;BR?&b->Cowt!JzZ{`I>f|@$Yuly! zc-GHu)G=_#CS*BUbJ+I&H*d#LC*M9~S}!tXEnWpSNm_|jpYm{>^S9nglOttk$oAf9 zjOMtj_qdDqCk+f~MQx=?!zMB7acT=eCL3`YGrslKytT#nQk zbMo%i$rZb9wwozQoTyDFZ2-3*Gh=fyeHM*OYm+)B!H2Ye9?Y3+EtmLb4hW8OeR|;` zEq1G?=|K{$v=S}Gsf}fewv{{6U&`TTE5%Boj+8^mUa!C|OMs~@=Ku6Q!0r`3XWzbz)mIMOUXR6U>%4{`n zq`Qjroj(bN;_Rjxm9e5rEsHCL_PEpL59=@eBL2ebmo8{-o{|O5^b%-Tq7xIB1VYw- z?av%8@D}5IdGjO(PtV`HDe>pCj{J)e!hzb2fbh-tM*U*)TeUWg-Xb>xZ+PAi2Qk^Q z(^Ix>A3lDZX^jkxXkAMAa9+ZzbgHzJ&pMpXjLpW|CNlEx4}sYh$GQ3WXR5)2wzCgH z-iEwC_~X4h(G-A<*S+^P{l#bnO7rE*mljt>8(zFfxTgLGosphyWEePo$6?dLVdk7E z_N9o3UMZw(&c@BH{Fq@y%$_Gv+;C;X$YAAOzyW%XXJ3AEr!wp{ne5u+-Mf@ldW532 zM{d*=)FM;~iJ}?gN@}VKZi?kP$nR5x;+*;b8HvcyWIC>Qzn1u-vRXg+g}J z9{*58A6;2mnv-K5LZ4|9>xLhJb)=WbAwGSgnI70DP098>1HZd_!7;Dcaf+WsnzKg- zRdFug8Z`)&z$xnu4FdxM8}uI|9OmX_233E}sEqa7+h@U5Ar1~@NMW{54K|nY1~je| z`JvLImoIx|f{p?WHiBr0Ogax)cQ3i?-*S6Gc8FPkDfH^02n=0gFbV?IGL`9jI~C~N z@YJ%V`E#(nD;e$${~WxY(okJpJ+KhHLC@K8LX3@}OGrH0Zk764`|hcR2L;Aq5vhR& zw9N~iyuMGA^w)glrDRN?yYk0)Q! zpuszn3j@&$3w3p8xZ}C~+Qcws4<7Izbxf-cPfmtvU3+4qfNC__wQ(b?zQk2qK3BSd zq>;WFfWJ^__C`5JL1P?DMpESTNj6Lfk>$L#nIvNbJTH=fD`A;>?0P39aZuGFER%*L zV=J^dgBE$Htb{RLwjcj1t6qvRd2sD;=t7T<9GcFE5;Zwo=n35DC&J%`9}j%n;&#rA7_+9X%+Bs(c04D zspMMG2*vY0+gqyPj{A$fIwA%+h2jKg0IaEb`#hJ3yn?N63&zgDM$|sJ%4j0T=WpJH zgT7Y5A{$@;UTzwf4SZR>kF{(n=#p*r&y0q?pGGwV55MwrC2E2yw?S3))<@UI(y)4EH?=D{g$B(wiuu4ZctOp4M62Mk zX9tCt;VThoS8~|McF%208ih~m3=_$oXn!qo7$y=|X0VSW!@kb2H}ardOzpP`mN)2> zG5?q^NZJ_f4B6l$@55BZbkt*>tzrzHyMD`HPGemc6pI?r{)F8Bxooixt5`+iE~25g9154i@<1Uv|_O za1{XUklKl+rkY=IrlRIohrm;{ud|{3^6#cYPt@KO1$>aM{*2zc{7yaWV_F_$=T_*_k0S(NMtmFps*a;0!8sKNg7XN2t$u%mnoAaZ04s zZ{~;LS@cj3Hb3qc_Oo}tUwzuFXYu4DJ7B_xDeEWHUrBrhnC4SUG3sP(LK-57z@@~xSocBx(1?8L@a;Ofq;Rkpfcj9gd5F5)o zs~&l`q}i{{Oe_x-|bGaEvdvd;2o_Xp<-Tn^i`o?VJLM88ARL-Ev=!`HxC_rDy9N7Q`Cy2 zUPdiY(B@)KPUhhH?!5T#kAc^w$BwU??{MkPD{s(r{ryqM`U^da66tzCsdIyEl5DWB z_oMtX&W-Jy;PAnjACfA=e~yq=;1c(B^GPxp_o01g$X6ls_3?G7=vEI=&pxS18tS>H z;y=M&2mmis-fmcWZGIfZ-*l5pem=w^AjvNHJMDvmOR>p)Wi`E2zf0^-5$M6$kA#Mi zsABGTyxcKjc#ve_+pj|eknX_aop&F5q66Bve*el`>@O|OTquOmQ^H_w{$w`}cslJ@ zhV;TN!&$o#A|=Ua5mmWQ3aR+G8oC(e(tvKw-*_sWj5Xx3<86VOQRD@XU8 z(_j8w?m2e1&v5%~&Bk^DCsl{K8j__h9FwC+QO3>udJNkx%HCmCw2z8*K$ zxt*b|Y0roQxF!@| z&3@NL?}2x2m3Tlgw<_w?T%(S>bL9BlJN1Kpuj9RSgR;Q{Zb97}Jomz@$;i;0|Vy-`&oC|?*(|FQncwq{9?TDQzR zk-e)|sHlaC7t8SW5Z;O)mbZlmdfxNdOWG=$$Yd%LGiF?|aEekoE=?_X@fGFEVAPo3 z`u4-&%PphbOxYK)ZJ4W1wrN(M7)CsnazA`cEly@xmJ@q2-`Qnku?|cy4RjONS0~Xd zMIu9<(Z@{_p|5|8yS6ZY_8v<)m%IYaHMYd8}dsG!@e6E!wb8D=PN2qFD__% zA8S>5%|FGwHLRtt&(05du=6;&K0>4bsv*BiF#M3e!NDk;9c|4ZD{55cY9Yahxsk6g zD%$R1HHrYj@ph@$LcX)pr{_4DZK9gZy?qzZZML-$9+C9`s<)i5!7Y^E*Hx2(hf={`n` zsG;MPk&tc|ixP__mEk4+r zS%1q#zGu?uT4Afp?g!53TpO9*)!d1^a_$0|SrcIz11b8QuJ}`&x2AkCl4sHarC-^0 zsiN5FID1YeoNflJo<`L}Av<>p74t3j7ah|=58twiXSek}v)MaBHWu24-Pz^*kS1x$%}4UdhWEYs z@Q_rHk@R7m`h}+ts=|wVt+0w;@pPxv<)(snd|fF(A|ty7+IYk+<5!rq(hXAh+6Znn z6QY_g@^xh1%DPa1V?Rq+E~s0gx9>UAX|atS!J}_Aa9`qQp3dz?Yjt-E!!Jw3B3C=? z>Rc9l<-+RxF1Whjk;UAYgTha|iD|){v>u|Yqy#)%@aG`@=oXsJDt9Lnhb{gyQ;cqE z#C31frCu}X$;Tu?snln65|y0;5^WFR=gjq(Dw5wj#J8M6h}ah2*{`x34|Nn1Npkt= zTH+lob<$owPlhFHKOuHTboBxwh^YNm!to^nVrTNLI=R4fc;(C6e8W|%KQ5}FHp4KP zkUV?pV61WnZb{)Uy7rIY@OdZIL)bAff@s2Z_PPGR_`#PJl*n$*97Qrd-%lz@B4 z0rViUb!c3xJdQqLqVp^QqL0M{)cf z%y&a;%JUMtOvmE6KRZ?}1$}Ak{$tdgTe-9h9|cHM~bXV=(NDqVBVGxFj3x*^YrCW(rf&EbFEe>_gT z^eOf4eUgp?(O+L#Q2SFb68>DP%rCkdXgYe$;!Bg7LnV1{0GX$UG-IpFsK&YAt5MOu zU>stn)R9c_RDrbKe3;-1LRl*D7zw|EchH@w&Tk&>mI_$YU4Ndmz+A6?vOjJ_^H%zG zZURfrpw)td%e{6z9zAcM%H}Z6>y{;OyIM&NjL?t;w2FJtAR>YUN0}Mq$Vw>hLTJm) zd5Q$=c)qCe02jmD*Msw%NE&l?(BL!z&2NP*`tu8$EGHKUdbtZPT)lzy z{4{ykvx@a-^6yRaS zi{%Wccm5Q~G%Kg{2WDj2M;X4$-#a90!Nq}QH4_w^z!X<<{V~Xt-;;B8i4^v{kZ_u1 zdIU*M0YKZcIDw+i_)6Y%e@5P?E6sW`1M_TOZjOZlHVJ758>$Zbs6KfBC;5;TxJlP4 zAMh{{l21z#ZHDeGroHl!2To6*wv)No+x(Hc>x;J78_xHQ)wku__rckt1$xqCLwE3i zB1tCaPsv2;+Gu7X6p;p#zsCjTWh%Zm0^ZLQAFL$`ME;m}q~?r1`8*n1r!spO@kt;i z%;@w~&6s4oG5;++SqvO^#ca9rD+qa}~9~AQ(5({`(>32&e*weiAHR66|=$Y30~jtZPhhT4!k%3RW0x zxniqf(8`o5JsEP@BnV@~iKj?q5bQtm2F!556>=mpYc5HXcfW1{f1A>N{{MuyVY_Nd zS_QPzr8A|>QzpcK? zS1Azj#Z3j8&VIRXwaE0S8WAk&31=S2m+HIDq2 znaf0J*6sRy8|{muE)`23TFvm<=R{_YfY0wl=9yT`b3WvsTxw=GZT*C#+axSH5sQpD z&&jg!+|=*7V5KKf9{R>z@Ci?kGiYN4TopFG`zh`m&Zv0p1ZqbUYdhBF{^ot4?ChKl zFXny?p8pB7A~ak;tywMS5shSFjm`EEEaB~^z>faT zz%o@}*+PJ?YGi{Q&(mX+=;|@b{5_ta_~e+ZF3vq80^GuI^UMF)d;Ies-oKxj@byqt zP_ZXSo-NO2#}=PS=?Qo0D&r(ClA~G5tp&E%0ZMtt2_-6aNsw~+CqOwqge`OVmhxt;A{dP< ztC7lAJ%W2@yrqGOU#f>Q>uzA*EUM2#q|{W;q0Uwz4qAEJ=l-oz+_TrSf_pFAReLtI z@F4;n_`Jx4c=)Z==HLhk;oO?EDV68=$P!opoI9t&R-RfR^$ladXRyt32w@%pa8JIF z@3tO)FZoamNbe(4sN&N9k~@sNx%->+*jw#Oq-xuAvPg60;EI78j{KQZMY7+pKZTks zVqTO*RAhtO2sa-JTDf7bPaL77WM>W|nLUH#k~Ah!_-rVh^17aTRnzli0L!lILeC}9 zqS#+I$vTZZf^B55w_o1jVyg|Gi(vuKzYwD`nL{u~w#;rgmGbaEVBZ=O!==40pZXaw zsCU0IAg4FWhdz69sl3mo*BL^f6<|sEEKw``k~fj1vCDJ{b}!_qv2nm3Y6x!tqYA*3 z&I}h{dJUvDo|`cVf-)C#OP5x;IfF(2aj~;ntkzv;BEiWzHJi7%5r(w*`IU|JGviPL z!D1X<;5svZb%=C|IZ1ID7SI``5ymCH+K)daF~^W?DGSniE%3|FOiEq_6Pm^rYdqeU zH-PgBz}-@lKKgJN?qFK>DtMnXExjAg*_izsO3AmmF{pj6blwcp~XXp~)u&N=^C zx}Nf7Cj_(7!L-P(h>fuesFxPn0G`OB;|%6JKd}f~-3S0Uy%+lzWefk=hCfD|1Klg0 z0n%*2&tEh_1A6%J2@+!pIos`7cjt+>b}adc%6wdNfH|OfBp%rPqB5dl2wYVfRTLyMyjAW{a#_vN?pEgxV z=&6A3o2rYA=kE?`%WjMy$Ft%^0xPe5ARLpgMO{jnd?OY%SGx5Es+s<-zGjw+1ohYI)Z7z zV8$}%d+$$@Q>vh_!B^uwtgeD}B%y;Ea}{;>j|arVf0~>?^;H*z?mX{mk`#8JF;l=M z`rmbTLc30lX4dZS$zdqTWob0~#uI~=^knNJ$g_6_bsT~^_n>G@UR$U~nEb8QU1C8y zwaGs@EZf0j%``%pqdu-IOU+Y=Gk@U_Ub`*j`0;pi;`@{ly!Uk$RYO_>B*W>h^=8@^ zQ>!2g>UbVzZNDe){tQ1Dpr^UTryjXGx8he6v4$lmbds(z|8Q0) z`ye~N7tZSY0?n7cO>VO$hTblOf~eEAnx`9{`;haoji3?;&`(*wZ#Hsolv%yyq?@<+ z<1cOiu-NJQfI;kZO(+a!6eQ`qX}O3c6@}eLZ&LG}fR#TJKk9+*DzVQprlkC)k9rFd zRRm@u5Y?C zf!JpUn@iqc{iviZJ~EU5yQyes9l~EwQ!vO-)M%nxz7Nu!$t`-)i`4t9&10!_rGmgv z475#RAOS4I{enCHz{&$GsUW%6JE4G;$Mt&|M~i@$<+0AJZPP;OP>E$$=WP3Y6zd4e z*b4h@b^-(CjIN{RVLQ%Y)a#-GA0V;ku7_1tnnWe^F*(dxQ5ayGzP$QoX(#3o`MZQ0 z0a9!4^xsxL2|~*sA;Jd!o2I5V1zKgMz5lc_jN#DtBz|gdm62x!e_vr=t>bfFy^j)L zA0=If-~U1Y7|5|6XQKcp&gPT66(HHe>fcB#TCvfu5U(Va!Tx4(P{=B4b5Aj1oh;nZ zqPQ1!^O|9L3QTzY9`5zZO%{JiPz9zyFQqf?h~jWwL(d97p=4ch@O5X>*Bc|8mbd8E z0BxDiUEX#1q`0H&6(wF1ROP0?7|!|NNsQx@>w@KwcG=)LCs3jLdAa^c)cT7i1TbCD zdV>E_OYPkv55Y)N(knrc@)Q{C&(oqip3oR=?&Kkxzw#(cKer9?O^dq6CjN(PEA9m% zhY`2MFpxJpUEmIw+ZBiSj^WnlEx4398%;bGEG#tjfHnQgUuKcgnVq*H#tjobuKczq z?c*lO-Cx~IEWl3sY`o}X{2p}o5$*%zv=GGgHinvw=R9*|*|l=$YMIMf>0wQtr}X;^ z`MdYF%qfnL3V>f*-wa9?hc-`Cg+`8Sj(8XHXy*(faT>B} zr(kYYlI5e_tk&yP7tT+j>i3~{>Zh`*MPG8Uwe%Z+V;rP7tSD_7C|C+zE*mELfpoo? z^PF1Lmm@x4`3Urzz-PXaUUWqd0=+RYp~z>&o&mF78T7858e?ziVl`(A^rTjtdw% zK4iw_+s44Hi$xV=z>ZjEZ|O-aFpNT|aDvw`7_RfAB7koNLjkW(ie(6e&)-R%Ic(I@v;Ll8Rsonl*I)I zJ)+p+nB&hlwLC%Wz;+Ow8gINAHAE@`s(A7R0incc8T;{$eaoMKUs3D(Q%~)bbVLwyq>I6h5)VOCA>ttgs6{W1p?m{sb?aT5UI}uhVN$ClouAXs4&-tM zJ%nz(bSIOCy}9&DsA6zbuM#e79o7L~2ojU1(?wdi21-MyE53S8U5%kTkU3$fmIk;( zfKx&%eRIGr-Q^>=xG-8-&irA4)h~*N*FmlLQ!}dZwCb?K8mO5rGsMXpPAM5~``P>6nm#Ou-*PA|>^drTr zn*cqP!2+Kh%e05)j%4RZpvF*$NGda+O;6&nPnrgHoH8L4jA_F%IY_Nc&GGu646jxm zvyJ>HeHqWK=0gcMMA+i02z9YTIiA!pv|g?gl|W3cme{y$jR z2;!|q`ZhBjnm|jo{SJSxu9wuSIjw%&>b}J2tQIIuuXVa1_(^>}?|YVLHg&U7!ttXo z1dTpp$=&TP=U1yXw()XBR`1Pwo1|4F-BK;@rtw-Ezt+gEy4C};sM|_0?Ny^}sgGjQ z8T)7cw7=U)o&5XuKC|?1*8uh}gz?7o-+upo-*?r$JH_xdt*){3tz62C_wA%F#Eoxd zCq{S_3;j~n$qdaDY`OUp5D4Fz3mu?cd?4Cg`Ga_{_@kohi)g6ph#|87rV*?uFj-r% z^GrfinC5R@l`X$lpkMKUdH)`)U4Ue%GDkLP&CBL+%100<5j*YQ3c9svOdpRy7$!gL zv-XGxqWWLEDF0Tvt2(SaOZqCltM#wS8DFmgQLURFVs}v5Y+3i4@L9BY;46~ZQhdH$)_{N>5K-9^-zi|@-dXKp(!}zmzx{VnD4mAf$t6{A+!pr7X z%e%z~RyT@68eb8-y^o1MZrRSFJ_h#h&%V30Q=Os0IQbfN8tnWR--Bc@CY6dE7kALJTD8WijPNsDC%U(7pOMbaO~k7!8HBoVjZL zhES+Trz4xU@++E4@70}RWk1!VxN87!AXlzgu`-@2YrvgagGIZXa2$3isq7jcE)c_Y zHi9@iC3rpn;+|pAY$deF@R_mZLEIE{BS&y$E;7cV(;Xj2wHiTmyw%$pU(^K4L}qG` z9$DAK-wX7e%Y)dNSxJ%?-z=rD=As(+pYs z-t7w2#cM$ICmORC`_SK4g%qAi{kxq9)dVxM;*cpVw&x@rPoa&UjPtpS-%vBeF=qGV z7W0^hQp#-Eg>xOBOP_bJEy&(!W1p2G@RqiJcy2S tz}}8t5xdx=F)BX9g$8O0eVq}8_8S3m@@{Rjr2qJk8R#18yw-Ax`G43enPUI| literal 0 HcmV?d00001 From 4bc46b3185ff0ea34226cdb5e2b6bc32ba6140a8 Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Sun, 15 Nov 2020 18:45:49 -0800 Subject: [PATCH 464/466] Create FUNDING.yml --- .github/FUNDING.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..e4842122 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: [fnando] +custom: [https://paypal.me/nandovieira/🍕] From f590c27703b325684ef49ffadba14b4d14eecb37 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 Nov 2020 18:46:29 -0800 Subject: [PATCH 465/466] Bump lodash from 4.17.10 to 4.17.20 (#589) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.10 to 4.17.20. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.10...4.17.20) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 615c1af1..8283260c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -91,8 +91,8 @@ jasmine-reporters@~1.0.0: mkdirp "~0.3.5" lodash@~4.17.10: - version "4.17.10" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" + version "4.17.20" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" From b9ae3c75c42dda5fd51a1a44f209fbc54849c933 Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Sun, 15 Nov 2020 18:47:21 -0800 Subject: [PATCH 466/466] Update FUNDING.yml --- .github/FUNDING.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index e4842122..2ae23928 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,3 @@ -github: [fnando] -custom: [https://paypal.me/nandovieira/🍕] +--- +github: ["fnando"] +custom: ["https://paypal.me/nandovieira/🍕"]