i18n-js 3.8.2 → 4.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +4 -0
  3. data/.github/FUNDING.yml +1 -1
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +41 -0
  5. data/.github/ISSUE_TEMPLATE/config.yml +5 -0
  6. data/.github/ISSUE_TEMPLATE/feature_request.md +23 -0
  7. data/.github/PULL_REQUEST_TEMPLATE.md +38 -0
  8. data/.github/dependabot.yml +15 -0
  9. data/.github/workflows/ruby-tests.yml +73 -0
  10. data/.gitignore +13 -7
  11. data/.rubocop.yml +19 -0
  12. data/CHANGELOG.md +47 -512
  13. data/CODE_OF_CONDUCT.md +74 -0
  14. data/CONTRIBUTING.md +79 -0
  15. data/Gemfile +3 -0
  16. data/LICENSE.md +20 -0
  17. data/MIGRATING_FROM_V3_TO_V4.md +192 -0
  18. data/README.md +425 -921
  19. data/Rakefile +10 -20
  20. data/bin/release +81 -0
  21. data/exe/i18n +5 -0
  22. data/i18n-js.gemspec +51 -29
  23. data/lib/guard/i18n-js/templates/Guardfile +10 -0
  24. data/lib/guard/i18n-js/version.rb +13 -0
  25. data/lib/guard/i18n-js.rb +95 -0
  26. data/lib/i18n-js/clean_hash.rb +13 -0
  27. data/lib/i18n-js/cli/check_command.rb +17 -0
  28. data/lib/i18n-js/cli/command.rb +79 -0
  29. data/lib/i18n-js/cli/export_command.rb +95 -0
  30. data/lib/i18n-js/cli/init_command.rb +52 -0
  31. data/lib/i18n-js/cli/lint_scripts_command.rb +159 -0
  32. data/lib/i18n-js/cli/lint_translations_command.rb +157 -0
  33. data/lib/i18n-js/cli/plugins_command.rb +67 -0
  34. data/lib/i18n-js/cli/ui.rb +64 -0
  35. data/lib/i18n-js/cli/version_command.rb +18 -0
  36. data/lib/i18n-js/cli.rb +66 -0
  37. data/lib/i18n-js/embed_fallback_translations_plugin.rb +70 -0
  38. data/lib/i18n-js/export_files_plugin.rb +103 -0
  39. data/lib/i18n-js/lint.js +150645 -0
  40. data/lib/i18n-js/lint.ts +196 -0
  41. data/lib/i18n-js/listen.rb +96 -0
  42. data/lib/i18n-js/plugin.rb +103 -0
  43. data/lib/i18n-js/schema.rb +216 -0
  44. data/lib/i18n-js/sort_hash.rb +12 -0
  45. data/lib/i18n-js/version.rb +5 -0
  46. data/lib/i18n-js.rb +107 -1
  47. data/package.json +5 -20
  48. metadata +149 -189
  49. data/.editorconfig +0 -24
  50. data/.github/workflows/tests.yaml +0 -100
  51. data/.npmignore +0 -27
  52. data/Appraisals +0 -44
  53. data/app/assets/javascripts/i18n/filtered.js.erb +0 -23
  54. data/app/assets/javascripts/i18n/shims.js +0 -240
  55. data/app/assets/javascripts/i18n/translations.js +0 -3
  56. data/app/assets/javascripts/i18n.js +0 -1095
  57. data/gemfiles/i18n_0_6.gemfile +0 -7
  58. data/gemfiles/i18n_0_7.gemfile +0 -7
  59. data/gemfiles/i18n_0_8.gemfile +0 -7
  60. data/gemfiles/i18n_0_9.gemfile +0 -7
  61. data/gemfiles/i18n_1_0.gemfile +0 -7
  62. data/gemfiles/i18n_1_1.gemfile +0 -7
  63. data/gemfiles/i18n_1_2.gemfile +0 -7
  64. data/gemfiles/i18n_1_3.gemfile +0 -7
  65. data/gemfiles/i18n_1_4.gemfile +0 -7
  66. data/gemfiles/i18n_1_5.gemfile +0 -7
  67. data/gemfiles/i18n_1_6.gemfile +0 -7
  68. data/gemfiles/i18n_1_7.gemfile +0 -7
  69. data/gemfiles/i18n_1_8.gemfile +0 -7
  70. data/i18njs.png +0 -0
  71. data/lib/i18n/js/dependencies.rb +0 -63
  72. data/lib/i18n/js/engine.rb +0 -87
  73. data/lib/i18n/js/fallback_locales.rb +0 -70
  74. data/lib/i18n/js/formatters/base.rb +0 -25
  75. data/lib/i18n/js/formatters/js.rb +0 -32
  76. data/lib/i18n/js/formatters/json.rb +0 -13
  77. data/lib/i18n/js/middleware.rb +0 -82
  78. data/lib/i18n/js/private/config_store.rb +0 -31
  79. data/lib/i18n/js/private/hash_with_symbol_keys.rb +0 -36
  80. data/lib/i18n/js/segment.rb +0 -80
  81. data/lib/i18n/js/utils.rb +0 -78
  82. data/lib/i18n/js/version.rb +0 -7
  83. data/lib/i18n/js.rb +0 -264
  84. data/lib/rails/generators/i18n/js/config/config_generator.rb +0 -19
  85. data/lib/rails/generators/i18n/js/config/templates/i18n-js.yml +0 -27
  86. data/lib/tasks/export.rake +0 -8
  87. data/spec/fixtures/custom_path.yml +0 -5
  88. data/spec/fixtures/default.yml +0 -5
  89. data/spec/fixtures/erb.yml +0 -5
  90. data/spec/fixtures/except_condition.yml +0 -7
  91. data/spec/fixtures/js_export_dir_custom.yml +0 -7
  92. data/spec/fixtures/js_export_dir_none.yml +0 -6
  93. data/spec/fixtures/js_extend_parent.yml +0 -6
  94. data/spec/fixtures/js_extend_segment.yml +0 -6
  95. data/spec/fixtures/js_file_per_locale.yml +0 -7
  96. data/spec/fixtures/js_file_per_locale_with_fallbacks_as_default_locale_symbol.yml +0 -4
  97. data/spec/fixtures/js_file_per_locale_with_fallbacks_as_hash.yml +0 -6
  98. data/spec/fixtures/js_file_per_locale_with_fallbacks_as_locale.yml +0 -4
  99. data/spec/fixtures/js_file_per_locale_with_fallbacks_as_locale_without_fallback_translations.yml +0 -4
  100. data/spec/fixtures/js_file_per_locale_with_fallbacks_enabled.yml +0 -4
  101. data/spec/fixtures/js_file_per_locale_without_fallbacks.yml +0 -4
  102. data/spec/fixtures/js_file_with_namespace_prefix_and_pretty_print.yml +0 -9
  103. data/spec/fixtures/js_sort_translation_keys_false.yml +0 -6
  104. data/spec/fixtures/js_sort_translation_keys_true.yml +0 -6
  105. data/spec/fixtures/json_only.yml +0 -18
  106. data/spec/fixtures/locales.yml +0 -133
  107. data/spec/fixtures/merge_plurals.yml +0 -6
  108. data/spec/fixtures/merge_plurals_with_no_overrides.yml +0 -4
  109. data/spec/fixtures/merge_plurals_with_partial_overrides.yml +0 -4
  110. data/spec/fixtures/millions.yml +0 -4
  111. data/spec/fixtures/multiple_conditions.yml +0 -7
  112. data/spec/fixtures/multiple_conditions_per_locale.yml +0 -7
  113. data/spec/fixtures/multiple_files.yml +0 -7
  114. data/spec/fixtures/no_config.yml +0 -2
  115. data/spec/fixtures/no_scope.yml +0 -4
  116. data/spec/fixtures/simple_scope.yml +0 -5
  117. data/spec/js/currency.spec.js +0 -62
  118. data/spec/js/current_locale.spec.js +0 -19
  119. data/spec/js/dates.spec.js +0 -276
  120. data/spec/js/defaults.spec.js +0 -31
  121. data/spec/js/extend.spec.js +0 -110
  122. data/spec/js/interpolation.spec.js +0 -124
  123. data/spec/js/jasmine/MIT.LICENSE +0 -20
  124. data/spec/js/jasmine/jasmine-html.js +0 -190
  125. data/spec/js/jasmine/jasmine.css +0 -166
  126. data/spec/js/jasmine/jasmine.js +0 -2476
  127. data/spec/js/jasmine/jasmine_favicon.png +0 -0
  128. data/spec/js/locales.spec.js +0 -31
  129. data/spec/js/localization.spec.js +0 -78
  130. data/spec/js/numbers.spec.js +0 -174
  131. data/spec/js/placeholder.spec.js +0 -24
  132. data/spec/js/pluralization.spec.js +0 -219
  133. data/spec/js/prepare_options.spec.js +0 -41
  134. data/spec/js/require.js +0 -2083
  135. data/spec/js/specs.html +0 -49
  136. data/spec/js/specs_requirejs.html +0 -72
  137. data/spec/js/translate.spec.js +0 -304
  138. data/spec/js/translations.js +0 -188
  139. data/spec/js/utility_functions.spec.js +0 -20
  140. data/spec/ruby/i18n/js/fallback_locales_spec.rb +0 -84
  141. data/spec/ruby/i18n/js/segment_spec.rb +0 -219
  142. data/spec/ruby/i18n/js/utils_spec.rb +0 -106
  143. data/spec/ruby/i18n/js_spec.rb +0 -748
  144. data/spec/spec_helper.rb +0 -80
  145. data/yarn.lock +0 -131
