i18n-js 4.0.0.alpha3 → 4.0.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: 7e0fd83a034e8eaa82e97f46beb39a96da4d671952762f7751d093d4823e1aed
4
- data.tar.gz: acff26df94aa46c0c50d5864c97b32bc1108a611a748c2dd7fff4d626ec41cf4
3
+ metadata.gz: 0b2fdace9fca14d471ff34c3b24055d4093d82ce2a1f72f2df06364a98454a31
4
+ data.tar.gz: 9ea6bd71626357c8fce24c28e991b9edc96f8a3230e55c4d55cf1dec3813c94e
5
5
  SHA512:
6
- metadata.gz: d72a28ddbe75742e605c86d8e3b06ce2e1ada21bc95f2b189e2e120b671c7c0d4d239dfc7c3753abd4b97f57820146001cfdfb89e2c407ae3054ea93fba98d72
7
- data.tar.gz: 5ed38c2ea91915fadcc6cc7a4b718d1473cc41afff0c5cd3f6dd6e1345e2eed29c53bace6403239dd516a180485b055414cd65c00291747d20c6055c6b2f6a2a
6
+ metadata.gz: 2c8da0348d02f04d805a013dadd24beb08fb7de555bfdded51e4e6c7c29ba392978a723b7e95be9d752dda2b35bbe6d6aefe4d4cf9166c1ddca09f003712533f
7
+ data.tar.gz: 882af1975b1f2e479ad4d8bdcbe812175fae165c80a2f915223e14fc71fb0b3236774254e103754169eab5a3d577e219e10803fd05e14fa3bac82471e4c770e1
@@ -4,8 +4,6 @@ name: ruby-tests
4
4
  on:
5
5
  pull_request_target:
6
6
  push:
7
- branches:
8
- - v4
9
7
  workflow_dispatch:
10
8
  inputs: {}
11
9
 
data/CHANGELOG.md CHANGED
@@ -11,6 +11,6 @@ Prefix your message with one of the following:
11
11
  - [Security] in case of vulnerabilities.
12
12
  -->
13
13
 
14
- ## Unreleased
14
+ ## Jul 29, 2022
15
15
 
16
- - Initial release.
16
+ - Official release of i18n-js v4.0.0.
data/README.md CHANGED
@@ -19,13 +19,11 @@
19
19
 
20
20
  <p align="center">
21
21
  <a href="https://github.com/fnando/i18n-js"><img src="https://github.com/fnando/i18n-js/workflows/ruby-tests/badge.svg" alt="Tests"></a>
22
- <a href="https://rubygems.org/gems/i18n-js"><img src="https://img.shields.io/gem/v/i18n-js.svg" alt="Gem"></a>
22
+ <a href="https://rubygems.org/gems/i18n-js"><img src="https://img.shields.io/gem/v/i18n-js.svg?include_prereleases" alt="Gem"></a>
23
23
  <a href="https://rubygems.org/gems/i18n-js"><img src="https://img.shields.io/gem/dt/i18n-js.svg" alt="Gem"></a>
24
24
  <a href="https://tldrlegal.com/license/mit-license"><img src="https://img.shields.io/:License-MIT-blue.svg" alt="MIT License"></a>
25
25
  </p>
26
26
 
27
- ![This branch contains the code for v4, our next major release.](https://messages-svg.herokuapp.com/warning.svg?message=This%20branch%20contains%20the%20code%20for%20v4%2C%20our%20next%20major%20release.)
28
-
29
27
  ## Installation
30
28
 
31
29
  ```bash
@@ -35,7 +33,7 @@ gem install i18n-js
35
33
  Or add the following line to your project's Gemfile:
36
34
 
37
35
  ```ruby
38
- gem "i18n-js", "~> 4.0.0.alpha1"
36
+ gem "i18n-js"
39
37
  ```
40
38
 
41
39
  ## Usage
@@ -82,14 +80,61 @@ I18nJS.call(config: config)
82
80
  The CLI API:
83
81
 
84
82
  ```console
85
- $ i18n init --config config/i18n.yml
86
- $ i18n export --config config/i18n.yml --require config/environment.rb
83
+ $ i18n --help
84
+ Usage: i18n COMMAND FLAGS
85
+
86
+ Commands:
87
+
88
+ - init: Initialize a project
89
+ - export: Export translations as JSON files
90
+ - version: Show package version
91
+ - check: Check for missing translations
92
+
93
+ Run `i18n COMMAND --help` for more information on specific commands.
87
94
  ```
88
95
 
89
96
  By default, `i18n` will use `config/i18n.yml` and `config/environment.rb` as the
90
97
  configuration files. If you don't have these files, then you'll need to specify
91
98
  both `--config` and `--require`.
92
99
 
100
+ ### Listing missing translations
101
+
102
+ To list missing and extraneous translations, you can use `i18n check`. This
103
+ command will load your translations similarly to how `i18n export` does, but
104
+ will output the list of keys that don't have a matching translation against the
105
+ default locale. Here's an example:
106
+
107
+ ![`i18n check` command in action](https://github.com/fnando/i18n-js/raw/main/images/i18njs-check.gif)
108
+
109
+ This command will exist with status 1 whenever there are missing translations.
110
+ This way you can use it as a CI linting.
111
+
112
+ You can ignore keys by adding a list to the config file:
113
+
114
+ ```yml
115
+ ---
116
+ translations:
117
+ - file: app/frontend/locales/en.json
118
+ patterns:
119
+ - "*"
120
+ - "!*.activerecord"
121
+ - "!*.errors"
122
+ - "!*.number.nth"
123
+
124
+ - file: app/frontend/locales/:locale.:digest.json
125
+ patterns:
126
+ - "*"
127
+
128
+ check:
129
+ ignore:
130
+ - en.mailer.login.subject
131
+ - en.mailer.login.body
132
+ ```
133
+
134
+ > **Note**: In order to avoid mistakenly ignoring keys, this configuration
135
+ > option only accepts the full translation scope, rather than accepting a
136
+ > pattern like `pt.ignored.scope.*`.
137
+
93
138
  ## Automatically export translations
94
139
 
95
140
  ### Using watchman
Binary file
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "benchmark"
4
+
5
+ module I18nJS
6
+ class CLI
7
+ class CheckCommand < Command
8
+ command_name "check"
9
+ description "Check for missing translations"
10
+
11
+ parse do |opts|
12
+ opts.banner = "Usage: i18n #{name} [options]"
13
+
14
+ opts.on(
15
+ "-cCONFIG_FILE",
16
+ "--config=CONFIG_FILE",
17
+ "The configuration file that will be used"
18
+ ) do |config_file|
19
+ options[:config_file] = config_file
20
+ end
21
+
22
+ opts.on(
23
+ "-rREQUIRE_FILE",
24
+ "--require=REQUIRE_FILE",
25
+ "A Ruby file that must be loaded"
26
+ ) do |require_file|
27
+ options[:require_file] = require_file
28
+ end
29
+
30
+ opts.on(
31
+ "--[no-]color",
32
+ "Force colored output"
33
+ ) do |colored|
34
+ options[:colored] = colored
35
+ end
36
+
37
+ opts.on("-h", "--help", "Prints this help") do
38
+ ui.exit_with opts.to_s
39
+ end
40
+ end
41
+
42
+ command do
43
+ set_defaults!
44
+ ui.colored = options[:colored]
45
+
46
+ unless options[:config_file]
47
+ ui.fail_with("=> ERROR: you need to specify the config file")
48
+ end
49
+
50
+ ui.stdout_print("=> Config file:", options[:config_file].inspect)
51
+ config_file = File.expand_path(options[:config_file])
52
+
53
+ if options[:require_file]
54
+ ui.stdout_print("=> Require file:", options[:require_file].inspect)
55
+ require_file = File.expand_path(options[:require_file])
56
+ end
57
+
58
+ unless File.file?(config_file)
59
+ ui.fail_with(
60
+ "=> ERROR: config file doesn't exist at",
61
+ config_file.inspect
62
+ )
63
+ end
64
+
65
+ if require_file && !File.file?(require_file)
66
+ ui.fail_with(
67
+ "=> ERROR: require file doesn't exist at",
68
+ require_file.inspect
69
+ )
70
+ end
71
+
72
+ config = Glob::SymbolizeKeys.call(YAML.load_file(config_file))
73
+ Schema.validate!(config)
74
+
75
+ load_require_file!(require_file) if require_file
76
+
77
+ default_locale = I18n.default_locale
78
+ available_locales = I18n.available_locales
79
+ ignored_keys = config.dig(:check, :ignore) || []
80
+
81
+ mapping = available_locales.each_with_object({}) do |locale, buffer|
82
+ buffer[locale] =
83
+ Glob::Map.call(Glob.filter(I18nJS.translations, ["#{locale}.*"]))
84
+ .map {|key| key.gsub(/^.*?\./, "") }
85
+ end
86
+
87
+ default_locale_keys = mapping.delete(default_locale)
88
+
89
+ if ignored_keys.any?
90
+ ui.stdout_print "=> Check #{options[:config_file].inspect} for " \
91
+ "ignored keys."
92
+ end
93
+
94
+ ui.stdout_print "=> #{default_locale}: #{default_locale_keys.size} " \
95
+ "translations"
96
+
97
+ total_missing_count = 0
98
+
99
+ mapping.each do |locale, partial_keys|
100
+ ignored_count = 0
101
+
102
+ # Compute list of filtered keys (i.e. keys not ignored)
103
+ filtered_keys = partial_keys.reject do |key|
104
+ key = "#{locale}.#{key}"
105
+
106
+ ignored = ignored_keys.include?(key)
107
+ ignored_count += 1 if ignored
108
+ ignored
109
+ end
110
+
111
+ extraneous = (partial_keys - default_locale_keys).reject do |key|
112
+ key = "#{locale}.#{key}"
113
+ ignored = ignored_keys.include?(key)
114
+ ignored_count += 1 if ignored
115
+ ignored
116
+ end
117
+
118
+ missing = (default_locale_keys - (filtered_keys - extraneous))
119
+ .reject {|key| ignored_keys.include?("#{locale}.#{key}") }
120
+
121
+ ignored_count += extraneous.size
122
+ total_missing_count += missing.size
123
+
124
+ ui.stdout_print "=> #{locale}: #{missing.size} missing, " \
125
+ "#{extraneous.size} extraneous, " \
126
+ "#{ignored_count} ignored"
127
+
128
+ all_keys = (default_locale_keys + extraneous + missing).uniq.sort
129
+
130
+ all_keys.each do |key|
131
+ next if ignored_keys.include?("#{locale}.#{key}")
132
+
133
+ label = if extraneous.include?(key)
134
+ ui.yellow("extraneous")
135
+ elsif missing.include?(key)
136
+ ui.red("missing")
137
+ else
138
+ next
139
+ end
140
+
141
+ ui.stdout_print(" - #{locale}.#{key} (#{label})")
142
+ end
143
+ end
144
+
145
+ exit(1) if total_missing_count.nonzero?
146
+ end
147
+
148
+ private def set_defaults!
149
+ config_file = "./config/i18n.yml"
150
+ require_file = "./config/environment.rb"
151
+
152
+ options[:config_file] ||= config_file if File.file?(config_file)
153
+ options[:require_file] ||= require_file if File.file?(require_file)
154
+ end
155
+ end
156
+ end
157
+ end
@@ -38,6 +38,32 @@ module I18nJS
38
38
  def options
39
39
  @options ||= {}
40
40
  end
41
+
42
+ private def load_require_file!(require_file)
43
+ require_without_warnings(require_file)
44
+ rescue Exception => error # rubocop:disable Lint/RescueException
45
+ ui.stderr_print("=> ERROR: couldn't load",
46
+ options[:require_file].inspect)
47
+ ui.fail_with(
48
+ "\n#{error_description(error)}\n#{error.backtrace.join("\n")}"
49
+ )
50
+ end
51
+
52
+ private def error_description(error)
53
+ [
54
+ error.class.name,
55
+ error.message
56
+ ].reject(&:empty?).join(" => ")
57
+ end
58
+
59
+ private def require_without_warnings(path)
60
+ old_verbose = $VERBOSE
61
+ $VERBOSE = nil
62
+
63
+ load path
64
+ ensure
65
+ $VERBOSE = old_verbose
66
+ end
41
67
  end
42
68
  end
43
69
  end
@@ -14,7 +14,7 @@ module I18nJS
14
14
  opts.on(
15
15
  "-cCONFIG_FILE",
16
16
  "--config=CONFIG_FILE",
17
- "The configuration file that will be generated"
17
+ "The configuration file that will be used"
18
18
  ) do |config_file|
19
19
  options[:config_file] = config_file
20
20
  end
@@ -35,16 +35,15 @@ module I18nJS
35
35
  command do
36
36
  set_defaults!
37
37
 
38
- ui.stdout_print("=> config file:", options[:config_file].inspect)
39
- ui.stdout_print("=> require file:", options[:require_file].inspect)
40
-
41
38
  unless options[:config_file]
42
39
  ui.fail_with("=> ERROR: you need to specify the config file")
43
40
  end
44
41
 
42
+ ui.stdout_print("=> Config file:", options[:config_file].inspect)
45
43
  config_file = File.expand_path(options[:config_file])
46
44
 
47
45
  if options[:require_file]
46
+ ui.stdout_print("=> Require file:", options[:require_file].inspect)
48
47
  require_file = File.expand_path(options[:require_file])
49
48
  end
50
49
 
@@ -67,7 +66,7 @@ module I18nJS
67
66
  I18nJS.call(config_file: config_file)
68
67
  end
69
68
 
70
- ui.stdout_print("=> done in #{time.round(2)}s")
69
+ ui.stdout_print("=> Done in #{time.round(2)}s")
71
70
  end
72
71
 
73
72
  private def set_defaults!
@@ -77,32 +76,6 @@ module I18nJS
77
76
  options[:config_file] ||= config_file if File.file?(config_file)
78
77
  options[:require_file] ||= require_file if File.file?(require_file)
79
78
  end
80
-
81
- private def load_require_file!(require_file)
82
- require_without_warnings(require_file)
83
- rescue Exception => error # rubocop:disable Lint/RescueException
84
- ui.stderr_print("=> ERROR: couldn't load",
85
- options[:require_file].inspect)
86
- ui.fail_with(
87
- "\n#{error_description(error)}\n#{error.backtrace.join("\n")}"
88
- )
89
- end
90
-
91
- private def error_description(error)
92
- [
93
- error.class.name,
94
- error.message
95
- ].reject(&:empty?).join(" => ")
96
- end
97
-
98
- private def require_without_warnings(path)
99
- old_verbose = $VERBOSE
100
- $VERBOSE = nil
101
-
102
- require path
103
- ensure
104
- $VERBOSE = old_verbose
105
- end
106
79
  end
107
80
  end
108
81
  end
@@ -3,17 +3,21 @@
3
3
  module I18nJS
4
4
  class CLI
5
5
  class UI
6
- def initialize(stdout:, stderr:)
6
+ attr_reader :stdout, :stderr
7
+ attr_accessor :colored
8
+
9
+ def initialize(stdout:, stderr:, colored: nil)
7
10
  @stdout = stdout
8
11
  @stderr = stderr
12
+ @colored = colored
9
13
  end
10
14
 
11
15
  def stdout_print(*message)
12
- @stdout << "#{message.join(' ')}\n"
16
+ stdout << "#{message.join(' ')}\n"
13
17
  end
14
18
 
15
19
  def stderr_print(*message)
16
- @stderr << "#{message.join(' ')}\n"
20
+ stderr << "#{message.join(' ')}\n"
17
21
  end
18
22
 
19
23
  def fail_with(*message)
@@ -25,6 +29,36 @@ module I18nJS
25
29
  stdout_print(message)
26
30
  exit(0)
27
31
  end
32
+
33
+ def yellow(text)
34
+ ansi(text, 33)
35
+ end
36
+
37
+ def red(text)
38
+ ansi(text, 31)
39
+ end
40
+
41
+ def colored?
42
+ colored_output = if colored.nil?
43
+ stdout.tty?
44
+ else
45
+ colored
46
+ end
47
+
48
+ colored_output && !no_color?
49
+ end
50
+
51
+ def ansi(text, code)
52
+ if colored?
53
+ "\e[#{code}m#{text}\e[0m"
54
+ else
55
+ text
56
+ end
57
+ end
58
+
59
+ def no_color?
60
+ !ENV["NO_COLOR"].nil? && ENV["NO_COLOR"] == "1"
61
+ end
28
62
  end
29
63
  end
30
64
  end
data/lib/i18n-js/cli.rb CHANGED
@@ -6,14 +6,15 @@ require_relative "cli/ui"
6
6
  require_relative "cli/init_command"
7
7
  require_relative "cli/version_command"
8
8
  require_relative "cli/export_command"
9
+ require_relative "cli/check_command"
9
10
 
10
11
  module I18nJS
11
12
  class CLI
12
13
  attr_reader :ui
13
14
 
14
- def initialize(argv:, stdout:, stderr:)
15
+ def initialize(argv:, stdout:, stderr:, colored: stdout.tty?)
15
16
  @argv = argv.dup
16
- @ui = UI.new(stdout: stdout, stderr: stderr)
17
+ @ui = UI.new(stdout: stdout, stderr: stderr, colored: colored)
17
18
  end
18
19
 
19
20
  def call
@@ -26,7 +27,7 @@ module I18nJS
26
27
  end
27
28
 
28
29
  private def command_classes
29
- [InitCommand, ExportCommand, VersionCommand]
30
+ [InitCommand, ExportCommand, VersionCommand, CheckCommand]
30
31
  end
31
32
 
32
33
  private def commands
@@ -4,8 +4,9 @@ module I18nJS
4
4
  class Schema
5
5
  InvalidError = Class.new(StandardError)
6
6
 
7
- ROOT_KEYS = %i[translations].freeze
7
+ ROOT_KEYS = %i[translations check].freeze
8
8
  REQUIRED_ROOT_KEYS = %i[translations].freeze
9
+ REQUIRED_CHECK_KEYS = %i[ignore].freeze
9
10
  REQUIRED_TRANSLATION_KEYS = %i[file patterns].freeze
10
11
  TRANSLATION_KEYS = %i[file patterns].freeze
11
12
 
@@ -25,6 +26,17 @@ module I18nJS
25
26
  expect_required_keys(REQUIRED_ROOT_KEYS, target)
26
27
  reject_extraneous_keys(ROOT_KEYS, target)
27
28
  validate_translations
29
+ validate_check
30
+ end
31
+
32
+ def validate_check
33
+ return unless target.key?(:check)
34
+
35
+ check = target[:check]
36
+
37
+ expect_type(:check, check, Hash, target)
38
+ expect_required_keys(REQUIRED_CHECK_KEYS, check)
39
+ expect_type(:ignore, check[:ignore], Array, check)
28
40
  end
29
41
 
30
42
  def validate_translations
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module I18nJS
4
- VERSION = "4.0.0.alpha3"
4
+ VERSION = "4.0.0"
5
5
  end
data/lib/i18n-js.rb CHANGED
@@ -21,24 +21,31 @@ module I18nJS
21
21
 
22
22
  config = Glob::SymbolizeKeys.call(config || YAML.load_file(config_file))
23
23
  Schema.validate!(config)
24
+ exported_files = []
24
25
 
25
26
  config[:translations].each do |group|
26
- export_group(group)
27
+ exported_files += export_group(group)
27
28
  end
29
+
30
+ exported_files
28
31
  end
29
32
 
30
33
  def self.export_group(group)
31
34
  filtered_translations = Glob.filter(translations, group[:patterns])
32
35
  output_file_path = File.expand_path(group[:file])
36
+ exported_files = []
33
37
 
34
38
  if output_file_path.include?(":locale")
35
39
  filtered_translations.each_key do |locale|
36
40
  locale_file_path = output_file_path.gsub(/:locale/, locale.to_s)
37
- write_file(locale_file_path, locale => filtered_translations[locale])
41
+ exported_files << write_file(locale_file_path,
42
+ locale => filtered_translations[locale])
38
43
  end
39
44
  else
40
- write_file(output_file_path, filtered_translations)
45
+ exported_files << write_file(output_file_path, filtered_translations)
41
46
  end
47
+
48
+ exported_files
42
49
  end
43
50
 
44
51
  def self.write_file(file_path, translations)
@@ -51,6 +58,8 @@ module I18nJS
51
58
  File.open(file_path, "w") do |file|
52
59
  file << contents
53
60
  end
61
+
62
+ file_path
54
63
  end
55
64
 
56
65
  def self.translations
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.0.0.alpha3
4
+ version: 4.0.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-07-20 00:00:00.000000000 Z
11
+ date: 2022-07-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: glob
@@ -178,11 +178,13 @@ files:
178
178
  - exe/i18n
179
179
  - i18n-js.gemspec
180
180
  - i18njs.png
181
+ - images/i18njs-check.gif
181
182
  - lib/guard/i18n-js.rb
182
183
  - lib/guard/i18n-js/templates/Guardfile
183
184
  - lib/guard/i18n-js/version.rb
184
185
  - lib/i18n-js.rb
185
186
  - lib/i18n-js/cli.rb
187
+ - lib/i18n-js/cli/check_command.rb
186
188
  - lib/i18n-js/cli/command.rb
187
189
  - lib/i18n-js/cli/export_command.rb
188
190
  - lib/i18n-js/cli/init_command.rb
@@ -198,10 +200,10 @@ metadata:
198
200
  rubygems_mfa_required: 'true'
199
201
  homepage_uri: https://github.com/fnando/i18n-js
200
202
  bug_tracker_uri: https://github.com/fnando/i18n-js/issues
201
- source_code_uri: https://github.com/fnando/i18n-js/tree/v4.0.0.alpha3
202
- changelog_uri: https://github.com/fnando/i18n-js/tree/v4.0.0.alpha3/CHANGELOG.md
203
- documentation_uri: https://github.com/fnando/i18n-js/tree/v4.0.0.alpha3/README.md
204
- license_uri: https://github.com/fnando/i18n-js/tree/v4.0.0.alpha3/LICENSE.md
203
+ source_code_uri: https://github.com/fnando/i18n-js/tree/v4.0.0
204
+ changelog_uri: https://github.com/fnando/i18n-js/tree/v4.0.0/CHANGELOG.md
205
+ documentation_uri: https://github.com/fnando/i18n-js/tree/v4.0.0/README.md
206
+ license_uri: https://github.com/fnando/i18n-js/tree/v4.0.0/LICENSE.md
205
207
  post_install_message:
206
208
  rdoc_options: []
207
209
  require_paths:
@@ -213,11 +215,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
213
215
  version: 2.6.0
214
216
  required_rubygems_version: !ruby/object:Gem::Requirement
215
217
  requirements:
216
- - - ">"
218
+ - - ">="
217
219
  - !ruby/object:Gem::Version
218
- version: 1.3.1
220
+ version: '0'
219
221
  requirements: []
220
- rubygems_version: 3.3.17
222
+ rubygems_version: 3.3.7
221
223
  signing_key:
222
224
  specification_version: 4
223
225
  summary: Export i18n translations and use them on JavaScript.