i18n-js 4.2.4 → 5.0.0.rc1

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: f067f8059ef5fe37c2fdb9624cf260e00b3eb60a87492aebccb0b13f170ff17d
4
- data.tar.gz: 73acf779ba9a46921e44e4106e66eab2c5d05b7b1163cb93b2889abf446977e0
3
+ metadata.gz: 4651d246704c3470ac5139dec7d2351e4aad13866be1d51e20e6bcf960afa2aa
4
+ data.tar.gz: 43ba1c9e9f74c9032c17ee00fd54489b91275d23f559ff042a3b1c1300ddd3e9
5
5
  SHA512:
6
- metadata.gz: a71ec7995b41c9e9c56168dccd1a87e1e872c3bfa98075c135aa9d6f4f0259fef6da8e8db81fd1fa63966775b5bd28185ee086a6206c0600b113827938b73412
7
- data.tar.gz: be12ef7d7665f61d40abd05849cb25dc303c1679c08e6ed535123e9389cb5d968946b4d716d541bc5e10f8d1384040b7935e56aa0c84d682d0964a0698683d50
6
+ metadata.gz: d4b83c24cb85bcfd1badeef6ecc4e2955a138be41f5fbd68cf5122c21daa0c34b8e5df69a5c16739aca8f007dde7141f1cc35cc4ee9f9166c54754b6d8b33d61
7
+ data.tar.gz: 91a2fb161e6b7f3d1e698f92e49dabd46aa37bb15153ca6ce8f28849acd704204fff033c79ed7a06f599d2246ef9b8ae285a66d8165a2e538b42140e1d5e95a5
@@ -9,14 +9,12 @@ on:
9
9
 
10
10
  jobs:
11
11
  build:
12
- name:
13
- Tests with Ruby ${{ matrix.ruby }}, Node ${{ matrix.node }} and ${{
14
- matrix.gemfile }}
12
+ name: Tests with Ruby ${{ matrix.ruby }}, Node ${{ matrix.node }} and ${{ matrix.gemfile }}
15
13
  runs-on: "ubuntu-latest"
16
14
  strategy:
17
15
  fail-fast: false
18
16
  matrix:
19
- ruby: ["3.3", "3.4"]
17
+ ruby: ["3.4", "3.5", "4.0"]
20
18
  node: ["22", "24"]
21
19
  gemfile:
22
20
  - Gemfile
@@ -25,26 +23,22 @@ jobs:
25
23
  github.actor != 'dependabot[bot]'
26
24
 
27
25
  steps:
28
- - uses: actions/checkout@v4
26
+ - uses: actions/checkout@v6
29
27
 
30
- - uses: actions/cache@v4
28
+ - uses: actions/cache@v5
31
29
  id: bundler-cache
32
30
  with:
33
31
  path: vendor/bundle
34
32
  key: >
35
- ${{ runner.os }}-${{ matrix.ruby }}-gems-${{
36
- hashFiles(matrix.gemfile) }}
37
-
38
- - uses: actions/cache@v4
33
+ ${{ runner.os }}-${{ matrix.ruby }}-gems-${{ hashFiles(matrix.gemfile) }}
34
+ - uses: actions/cache@v5
39
35
  id: npm-cache
40
36
  with:
41
37
  path: vendor/bundle
42
38
  key: >
43
- ${{ runner.os }}-${{ matrix.node }}-npm-${{
44
- hashFiles('package.json') }}
45
-
39
+ ${{ runner.os }}-${{ matrix.node }}-npm-${{ hashFiles('package.json') }}
46
40
  - name: Set up Node
47
- uses: actions/setup-node@v4.0.2
41
+ uses: actions/setup-node@v6.4.0
48
42
  with:
49
43
  node-version: ${{ matrix.node }}
50
44
 
data/CHANGELOG.md CHANGED
@@ -11,6 +11,43 @@ Prefix your message with one of the following:
11
11
  - [Security] in case of vulnerabilities.