data/README.md CHANGED
@@ -1,1069 +1,573 @@
1
1
  <p align="center">
2
- <img width="250" height="58" src="https://github.com/fnando/i18n-js/raw/main/i18njs.png" alt="i18n.js">
2
+ <img width="250" height="58" src="https://github.com/fnando/i18n-js/raw/main/images/i18njs.png" alt="i18n.js">
3
3
  </p>
4
4
 
5
5
  <p align="center">
6
- It's a small library to provide the Rails I18n translations on the JavaScript.
6
+ Export <a href="https://rubygems.org/gems/i18n">i18n</a> translations to JSON.
7
+ <br>
8
+ A perfect fit if you want to export translations to JavaScript.
7
9
  </p>
8
10
 
9
11
  <p align="center">
10
- <a href="https://github.com/fnando/i18n-js/actions?query=workflow%3ATests"><img src="https://github.com/fnando/i18n-js/workflows/Tests/badge.svg" alt="Tests"></a>
11
- <a href="http://badge.fury.io/rb/i18n-js"><img src="http://img.shields.io/gem/v/i18n-js.svg" alt="Gem Version"></a>
12
- <a href="https://www.npmjs.com/package/i18n-js"><img src="https://img.shields.io/npm/v/i18n-js.svg" alt="npm"></a>
13
- <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
14
- <a href="https://travis-ci.org/fnando/i18n-js"><img src="http://img.shields.io/travis/fnando/i18n-js.svg" alt="Build Status"></a>
15
- <a href="https://coveralls.io/r/fnando/i18n-js"><img src="http://img.shields.io/coveralls/fnando/i18n-js.svg" alt="Coverage Status"></a>
16
- <a href="https://gitter.im/fnando/i18n-js"><img src="https://img.shields.io/badge/gitter-join%20chat-1dce73.svg" alt="Gitter"></a>
12
+ <small>
13
+ Oh, you don't use Ruby? No problem! You can still use i18n-js
14
+ <br>
15
+ and the
16
+ <a href="https://www.npmjs.com/package/i18n-js/v/latest">companion JavaScript package</a>.
17
+ </small>
17
18
  </p>
18
19
 
19
- ---
20
-
21
- Features:
22
-
23
- - Pluralization
24
- - Date/Time localization
25
- - Number localization
26
- - Locale fallback
27
- - Asset pipeline support
28
- - Lots more! :)
29
-
30
- ## Version Notice
31
-
32
- The `main` branch (including this README) is for latest `3.0.0` instead of
33
- `2.x`.
34
-
35
- ## Usage
20
+ <p align="center">
21
+ <a href="https://github.com/fnando/i18n-js"><img src="https://github.com/fnando/i18n-js/workflows/ruby-tests/badge.svg" alt="Tests"></a>
22
+ <a href="https://rubygems.org/gems/i18n-js"><img src="https://img.shields.io/gem/v/i18n-js.svg" alt="Gem"></a>
23
+ <a href="https://rubygems.org/gems/i18n-js"><img src="https://img.shields.io/gem/dt/i18n-js.svg" alt="Gem"></a>
24
+ <a href="https://tldrlegal.com/license/mit-license"><img src="https://img.shields.io/:License-MIT-blue.svg" alt="MIT License"></a>
25
+ </p>
36
26
 
37
- ### Installation
27
+ ## Installation
38
28
 
39
- #### Rails app
29
+ ```bash
30
+ gem install i18n-js
31
+ ```
40
32
 
41
- Add the gem to your Gemfile.
33
+ Or add the following line to your project's Gemfile:
42
34
 
43
35
  ```ruby
44
36
  gem "i18n-js"
45
37
  ```
46
38
 
47
- #### Rails with [webpacker](https://github.com/rails/webpacker)
48
-
49
- If you're using `webpacker`, you may need to add the dependencies to your client
50
- with:
51
-
52
- ```
53
- yarn add i18n-js
54
- # or, if you're using npm,
55
- npm install i18n-js
56
- ```
57
-
58
- For more details, see:
59
- - [this gist](https://gist.github.com/bazzel/ecdff4718962e57c2d5569cf01d332fe)
60
- - https://github.com/fnando/i18n-js/issues/597
61
-
62
- #### Rails app with [Asset Pipeline](http://guides.rubyonrails.org/asset_pipeline.html)
63
-
64
- If you're using the
65
- [asset pipeline](http://guides.rubyonrails.org/asset_pipeline.html), then you
66
- must add the following line to your `app/assets/javascripts/application.js`.
67
-
68
- ```javascript
69
- //
70
- // This is optional (in case you have `I18n is not defined` error)
71
- // If you want to put this line, you must put it BEFORE `i18n/translations`
72
- //= require i18n
73
- // Some people even need to add the extension to make it work, see https://github.com/fnando/i18n-js/issues/283
74
- //= require i18n.js
75
- //
76
- // This is a must
77
- //= require i18n/translations
78
- ```
79
-
80
- #### Rails app without [Asset Pipeline](http://guides.rubyonrails.org/asset_pipeline.html)
81
-
82
- First, put this in your `application.html` (layout file). Then get the JS files
83
- following the instructions below.
84
-
85
- ```erb
86
- <%# This is just an example, you can put `i18n.js` and `translations.js` anywhere you like %>
87
- <%# Unlike the Asset Pipeline example, you need to require both **in order** %>
88
- <%= javascript_include_tag "i18n" %>
89
- <%= javascript_include_tag "translations", skip_pipeline: true %>
90
- ```
91
-
92
- **There are two ways to get `translations.js` (For Rails app without Asset
93
- Pipeline).**
94
-
95
- 1. This `translations.js` file can be automatically generated by the
96
- `I18n::JS::Middleware`. Just add `config.middleware.use I18n::JS::Middleware`
97
- to your `config/application.rb` file.
98
- 2. If you can't or prefer not to generate this file, you can move the middleware
99
- line to your `config/environments/development.rb` file and run
100
- `rake i18n:js:export` before deploying. This will export all translation
101
- files, including the custom scopes you may have defined on
102
- `config/i18n-js.yml`. If `I18n.available_locales` is set (e.g. in your Rails
103
- `config/application.rb` file) then only the specified locales will be
104
- exported. Current version of `i18n.js` will also be exported to avoid version
105
- mismatching by downloading.
106
-
107
- #### Export Configuration (For translations)
108
-
109
- Exported translation files generated by `I18n::JS::Middleware` or
110
- `rake i18n:js:export` can be customized with config file `config/i18n-js.yml`
111
- (use `rails generate i18n:js:config` to create it). You can even get more files
112
- generated to different folders and with different translations to best suit your
113
- needs. The config file also affects developers using Asset Pipeline to require
114
- translations. Except the option `file`, since all translations are required by
115
- adding `//= require i18n/translations`.
116
-
117
- Examples:
118
-
119
- ```yaml
120
- translations:
121
- - file: "public/javascripts/path-to-your-messages-file.js"
122
- only: "*.date.formats"
123
- - file: "public/javascripts/path-to-your-second-file.js"
124
- only: ["*.activerecord", "*.admin.*.title"]
125
- ```
126
-
127
- If `only` is omitted all the translations will be saved. Also, make sure you add
128
- that initial `*`; it specifies that all languages will be exported. If you want
129
- to export only one language, you can do something like this:
130
-
131
- ```yaml
132
- translations:
133
- - file: "public/javascripts/en.js"
134
- only: "en.*"
135
- - file: "public/javascripts/pt-BR.js"
136
- only: "pt-BR.*"
137
- ```
138
-
139
- Optionally, you can auto generate a translation file per available locale if you
140
- specify the `%{locale}` placeholder.
141
-
142
- ```yaml
143
- translations:
144
- - file: "public/javascripts/i18n/%{locale}.js"
145
- only: "*"
146
- - file: "public/javascripts/frontend/i18n/%{locale}.js"
147
- only: ["*.frontend", "*.users.*"]
148
- ```
149
-
150
- You can also include ERB in your config file.
151
-
152
- ```yaml
153
- translations:
154
- <% Widgets.each do |widget| %>
155
- - file: <%= "'#{widget.file}'" %>
156
- only: <%= "'#{widget.only}'" %>
157
- <% end %>
158
- ```
159
-
160
- You are able to exclude certain phrases or whole groups of phrases by specifying
161
- the YAML key(s) in the `except` configuration option. The outputted JS
162
- translations file (exported or generated by the middleware) will omit any keys
163
- listed in `except` configuration param:
164
-
165
- ```yaml
166
- translations:
167
- - except: ["*.active_admin", "*.ransack", "*.activerecord.errors"]
168
- ```
169
-
170
- #### Export Configuration (For other things)
171
-
172
- - `I18n::JS.config_file_path` Expected Type: `String` Default:
173
- `config/i18n-js.yml` Behaviour: Try to read the config file from that location
174
-
175
- - `I18n::JS.export_i18n_js_dir_path` Expected Type: `String` Default:
176
- `public/javascripts` Behaviour:
177
-
178
- - Any `String`: considered as a relative path for a folder to `Rails.root` and
179
- export `i18n.js` to that folder for `rake i18n:js:export`
180
- - Any non-`String` (`nil`, `false`, `:none`, etc): Disable `i18n.js` exporting
181
-
182
- - `I18n::JS.sort_translation_keys` Expected Type: `Boolean` Default: `true`
183
- Behaviour:
184
-
185
- - Sets whether or not to deep sort all translation keys in order to generate
186
- identical output for the same translations
187
- - Set to true to ensure identical asset fingerprints for the asset pipeline
188
-
189
- - You may also set `export_i18n_js` and `sort_translation_keys` in your config
190
- file, e.g.:
191
-
192
- ```yaml
193
- export_i18n_js: false
194
- # OR
195
- export_i18n_js: "my/path"
196
-
197
- sort_translation_keys: false
198
-
199
- translations:
200
- - ...
201
- ```
202
-
203
- To find more examples on how to use the configuration file please refer to the
204
- tests.
39
+ ## Usage
205
40
 
206
- #### Fallbacks
41
+ About patterns:
207
42
 
208
- If you specify the `fallbacks` option, you will be able to fill missing
209
- translations with those inside fallback locale(s). Default value is `true`.
43
+ - Patterns can use `*` as a wildcard and can appear more than once.
44
+ - `*` will include everything
45
+ - `*.messages.*`
46
+ - Patterns starting with `!` are excluded.
47
+ - `!*.activerecord.*` will exclude all ActiveRecord translations.
48
+ - You can use groups:
49
+ - `{pt-BR,en}.js.*` will include only `pt-BR` and `en` translations, even if
50
+ more languages are available.
210
51
 
211
- Examples:
52
+ > **Note**:
53
+ >
54
+ > Patterns use [glob](https://rubygems.org/gems/glob), so check it out for the
55
+ > most up-to-date documentation about what's available.
212
56
 
213
- ```yaml
214
- fallbacks: true
57
+ The config file:
215
58
 
59
+ ```yml
60
+ ---
216
61
  translations:
217
- - file: "public/javascripts/i18n/%{locale}.js"
218
- only: "*"
219
- ```
220
-
221
- This will enable merging fallbacks into each file. (set to `false` to disable).
222
- If you use `I18n` with fallbacks, the fallbacks defined there will be used.
223
- Otherwise `I18n.default_locale` will be used.
224
-
225
- ```yaml
226
- fallbacks: :de
62
+ - file: app/frontend/locales/en.json
63
+ patterns:
64
+ - "*"
65
+ - "!*.activerecord"
66
+ - "!*.errors"
67
+ - "!*.number.nth"
227
68
 
228
- translations:
229
- - file: "public/javascripts/i18n/%{locale}.js"
230
- only: "*"
69
+ - file: app/frontend/locales/:locale.:digest.json
70
+ patterns:
71
+ - "*"
231
72
  ```
232
73
 
233
- Here, the specified locale `:de` will be used as fallback for all locales.
74
+ The output path can use the following placeholders:
234
75
 
235
- ```yaml
236
- fallbacks:
237
- fr: ["de", "en"]
238
- de: "en"
76
+ - `:locale`: the language that's being exported.
77
+ - `:digest`: the MD5 hex digest of the exported file.
239
78
 
240
- translations:
241
- - file: "public/javascripts/i18n/%{locale}.js"
242
- only: "*"
243
- ```
79
+ The config file is processed as erb, so you can have dynamic content on it if
80
+ you want. The following example shows how to use groups from a variable.
244
81
 
245
- Fallbacks defined will be used, if not defined (e.g. `:pl`) `I18n.fallbacks` or
246
- `I18n.default_locale` will be used.
247
-
248
- ```yaml
249
- fallbacks: :default_locale
82
+ ```yml
83
+ ---
84
+ <% group = "{en,pt}" %>
250
85
 
251
86
  translations:
252
- - file: "public/javascripts/i18n/%{locale}.js"
253
- only: "*"
87
+ - file: app/frontend/translations.json
88
+ patterns:
89
+ - "<%= group %>.*"
90
+ - "!<%= group %>.activerecord"
91
+ - "!<%= group %>.errors"
92
+ - "!<%= group %>.number.nth"
254
93
  ```
255
94
 
256
- Setting the option to `:default_locale` will enforce the fallback to use the
257
- `I18n.default_locale`, ignoring `I18n.fallbacks`.
258
-
259
- Examples:
95
+ The Ruby API:
260
96
 
261
- ```yaml
262
- fallbacks: false
97
+ ```ruby
98
+ require "i18n-js"
263
99
 
264
- translations:
265
- - file: "public/javascripts/i18n/%{locale}.js"
266
- only: "*"
100
+ I18nJS.call(config_file: "config/i18n.yml")
101
+ I18nJS.call(config: config)
267
102
  ```
268
103
 
269
- You must disable this feature by setting the option to `false`.
270
-
271
- To find more examples on how to use the configuration file please refer to the
272
- tests.
104
+ The CLI API:
273
105
 
274
- #### Namespace
106
+ ```console
107
+ $ i18n --help
108
+ Usage: i18n COMMAND FLAGS
275
109
 
276
- Setting the `namespace` option will change the namespace of the output
277
- Javascript file to something other than `I18n`. This can be useful in
278
- no-conflict scenarios. Example:
279
-
280
- ```yaml
281
- translations:
282
- - file: "public/javascripts/i18n/translations.js"
283
- namespace: "MyNamespace"
284
- ```
110
+ Commands:
285
111
 
286
- will create:
112
+ - init: Initialize a project
113
+ - export: Export translations as JSON files
114
+ - version: Show package version
115
+ - plugins: List plugins that will be activated
116
+ - lint:translations: Check for missing translations
117
+ - lint:scripts: Lint files using TypeScript
287
118
 
119
+ Run `i18n COMMAND --help` for more information on specific commands.
288
120
  ```
289
- MyNamespace.translations || (MyNamespace.translations = {});
290
- MyNamespace.translations["en"] = { ... }
291
- ```
292
-
293
- ### Adding prefix & suffix to the translations file(s)
294
121
 
295
- Setting the `prefix: "import I18n from 'i18n-js';\n"` option will add the line
296
- at the beginning of the resultant translation file. This can be useful to use
297
- this gem with the [i18n-js](https://www.npmjs.com/package/i18n-js) npm package,
298
- which is quite useful to use it with webpack. The user should provide the
299
- semi-colon and the newline character if needed.
122
+ By default, `i18n` will use `config/i18n.yml` and `config/environment.rb` as the
123
+ configuration files. If you don't have these files, then you'll need to specify
124
+ both `--config` and `--require`.
300
125
 
301
- For example:
126
+ ### Plugins
302
127
 
303
- ```yaml
304
- translations:
305
- - file: "public/javascripts/i18n/translations.js"
306
- prefix: "import I18n from 'i18n-js';\n"
307
- ```
308
-
309
- will create:
310
-
311
- ```
312
- import I18n from 'i18n-js';
313
- I18n.translations || (I18n.translations = {});
314
- ```
128
+ #### Built-in plugins:
315
129
 
316
- `suffix` option is added in https://github.com/fnando/i18n-js/pull/561.
317
- It's similar to `prefix` so won't explain it in details.
130
+ ##### `embed_fallback_translations`:
318
131
 
319
- #### Pretty Print
132
+ Embed fallback translations inferred from the default locale. This can be useful
133
+ in cases where you have multiple large translation files and don't want to load
134
+ the default locale together with the target locale.
320
135
 
321
- Set the `pretty_print` option if you would like whitespace and indentation in
322
- your output file (default: false)
136
+ To use it, add the following to your configuration file:
323
137
 
324
138
  ```yaml
325
- translations:
326
- - file: "public/javascripts/i18n/translations.js"
327
- pretty_print: true
139
+ embed_fallback_translations:
140
+ enabled: true
328
141
  ```
329
142
 
330
- #### Javascript Deep Merge (:js_extend option)
143
+ ##### `export_files`:
331
144
 
332
- By default, the output file Javascript will call the `I18n.extend` method to
333
- ensure that newly loaded locale files are deep-merged with any locale data
334
- already in memory. To disable this either globally or per-file, set the
335
- `js_extend` option to false
145
+ By default, i18n-js will export only JSON files out of your translations. This
146
+ plugin allows exporting other file formats. To use it, add the following to your
147
+ configuration file:
336
148
 
337
149
  ```yaml
338
- js_extend: false # this will disable Javascript I18n.extend globally
339
- translations:
340
- - file: "public/javascripts/i18n/translations.js"
341
- js_extend: false # this will disable Javascript I18n.extend for this file
150
+ export_files:
151
+ enabled: true
152
+ files:
153
+ - template: path/to/template.erb
154
+ output: "%{dir}/%{base_name}.ts"
342
155
  ```
343
156
 
344
- #### Vanilla JavaScript
345
-
346
- Just add the `i18n.js` file to your page. You'll have to build the translations
347
- object by hand or using your favorite programming language. More info below.
348
-
349
- #### Via NPM with webpack and CommonJS
157
+ You can export multiple files by define more entries.
350
158
 
351
- Add the following line to your package.json dependencies where version is the
352
- version you want:
159
+ The output name can use the following placeholders:
353
160
 
354
- ```javascript
355
- "i18n-js": "{version_constraint}"
161
+ - `%{dir}`: the directory where the translation file is.
162
+ - `%{name}`: file name with extension.
163
+ - `%{base_name}`: file name without extension.
164
+ - `%{digest}`: MD5 hexdigest from the generated file.
356
165
 
357
- // Or if you want unreleased version
358
- // npm install requires it to be the gzipped tarball, see [npm install](https://www.npmjs.org/doc/cli/npm-install.html)
359
- "i18n-js": "https://github.com/fnando/i18n-js/archive/{tag_name_or_branch_name_or_commit_sha}.tar.gz"
360
- ```
361
-
362
- Run npm install then use via
363
-
364
- ```javascript
365
- var i18n = require("i18n-js");
366
- ```
367
-
368
- ### Setting up
369
-
370
- You **don't** need to set up a thing. The default settings will work just okay.
371
- But if you want to split translations into several files or specify contexts,
372
- you can follow the rest of this setting up section.
373
-
374
- Set your locale is easy as
375
-
376
- ```javascript
377
- I18n.defaultLocale = "pt-BR";
378
- I18n.locale = "pt-BR";
379
- I18n.currentLocale();
380
- // pt-BR
381
- ```
382
-
383
- **NOTE:** You can now apply your configuration **before I18n** is loaded like
384
- this:
385
-
386
- ```javascript
387
- I18n = {}; // You must define this object in top namespace, which should be `window`
388
- I18n.defaultLocale = "pt-BR";
389
- I18n.locale = "pt-BR";
390
-
391
- // Load I18n from `i18n.js`, `application.js` or whatever
392
-
393
- I18n.currentLocale();
394
- // pt-BR
395
- ```
396
-
397
- In practice, you'll have something like the following in your
398
- `application.html.erb`:
166
+ The template file must be a valid eRB template. You can execute arbitrary Ruby
167
+ code, so be careful. An example of how you can generate a file can be seen
168
+ below:
399
169
 
400
170
  ```erb
401
- <script type="text/javascript">
402
- I18n.defaultLocale = "<%= I18n.default_locale %>";
403
- I18n.locale = "<%= I18n.locale %>";
404
- </script>
405
- ```
406
-
407
- You can use translate your messages:
171
+ /* eslint-disable */
172
+ <%= banner %>
408
173
 
409
- ```javascript
410
- I18n.t("some.scoped.translation");
411
- // or translate with explicit setting of locale
412
- I18n.t("some.scoped.translation", { locale: "fr" });
413
- ```
414
-
415
- You can also interpolate values:
174
+ import { i18n } from "config/i18n";
416
175
 
417
- ```javascript
418
- // You need the `translations` object setup first
419
- I18n.translations["en"] = {
420
- greeting: "Hello %{name}",
421
- };
422
-
423
- I18n.t("greeting", { name: "John Doe" });
176
+ i18n.store(<%= JSON.pretty_generate(translations) %>);
424
177
  ```
425
178
 
426
- You can set default values for missing scopes:
179
+ This template is loading the instance from `config/i18n` and storing the
180
+ translations that have been loaded. The
181
+ `banner(comment: "// ", include_time: true)` method is built-in. The generated
182
+ file will look something like this:
427
183
 
428
- ```javascript
429
- // simple translation
430
- I18n.t("some.missing.scope", { defaultValue: "A default message" });
184
+ ```typescript
185
+ /* eslint-disable */
186
+ // File generated by i18n-js on 2022-12-10 15:37:00 +0000
431
187
 
432
- // with interpolation
433
- I18n.t("noun", { defaultValue: "I'm a {{noun}}", noun: "Mac" });
434
- ```
188
+ import { i18n } from "config/i18n";
435
189
 
436
- You can also provide a list of default fallbacks for missing scopes:
437
-
438
- ```javascript
439
- // As a scope
440
- I18n.t("some.missing.scope", { defaults: [{ scope: "some.existing.scope" }] });
441
-
442
- // As a simple translation
443
- I18n.t("some.missing.scope", { defaults: [{ message: "Some message" }] });
444
- ```
445
-
446
- Default values must be provided as an array of hashes where the key is the type
447
- of translation desired, a `scope` or a `message`. The translation returned will
448
- be either the first scope recognized, or the first message defined.
449
-
450
- The translation will fallback to the `defaultValue` translation if no scope in
451
- `defaults` matches and if no default of type `message` is found.
452
-
453
- Translation fallback can be enabled by enabling the `I18n.fallbacks` option:
454
-
455
- ```erb
456
- <script type="text/javascript">
457
- I18n.fallbacks = true;
458
- </script>
459
- ```
460
-
461
- By default missing translations will first be looked for in less specific
462
- versions of the requested locale and if that fails by taking them from your
463
- `I18n.defaultLocale`.
464
-
465
- ```javascript
466
- // if I18n.defaultLocale = "en" and translation doesn't exist
467
- // for I18n.locale = "de-DE" this key will be taken from "de" locale scope
468
- // or, if that also doesn't exist, from "en" locale scope
469
- I18n.t("some.missing.scope");
470
- ```
471
-
472
- Custom fallback rules can also be specified for a particular language. There are
473
- three different ways of doing it so:
474
-
475
- ```javascript
476
- I18n.locales.no = ["nb", "en"];
477
- I18n.locales.no = "nb";
478
- I18n.locales.no = function (locale) {
479
- return ["nb"];
480
- };
190
+ i18n.store({
191
+ en: {
192
+ "bunny rabbit adventure": "bunny rabbit adventure",
193
+ "hello sunshine!": "hello sunshine!",
194
+ "time for bed!": "time for bed!",
195
+ },
196
+ es: {
197
+ "bunny rabbit adventure": "conejito conejo aventura",
198
+ bye: "adios",
199
+ "time for bed!": "hora de acostarse!",
200
+ },
201
+ pt: {
202
+ "bunny rabbit adventure": "a aventura da coelhinha",
203
+ bye: "tchau",
204
+ "time for bed!": "hora de dormir!",
205
+ },
206
+ });
481
207
  ```
482
208
 
483
- By default a missing translation will be displayed as
484
-
485
- [missing "name of scope" translation]
209
+ #### Plugin API
486
210
 
487
- While you are developing or if you do not want to provide a translation in the
488
- default language you can set
489
-
490
- ```javascript
491
- I18n.missingBehaviour = "guess";
492
- ```
211
+ You can transform the exported translations by adding plugins. A plugin must
212
+ inherit from `I18nJS::Plugin` and can have 4 class methods (they're all optional
213
+ and will default to a noop implementation). For real examples, see [lib/i18n-js/embed_fallback_translations_plugin.rb](https://github.com/fnando/i18n-js/blob/main/lib/i18n-js/embed_fallback_translations_plugin.rb) and [lib/i18n-js/export_files_plugin.rb](https://github.com/fnando/i18n-js/blob/main/lib/i18n-js/export_files_plugin.rb)
493
214
 
494
- this will take the last section of your scope and guess the intended value.
495
- Camel case becomes lower cased text and underscores are replaced with space
496
-
497
- questionnaire.whatIsYourFavorite_ChristmasPresent
215
+ ```ruby
216
+ # frozen_string_literal: true
217
+
218
+ module I18nJS
219
+ class SamplePlugin < I18nJS::Plugin
220
+ # This method is responsible for transforming the translations. The
221
+ # translations you'll receive may be already be filtered by other plugins
222
+ # and by the default filtering itself. If you need to access the original
223
+ # translations, use `I18nJS.translations`.
224
+ def transform(translations:)
225
+ # transform `translations` here…
226
+
227
+ translations
228
+ end
498
229
 
499
- becomes "what is your favorite Christmas present"
230
+ # In case your plugin accepts configuration, this is where you must validate
231
+ # the configuration, making sure only valid keys and type is provided.
232
+ # If the configuration contains invalid data, then you must raise an
233
+ # exception using something like
234
+ # `raise I18nJS::Schema::InvalidError, error_message`.
235
+ #
236
+ # Notice the validation will only happen when the plugin configuration is
237
+ # set (i.e. the configuration contains your config key).
238
+ def validate_schema
239
+ # validate plugin schema here…
240
+ end
500
241
 
501
- In order to still detect untranslated strings, you can set
502
- `i18n.missingTranslationPrefix` to something like:
242
+ # This method must set up the basic plugin configuration, like adding the
243
+ # config's root key in case your plugin accepts configuration (defined via
244
+ # the config file).
245
+ #
246
+ # If you don't add this key, the linter will prevent non-default keys from
247
+ # being added to the configuration file.
248
+ def setup
249
+ # If you plugin has configuration, uncomment the line below
250
+ # I18nJS::Schema.root_keys << config_key
251
+ end
503
252
 
504
- ```javascript
505
- I18n.missingTranslationPrefix = "EE: ";
253
+ # This method is called whenever `I18nJS.call(**kwargs)` finishes exporting
254
+ # JSON files based on your configuration.
255
+ #
256
+ # You can use it to further process exported files, or generate new files
257
+ # based on the translations that have been exported.
258
+ def after_export(files:)
259
+ # process exported files here…
260
+ end
261
+ end
262
+ end
506
263
  ```
507
264
 
508
- And result will be:
265
+ The class `I18nJS::Plugin` implements some helper methods that you can use:
509
266
 
510
- ```javascript
511
- "EE: what is your favorite Christmas present";
267
+ - `I18nJS::Plugin#config_key`: the configuration key that was inferred out of
268
+ your plugin's class name.
269
+ - `I18nJS::Plugin#config`: the plugin configuration.
270
+ - `I18nJS::Plugin#enabled?`: whether the plugin is enabled or not based on the
271
+ plugin's configuration.
512
272
 
513
- ```
273
+ To distribute this plugin, you need to create a gem package that matches the
274
+ pattern `i18n-js/*_plugin.rb`. You can test whether your plugin will be found by
275
+ installing your gem, opening a iRB session and running
276
+ `Gem.find_files("i18n-js/*_plugin.rb")`. If your plugin is not listed, then you
277
+ need to double check your gem load path and see why the file is not being
278
+ loaded.
514
279
 
515
- This will help you doing automated tests against your localisation assets.
280
+ ### Listing missing translations
516
281
 
517
- Some people prefer returning `null` for missing translation:
282
+ To list missing and extraneous translations, you can use
283
+ `i18n lint:translations`. This command will load your translations similarly to
284
+ how `i18n export` does, but will output the list of keys that don't have a
285
+ matching translation against the default locale. Here's an example:
518
286
 
519
- ```javascript
520
- I18n.missingTranslation = function () {
521
- return undefined;
522
- };
287
+ ```console
288
+ $ i18n lint:translations
289
+ => Config file: "./config/i18n.yml"
290
+ => Require file: "./config/environment.rb"
291
+ => Check "./config/i18n.yml" for ignored keys.
292
+ => en: 232 translations
293
+ => pt-BR: 5 missing, 1 extraneous, 1 ignored
294
+ - pt-BR.actors.github.metrics (missing)
295
+ - pt-BR.actors.github.metrics_hint (missing)
296
+ - pt-BR.actors.github.repo_metrics (missing)
297
+ - pt-BR.actors.github.repository (missing)
298
+ - pt-BR.actors.github.user_metrics (missing)
299
+ - pt-BR.github.repository (extraneous)
523
300
  ```
524
301
 
525
- Pluralization is possible as well and by default provides English rules:
302
+ This command will exist with status 1 whenever there are missing translations.
303
+ This way you can use it as a CI linting.
526
304
 
527
- ```javascript
528
- I18n.t("inbox.counting", { count: 10 }); // You have 10 messages
529
- ```
305
+ You can ignore keys by adding a list to the config file:
530
306
 
531
- The sample above expects the following translation:
307
+ ```yml
308
+ ---
309
+ translations:
310
+ - file: app/frontend/locales/en.json
311
+ patterns:
312
+ - "*"
313
+ - "!*.activerecord"
314
+ - "!*.errors"
315
+ - "!*.number.nth"
316
+
317
+ - file: app/frontend/locales/:locale.:digest.json
318
+ patterns:
319
+ - "*"
320
+
321
+ lint_translations:
322
+ ignore:
323
+ - en.mailer.login.subject
324
+ - en.mailer.login.body
325
+ ```
326
+
327
+ > **Note**:
328
+ >
329
+ > In order to avoid mistakenly ignoring keys, this configuration option only
330
+ > accepts the full translation scope, rather than accepting a pattern like
331
+ > `pt.ignored.scope.*`.
332
+
333
+ ### Linting your JavaScript/TypeScript files
334
+
335
+ To lint your script files and check for missing translations (which can signal
336
+ that you're either using wrong scopes or forgot to add the translation), use
337
+ `i18n lint:scripts`. This command will parse your JavaScript/TypeScript files
338
+ and extract all scopes being used. This command requires a Node.js runtime. You
339
+ can either specify one via `--node-path`, or let the plugin infer a binary from
340
+ your `$PATH`.
341
+
342
+ The comparison will be made against the export JSON files, which means it'll
343
+ consider transformations performed by plugins (e.g. the output files may be
344
+ affected by `embed_fallback_translations` plugin).
345
+
346
+ The translations that will be extract must be called as one of the following
347
+ ways:
348
+
349
+ - `i18n.t(scope, options)`
350
+ - `i18n.translate(scope, options)`
351
+ - `t(scope, options)`
352
+
353
+ Notice that only literal strings can be used, as in `i18n.t("message")`. If
354
+ you're using dynamic scoping through variables (e.g.
355
+ `const scope = "message"; i18n.t(scope)`), they will be skipped.
356
+
357
+ ```console
358
+ $ i18n lint:scripts
359
+ => Config file: "./config/i18n.yml"
360
+ => Require file: "./config/environment.rb"
361
+ => Node: "/Users/fnando/.asdf/shims/node"
362
+ => Available locales: [:en, :es, :pt]
363
+ => Patterns: ["!(node_modules)/**/*.js", "!(node_modules)/**/*.ts", "!(node_modules)/**/*.jsx", "!(node_modules)/**/*.tsx"]
364
+ => 9 translations, 11 missing, 4 ignored
365
+ - test/scripts/lint/file.js:1:1: en.js.missing
366
+ - test/scripts/lint/file.js:1:1: es.js.missing
367
+ - test/scripts/lint/file.js:1:1: pt.js.missing
368
+ - test/scripts/lint/file.js:2:8: en.base.js.missing
369
+ - test/scripts/lint/file.js:2:8: es.base.js.missing
370
+ - test/scripts/lint/file.js:2:8: pt.base.js.missing
371
+ - test/scripts/lint/file.js:4:8: en.js.missing
372
+ - test/scripts/lint/file.js:4:8: es.js.missing
373
+ - test/scripts/lint/file.js:4:8: pt.js.missing
374
+ - test/scripts/lint/file.js:6:1: en.another_ignore_scope
375
+ - test/scripts/lint/file.js:6:1: es.another_ignore_scope
376
+ ```
377
+
378
+ This command will list all locales and their missing translations. Avoid listing
379
+ a particular translation, you can set `lint.ignore` on your config file.
532
380
 
533
381
  ```yaml
534
- en:
535
- inbox:
536
- counting:
537
- one: You have 1 new message
538
- other: You have {{count}} new messages
539
- zero: You have no messages
540
- ```
541
-
542
- **NOTE:** Rails I18n recognizes the `zero` option.
543
-
544
- If you need special rules just define them for your language, for example
545
- Russian, just add a new pluralizer:
546
-
547
- ```javascript
548
- I18n.pluralization["ru"] = function (count) {
549
- var key =
550
- count % 10 == 1 && count % 100 != 11
551
- ? "one"
552
- : [2, 3, 4].indexOf(count % 10) >= 0 &&
553
- [12, 13, 14].indexOf(count % 100) < 0
554
- ? "few"
555
- : count % 10 == 0 ||
556
- [5, 6, 7, 8, 9].indexOf(count % 10) >= 0 ||
557
- [11, 12, 13, 14].indexOf(count % 100) >= 0
558
- ? "many"
559
- : "other";
560
- return [key];
561
- };
562
- ```
563
-
564
- You can find all rules on
565
- <https://unicode-org.github.io/cldr-staging/charts/37/supplemental/language_plural_rules.html>.
566
-
567
- If you're using the same scope over and over again, you may use the `scope`
568
- option.
569
-
570
- ```javascript
571
- var options = { scope: "activerecord.attributes.user" };
572
-
573
- I18n.t("name", options);
574
- I18n.t("email", options);
575
- I18n.t("username", options);
576
- ```
577
-
578
- You can also provide an array as scope.
579
-
580
- ```javascript
581
- // use the greetings.hello scope
582
- I18n.t(["greetings", "hello"]);
583
- ```
584
-
585
- #### Number formatting
586
-
587
- Similar to Rails helpers, you have localized number and currency formatting.
588
-
589
- ```javascript
590
- I18n.l("currency", 1990.99);
591
- // $1,990.99
592
-
593
- I18n.l("number", 1990.99);
594
- // 1,990.99
595
-
596
- I18n.l("percentage", 123.45);
597
- // 123.450%
598
- ```
599
-
600
- To have more control over number formatting, you can use the `I18n.toNumber`,
601
- `I18n.toPercentage`, `I18n.toCurrency` and `I18n.toHumanSize` functions.
602
-
603
- ```javascript
604
- I18n.toNumber(1000); // 1,000.000
605
- I18n.toCurrency(1000); // $1,000.00
606
- I18n.toPercentage(100); // 100.000%
607
- ```
608
-
609
- The `toNumber` and `toPercentage` functions accept the following options:
610
-
611
- - `precision`: defaults to `3`
612
- - `separator`: defaults to `.`
613
- - `delimiter`: defaults to `,`
614
- - `strip_insignificant_zeros`: defaults to `false`
615
-
616
- See some number formatting examples:
617
-
618
- ```javascript
619
- I18n.toNumber(1000, { precision: 0 }); // 1,000
620
- I18n.toNumber(1000, { delimiter: ".", separator: "," }); // 1.000,000
621
- I18n.toNumber(1000, { delimiter: ".", precision: 0 }); // 1.000
622
- ```
623
-
624
- The `toCurrency` function accepts the following options:
625
-
626
- - `precision`: sets the level of precision
627
- - `separator`: sets the separator between the units
628
- - `delimiter`: sets the thousands delimiter
629
- - `format`: sets the format of the output string
630
- - `unit`: sets the denomination of the currency
631
- - `strip_insignificant_zeros`: defaults to `false`
632
- - `sign_first`: defaults to `true`
633
-
634
- You can provide only the options you want to override:
382
+ ---
383
+ translations:
384
+ - file: app/frontend/translations.json
385
+ patterns:
386
+ - "*"
635
387
 
636
- ```javascript
637
- I18n.toCurrency(1000, { precision: 0 }); // $1,000
388
+ lint_scripts:
389
+ ignore:
390
+ - ignore_scope # will ignore this scope on all languages
391
+ - pt.another_ignore_scope # will ignore this scope only on `pt`
638
392
  ```
639
393
 
640
- The `toHumanSize` function accepts the following options:
394
+ You can also set the patterns that will be looked up. By default, it scans all
395
+ JavaScript and TypeScript files that don't live on `node_modules`.
641
396
 
642
- - `precision`: defaults to `1`
643
- - `separator`: defaults to `.`
644
- - `delimiter`: defaults to `""`
645
- - `strip_insignificant_zeros`: defaults to `false`
646
- - `format`: defaults to `%n%u`
647
- - `scope`: defaults to `""`
648
-
649
- <!---->
650
-
651
- ```javascript
652
- I18n.toHumanSize(1234); // 1KB
653
- I18n.toHumanSize(1234 * 1024); // 1MB
654
- ```
397
+ ```yaml
398
+ ---
399
+ translations:
400
+ - file: app/frontend/translations.json
401
+ patterns:
402
+ - "*"
655
403
 
656
- #### Date formatting
657
-
658
- ```javascript
659
- // accepted formats
660
- I18n.l("date.formats.short", "2009-09-18"); // yyyy-mm-dd
661
- I18n.l("time.formats.short", "2009-09-18 23:12:43"); // yyyy-mm-dd hh:mm:ss
662
- I18n.l("time.formats.short", "2009-11-09T18:10:34"); // JSON format with local Timezone (part of ISO-8601)
663
- I18n.l("time.formats.short", "2009-11-09T18:10:34Z"); // JSON format in UTC (part of ISO-8601)
664
- I18n.l("date.formats.short", 1251862029000); // Epoch time
665
- I18n.l("date.formats.short", "09/18/2009"); // mm/dd/yyyy
666
- I18n.l("date.formats.short", new Date()); // Date object
404
+ lint:
405
+ patterns:
406
+ - "app/assets/**/*.ts"
667
407
  ```
668
408
 
669
- You can also add placeholders to the date format:
670
-
671
- ```javascript
672
- I18n.translations["en"] = {
673
- date: {
674
- formats: {
675
- ordinal_day: "%B %{day}",
676
- },
677
- },
678
- };
679
-
680
- I18n.l("date.formats.ordinal_day", "2009-09-18", { day: "18th" }); // Sep 18th
681
- ```
409
+ ## Automatically export translations
682
410
 
683
- If you prefer, you can use the `I18n.toTime` and `I18n.strftime` functions to
684
- format dates.
411
+ ### Using [watchman](https://facebook.github.io/watchman/)
685
412
 
686
- ```javascript
687
- var date = new Date();
688
- I18n.toTime("date.formats.short", "2009-09-18");
689
- I18n.toTime("date.formats.short", date);
690
- I18n.strftime(date, "%d/%m/%Y");
691
- ```
413
+ Create a script at `bin/i18n-watch`.
692
414
 
693
- The accepted formats for `I18n.strftime` are:
694
-
695
- %a - The abbreviated weekday name (Sun)
696
- %A - The full weekday name (Sunday)
697
- %b - The abbreviated month name (Jan)
698
- %B - The full month name (January)
699
- %c - The preferred local date and time representation
700
- %d - Day of the month (01..31)
701
- %-d - Day of the month (1..31)
702
- %H - Hour of the day, 24-hour clock (00..23)
703
- %-H/%k - Hour of the day, 24-hour clock (0..23)
704
- %I - Hour of the day, 12-hour clock (01..12)
705
- %-I/%l - Hour of the day, 12-hour clock (1..12)
706
- %m - Month of the year (01..12)
707
- %-m - Month of the year (1..12)
708
- %M - Minute of the hour (00..59)
709
- %-M - Minute of the hour (0..59)
710
- %p - Meridian indicator (AM or PM)
711
- %P - Meridian indicator (am or pm)
712
- %S - Second of the minute (00..60)
713
- %-S - Second of the minute (0..60)
714
- %w - Day of the week (Sunday is 0, 0..6)
715
- %y - Year without a century (00..99)
716
- %-y - Year without a century (0..99)
717
- %Y - Year with century
718
- %z/%Z - Timezone offset (+0545)
719
-
720
- Check out `spec/*.spec.js` files for more examples!
721
-
722
- #### Using pluralization and number formatting together
723
-
724
- Sometimes you might want to display translation with formatted number, like
725
- adding thousand delimiters to displayed number You can do this:
726
-
727
- ```json
728
- {
729
- "en": {
730
- "point": {
731
- "one": "1 Point",
732
- "other": "{{formatted_number}} Points",
733
- "zero": "0 Points"
734
- }
415
+ ```bash
416
+ #!/usr/bin/env bash
417
+
418
+ root=`pwd`
419
+
420
+ watchman watch-del "$root"
421
+ watchman watch-project "$root"
422
+ watchman trigger-del "$root" i18n
423
+
424
+ watchman -j <<-JSON
425
+ [
426
+ "trigger",
427
+ "$root",
428
+ {
429
+ "name": "i18n",
430
+ "expression": [
431
+ "anyof",
432
+ ["match", "config/locales/**/*.yml", "wholename"],
433
+ ["match", "config/i18n.yml", "wholename"]
434
+ ],
435
+ "command": ["i18n", "export"]
735
436
  }
736
- }
737
- ```
437
+ ]
438
+ JSON
738
439
 
739
- ```js
740
- var point_in_number = 1000;
741
- I18n.t("point", {
742
- count: point_in_number,
743
- formatted_number: I18n.toNumber(point_in_number),
744
- });
440
+ # If you're running this through Foreman,
441
+ # the uncomment the following lines:
442
+ # while true; do
443
+ # sleep 1
444
+ # done
745
445
  ```
746
446
 
747
- Output should be `1,000 points`
748
-
749
- ## Using multiple exported translation files on a page.
750
-
751
- This method is useful for very large apps where a single contained
752
- translations.js file is not desirable. Examples would be a global translations
753
- file and a more specific route translation file.
754
-
755
- ### Rails without asset pipeline
756
-
757
- 1. Setup your `config/i18n-js.yml` to have multiple files and try to minimize
758
- any overlap.
759
-
760
- ```yaml
761
- sort_translation_keys: true
762
- fallbacks: false
447
+ Make it executable with `chmod +x bin/i18n-watch`. To watch for changes, run
448
+ `./bin/i18n-watch`. If you're using Foreman, make sure you uncommented the lines
449
+ that keep the process running (`while..`), and add something like the following
450
+ line to your Procfile:
763
451
 
764
- translations:
765
- + file: "app/assets/javascript/nls/welcome.js"
766
- only:
767
- + '*.welcome.*'
768
-
769
- + file: "app/assets/javascript/nls/albums.js"
770
- only:
771
- + '*.albums.*'
772
-
773
- + file: "app/assets/javascript/nls/global.js"
774
- only:
775
- + '*'
776
- # Exempt any routes specific translations from being
777
- # included in the global translation file
778
- except:
779
- + '*.welcome.*'
780
- + '*.albums.*'
781
452
  ```
782
-
783
- When `rake i18n:js:export` is executed it will create 3 translations files that
784
- can be loaded via the `javascript_include_tag`
785
-
786
- 2. Add the `javascript_include_tag` to your layout and to any route specific
787
- files that will require it.
788
-
789
- ```ruby
790
- # views/layouts/application.html.erb
791
- <%= javascript_include_tag(
792
- "i18n"
793
- "nls/global"
794
- ) %>
453
+ i18n: ./bin/i18n-watch
795
454
  ```
796
455
 
797
- and in the route specific
456
+ ### Using [guard](https://rubygems.org/gems/guard)
798
457
 
799
- ```ruby
800
- # views/welcome/index.html.erb
801
- <%= javascript_include_tag(
802
- "nls/welcome"
803
- ) %>
804
- ```
805
-
806
- 3. Make sure that you add these files to your `config/application.rb`
458
+ Install [guard](https://rubygems.org/gems/guard) and
459
+ [guard-compat](https://rubygems.org/gems/guard-compat). Then create a Guardfile
460
+ with the following configuration:
807
461
 
808
462
  ```ruby
809
- config.assets.precompile += %w(
810
- i18n
811
- nls/*
812
- )
813
- ```
814
-
815
- ### Using require.js / r.js
816
-
817
- To use this with require.js we are only going to change a few things from above.
818
-
819
- 1. In your `config/i18n-js.yml` we need to add a better location for the i18n to
820
- be exported to. You want to use this location so that it can be properly
821
- precompiled by r.js.
822
-
823
- ```yaml
824
- export_i18n_js: "app/assets/javascript/nls"
825
- ```
826
-
827
- 2. In your `config/require.yml` we need to add a map, shim all the translations,
828
- and include them into the appropriate modules
829
-
830
- ```yaml
831
- # In your maps add (if you do not have this you will need to add it)
832
- map:
833
- '*':
834
- i18n: 'nls/i18n'
835
-
836
- # In your shims
837
- shims:
838
- nls/welcome:
839
- deps:
840
- + i18n
841
-
842
- nls/global:
843
- deps:
844
- + i18n
845
-
846
- # Finally in your modules
847
- modules:
848
- + name: 'application'
849
- include:
850
- + i18n
851
- + 'nls/global'
852
-
853
- + name: 'welcome'
854
- exclude:
855
- + application
856
- include:
857
- + 'nls/welcome'
858
- ```
859
-
860
- 3. When `rake assets:precompile` is executed it will optimize the translations
861
- into the correct modules so they are loaded with their assigned module, and
862
- loading them with requirejs is as simple as requiring any other shim.
863
-
864
- ```javascript
865
- define(["welcome/other_asset", "nls/welcome"], function (otherAsset) {
866
- // ...
867
- });
868
- ```
869
-
870
- 4. (optional) As an additional configuration we can make a task to be run before
871
- the requirejs optimizer. This will allow any automated scripts that run the
872
- requirejs optimizer to export the strings before we run r.js.
873
-
874
- ```rake
875
- # lib/tasks/i18n.rake
876
- Rake::Task[:'i18n:js:export'].prerequisites.clear
877
- task :'i18n:js:export' => :'i18n:js:before_export'
878
- task :'requirejs:precompile:external' => :'i18n:js:export'
879
-
880
- namespace :i18n do
881
- namespace :js do
882
- task :before_export => :'assets:environment' do
883
- I18n.load_path += Dir[Rails.root.join('config', 'locales', '*.{yml,rb}')]
884
- I18n.backend.load_translations
885
- end
886
- end
463
+ guard(:"i18n-js",
464
+ run_on_start: true,
465
+ config_file: "./config/i18n.yml",
466
+ require_file: "./config/environment.rb") do
467
+ watch(%r{^(app|config)/locales/.+\.(yml|po)$})
468
+ watch(%r{^config/i18n.yml$})
469
+ watch("Gemfile")
887
470
  end
888
471
  ```
889
472
 
890
- ## Using I18n.js with other languages (Python, PHP, ...)
473
+ If your files are located in a different path, remember to configure file paths
474
+ accordingly.
891
475
 
892
- The JavaScript library is language agnostic; so you can use it with PHP, Python,
893
- [your favorite language here]. The only requirement is that you need to set the
894
- `translations` attribute like following:
476
+ Now you can run `guard start -i`.
895
477
 
896
- ```javascript
897
- I18n.translations = {};
478
+ ### Using [listen](https://rubygems.org/gems/listen)
898
479
 
899
- I18n.translations["en"] = {
900
- message: "Some special message for you",
901
- };
480
+ Create a file under `config/initializers/i18n.rb` with the following content:
902
481
 
903
- I18n.translations["pt-BR"] = {
904
- message: "Uma mensagem especial para você",
905
- };
482
+ ```ruby
483
+ Rails.application.config.after_initialize do
484
+ require "i18n-js/listen"
485
+ I18nJS.listen
486
+ end
906
487
  ```
907
488
 
908
- ## Known Issues
909
-
910
- ### Missing translations in precompiled file(s) after adding any new locale file
911
-
912
- Due to the design of `sprockets`:
913
-
914
- - `depend_on` only takes file paths, not directory paths
915
- - registered `preprocessors` are only run when the fingerprint of any asset
916
- file, including `.erb` files, is changed
917
-
918
- This means that new locale files will not be detected, and so they will not
919
- trigger a i18n-js refresh. There are a few approaches to work around this:
920
-
921
- 1. You can force i18n-js to update its translations by completely clearing the
922
- assets cache. Use one of the following:
489
+ The code above will watch for changes based on `config/i18n.yml` and
490
+ `config/locales`. You can customize these options:
923
491
 
924
- ```bash
925
- $ rake assets:clobber
926
- # Or, with older versions of Rails:
927
- $ rake tmp:cache:clear
928
- ```
492
+ - `config_file` - i18n-js configuration file
493
+ - `locales_dir` - one or multiple directories to watch for locales changes
494
+ - `options` - passed directly to
495
+ [listen](https://github.com/guard/listen/#options)
496
+ - `run_on_start` - export files on start. Defaults to `true`. When disabled,
497
+ files will be exported only when there are file changes.
929
498
 
930
- These commands will remove _all_ fingerprinted assets, and you will have to
931
- recompile them with
499
+ Example:
932
500
 
933
- ```bash
934
- $ rake assets:precompile
501
+ ```ruby
502
+ I18nJS.listen(
503
+ config_file: "config/i18n.yml",
504
+ locales_dir: ["config/locales", "app/views"],
505
+ options: {only: %r{.yml$}},
506
+ run_on_start: false
507
+ )
935
508
  ```
936
509
 
937
- or similar commands. If you are precompiling assets on the target machine(s),
938
- cached pages may be broken by this, so they will need to be refreshed.
939
-
940
- 2. You can change something in a different locale file.
941
-
942
- 3. Finally, you can change `config.assets.version`.
943
-
944
- **Note:** See issue [#213](https://github.com/fnando/i18n-js/issues/213) for
945
- more details and discussion of this issue.
946
-
947
- ### Translations in JS are not updated when Sprockets not loaded before this gem
948
-
949
- The "rails engine" declaration will try to detect existence of "sprockets"
950
- before adding the initailizer If sprockets is loaded after this gem, the
951
- preprocessor for making JS translations file cache to depend on content of
952
- locale files will not be hooked. So ensure sprockets is loaded before this gem
953
- by moving the entry of sprockets in the Gemfile or adding "require" statements
954
- for sprockets somewhere.
510
+ ### Integrating with your frontend
955
511
 
956
- **Note:** See issue [#404](https://github.com/fnando/i18n-js/issues/404) for
957
- more details and discussion of this issue.
512
+ You're done exporting files, now what? Well, go to
513
+ [i18n](https://github.com/fnando/i18n) to discover how to use the NPM package
514
+ that loads all the exported translation.
958
515
 
959
- ### JS `I18n.toCurrency` & `I18n.toNumber` cannot handle large integers
516
+ ### FAQ
960
517
 
961
- The above methods use `toFixed` and it only supports 53 bit integers. Ref:
962
- http://2ality.com/2012/07/large-integers.html
518
+ #### I'm running v3. Is there a migration plan?
963
519
 
964
- Feel free to find & discuss possible solution(s) at issue
965
- [#511](https://github.com/fnando/i18n-js/issues/511)
520
+ [There's a document](https://github.com/fnando/i18n-js/tree/main/MIGRATING_FROM_V3_TO_V4.md)
521
+ outlining some of the things you need to do to migrate from v3 to v4. It may not
522
+ be as complete as we'd like it to be, so let us know if you face any issues
523
+ during the migration is not outline is that document.
966
524
 
967
- ### Only works with `Simple` backend
525
+ #### How can I export translations without having a database around?
968
526
 
969
- If you set `I18n.backend` to something other than the default `Simple` backend,
970
- you will likely get an exception like this:
971
-
972
- ```
973
- Undefined method 'initialized?' for <your backend class>
974
- ```
975
-
976
- For now, i18n-js is only compatible with the `Simple` backend. If you need a
977
- more sophisticated backend for your rails application, like
978
- `I18n::Backend::ActiveRecord`, you can setup i18n-js to get translations from a
979
- separate `Simple` backend, by adding the following in an initializer:
527
+ Some people may have a build process using something like Docker that don't
528
+ necessarily have a database available. In this case, you may define your own
529
+ loading file by using something like
530
+ `i18n export --require ./config/i18n_export.rb`, where `i18n_export.rb` may look
531
+ like this:
980
532
 
981
533
  ```ruby
982
- I18n::JS.backend = I18n.backend
983
- I18n.backend = I18n::Backend::Chain.new(<your other backend(s)>, I18n.backend)
984
- ```
985
-
986
- This will use your backend with the default `Simple` backend as fallback, while
987
- i18n-js only sees and uses the simple backend. This means however, that only
988
- translations from your static locale files will be present in JavaScript.
534
+ # frozen_string_literal: true
989
535
 
990
- If you do cannot use a `Chain`-Backend for some reason, you can also set
536
+ require "bundler/setup"
537
+ require "rails"
538
+ require "active_support/railtie"
539
+ require "action_view/railtie"
991
540
 
992
- ```ruby
993
- I18n::JS.backend = I18n::Backend::Simple.new
994
- I18n.backend = <something different>
541
+ I18n.load_path += Dir["./config/locales/**/*.yml"]
995
542
  ```
996
543
 
997
- However, the automatic reloading of translations in developement will not work
998
- in this case. This is because Rails calls `I18n.reload!` for each request in
999
- development, but `reload!` will not be called on `I18n::JS.backend`, since it is
1000
- a different object. One option would be to patch `I18n.reload!` in an
1001
- initializer:
1002
-
1003
- ```ruby
1004
- module I18n
1005
- def self.reload!
1006
- I18n::JS.backend.reload!
1007
- super
1008
- end
1009
- end
1010
- ```
1011
-
1012
- See issue [#428](https://github.com/fnando/i18n-js/issues/428) for more details
1013
- and discussion of this issue.
544
+ > **Note**:
545
+ >
546
+ > You may not need to load ActiveSupport and ActionView lines, or even may need
547
+ > to add additional requires for other libs. With this approach you have full
548
+ > control on what's going to be loaded.
1014
549
 
1015
550
  ## Maintainer
1016
551
 
1017
- - Nando Vieira - <http://nandovieira.com>
1018
-
1019
- ## Contributing
1020
-
1021
- Once you've made your great commits:
552
+ - [Nando Vieira](https://github.com/fnando)
1022
553
 
1023
- 1. [Fork](http://help.github.com/forking/) I18n.js
1024
- 2. Create a branch with a clear name
1025
- 3. Make your changes (Please also add/change spec, README and CHANGELOG if
1026
- applicable)
1027
- 4. Push changes to the created branch
1028
- 5. [Create an Pull Request](http://github.com/fnando/i18n-js/pulls)
1029
- 6. That's it!
554
+ ## Contributors
1030
555
 
1031
- Please respect the indentation rules and code style. And use 2 spaces, not tabs.
1032
- And don't touch the versioning thing.
556
+ - https://github.com/fnando/i18n-js/contributors
1033
557
 
1034
- ## Running tests
1035
-
1036
- You can run I18n tests using Node.js or your browser.
1037
-
1038
- To use Node.js, install the `jasmine-node` library:
1039
-
1040
- $ npm install jasmine-node
1041
-
1042
- Then execute the following command from the lib's root directory:
1043
-
1044
- $ npm test
1045
-
1046
- To run using your browser, just open the `spec/js/specs.html` file.
558
+ ## Contributing
1047
559
 
1048
- You can run both Ruby and JavaScript specs with `rake spec`.
560
+ For more details about how to contribute, please read
561
+ https://github.com/fnando/i18n-js/blob/main/CONTRIBUTING.md.
1049
562
 
1050
563
  ## License
1051
564
 
1052
- (The MIT License)
1053
-
1054
- Permission is hereby granted, free of charge, to any person obtaining a copy of
1055
- this software and associated documentation files (the 'Software'), to deal in
1056
- the Software without restriction, including without limitation the rights to
1057
- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
1058
- the Software, and to permit persons to whom the Software is furnished to do so,
1059
- subject to the following conditions:
565
+ The gem is available as open source under the terms of the
566
+ [MIT License](https://opensource.org/licenses/MIT). A copy of the license can be
567
+ found at https://github.com/fnando/i18n-js/blob/main/LICENSE.md.
1060
568
 
1061
- The above copyright notice and this permission notice shall be included in all
1062
- copies or substantial portions of the Software.
569
+ ## Code of Conduct
1063
570
 
1064
- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1065
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
1066
- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
1067
- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
1068
- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
1069
- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
571
+ Everyone interacting in the i18n-js project's codebases, issue trackers, chat
572
+ rooms and mailing lists is expected to follow the
573
+ [code of conduct](https://github.com/fnando/i18n-js/blob/main/CODE_OF_CONDUCT.md).