i18n-js 4.0.1 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
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"