i18n-js 3.9.2 → 4.2.2

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