12
12
  -->
13
13
 
14
+ ## v5.0.0.rc1 - Jun 02, 2026
15
+
16
+ - [Added] `embed_fallback_translations` plugin now supports `I18n.fallbacks`,
17
+ and will switch to it automatically if you're using a backend that includes
18
+ the `I18n::Backend::Fallbacks` module and have `I18n.fallbacks` configured.
19
+ - [Changed] `i18n lint:translations` and `i18n lint:scripts` now support globs
20
+ in their `ignore` option.
21
+ - [Removed] Previous versions allowed bare keys in lint's `:ignore` rule. This
22
+ is no longer supported; instead, use `*.key`.
23
+ - [Removed] Remove `i18n check`; use `i18n lint:translations` instead.
24
+ - [Fixed] Return the number of missing translations as the exit code when
25
+ running `i18n lint:scripts`.
26
+ - [Changed] Plugin configuration moved from top-level keys to a `pipeline:`
27
+ array in the config file. Each entry requires a `plugin:` key identifying the
28
+ plugin and an `enabled:` key. Plugin-specific options are defined inline in
29
+ the same stage object.
30
+ - [Changed] `I18nJS::Plugin.key` is now a class method returning a String.
31
+ Previously it was an instance method called `config_key` that returned a
32
+ Symbol.
33
+ - [Changed] `I18nJS::Plugin#initialize` now accepts `plugin_config:` and
34
+ `main_config:` instead of a single `config:` argument.
35
+ - [Changed] `I18nJS::Plugin#config` now returns only the plugin's own
36
+ configuration slice instead of the full main config.
37
+ - [Changed] `I18nJS.initialize_plugins!` now returns the list of active plugin
38
+ instances. The `I18nJS.plugins` accessor has been removed.
39
+ - [Changed] `I18nJS::Schema.root_keys` is now a frozen Array. Plugins no longer
40
+ need to register a root key in their `setup` method.
41
+ - [Changed] Schema validation paths in `validate_schema` are now relative to the
42
+ plugin's own config root. Remove the leading `config_key` segment from all
43
+ paths passed to `schema.*` helpers.
44
+ - [Removed] The `check:` root key is no longer recognised by the schema
45
+ validator. Remove it from your config file.
46
+ - [Added] A single plugin class can now appear multiple times in the pipeline
47
+ with different configurations, each running as an independent instance.
48
+ - [Changed] Export translations in parallel. To enable parallel mode, your
49
+ output file definition must include the `:locale` placeholder.
50
+
14
51
  ## v4.2.3 - Mar 29, 2023
15
52
 
16
53
  - [Fixed] Load plugins when running `i18n lint:*` commands.
@@ -0,0 +1,200 @@
1
+ # Migrating Your Configuration File
2
+
3
+ This guide covers the changes you need to make to your `config/i18n.yml` (or
4
+ equivalent) after the switch to the `pipeline:` API.
5
+
6
+ ---
7
+
8
+ ## What changed
9
+
10
+ Plugin configuration used to live at the top level of the config file, each
11
+ plugin using its own root key. All plugins are now listed in a single
12
+ `pipeline:` array. The order of entries in the array determines the order in
13
+ which plugins run.
14
+
15
+ ---
16
+
17
+ ## 1. `embed_fallback_translations`
18
+
19
+ **Before**
20
+
21
+ ```yaml
22
+ translations:
23
+ - file: app/javascript/locales/%{locale}.json
24
+ patterns:
25
+ - "*"
26
+
27
+ embed_fallback_translations:
28
+ enabled: true
29
+ ```
30
+
31
+ **After**
32
+
33
+ ```yaml
34
+ translations:
35
+ - file: app/javascript/locales/%{locale}.json
36
+ patterns:
37
+ - "*"
38
+
39
+ pipeline:
40
+ - plugin: embed_fallback_translations
41
+ enabled: true
42
+ ```
43
+
44
+ ---
45
+
46
+ ## 2. `export_files`
47
+
48
+ **Before**
49
+
50
+ ```yaml
51
+ translations:
52
+ - file: app/javascript/locales/%{locale}.json
53
+ patterns:
54
+ - "*"
55
+
56
+ export_files:
57
+ enabled: true
58
+ files:
59
+ - template: path/to/template.erb
60
+ output: "%{dir}/%{base_name}.ts"
61
+ ```
62
+
63
+ **After**
64
+
65
+ ```yaml
66
+ translations:
67
+ - file: app/javascript/locales/%{locale}.json
68
+ patterns:
69
+ - "*"
70
+
71
+ pipeline:
72
+ - plugin: export_files
73
+ enabled: true
74
+ files:
75
+ - template: path/to/template.erb
76
+ output: "%{dir}/%{base_name}.ts"
77
+ ```
78
+
79
+ Plugin-specific keys (`files:` in this case) move into the pipeline stage
80
+ object, directly alongside `plugin:` and `enabled:`.
81
+
82
+ ---
83
+
84
+ ## 3. Multiple plugins
85
+
86
+ When you use more than one plugin, list them all under the same `pipeline:`
87
+ key. The plugins run in the order they are listed.
88
+
89
+ **Before**
90
+
91
+ ```yaml
92
+ translations:
93
+ - file: app/javascript/locales/%{locale}.json
94
+ patterns:
95
+ - "*"
96
+
97
+ embed_fallback_translations:
98
+ enabled: true
99
+
100
+ export_files:
101
+ enabled: true
102
+ files:
103
+ - template: path/to/template.erb
104
+ output: "%{dir}/%{base_name}.ts"
105
+ ```
106
+
107
+ **After**
108
+
109
+ ```yaml
110
+ translations:
111
+ - file: app/javascript/locales/%{locale}.json
112
+ patterns:
113
+ - "*"
114
+
115
+ pipeline:
116
+ - plugin: embed_fallback_translations
117
+ enabled: true
118
+
119
+ - plugin: export_files
120
+ enabled: true
121
+ files:
122
+ - template: path/to/template.erb
123
+ output: "%{dir}/%{base_name}.ts"
124
+ ```
125
+
126
+ ---
127
+
128
+ ## 4. Temporarily disabling a plugin
129
+
130
+ Set `enabled: false` on the pipeline stage. The stage stays in the file so you
131
+ can re-enable it without rewriting the config.
132
+
133
+ ```yaml
134
+ pipeline:
135
+ - plugin: embed_fallback_translations
136
+ enabled: false # temporarily disabled
137
+ ```
138
+
139
+ Previously, removing the top-level key was the only way to disable a plugin.
140
+
141
+ ---
142
+
143
+ ## 5. Running the same plugin more than once
144
+
145
+ The pipeline allows the same plugin to appear multiple times with different
146
+ options. This was not possible with the old top-level key approach.
147
+
148
+ ```yaml
149
+ pipeline:
150
+ - plugin: export_files
151
+ enabled: true
152
+ files:
153
+ - template: templates/esm.erb
154
+ output: "%{dir}/%{base_name}.mjs"
155
+
156
+ - plugin: export_files
157
+ enabled: true
158
+ files:
159
+ - template: templates/cjs.erb
160
+ output: "%{dir}/%{base_name}.cjs"
161
+ ```
162
+
163
+ ---
164
+
165
+ ## 6. The `check` key was removed
166
+
167
+ The `check:` root key is no longer a recognised configuration option and will
168
+ cause a validation error if present. If you used `check.ignore` to suppress
169
+ known-missing translation keys, move those entries to `lint_translations.ignore`
170
+ — it serves the same purpose.
171
+
172
+ **Before**
173
+
174
+ ```yaml
175
+ check:
176
+ ignore:
177
+ - "es.bye"
178
+ - "pt.bye"
179
+ ```
180
+
181
+ **After**
182
+
183
+ ```yaml
184
+ lint_translations:
185
+ ignore:
186
+ - "es.bye"
187
+ - "pt.bye"
188
+ ```
189
+
190
+ If your `check:` block contained no `ignore:` list, delete it entirely.
191
+
192
+ ---
193
+
194
+ ## Quick checklist
195
+
196
+ - [ ] Remove the top-level `embed_fallback_translations:` block and replace with a `pipeline:` entry
197
+ - [ ] Remove the top-level `export_files:` block and replace with a `pipeline:` entry (move `files:` into the stage)
198
+ - [ ] Do the same for any third-party plugin that had its own top-level key
199
+ - [ ] Move `check.ignore` entries to `lint_translations.ignore`, then delete the `check:` block
200
+ - [ ] Verify the `pipeline:` key is a YAML sequence (starts with `-`), not a mapping
@@ -0,0 +1,299 @@
1
+ # Migrating Plugins to the Pipeline API
2
+
3
+ This guide covers the breaking changes to the plugin API introduced in the
4
+ commit that switched plugins from top-level config keys to a `pipeline` array.
5
+
6
+ ---
7
+
8
+ ## Overview of changes
9
+
10
+ | Area | Before | After |
11
+ |------|--------|-------|
12
+ | Config location | Top-level key per plugin | `pipeline:` array |
13
+ | Key method | `config_key` (instance, Symbol) | `Plugin.key` (class method, String) |
14
+ | Constructor | `initialize(config:)` | `initialize(plugin_config:, main_config:)` |
15
+ | `config` accessor | `main_config[config_key]` | plugin-specific hash from pipeline entry |
16
+ | `setup` | registers root schema key | no schema registration needed |
17
+ | `validate_schema` paths | prefixed with `config_key` | relative to plugin config root |
18
+ | `I18nJS.plugins` | global accessor | return value of `initialize_plugins!` |
19
+ | `Schema.root_keys` | mutable Set | frozen Array |
20
+
21
+ ---
22
+
23
+ ## 1. Update the config file
24
+
25
+ Plugin configuration no longer lives at the top level of the YAML file. Move
26
+ every plugin block into the `pipeline:` array and add a `plugin:` key that
27
+ identifies the plugin by its inferred key name (snake_case, no `Plugin`
28
+ suffix).
29
+
30
+ **Before**
31
+
32
+ ```yaml
33
+ translations:
34
+ - file: app/javascript/locales/%{locale}.json
35
+ patterns:
36
+ - "*"
37
+
38
+ embed_fallback_translations:
39
+ enabled: true
40
+
41
+ export_files:
42
+ enabled: true
43
+ files:
44
+ - template: templates/export.erb
45
+ output: "%{dir}/%{base_name}-%{digest}.ts"
46
+ ```
47
+
48
+ **After**
49
+
50
+ ```yaml
51
+ translations:
52
+ - file: app/javascript/locales/%{locale}.json
53
+ patterns:
54
+ - "*"
55
+
56
+ pipeline:
57
+ - plugin: embed_fallback_translations
58
+ enabled: true
59
+
60
+ - plugin: export_files
61
+ enabled: true
62
+ files:
63
+ - template: templates/export.erb
64
+ output: "%{dir}/%{base_name}-%{digest}.ts"
65
+ ```
66
+
67
+ All plugin-specific keys (like `files:` above) now live directly inside the
68
+ pipeline stage object, alongside `plugin:` and `enabled:`.
69
+
70
+ ---
71
+
72
+ ## 2. Rename `config_key` to `Plugin.key`
73
+
74
+ The method that infers the plugin's identifier moved from an **instance**
75
+ method returning a Symbol to a **class** method returning a String.
76
+
77
+ **Before**
78
+
79
+ ```ruby
80
+ # instance method, returns Symbol
81
+ def config_key
82
+ :my_plugin
83
+ end
84
+ ```
85
+
86
+ **After**
87
+
88
+ ```ruby
89
+ # class method, returns String
90
+ def self.key
91
+ "my_plugin"
92
+ end
93
+ ```
94
+
95
+ In most cases you do **not** need to override this method at all — it is
96
+ automatically inferred from the class name:
97
+
98
+ | Class name | Inferred key |
99
+ |------------|-------------|
100
+ | `SamplePlugin` | `"sample"` |
101
+ | `EmbedFallbackTranslationsPlugin` | `"embed_fallback_translations"` |
102
+ | `FetchFromHTTPPlugin` | `"fetch_from_http"` |
103
+
104
+ Override only if the inferred name does not match what you want to expose in the
105
+ config file.
106
+
107
+ ---
108
+
109
+ ## 3. Update the constructor
110
+
111
+ The constructor signature changed. `config:` (the full main config) was
112
+ replaced by two keyword arguments.
113
+
114
+ **Before**
115
+
116
+ ```ruby
117
+ def initialize(config:)
118
+ @main_config = config
119
+ @schema = I18nJS::Schema.new(@main_config)
120
+ end
121
+ ```
122
+
123
+ **After**
124
+
125
+ The base class handles initialization. You rarely need to override `initialize`
126
+ at all. If you do, call `super` and use the provided accessors:
127
+
128
+ ```ruby
129
+ def initialize(plugin_config:, main_config:)
130
+ super
131
+ # @config => plugin-specific hash (the pipeline stage, minus :plugin key)
132
+ # @main_config => full config hash
133
+ # @schema => Schema.new(@config)
134
+ end
135
+ ```
136
+
137
+ ---
138
+
139
+ ## 4. Use `config` instead of `main_config[config_key]`
140
+
141
+ `config` now returns only the plugin's own slice of configuration (the pipeline
142
+ stage hash, with the `plugin:` key removed). You no longer need to dig into
143
+ `main_config` to find your plugin's settings.
144
+
145
+ **Before**
146
+
147
+ ```ruby
148
+ def transform(translations:)
149
+ return translations unless main_config[config_key][:enabled]
150
+ # work with main_config[config_key][:my_option]
151
+ end
152
+ ```
153
+
154
+ **After**
155
+
156
+ ```ruby
157
+ def transform(translations:)
158
+ # work with config[:my_option] directly
159
+ end
160
+ ```
161
+
162
+ `enabled?` is also handled by the base class and returns `config[:enabled]`.
163
+ Only enabled plugins are placed in the pipeline, so you rarely need to check
164
+ `enabled?` inside `transform`.
165
+
166
+ ---
167
+
168
+ ## 5. Remove schema root key registration from `setup`
169
+
170
+ Plugins used to register their top-level config key so the schema validator
171
+ wouldn't reject it. This is no longer necessary — the `pipeline:` array is
172
+ already a recognised root key, and each stage is validated generically.
173
+
174
+ **Before**
175
+
176
+ ```ruby
177
+ def setup
178
+ I18nJS::Schema.root_keys << config_key
179
+ end
180
+ ```
181
+
182
+ **After**
183
+
184
+ ```ruby
185
+ def setup
186
+ # plugin-specific setup only, no schema registration needed
187
+ end
188
+ ```
189
+
190
+ If `setup` contained only the `root_keys <<` line, you can delete the method
191
+ entirely.
192
+
193
+ ---
194
+
195
+ ## 6. Update `validate_schema` paths
196
+
197
+ `Schema` is now initialized with the plugin's own config hash rather than the
198
+ full main config. All paths passed to schema helpers are therefore **relative to
199
+ the plugin config root**, not the global config root.
200
+
201
+ **Before**
202
+
203
+ ```ruby
204
+ def validate_schema
205
+ valid_keys = %i[enabled files]
206
+
207
+ schema.expect_required_keys(keys: valid_keys, path: [config_key])
208
+ schema.reject_extraneous_keys(keys: valid_keys, path: [config_key])
209
+ schema.expect_array_with_items(path: [config_key, :files])
210
+
211
+ config[:files].each_with_index do |_, index|
212
+ export_keys = %i[template output]
213
+ schema.expect_required_keys(keys: export_keys, path: [config_key, :files, index])
214
+ schema.expect_type(path: [config_key, :files, index, :template], types: String)
215
+ end
216
+ end
217
+ ```
218
+
219
+ **After**
220
+
221
+ ```ruby
222
+ def validate_schema
223
+ valid_keys = %i[enabled files]
224
+
225
+ schema.expect_required_keys(keys: valid_keys) # path defaults to []
226
+ schema.reject_extraneous_keys(keys: valid_keys)
227
+ schema.expect_array_with_items(path: [:files]) # relative path
228
+
229
+ export_keys = %i[template output]
230
+ config[:files].each_with_index do |_, index|
231
+ schema.expect_required_keys(keys: export_keys, path: [:files, index])
232
+ schema.expect_type(path: [:files, index, :template], types: String)
233
+ end
234
+ end
235
+ ```
236
+
237
+ The rule: **drop the leading `config_key` segment from every path**.
238
+
239
+ ---
240
+
241
+ ## 7. Stop using `I18nJS.plugins`
242
+
243
+ The global `I18nJS.plugins` accessor was removed. `initialize_plugins!` now
244
+ returns the list of active plugin instances directly.
245
+
246
+ **Before**
247
+
248
+ ```ruby
249
+ I18nJS.initialize_plugins!(config:)
250
+ I18nJS.plugins.each { |p| p.do_something }
251
+ ```
252
+
253
+ **After**
254
+
255
+ ```ruby
256
+ plugins = I18nJS.initialize_plugins!(config:)
257
+ plugins.each { |p| p.do_something }
258
+ ```
259
+
260
+ If you were calling `I18nJS.plugins.clear` in tests, switch to
261
+ `I18nJS.available_plugins.clear` (which clears registered plugin classes) or
262
+ simply stop clearing — `initialize_plugins!` now reads solely from the
263
+ `pipeline:` config so stale instances are never carried over.
264
+
265
+ ---
266
+
267
+ ## 8. One plugin class, multiple pipeline stages
268
+
269
+ A single plugin class can now appear **multiple times** in the pipeline with
270
+ different configurations. Each entry creates an independent plugin instance.
271
+ Each instance receives its own `config` hash.
272
+
273
+ ```yaml
274
+ pipeline:
275
+ - plugin: export_files
276
+ enabled: true
277
+ files:
278
+ - template: templates/esm.erb
279
+ output: "app/js/%{base_name}.mjs"
280
+
281
+ - plugin: export_files
282
+ enabled: true
283
+ files:
284
+ - template: templates/cjs.erb
285
+ output: "app/js/%{base_name}.cjs"
286
+ ```
287
+
288
+ ---
289
+
290
+ ## Quick checklist
291
+
292
+ - [ ] Move all plugin blocks from the top level into a `pipeline:` array
293
+ - [ ] Add a `plugin:` key to each pipeline stage
294
+ - [ ] Rename `config_key` instance method to `self.key` class method; change return type from Symbol to String
295
+ - [ ] Remove `initialize` override if it only delegated to `super`; otherwise update signature to `(plugin_config:, main_config:)`
296
+ - [ ] Replace `main_config[config_key][:option]` with `config[:option]`
297
+ - [ ] Delete `setup` if it only registered a root schema key
298
+ - [ ] Drop the leading `config_key` segment from all `schema.*` paths in `validate_schema`
299
+ - [ ] Replace `I18nJS.plugins` with the return value of `initialize_plugins!`