i18n-js 4.0.1 → 4.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb5f60568907f90a59b7d19a6b2f61cf9a3fff95ea419b1b8b31deab16529dc6
4
- data.tar.gz: ca65b2bf6717b93218092e8c994cdbac8af6bc69fddab421ebef976c87dc5503
3
+ metadata.gz: 6dac70b6202710a44a8303deff7206848d702b4cdb3cc926552aec41114ff745
4
+ data.tar.gz: 2631494d675d0d8459ab5450bb634fee6c305dd6c01d32bb03d647ac2a657d9e
5
5
  SHA512:
6
- metadata.gz: 3e33c8605b5197cbdc6a8f30ef1cb7e5f911f84dd177d488489fcfc82e5dd2b8fb3e387b0fe89683c3e71c6025ff7b47685163317540c4e2c5c756936660be55
7
- data.tar.gz: b4367db58b89392c42c94121ccdde7179e1ea455de6c20685ded2c034304ce4c8c44cbfa1a4967610443e4f91116058ee86bc14d2e913a2c6006ee648c189dd9
6
+ metadata.gz: 67c7b4a499d4848967461c1d3a798de2d814af972049f6977d7cf4dc5c6781bfc6ecdc1129068f963ba8dc115ebe5c219f078ef00b92204f86c54a114d55aba3
7
+ data.tar.gz: 329799184d84e27455783a6a4522b9a0cce446ba33ab3d5cfe74e22d08f685736abb4329e2506e1c2d83504d963c777317f8998a838fb3fae8c358f65569c5b5
@@ -9,12 +9,15 @@ on:
9
9
 
10
10
  jobs:
11
11
  build:
12
- name: Tests with Ruby ${{ matrix.ruby }} and ${{ matrix.gemfile }}
12
+ name:
13
+ Tests with Ruby ${{ matrix.ruby }}, Node ${{ matrix.node }} and ${{
14
+ matrix.gemfile }}
13
15
  runs-on: "ubuntu-latest"
14
16
  strategy:
15
17
  fail-fast: false
16
18
  matrix:
17
19
  ruby: ["2.7", "3.0", "3.1"]
20
+ node: ["16", "18"]
18
21
  gemfile:
19
22
  - Gemfile
20
23
  if: |
@@ -32,6 +35,23 @@ jobs:
32
35
  ${{ runner.os }}-${{ matrix.ruby }}-gems-${{
33
36
  hashFiles(matrix.gemfile) }}
34
37
 
38
+ - uses: actions/cache@v3
39
+ id: npm-cache
40
+ with:
41
+ path: vendor/bundle
42
+ key: >
43
+ ${{ runner.os }}-${{ matrix.node }}-npm-${{
44
+ hashFiles('package.json') }}
45
+
46
+ - name: Set up Node
47
+ uses: actions/setup-node@v2-beta
48
+ with:
49
+ node-version: ${{ matrix.node }}
50
+
51
+ - name: Install npm dependencies
52
+ run: |
53
+ yarn install
54
+
35
55
  - name: Set up Ruby
36
56
  uses: ruby/setup-ruby@v1
37
57
  with:
@@ -49,4 +69,5 @@ jobs:
49
69
  env:
50
70
  BUNDLE_GEMFILE: ${{ matrix.gemfile }}
51
71
  run: |
72
+ yarn compile
52
73
  bundle exec rake
data/.gitignore CHANGED
@@ -9,3 +9,5 @@
9
9
  /vendor/
10
10
  *.log
11
11
  *.lock
12
+ /lib/**/*.js
13
+ /test/output
data/.rubocop.yml CHANGED
@@ -14,3 +14,6 @@ Naming/FileName:
14
14
  Exclude:
15
15
  - lib/i18n-js.rb
16
16
  - lib/guard/i18n-js.rb
17
+
18
+ Style/PerlBackrefs:
19
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -11,13 +11,35 @@ Prefix your message with one of the following:
11
11
  - [Security] in case of vulnerabilities.
12
12
  -->
13
13
 
