i18n-js 4.0.1 → 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 +4 -4
- data/.github/workflows/ruby-tests.yml +22 -1
- data/.gitignore +2 -0
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +24 -2
- data/MIGRATING_FROM_V3_TO_V4.md +1 -1
- data/README.md +267 -9
- data/bin/release +81 -0
- data/i18n-js.gemspec +4 -1
- data/lib/i18n-js/cli/check_command.rb +7 -147
- data/lib/i18n-js/cli/command.rb +10 -0
- data/lib/i18n-js/cli/lint_scripts_command.rb +157 -0
- data/lib/i18n-js/cli/lint_translations_command.rb +155 -0
- data/lib/i18n-js/cli/plugins_command.rb +67 -0
- data/lib/i18n-js/cli.rb +12 -1
- data/lib/i18n-js/embed_fallback_translations_plugin.rb +59 -0
- data/lib/i18n-js/export_files_plugin.rb +97 -0
- data/lib/i18n-js/lint.js +150645 -0
- data/lib/i18n-js/lint.ts +196 -0
- data/lib/i18n-js/listen.rb +3 -2
- data/lib/i18n-js/plugin.rb +66 -0
- data/lib/i18n-js/schema.rb +53 -14
- data/lib/i18n-js/version.rb +1 -1
- data/lib/i18n-js.rb +24 -3
- data/package.json +10 -0
- metadata +33 -9
@@ -1,156 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "benchmark"
|
4
|
-
|
5
3
|
module I18nJS
|
6
4
|
class CLI
|
7
|
-
class CheckCommand <
|
5
|
+
class CheckCommand < LintTranslationsCommand
|
8
6
|
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"
|
7
|
+
description "Check for missing translations based on the default " \
|
8
|
+
"locale (DEPRECATED: Use `i18n lint:translations` instead)"
|
151
9
|
|
152
|
-
|
153
|
-
|
10
|
+
def command
|
11
|
+
ui.stderr_print "=> WARNING: `i18n check` has been deprecated in " \
|
12
|
+
"favor of `i18n lint:translations`"
|
13
|
+
super
|
154
14
|
end
|
155
15
|
end
|
156
16
|
end
|
data/lib/i18n-js/cli/command.rb
CHANGED
@@ -39,6 +39,16 @@ module I18nJS
|
|
39
39
|
@options ||= {}
|
40
40
|
end
|
41
41
|
|
42
|
+
private def load_config_file(config_file)
|
43
|
+
config = Glob::SymbolizeKeys.call(YAML.load_file(config_file))
|
44
|
+
|
45
|
+
if config.key?(:check)
|
46
|
+
config[:lint_translations] ||= config.delete(:check)
|
47
|
+
end
|
48
|
+
|
49
|
+
config
|
50
|
+
end
|
51
|
+
|
42
52
|
private def load_require_file!(require_file)
|
43
53
|
require_without_warnings(require_file)
|
44
54
|
rescue Exception => error # rubocop:disable Lint/RescueException
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module I18nJS
|
4
|
+
class CLI
|
5
|
+
class LintScriptsCommand < Command
|
6
|
+
command_name "lint:scripts"
|
7
|
+
description "Lint files using TypeScript"
|
8
|
+
|
9
|
+
parse do |opts|
|
10
|
+
opts.banner = "Usage: i18n #{name} [options]"
|
11
|
+
|
12
|
+
opts.on(
|
13
|
+
"-cCONFIG_FILE",
|
14
|
+
"--config=CONFIG_FILE",
|
15
|
+
"The configuration file that will be used"
|
16
|
+
) do |config_file|
|
17
|
+
options[:config_file] = config_file
|
18
|
+
end
|
19
|
+
|
20
|
+
opts.on(
|
21
|
+
"-rREQUIRE_FILE",
|
22
|
+
"--require=REQUIRE_FILE",
|
23
|
+
"A Ruby file that must be loaded"
|
24
|
+
) do |require_file|
|
25
|
+
options[:require_file] = require_file
|
26
|
+
end
|
27
|
+
|
28
|
+
opts.on(
|
29
|
+
"-nNODE_PATH",
|
30
|
+
"--node-path=NODE_PATH",
|
31
|
+
"Set node.js path"
|
32
|
+
) do |node_path|
|
33
|
+
options[:node_path] = node_path
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on("-h", "--help", "Prints this help") do
|
37
|
+
ui.exit_with opts.to_s
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
command do
|
42
|
+
set_defaults!
|
43
|
+
ui.colored = options[:colored]
|
44
|
+
|
45
|
+
unless options[:config_file]
|
46
|
+
ui.fail_with("=> ERROR: you need to specify the config file")
|
47
|
+
end
|
48
|
+
|
49
|
+
ui.stdout_print("=> Config file:", options[:config_file].inspect)
|
50
|
+
config_file = File.expand_path(options[:config_file])
|
51
|
+
|
52
|
+
if options[:require_file]
|
53
|
+
ui.stdout_print("=> Require file:", options[:require_file].inspect)
|
54
|
+
require_file = File.expand_path(options[:require_file])
|
55
|
+
end
|
56
|
+
|
57
|
+
node_path = options[:node_path] || find_node
|
58
|
+
ui.stdout_print("=> Node:", node_path.inspect)
|
59
|
+
|
60
|
+
unless File.file?(config_file)
|
61
|
+
ui.fail_with(
|
62
|
+
"=> ERROR: config file doesn't exist at",
|
63
|
+
config_file.inspect
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
if require_file && !File.file?(require_file)
|
68
|
+
ui.fail_with(
|
69
|
+
"=> ERROR: require file doesn't exist at",
|
70
|
+
require_file.inspect
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
found_node = node_path && File.executable?(File.expand_path(node_path))
|
75
|
+
|
76
|
+
unless found_node
|
77
|
+
ui.fail_with(
|
78
|
+
"=> ERROR: node.js couldn't be found (path: #{node_path})"
|
79
|
+
)
|
80
|
+
end
|
81
|
+
|
82
|
+
config = load_config_file(config_file)
|
83
|
+
Schema.validate!(config)
|
84
|
+
|
85
|
+
load_require_file!(require_file) if require_file
|
86
|
+
|
87
|
+
available_locales = I18n.available_locales
|
88
|
+
ignored_keys = config.dig(:lint_scripts, :ignore) || []
|
89
|
+
|
90
|
+
ui.stdout_print "=> Available locales: #{available_locales.inspect}"
|
91
|
+
|
92
|
+
exported_files = I18nJS.call(config_file: config_file)
|
93
|
+
data = exported_files.each_with_object({}) do |file, buffer|
|
94
|
+
buffer.merge!(JSON.load_file(file, symbolize_names: true))
|
95
|
+
end
|
96
|
+
|
97
|
+
lint_file = File.expand_path(File.join(__dir__, "../lint.js"))
|
98
|
+
patterns = config.dig(:lint_scripts, :patterns) || %w[
|
99
|
+
!(node_modules)/**/*.js
|
100
|
+
!(node_modules)/**/*.ts
|
101
|
+
!(node_modules)/**/*.jsx
|
102
|
+
!(node_modules)/**/*.tsx
|
103
|
+
]
|
104
|
+
|
105
|
+
ui.stdout_print "=> Patterns: #{patterns.inspect}"
|
106
|
+
|
107
|
+
out = IO.popen([node_path, lint_file, patterns.join(":")]).read
|
108
|
+
scopes = JSON.parse(out, symbolize_names: true)
|
109
|
+
map = Glob::Map.call(data)
|
110
|
+
missing_count = 0
|
111
|
+
ignored_count = 0
|
112
|
+
|
113
|
+
messages = []
|
114
|
+
|
115
|
+
available_locales.each do |locale|
|
116
|
+
scopes.each do |scope|
|
117
|
+
scope_with_locale = "#{locale}.#{scope[:full]}"
|
118
|
+
|
119
|
+
ignored = ignored_keys.include?(scope[:full]) ||
|
120
|
+
ignored_keys.include?(scope_with_locale)
|
121
|
+
|
122
|
+
if ignored
|
123
|
+
ignored_count += 1
|
124
|
+
next
|
125
|
+
end
|
126
|
+
|
127
|
+
next if map.include?(scope_with_locale)
|
128
|
+
|
129
|
+
missing_count += 1
|
130
|
+
messages << " - #{scope[:location]}: #{scope_with_locale}"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
ui.stdout_print "=> #{map.size} translations, #{missing_count} " \
|
135
|
+
"missing, #{ignored_count} ignored"
|
136
|
+
ui.stdout_print messages.sort.join("\n")
|
137
|
+
|
138
|
+
exit(missing_count.size)
|
139
|
+
end
|
140
|
+
|
141
|
+
private def set_defaults!
|
142
|
+
config_file = "./config/i18n.yml"
|
143
|
+
require_file = "./config/environment.rb"
|
144
|
+
|
145
|
+
options[:config_file] ||= config_file if File.file?(config_file)
|
146
|
+
options[:require_file] ||= require_file if File.file?(require_file)
|
147
|
+
end
|
148
|
+
|
149
|
+
private def find_node
|
150
|
+
ENV["PATH"]
|
151
|
+
.split(File::PATH_SEPARATOR)
|
152
|
+
.map {|dir| File.join(dir, "node") }
|
153
|
+
.find {|bin| File.executable?(bin) }
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module I18nJS
|
4
|
+
class CLI
|
5
|
+
class LintTranslationsCommand < Command
|
6
|
+
command_name "lint:translations"
|
7
|
+
description "Check for missing translations based on the default locale"
|
8
|
+
|
9
|
+
parse do |opts|
|
10
|
+
opts.banner = "Usage: i18n #{name} [options]"
|
11
|
+
|
12
|
+
opts.on(
|
13
|
+
"-cCONFIG_FILE",
|
14
|
+
"--config=CONFIG_FILE",
|
15
|
+
"The configuration file that will be used"
|
16
|
+
) do |config_file|
|
17
|
+
options[:config_file] = config_file
|
18
|
+
end
|
19
|
+
|
20
|
+
opts.on(
|
21
|
+
"-rREQUIRE_FILE",
|
22
|
+
"--require=REQUIRE_FILE",
|
23
|
+
"A Ruby file that must be loaded"
|
24
|
+
) do |require_file|
|
25
|
+
options[:require_file] = require_file
|
26
|
+
end
|
27
|
+
|
28
|
+
opts.on(
|
29
|
+
"--[no-]color",
|
30
|
+
"Force colored output"
|
31
|
+
) do |colored|
|
32
|
+
options[:colored] = colored
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on("-h", "--help", "Prints this help") do
|
36
|
+
ui.exit_with opts.to_s
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
command do
|
41
|
+
set_defaults!
|
42
|
+
ui.colored = options[:colored]
|
43
|
+
|
44
|
+
unless options[:config_file]
|
45
|
+
ui.fail_with("=> ERROR: you need to specify the config file")
|
46
|
+
end
|
47
|
+
|
48
|
+
ui.stdout_print("=> Config file:", options[:config_file].inspect)
|
49
|
+
config_file = File.expand_path(options[:config_file])
|
50
|
+
|
51
|
+
if options[:require_file]
|
52
|
+
ui.stdout_print("=> Require file:", options[:require_file].inspect)
|
53
|
+
require_file = File.expand_path(options[:require_file])
|
54
|
+
end
|
55
|
+
|
56
|
+
unless File.file?(config_file)
|
57
|
+
ui.fail_with(
|
58
|
+
"=> ERROR: config file doesn't exist at",
|
59
|
+
config_file.inspect
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
if require_file && !File.file?(require_file)
|
64
|
+
ui.fail_with(
|
65
|
+
"=> ERROR: require file doesn't exist at",
|
66
|
+
require_file.inspect
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
config = load_config_file(config_file)
|
71
|
+
Schema.validate!(config)
|
72
|
+
|
73
|
+
load_require_file!(require_file) if require_file
|
74
|
+
|
75
|
+
default_locale = I18n.default_locale
|
76
|
+
available_locales = I18n.available_locales
|
77
|
+
ignored_keys = config.dig(:lint_translations, :ignore) || []
|
78
|
+
|
79
|
+
mapping = available_locales.each_with_object({}) do |locale, buffer|
|
80
|
+
buffer[locale] =
|
81
|
+
Glob::Map.call(Glob.filter(I18nJS.translations, ["#{locale}.*"]))
|
82
|
+
.map {|key| key.gsub(/^.*?\./, "") }
|
83
|
+
end
|
84
|
+
|
85
|
+
default_locale_keys = mapping.delete(default_locale)
|
86
|
+
|
87
|
+
if ignored_keys.any?
|
88
|
+
ui.stdout_print "=> Check #{options[:config_file].inspect} for " \
|
89
|
+
"ignored keys."
|
90
|
+
end
|
91
|
+
|
92
|
+
ui.stdout_print "=> #{default_locale}: #{default_locale_keys.size} " \
|
93
|
+
"translations"
|
94
|
+
|
95
|
+
total_missing_count = 0
|
96
|
+
|
97
|
+
mapping.each do |locale, partial_keys|
|
98
|
+
ignored_count = 0
|
99
|
+
|
100
|
+
# Compute list of filtered keys (i.e. keys not ignored)
|
101
|
+
filtered_keys = partial_keys.reject do |key|
|
102
|
+
key = "#{locale}.#{key}"
|
103
|
+
|
104
|
+
ignored = ignored_keys.include?(key)
|
105
|
+
ignored_count += 1 if ignored
|
106
|
+
ignored
|
107
|
+
end
|
108
|
+
|
109
|
+
extraneous = (partial_keys - default_locale_keys).reject do |key|
|
110
|
+
key = "#{locale}.#{key}"
|
111
|
+
ignored = ignored_keys.include?(key)
|
112
|
+
ignored_count += 1 if ignored
|
113
|
+
ignored
|
114
|
+
end
|
115
|
+
|
116
|
+
missing = (default_locale_keys - (filtered_keys - extraneous))
|
117
|
+
.reject {|key| ignored_keys.include?("#{locale}.#{key}") }
|
118
|
+
|
119
|
+
ignored_count += extraneous.size
|
120
|
+
total_missing_count += missing.size
|
121
|
+
|
122
|
+
ui.stdout_print "=> #{locale}: #{missing.size} missing, " \
|
123
|
+
"#{extraneous.size} extraneous, " \
|
124
|
+
"#{ignored_count} ignored"
|
125
|
+
|
126
|
+
all_keys = (default_locale_keys + extraneous + missing).uniq.sort
|
127
|
+
|
128
|
+
all_keys.each do |key|
|
129
|
+
next if ignored_keys.include?("#{locale}.#{key}")
|
130
|
+
|
131
|
+
label = if extraneous.include?(key)
|
132
|
+
ui.yellow("extraneous")
|
133
|
+
elsif missing.include?(key)
|
134
|
+
ui.red("missing")
|
135
|
+
else
|
136
|
+
next
|
137
|
+
end
|
138
|
+
|
139
|
+
ui.stdout_print(" - #{locale}.#{key} (#{label})")
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
exit(1) if total_missing_count.nonzero?
|
144
|
+
end
|
145
|
+
|
146
|
+
private def set_defaults!
|
147
|
+
config_file = "./config/i18n.yml"
|
148
|
+
require_file = "./config/environment.rb"
|
149
|
+
|
150
|
+
options[:config_file] ||= config_file if File.file?(config_file)
|
151
|
+
options[:require_file] ||= require_file if File.file?(require_file)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module I18nJS
|
4
|
+
class CLI
|
5
|
+
class PluginsCommand < Command
|
6
|
+
command_name "plugins"
|
7
|
+
description "List plugins that will be activated"
|
8
|
+
|
9
|
+
parse do |opts|
|
10
|
+
opts.banner = "Usage: i18n #{name} [options]"
|
11
|
+
|
12
|
+
opts.on(
|
13
|
+
"-rREQUIRE_FILE",
|
14
|
+
"--require=REQUIRE_FILE",
|
15
|
+
"A Ruby file that must be loaded"
|
16
|
+
) do |require_file|
|
17
|
+
options[:require_file] = require_file
|
18
|
+
end
|
19
|
+
|
20
|
+
opts.on("-h", "--help", "Prints this help") do
|
21
|
+
ui.exit_with opts.to_s
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
command do
|
26
|
+
set_defaults!
|
27
|
+
ui.colored = options[:colored]
|
28
|
+
|
29
|
+
if options[:require_file]
|
30
|
+
ui.stdout_print("=> Require file:", options[:require_file].inspect)
|
31
|
+
require_file = File.expand_path(options[:require_file])
|
32
|
+
end
|
33
|
+
|
34
|
+
if require_file && !File.file?(require_file)
|
35
|
+
ui.fail_with(
|
36
|
+
"=> ERROR: require file doesn't exist at",
|
37
|
+
require_file.inspect
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
load_require_file!(require_file) if require_file
|
42
|
+
|
43
|
+
files = I18nJS.plugin_files
|
44
|
+
|
45
|
+
if files.empty?
|
46
|
+
ui.stdout_print("=> No plugins have been detected.")
|
47
|
+
else
|
48
|
+
ui.stdout_print("=> Plugins that will be activated:")
|
49
|
+
|
50
|
+
files.each do |file|
|
51
|
+
file = file.gsub("#{Dir.home}/", "~/")
|
52
|
+
|
53
|
+
ui.stdout_print(" * #{file}")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private def set_defaults!
|
59
|
+
config_file = "./config/i18n.yml"
|
60
|
+
require_file = "./config/environment.rb"
|
61
|
+
|
62
|
+
options[:config_file] ||= config_file if File.file?(config_file)
|
63
|
+
options[:require_file] ||= require_file if File.file?(require_file)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/i18n-js/cli.rb
CHANGED
@@ -6,6 +6,9 @@ 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/plugins_command"
|
10
|
+
require_relative "cli/lint_translations_command"
|
11
|
+
require_relative "cli/lint_scripts_command"
|
9
12
|
require_relative "cli/check_command"
|
10
13
|
|
11
14
|
module I18nJS
|
@@ -27,7 +30,15 @@ module I18nJS
|
|
27
30
|
end
|
28
31
|
|
29
32
|
private def command_classes
|
30
|
-
[
|
33
|
+
[
|
34
|
+
InitCommand,
|
35
|
+
ExportCommand,
|
36
|
+
VersionCommand,
|
37
|
+
PluginsCommand,
|
38
|
+
LintTranslationsCommand,
|
39
|
+
LintScriptsCommand,
|
40
|
+
CheckCommand
|
41
|
+
]
|
31
42
|
end
|
32
43
|
|
33
44
|
private def commands
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module I18nJS
|
4
|
+
require "i18n-js/plugin"
|
5
|
+
|
6
|
+
class EmbedFallbackTranslationsPlugin < I18nJS::Plugin
|
7
|
+
CONFIG_KEY = :embed_fallback_translations
|
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]
|
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
|
+
end
|
24
|
+
|
25
|
+
def self.transform(translations:, config:)
|
26
|
+
return translations unless config.dig(CONFIG_KEY, :enabled)
|
27
|
+
|
28
|
+
translations_glob = Glob.new(translations)
|
29
|
+
translations_glob << "*"
|
30
|
+
|
31
|
+
mapping = translations.keys.each_with_object({}) do |locale, buffer|
|
32
|
+
buffer[locale] = Glob.new(translations[locale]).tap do |glob|
|
33
|
+
glob << "*"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
default_locale = I18n.default_locale
|
38
|
+
default_locale_glob = mapping.delete(default_locale)
|
39
|
+
default_locale_paths = default_locale_glob.paths
|
40
|
+
|
41
|
+
mapping.each do |locale, glob|
|
42
|
+
missing_keys = default_locale_paths - glob.paths
|
43
|
+
|
44
|
+
missing_keys.each do |key|
|
45
|
+
components = key.split(".").map(&:to_sym)
|
46
|
+
fallback_translation = translations.dig(default_locale, *components)
|
47
|
+
|
48
|
+
next unless fallback_translation
|
49
|
+
|
50
|
+
translations_glob.set([locale, key].join("."), fallback_translation)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
translations_glob.to_h
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
I18nJS.register_plugin(EmbedFallbackTranslationsPlugin)
|
59
|
+
end
|