i18n-js 4.1.0 → 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: 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: