i18n-js 4.1.0 → 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: 60f1ad0e9477d8e6570573f64ac2a55136653da8e4791c6fc32e69c9547e197b
4
- data.tar.gz: d46a3d8b8a25020635d9c108b2cb2caf3a256e419112a21a0d986f94e1e19c37
3
+ metadata.gz: 6dac70b6202710a44a8303deff7206848d702b4cdb3cc926552aec41114ff745
4
+ data.tar.gz: 2631494d675d0d8459ab5450bb634fee6c305dd6c01d32bb03d647ac2a657d9e
5
5
  SHA512:
6
- metadata.gz: a1d63d094866a836a69a9b3fc8f2989608a1b794daf5d5c82062e17b3bcb07ab7fb0ec8f2c05aa27a2585236cff33d974c313ca81be96de129340ecce8f78490
7
- data.tar.gz: 7ec126b41663f765a421950c1ddcb215d460681355088fc77731673c44f3baf590cc58c6e5841bc9dda6f58732249488cdad3b020287b57d7d1de70cf8e95716
6
+ metadata.gz: 67c7b4a499d4848967461c1d3a798de2d814af972049f6977d7cf4dc5c6781bfc6ecdc1129068f963ba8dc115ebe5c219f078ef00b92204f86c54a114d55aba3
7
+ data.tar.gz: 329799184d84e27455783a6a4522b9a0cce446ba33ab3d5cfe74e22d08f685736abb4329e2506e1c2d83504d963c777317f8998a838fb3fae8c358f65569c5b5
data/CHANGELOG.md CHANGED
@@ -11,6 +11,14 @@ Prefix your message with one of the following:
11
11
  - [Security] in case of vulnerabilities.
12
12
  -->
13
13
 
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
+
14
22
  ## v4.1.0 - Dec 09, 2022
15
23
 
16
24
  - [Added] Parse configuration files as erb.
@@ -18,6 +26,8 @@ Prefix your message with one of the following:
18
26
  exported during `I18n.listen`'s boot. The default value is `true`.
19
27
  - [Added] Now it's possible to transform translations before exporting them
20
28
  using a stable plugin api.
29
+ - [Added] Built-in plugin `I18nJS::EmbedFallbackTranslationsPlugin`, which
30
+ allows embedding missing translations on exported files.
21
31
  - [Deprecated] The `i18n check` has been deprecated. Use
22
32
  `i18n lint:translations` instead.
23
33
  - [Added] Use `i18n lint:scripts` to lint JavaScript/TypeScript.
data/README.md CHANGED
@@ -140,30 +140,96 @@ embed_fallback_translations:
140
140
  enabled: true
141
141
  ```
142
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
+
143
209
  #### Plugin API
144
210
 
145
211
  You can transform the exported translations by adding plugins. A plugin must
146
- inherit from `I18nJS::Plugin` and can have 3 class methods. The following
147
- example shows how the built-in `embed_fallback_translations` plugin is
148
- implemented.
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:
149
217
 
150
218
  ```ruby
151
219
  # frozen_string_literal: true
152
220
 
153
221
  module I18nJS
154
- require "i18n-js/plugin"
155
-
156
- class EmbedFallbackTranslationsPlugin < I18nJS::Plugin
157
- CONFIG_KEY = :embed_fallback_translations
158
-
159
- # This method must set up the basic plugin configuration, like adding the
160
- # config's root key in case your plugin accepts configuration (defined via
161
- # the config file).
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`.
162
227
  #
163
- # If you don't add this key, the linter will prevent non-default keys from
164
- # being added to the configuration file.
165
- def self.setup
166
- I18nJS::Schema.root_keys << CONFIG_KEY
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
167
233
  end
168
234
 
169
235
  # In case your plugin accepts configuration, this is where you must validate
@@ -172,59 +238,28 @@ module I18nJS
172
238
  # exception using something like
173
239
  # `raise I18nJS::Schema::InvalidError, error_message`.
174
240
  def self.validate_schema(config:)
175
- return unless config.key?(CONFIG_KEY)
176
-
177
- plugin_config = config[CONFIG_KEY]
178
- valid_keys = %i[enabled]
179
- schema = I18nJS::Schema.new(config)
241
+ end
180
242
 
181
- schema.expect_required_keys(valid_keys, plugin_config)
182
- schema.reject_extraneous_keys(valid_keys, plugin_config)
183
- schema.expect_enabled_config(CONFIG_KEY, plugin_config[:enabled])
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
184
250
  end
185
251
 
186
- # This method is responsible for transforming the translations. The
187
- # translations you'll receive may be already be filtered by other plugins
188
- # and by the default filtering itself. If you need to access the original
189
- # translations, use `I18nJS.translations`.
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.
190
257
  #
191
258
  # Make sure you always check whether your plugin is active before
192
- # transforming translations; otherwise, opting out transformation won't be
193
- # possible.
194
- def self.transform(translations:, config:)
195
- return translations unless config.dig(CONFIG_KEY, :enabled)
196
-
197
- translations_glob = Glob.new(translations)
198
- translations_glob << "*"
199
-
200
- mapping = translations.keys.each_with_object({}) do |locale, buffer|
201
- buffer[locale] = Glob.new(translations[locale]).tap do |glob|
202
- glob << "*"
203
- end
204
- end
205
-
206
- default_locale = I18n.default_locale
207
- default_locale_glob = mapping.delete(default_locale)
208
- default_locale_paths = default_locale_glob.paths
209
-
210
- mapping.each do |locale, glob|
211
- missing_keys = default_locale_paths - glob.paths
212
-
213
- missing_keys.each do |key|
214
- components = key.split(".").map(&:to_sym)
215
- fallback_translation = translations.dig(default_locale, *components)
216
-
217
- next unless fallback_translation
218
-
219
- translations_glob.set([locale, key].join("."), fallback_translation)
220
- end
221
- end
222
-
223
- translations_glob.to_h
259
+ # processing files; otherwise, opting out won't be possible.
260
+ def self.after_export(files:, config:)
224
261
  end
225
262
  end
226
-
227
- I18nJS.register_plugin(EmbedFallbackTranslationsPlugin)
228
263
  end
229
264
  ```
230
265
 
@@ -242,7 +277,20 @@ To list missing and extraneous translations, you can use
242
277
  how `i18n export` does, but will output the list of keys that don't have a
243
278
  matching translation against the default locale. Here's an example:
244
279
 