14
- ## v4.0.1
14
+ ## v4.2.0 - Dec 10, 2022
15
+
16
+ - [Added] Add `I18nJS::Plugin.after_export(files:, config:)` method, that's
17
+ called whenever whenever I18nJS finishes exporting files. You can use it to
18
+ further process files, or generate new files based on the exported files.
19
+ - [Added] Bult-in plugin `I18nJS::ExportFilesPlugin`, which allows exporting
20
+ files out of the translations file by using a custom template.
21
+
22
+ ## v4.1.0 - Dec 09, 2022
23
+
24
+ - [Added] Parse configuration files as erb.
25
+ - [Changed] `I18n.listen(run_on_start:)` was added to control if files should be
26
+ exported during `I18n.listen`'s boot. The default value is `true`.
27
+ - [Added] Now it's possible to transform translations before exporting them
28
+ using a stable plugin api.
29
+ - [Added] Built-in plugin `I18nJS::EmbedFallbackTranslationsPlugin`, which
30
+ allows embedding missing translations on exported files.
31
+ - [Deprecated] The `i18n check` has been deprecated. Use
32
+ `i18n lint:translations` instead.
33
+ - [Added] Use `i18n lint:scripts` to lint JavaScript/TypeScript.
34
+ - [Fixed] Expand paths passed to `I18nJS.listen(locales_dir:)`.
35
+
36
+ ## v4.0.1 - Aug 25, 2022
15
37
 
16
38
  - [Fixed] Shell out export to avoid handling I18n reloading heuristics.
17
39
  - [Changed] `I18nJS.listen` now accepts a directories list to watch.
18
40
  - [Changed] `I18nJS.listen` now accepts
19
41
  [listen](https://rubygems.org/gems/listen) options via `:options`.
20
42
 
21
- ## Jul 29, 2022
43
+ ## v4.0.0 - Jul 29, 2022
22
44
 
23
45
  - Official release of i18n-js v4.0.0.
@@ -135,7 +135,7 @@ not even using it on my projects) or eRb files.
135
135
 
136
136
  ### Upgrading the configuration file
137
137
 
138
- The configuration file loaded from `config/i18nyml` has changed. Given the v3
138
+ The configuration file loaded from `config/i18n.yml` has changed. Given the v3
139
139
  configuration below
140
140
 
141
141
  ```yaml
data/README.md CHANGED
@@ -76,6 +76,22 @@ The output path can use the following placeholders:
76
76
  - `:locale`: the language that's being exported.
77
77
  - `:digest`: the MD5 hex digest of the exported file.
78
78
 
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.
81
+
82
+ ```yml
83
+ ---
84
+ <% group = "{en,pt}" %>
85
+
86
+ translations:
87
+ - file: app/frontend/translations.json
88
+ patterns:
89
+ - "<%= group %>.*"
90
+ - "!<%= group %>.activerecord"
91
+ - "!<%= group %>.errors"
92
+ - "!<%= group %>.number.nth"
93
+ ```
94
+
79
95
  The Ruby API:
80
96
 
81
97
  ```ruby
@@ -96,7 +112,9 @@ Commands:
96
112
  - init: Initialize a project
97
113
  - export: Export translations as JSON files
98
114
  - version: Show package version
99
- - check: Check for missing translations
115
+ - plugins: List plugins that will be activated
116
+ - lint:translations: Check for missing translations
117
+ - lint:scripts: Lint files using TypeScript
100
118
 
101
119
  Run `i18n COMMAND --help` for more information on specific commands.
102
120
  ```
@@ -105,14 +123,174 @@ By default, `i18n` will use `config/i18n.yml` and `config/environment.rb` as the
105
123
  configuration files. If you don't have these files, then you'll need to specify
106
124
  both `--config` and `--require`.
107
125
 
126
+ ### Plugins
127
+
128
+ #### Built-in plugins:
129
+
130
+ ##### `embed_fallback_translations`:
131
+
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.
135
+
136
+ To use it, add the following to your configuration file:
137
+
138
+ ```yaml
139
+ embed_fallback_translations:
140
+ enabled: true
141
+ ```
142
+
143
+ ##### `export_files`:
144
+
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:
148
+
149
+ ```yaml
150
+ export_files:
151
+ enabled: true
152
+ files:
153
+ - template: path/to/template.erb
154
+ output: "%{dir}/%{base_name}.ts"
155
+ ```
156
+
157
+ You can export multiple files by define more entries.
158
+
159
+ The output name can use the following placeholders:
160
+
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.
165
+
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:
169
+
170
+ ```erb
171
+ /* eslint-disable */
172
+ <%= banner %>
173
+
174
+ import { i18n } from "config/i18n";
175
+
176
+ i18n.store(<%= JSON.pretty_generate(translations) %>);
177
+ ```
178
+
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:
183
+
184
+ ```typescript
185
+ /* eslint-disable */
186
+ // File generated by i18n-js on 2022-12-10 15:37:00 +0000
187
+
188
+ import { i18n } from "config/i18n";
189
+
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
+ });
207
+ ```
208
+
209
+ #### Plugin API
210
+
211
+ You can transform the exported translations by adding plugins. A plugin must
212
+ inherit from `I18nJS::Plugin` and can have 4 class methods. To see a real
213
+ example, see
214
+ [lib/i18n-js/embed_fallback_translations_plugin.rb](https://github.com/fnando/i18n-js/blob/main/lib/i18n-js/embed_fallback_translations_plugin.rb)
215
+
216
+ Here's the base `I18nJS::Plugin` class with the documented api:
217
+
218
+ ```ruby
219
+ # frozen_string_literal: true
220
+
221
+ module I18nJS
222
+ class Plugin
223
+ # This method is responsible for transforming the translations. The
224
+ # translations you'll receive may be already be filtered by other plugins
225
+ # and by the default filtering itself. If you need to access the original
226
+ # translations, use `I18nJS.translations`.
227
+ #
228
+ # Make sure you always check whether your plugin is active before
229
+ # transforming translations; otherwise, opting out transformation won't be
230
+ # possible.
231
+ def self.transform(translations:, config:)
232
+ translations
233
+ end
234
+
235
+ # In case your plugin accepts configuration, this is where you must validate
236
+ # the configuration, making sure only valid keys and type is provided.
237
+ # If the configuration contains invalid data, then you must raise an
238
+ # exception using something like
239
+ # `raise I18nJS::Schema::InvalidError, error_message`.
240
+ def self.validate_schema(config:)
241
+ end
242
+
243
+ # This method must set up the basic plugin configuration, like adding the
244
+ # config's root key in case your plugin accepts configuration (defined via
245
+ # the config file).
246
+ #
247
+ # If you don't add this key, the linter will prevent non-default keys from
248
+ # being added to the configuration file.
249
+ def self.setup
250
+ end
251
+
252
+ # This method is called whenever `I18nJS.call(**kwargs)` finishes exporting
253
+ # JSON files based on your configuration.
254
+ #
255
+ # You can use it to further process exported files, or generate new files
256
+ # based on the translations that have been exported.
257
+ #
258
+ # Make sure you always check whether your plugin is active before
259
+ # processing files; otherwise, opting out won't be possible.
260
+ def self.after_export(files:, config:)
261
+ end
262
+ end
263
+ end
264
+ ```
265
+
266
+ To distribute this plugin, you need to create a gem package that matches the
267
+ pattern `i18n-js/*_plugin.rb`. You can test whether your plugin will be found by
268
+ installing your gem, opening a iRB session and running
269
+ `Gem.find_files("i18n-js/*_plugin.rb")`. If your plugin is not listed, then you
270
+ need to double check your gem load path and see why the file is not being
271
+ loaded.
272
+
108
273
  ### Listing missing translations
109
274
 
110
- To list missing and extraneous translations, you can use `i18n check`. This
111
- command will load your translations similarly to how `i18n export` does, but
112
- will output the list of keys that don't have a matching translation against the
113
- default locale. Here's an example:
275
+ To list missing and extraneous translations, you can use
276
+ `i18n lint:translations`. This command will load your translations similarly to
277
+ how `i18n export` does, but will output the list of keys that don't have a
278
+ matching translation against the default locale. Here's an example:
114
279
 
115
- ![`i18n check` command in action](https://github.com/fnando/i18n-js/raw/main/images/i18njs-check.gif)
280
+ ```console
281
+ $ i18n lint:translations
282
+ => Config file: "./config/i18n.yml"
283
+ => Require file: "./config/environment.rb"
284
+ => Check "./config/i18n.yml" for ignored keys.
285
+ => en: 232 translations
286
+ => pt-BR: 5 missing, 1 extraneous, 1 ignored
287
+ - pt-BR.actors.github.metrics (missing)
288
+ - pt-BR.actors.github.metrics_hint (missing)
289
+ - pt-BR.actors.github.repo_metrics (missing)
290
+ - pt-BR.actors.github.repository (missing)
291
+ - pt-BR.actors.github.user_metrics (missing)
292
+ - pt-BR.github.repository (extraneous)
293
+ ```
116
294
 
117
295
  This command will exist with status 1 whenever there are missing translations.
118
296
  This way you can use it as a CI linting.
@@ -133,7 +311,7 @@ translations:
133
311
  patterns:
134
312
  - "*"
135
313
 
136
- check:
314
+ lint_translations:
137
315
  ignore:
138
316
  - en.mailer.login.subject
139
317
  - en.mailer.login.body
@@ -145,6 +323,82 @@ check:
145
323
  > accepts the full translation scope, rather than accepting a pattern like
146
324
  > `pt.ignored.scope.*`.
147
325
 
326
+ ### Linting your JavaScript/TypeScript files
327
+
328
+ To lint your script files and check for missing translations (which can signal
329
+ that you're either using wrong scopes or forgot to add the translation), use
330
+ `i18n lint:scripts`. This command will parse your JavaScript/TypeScript files
331
+ and extract all scopes being used. This command requires a Node.js runtime. You
332
+ can either specify one via `--node-path`, or let the plugin infer a binary from
333
+ your `$PATH`.
334
+
335
+ The comparison will be made against the export JSON files, which means it'll
336
+ consider transformations performed by plugins (e.g. the output files may be
337
+ affected by `embed_fallback_translations` plugin).
338
+
339
+ The translations that will be extract must be called as one of the following
340
+ ways:
341
+
342
+ - `i18n.t(scope, options)`
343
+ - `i18n.translate(scope, options)`
344
+ - `t(scope, options)`
345
+
346
+ Notice that only literal strings can be used, as in `i18n.t("message")`. If
347
+ you're using dynamic scoping through variables (e.g.
348
+ `const scope = "message"; i18n.t(scope)`), they will be skipped.
349
+
350
+ ```console
351
+ $ i18n lint:scripts
352
+ => Config file: "./config/i18n.yml"
353
+ => Require file: "./config/environment.rb"
354
+ => Node: "/Users/fnando/.asdf/shims/node"
355
+ => Available locales: [:en, :es, :pt]
356
+ => Patterns: ["!(node_modules)/**/*.js", "!(node_modules)/**/*.ts", "!(node_modules)/**/*.jsx", "!(node_modules)/**/*.tsx"]
357
+ => 9 translations, 11 missing, 4 ignored
358
+ - test/scripts/lint/file.js:1:1: en.js.missing
359
+ - test/scripts/lint/file.js:1:1: es.js.missing
360
+ - test/scripts/lint/file.js:1:1: pt.js.missing
361
+ - test/scripts/lint/file.js:2:8: en.base.js.missing
362
+ - test/scripts/lint/file.js:2:8: es.base.js.missing
363
+ - test/scripts/lint/file.js:2:8: pt.base.js.missing
364
+ - test/scripts/lint/file.js:4:8: en.js.missing
365
+ - test/scripts/lint/file.js:4:8: es.js.missing
366
+ - test/scripts/lint/file.js:4:8: pt.js.missing
367
+ - test/scripts/lint/file.js:6:1: en.another_ignore_scope
368
+ - test/scripts/lint/file.js:6:1: es.another_ignore_scope
369
+ ```
370
+
371
+ This command will list all locales and their missing translations. Avoid listing
372
+ a particular translation, you can set `lint.ignore` on your config file.
373
+
374
+ ```yaml
375
+ ---
376
+ translations:
377
+ - file: app/frontend/translations.json
378
+ patterns:
379
+ - "*"
380
+
381
+ lint_scripts:
382
+ ignore:
383
+ - ignore_scope # will ignore this scope on all languages
384
+ - pt.another_ignore_scope # will ignore this scope only on `pt`
385
+ ```
386
+
387
+ You can also set the patterns that will be looked up. By default, it scans all
388
+ JavaScript and TypeScript files that don't live on `node_modules`.
389
+
390
+ ```yaml
391
+ ---
392
+ translations:
393
+ - file: app/frontend/translations.json
394
+ patterns:
395
+ - "*"
396
+
397
+ lint:
398
+ patterns:
399
+ - "app/assets/**/*.ts"
400
+ ```
401
+
148
402
  ## Automatically export translations
149
403
 
150
404
  ### Using [watchman](https://facebook.github.io/watchman/)
@@ -230,7 +484,10 @@ The code above will watch for changes based on `config/i18n.yml` and
230
484
 
231
485
  - `config_file` - i18n-js configuration file
232
486
  - `locales_dir` - one or multiple directories to watch for locales changes
233
- - `options` - passed directly to [listen](https://github.com/guard/listen/#options)
487
+ - `options` - passed directly to
488
+ [listen](https://github.com/guard/listen/#options)
489
+ - `run_on_start` - export files on start. Defaults to `true`. When disabled,
490
+ files will be exported only when there are file changes.
234
491
 
235
492
  Example:
236
493
 
@@ -238,7 +495,8 @@ Example:
238
495
  I18nJS.listen(
239
496
  config_file: "config/i18n.yml",
240
497
  locales_dir: ["config/locales", "app/views"],
241
- options: {only: %r{.yml$}
498
+ options: {only: %r{.yml$}},
499
+ run_on_start: false
242
500
  )
243
501
  ```
244
502
 
data/bin/release ADDED
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "optparse"
5
+ require_relative "../lib/i18n-js/version"
6
+
7
+ def write_file(path, contents)
8
+ File.open(File.expand_path(path), "w") do |io|
9
+ io << contents
10
+ end
11
+ end
12
+
13
+ changelog_path = "./CHANGELOG.md"
14
+ version_path = "./lib/i18n-js/version.rb"
15
+
16
+ version = nil
17
+ segments = I18nJS::VERSION.split(".")
18
+ major, minor, patch = *segments.take(3).map(&:to_i)
19
+ pre = segments[4].to_s
20
+ pre_version = pre.gsub(/[^\d]/m, "").to_i
21
+ date = Time.now.strftime("%b %d, %Y")
22
+ dry_run = false
23
+ alpha = false
24
+
25
+ OptionParser.new do |opts|
26
+ opts.on("--major") do
27
+ version = "#{major + 1}.0.0"
28
+ end
29
+
30
+ opts.on("--minor") do
31
+ version = "#{major}.#{minor + 1}.0"
32
+ end
33
+
34
+ opts.on("--patch") do
35
+ version = "#{major}.#{minor}.#{patch + 1}"
36
+ end
37
+
38
+ opts.on("--alpha") do
39
+ alpha = true
40
+ end
41
+
42
+ opts.on("--dry-run") do
43
+ dry_run = true
44
+ end
45
+ end.parse!
46
+
47
+ version = "#{version}.alpha#{pre_version + 1}" if alpha
48
+
49
+ unless version
50
+ puts "ERROR: You need to use either one of: --major, --minor, --patch"
51
+ exit 1
52
+ end
53
+
54
+ puts "=> Current version: #{I18nJS::VERSION}"
55
+ puts "=> Next version: #{version}"
56
+
57
+ system "yarn", "install"
58
+ system "yarn", "compile"
59
+
60
+ write_file changelog_path,
61
+ File.read(changelog_path)
62
+ .gsub("Unreleased", "v#{version} - #{date}")
63
+
64
+ puts "=> Updated #{changelog_path}"
65
+
66
+ write_file version_path,
67
+ File.read(version_path)
68
+ .gsub(/VERSION = ".*?"/, %[VERSION = "#{version}"])
69
+
70
+ puts "=> Updated #{version_path}"
71
+
72
+ unless dry_run
73
+ system "git", "add", changelog_path, version_path
74
+ system "git", "commit", "-m", "Bump up version (v#{version})"
75
+ system "rake", "release"
76
+ end
77
+
78
+ if dry_run
79
+ system "rake", "build"
80
+ system "git", "checkout", changelog_path, version_path
81
+ end
data/i18n-js.gemspec CHANGED
@@ -31,16 +31,19 @@ Gem::Specification.new do |spec|
31
31
  .reject {|f| f.match(%r{^(test|spec|features|images)/}) }
32
32
  end
33
33
 
34
+ spec.files << "lib/i18n-js/lint.js"
35
+
34
36
  spec.bindir = "exe"
35
37
  spec.executables = spec.files.grep(%r{^exe/}) {|f| File.basename(f) }
36
38
  spec.require_paths = ["lib"]
37
39
 
38
- spec.add_dependency "glob"
40
+ spec.add_dependency "glob", ">= 0.4.0"
39
41
  spec.add_dependency "i18n"
40
42
 
41
43
  spec.add_development_dependency "activesupport"
42
44
  spec.add_development_dependency "minitest"
43
45
  spec.add_development_dependency "minitest-utils"
46
+ spec.add_development_dependency "mocha"
44
47
  spec.add_development_dependency "pry-meta"
45
48
  spec.add_development_dependency "rake"
46
49
  spec.add_development_dependency "rubocop"