245
- ![`i18n lint:translations` 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
+ ```
246
294
 
247
295
  This command will exist with status 1 whenever there are missing translations.
248
296
  This way you can use it as a CI linting.
@@ -300,9 +348,9 @@ you're using dynamic scoping through variables (e.g.
300
348
  `const scope = "message"; i18n.t(scope)`), they will be skipped.
301
349
 
302
350
  ```console
303
- $ i18n lint:scripts --config test/config/lint.yml --require test/config/require.rb
304
- => Config file: "test/config/lint.yml"
305
- => Require file: "test/config/require.rb"
351
+ $ i18n lint:scripts
352
+ => Config file: "./config/i18n.yml"
353
+ => Require file: "./config/environment.rb"
306
354
  => Node: "/Users/fnando/.asdf/shims/node"
307
355
  => Available locales: [:en, :es, :pt]
308
356
  => Patterns: ["!(node_modules)/**/*.js", "!(node_modules)/**/*.ts", "!(node_modules)/**/*.jsx", "!(node_modules)/**/*.tsx"]
@@ -19,7 +19,7 @@ major, minor, patch = *segments.take(3).map(&:to_i)
19
19
  pre = segments[4].to_s
20
20
  pre_version = pre.gsub(/[^\d]/m, "").to_i
21
21
  date = Time.now.strftime("%b %d, %Y")
22
- release = false
22
+ dry_run = false
23
23
  alpha = false
24
24
 
25
25
  OptionParser.new do |opts|
@@ -39,8 +39,8 @@ OptionParser.new do |opts|
39
39
  alpha = true
40
40
  end
41
41
 
42
- opts.on("--release") do
43
- release = true
42
+ opts.on("--dry-run") do
43
+ dry_run = true
44
44
  end
45
45
  end.parse!
46
46
 
@@ -69,11 +69,13 @@ write_file version_path,
69
69
 
70
70
  puts "=> Updated #{version_path}"
71
71
 
72
- if release
72
+ unless dry_run
73
73
  system "git", "add", changelog_path, version_path
74
74
  system "git", "commit", "-m", "Bump up version (v#{version})"
75
- system "git", "tag", "v#{version}"
75
+ system "rake", "release"
76
76
  end
77
77
 
78
- system "rake", "build"
79
- system "git", "checkout", changelog_path, version_path unless release
78
+ if dry_run
79
+ system "rake", "build"
80
+ system "git", "checkout", changelog_path, version_path
81
+ end
@@ -6,21 +6,10 @@ module I18nJS
6
6
  class EmbedFallbackTranslationsPlugin < I18nJS::Plugin
7
7
  CONFIG_KEY = :embed_fallback_translations
8
8
 
9
- # This method must set up the basic plugin configuration, like adding the
10
- # config's root key in case your plugin accepts configuration (defined via
11
- # the config file).
12
- #
13
- # If you don't add this key, the linter will prevent non-default keys from
14
- # being added to the configuration file.
15
9
  def self.setup
16
10
  I18nJS::Schema.root_keys << CONFIG_KEY
17
11
  end
18
12
 
19
- # In case your plugin accepts configuration, this is where you must validate
20
- # the configuration, making sure only valid keys and type is provided.
21
- # If the configuration contains invalid data, then you must raise an
22
- # exception using something like
23
- # `raise I18nJS::Schema::InvalidError, error_message`.
24
13
  def self.validate_schema(config:)
25
14
  return unless config.key?(CONFIG_KEY)
26
15
 
@@ -33,14 +22,6 @@ module I18nJS
33
22
  schema.expect_enabled_config(CONFIG_KEY, plugin_config[:enabled])
34
23
  end
35
24
 
36
- # This method is responsible for transforming the translations. The
37
- # translations you'll receive may be already be filtered by other plugins
38
- # and by the default filtering itself. If you need to access the original
39
- # translations, use `I18nJS.translations`.
40
- #
41
- # Make sure you always check whether your plugin is active before
42
- # transforming translations; otherwise, opting out transformation won't be
43
- # possible.
44
25
  def self.transform(translations:, config:)
45
26
  return translations unless config.dig(CONFIG_KEY, :enabled)
46
27
 
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18nJS
4
+ require "i18n-js/plugin"
5
+
6
+ class ExportFilesPlugin < I18nJS::Plugin
7
+ CONFIG_KEY = :export_files
8
+
9
+ def self.setup
10
+ I18nJS::Schema.root_keys << CONFIG_KEY
11
+ end
12
+
13
+ def self.validate_schema(config:)
14
+ return unless config.key?(CONFIG_KEY)
15
+
16
+ plugin_config = config[CONFIG_KEY]
17
+ valid_keys = %i[enabled files]
18
+ schema = I18nJS::Schema.new(config)
19
+
20
+ schema.expect_required_keys(valid_keys, plugin_config)
21
+ schema.reject_extraneous_keys(valid_keys, plugin_config)
22
+ schema.expect_enabled_config(CONFIG_KEY, plugin_config[:enabled])
23
+ schema.expect_array_with_items(:files, plugin_config[:files])
24
+
25
+ plugin_config[:files].each do |exports|
26
+ schema.expect_required_keys(%i[template output], exports)
27
+ schema.reject_extraneous_keys(%i[template output], exports)
28
+ schema.expect_type(:template, exports[:template], String, exports)
29
+ schema.expect_type(:output, exports[:output], String, exports)
30
+ end
31
+ end
32
+
33
+ def self.after_export(files:, config:)
34
+ return unless config.dig(CONFIG_KEY, :enabled)
35
+
36
+ exports = config.dig(CONFIG_KEY, :files)
37
+
38
+ require "erb"
39
+ require "digest/md5"
40
+ require "json"
41
+
42
+ files.each do |file|
43
+ dir = File.dirname(file)
44
+ name = File.basename(file)
45
+ extension = File.extname(name)
46
+ base_name = File.basename(file, extension)
47
+
48
+ exports.each do |export|
49
+ translations = JSON.load_file(file)
50
+ template = Template.new(
51
+ file: file,
52
+ translations: translations,
53
+ template: export[:template]
54
+ )
55
+
56
+ contents = template.render
57
+
58
+ output = format(
59
+ export[:output],
60
+ dir: dir,
61
+ name: name,
62
+ extension: extension,
63
+ digest: Digest::MD5.hexdigest(contents),
64
+ base_name: base_name
65
+ )
66
+
67
+ File.open(output, "w") do |io|
68
+ io << contents
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ class Template
75
+ attr_accessor :file, :translations, :template
76
+
77
+ def initialize(**kwargs)
78
+ kwargs.each do |key, value|
79
+ public_send("#{key}=", value)
80
+ end
81
+ end
82
+
83
+ def banner(comment: "// ", include_time: true)
84
+ [
85
+ "#{comment}File generated by i18n-js",
86
+ include_time ? " on #{Time.now}" : nil
87
+ ].compact.join
88
+ end
89
+
90
+ def render
91
+ ERB.new(File.read(template)).result(binding)
92
+ end
93
+ end
94
+ end
95
+
96
+ I18nJS.register_plugin(ExportFilesPlugin)
97
+ end
@@ -23,16 +23,44 @@ module I18nJS
23
23
  end
24
24
 
25
25
  class Plugin
26
+ # This method is responsible for transforming the translations. The
27
+ # translations you'll receive may be already be filtered by other plugins
28
+ # and by the default filtering itself. If you need to access the original
29
+ # translations, use `I18nJS.translations`.
30
+ #
31
+ # Make sure you always check whether your plugin is active before
32
+ # transforming translations; otherwise, opting out transformation won't be
33
+ # possible.
26
34
  def self.transform(translations:, config:) # rubocop:disable Lint/UnusedMethodArgument
27
35
  translations
28
36
  end
29
37
 
30
- # Must raise I18nJS::SchemaInvalidError with the error message if schema
31
- # validation has failed.
38
+ # In case your plugin accepts configuration, this is where you must validate
39
+ # the configuration, making sure only valid keys and type is provided.
40
+ # If the configuration contains invalid data, then you must raise an
41
+ # exception using something like
42
+ # `raise I18nJS::Schema::InvalidError, error_message`.
32
43
  def self.validate_schema(config:)
33
44
  end
34
45
 
46
+ # This method must set up the basic plugin configuration, like adding the
47
+ # config's root key in case your plugin accepts configuration (defined via
48
+ # the config file).
49
+ #
50
+ # If you don't add this key, the linter will prevent non-default keys from
51
+ # being added to the configuration file.
35
52
  def self.setup
36
53
  end
54
+
55
+ # This method is called whenever `I18nJS.call(**kwargs)` finishes exporting
56
+ # JSON files based on your configuration.
57
+ #
58
+ # You can use it to further process exported files, or generate new files
59
+ # based on the translations that have been exported.
60
+ #
61
+ # Make sure you always check whether your plugin is active before
62
+ # processing files; otherwise, opting out won't be possible.
63
+ def self.after_export(files:, config:)
64
+ end
37
65
  end
38
66
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module I18nJS
4
- VERSION = "4.1.0"
4
+ VERSION = "4.2.0"
5
5
  end
data/lib/i18n-js.rb CHANGED
@@ -34,6 +34,10 @@ module I18nJS
34
34
  exported_files += export_group(group, config)
35
35
  end
36
36
 
37
+ plugins.each do |plugin|
38
+ plugin.after_export(files: exported_files.dup, config: config)
39
+ end
40
+
37
41
  exported_files
38
42
  end
39
43
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: i18n-js
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.0
4
+ version: 4.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nando Vieira
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-12-09 00:00:00.000000000 Z
11
+ date: 2022-12-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: glob
@@ -190,7 +190,7 @@ files:
190
190
  - MIGRATING_FROM_V3_TO_V4.md
191
191
  - README.md
192
192
  - Rakefile
193
- - bin/pack
193
+ - bin/release
194
194
  - exe/i18n
195
195
  - i18n-js.gemspec
196
196
  - lib/guard/i18n-js.rb
@@ -208,6 +208,7 @@ files:
208
208
  - lib/i18n-js/cli/ui.rb
209
209
  - lib/i18n-js/cli/version_command.rb
210
210
  - lib/i18n-js/embed_fallback_translations_plugin.rb
211
+ - lib/i18n-js/export_files_plugin.rb
211
212
  - lib/i18n-js/lint.js
212
213
  - lib/i18n-js/lint.ts
213
214
  - lib/i18n-js/listen.rb
@@ -222,10 +223,10 @@ metadata:
222
223
  rubygems_mfa_required: 'true'
223
224
  homepage_uri: https://github.com/fnando/i18n-js
224
225
  bug_tracker_uri: https://github.com/fnando/i18n-js/issues
225
- source_code_uri: https://github.com/fnando/i18n-js/tree/v4.1.0
226
- changelog_uri: https://github.com/fnando/i18n-js/tree/v4.1.0/CHANGELOG.md
227
- documentation_uri: https://github.com/fnando/i18n-js/tree/v4.1.0/README.md
228
- license_uri: https://github.com/fnando/i18n-js/tree/v4.1.0/LICENSE.md
226
+ source_code_uri: https://github.com/fnando/i18n-js/tree/v4.2.0
227
+ changelog_uri: https://github.com/fnando/i18n-js/tree/v4.2.0/CHANGELOG.md
228
+ documentation_uri: https://github.com/fnando/i18n-js/tree/v4.2.0/README.md
229
+ license_uri: https://github.com/fnando/i18n-js/tree/v4.2.0/LICENSE.md
229
230
  post_install_message:
230
231
  rdoc_options: []
231
232
  require_paths: