i18n-js 4.0.1 → 4.1.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 +14 -2
- data/MIGRATING_FROM_V3_TO_V4.md +1 -1
- data/README.md +219 -9
- data/bin/pack +79 -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 +78 -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 +38 -0
- data/lib/i18n-js/schema.rb +53 -14
- data/lib/i18n-js/version.rb +1 -1
- data/lib/i18n-js.rb +20 -3
- data/package.json +10 -0
- metadata +32 -9
data/lib/i18n-js/lint.ts
ADDED
@@ -0,0 +1,196 @@
|
|
1
|
+
import { readFileSync, statSync } from "fs";
|
2
|
+
import * as ts from "typescript";
|
3
|
+
import { glob } from "glob";
|
4
|
+
|
5
|
+
type ScopeInfo = {
|
6
|
+
type: "default" | "scope" | "base";
|
7
|
+
location: string;
|
8
|
+
base: string | null;
|
9
|
+
full: string;
|
10
|
+
scope: string;
|
11
|
+
};
|
12
|
+
|
13
|
+
function location(node: ts.Node, append: string = ":") {
|
14
|
+
const sourceFile = node.getSourceFile();
|
15
|
+
let { line, character } = sourceFile.getLineAndCharacterOfPosition(
|
16
|
+
node.getStart(sourceFile)
|
17
|
+
);
|
18
|
+
|
19
|
+
line += 1;
|
20
|
+
character += 1;
|
21
|
+
const file = sourceFile.fileName;
|
22
|
+
const location = `${file}:${line}:${character}`;
|
23
|
+
|
24
|
+
return `${location}${append}`;
|
25
|
+
}
|
26
|
+
|
27
|
+
const callExpressions = ["t", "i18n.t", "i18n.translate"];
|
28
|
+
|
29
|
+
function tsKind(node: ts.Node) {
|
30
|
+
const keys = Object.keys(ts.SyntaxKind);
|
31
|
+
const values = Object.values(ts.SyntaxKind);
|
32
|
+
|
33
|
+
return keys[values.indexOf(node.kind)];
|
34
|
+
}
|
35
|
+
|
36
|
+
function getTranslationScopesFromFile(filePath: string) {
|
37
|
+
const scopes: ScopeInfo[] = [];
|
38
|
+
|
39
|
+
const sourceFile = ts.createSourceFile(
|
40
|
+
filePath,
|
41
|
+
readFileSync(filePath).toString(),
|
42
|
+
ts.ScriptTarget.ES2015,
|
43
|
+
true
|
44
|
+
);
|
45
|
+
|
46
|
+
inspect(sourceFile);
|
47
|
+
|
48
|
+
return scopes;
|
49
|
+
|
50
|
+
function inspect(node: ts.Node) {
|
51
|
+
const next = () => {
|
52
|
+
ts.forEachChild(node, inspect);
|
53
|
+
};
|
54
|
+
|
55
|
+
if (node.kind !== ts.SyntaxKind.CallExpression) {
|
56
|
+
return next();
|
57
|
+
}
|
58
|
+
|
59
|
+
const expr = node.getChildAt(0).getText();
|
60
|
+
const text = JSON.stringify(node.getText(sourceFile));
|
61
|
+
|
62
|
+
if (!callExpressions.includes(expr)) {
|
63
|
+
return next();
|
64
|
+
}
|
65
|
+
|
66
|
+
const syntaxList = node.getChildAt(2);
|
67
|
+
|
68
|
+
if (!syntaxList.getText().trim()) {
|
69
|
+
return next();
|
70
|
+
}
|
71
|
+
|
72
|
+
const scopeNode = syntaxList.getChildAt(0) as ts.StringLiteral;
|
73
|
+
const optionsNode = syntaxList.getChildAt(2) as ts.ObjectLiteralExpression;
|
74
|
+
|
75
|
+
if (scopeNode.kind !== ts.SyntaxKind.StringLiteral) {
|
76
|
+
return next();
|
77
|
+
}
|
78
|
+
|
79
|
+
if (
|
80
|
+
optionsNode &&
|
81
|
+
optionsNode.kind !== ts.SyntaxKind.ObjectLiteralExpression
|
82
|
+
) {
|
83
|
+
return next();
|
84
|
+
}
|
85
|
+
|
86
|
+
if (!optionsNode) {
|
87
|
+
scopes.push({
|
88
|
+
type: "scope",
|
89
|
+
scope: scopeNode.text,
|
90
|
+
base: null,
|
91
|
+
full: scopeNode.text,
|
92
|
+
location: location(node, ""),
|
93
|
+
});
|
94
|
+
return next();
|
95
|
+
}
|
96
|
+
|
97
|
+
scopes.push(...getScopes(scopeNode, optionsNode));
|
98
|
+
}
|
99
|
+
|
100
|
+
function mapProperties(node: ts.ObjectLiteralExpression): {
|
101
|
+
name: string;
|
102
|
+
value: ts.Node;
|
103
|
+
}[] {
|
104
|
+
return node.properties.map((p) => ({
|
105
|
+
name: (p.name as ts.Identifier).escapedText.toString(),
|
106
|
+
value: p.getChildAt(2),
|
107
|
+
}));
|
108
|
+
}
|
109
|
+
|
110
|
+
function getScopes(
|
111
|
+
scopeNode: ts.StringLiteral,
|
112
|
+
node: ts.ObjectLiteralExpression
|
113
|
+
): ScopeInfo[] {
|
114
|
+
const suffix = scopeNode.text;
|
115
|
+
|
116
|
+
const result: ScopeInfo[] = [];
|
117
|
+
const properties = mapProperties(node);
|
118
|
+
|
119
|
+
if (
|
120
|
+
properties.length === 0 ||
|
121
|
+
!properties.some((p) => p.name === "scope")
|
122
|
+
) {
|
123
|
+
result.push({
|
124
|
+
type: "scope",
|
125
|
+
scope: suffix,
|
126
|
+
base: null,
|
127
|
+
full: suffix,
|
128
|
+
location: location(scopeNode, ""),
|
129
|
+
});
|
130
|
+
}
|
131
|
+
|
132
|
+
properties.forEach((property) => {
|
133
|
+
if (
|
134
|
+
property.name === "scope" &&
|
135
|
+
property.value.kind === ts.SyntaxKind.StringLiteral
|
136
|
+
) {
|
137
|
+
const base = (property.value as ts.StringLiteral).text;
|
138
|
+
|
139
|
+
result.push({
|
140
|
+
type: "base",
|
141
|
+
scope: suffix,
|
142
|
+
base,
|
143
|
+
full: `${base}.${suffix}`,
|
144
|
+
location: location(scopeNode, ""),
|
145
|
+
});
|
146
|
+
}
|
147
|
+
|
148
|
+
if (
|
149
|
+
property.name === "defaults" &&
|
150
|
+
property.value.kind === ts.SyntaxKind.ArrayLiteralExpression
|
151
|
+
) {
|
152
|
+
const op = property.value as ts.ArrayLiteralExpression;
|
153
|
+
const values = op.getChildAt(1);
|
154
|
+
const objects = (
|
155
|
+
values
|
156
|
+
.getChildren()
|
157
|
+
.filter(
|
158
|
+
(n) => n.kind === ts.SyntaxKind.ObjectLiteralExpression
|
159
|
+
) as ts.ObjectLiteralExpression[]
|
160
|
+
).map(mapProperties);
|
161
|
+
|
162
|
+
objects.forEach((object) => {
|
163
|
+
object.forEach((prop) => {
|
164
|
+
if (
|
165
|
+
prop.name === "scope" &&
|
166
|
+
prop.value.kind === ts.SyntaxKind.StringLiteral
|
167
|
+
) {
|
168
|
+
const text = (prop.value as ts.StringLiteral).text;
|
169
|
+
|
170
|
+
result.push({
|
171
|
+
type: "default",
|
172
|
+
scope: text,
|
173
|
+
base: null,
|
174
|
+
full: text,
|
175
|
+
location: location(prop.value, ""),
|
176
|
+
});
|
177
|
+
}
|
178
|
+
});
|
179
|
+
});
|
180
|
+
}
|
181
|
+
});
|
182
|
+
|
183
|
+
return result;
|
184
|
+
}
|
185
|
+
}
|
186
|
+
|
187
|
+
const patterns = (
|
188
|
+
process.argv[2] ??
|
189
|
+
"!(node_modules)/**/*.js:!(node_modules)/**/*.ts:!(node_modules)/**/*.jsx:!(node_modules)/**/*.tsx"
|
190
|
+
).split(":");
|
191
|
+
const files = patterns.flatMap((pattern) => glob.sync(pattern));
|
192
|
+
const scopes = files
|
193
|
+
.filter((filePath) => statSync(filePath).isFile())
|
194
|
+
.flatMap((path) => getTranslationScopesFromFile(path));
|
195
|
+
|
196
|
+
console.log(JSON.stringify(scopes, null, 2));
|
data/lib/i18n-js/listen.rb
CHANGED
@@ -8,6 +8,7 @@ module I18nJS
|
|
8
8
|
def self.listen(
|
9
9
|
config_file: Rails.root.join("config/i18n.yml"),
|
10
10
|
locales_dir: Rails.root.join("config/locales"),
|
11
|
+
run_on_start: true,
|
11
12
|
options: {}
|
12
13
|
)
|
13
14
|
return unless Rails.env.development?
|
@@ -19,7 +20,7 @@ module I18nJS
|
|
19
20
|
|
20
21
|
self.started = true
|
21
22
|
|
22
|
-
locales_dirs = Array(locales_dir)
|
23
|
+
locales_dirs = Array(locales_dir).map {|path| File.expand_path(path) }
|
23
24
|
|
24
25
|
relative_paths =
|
25
26
|
[config_file, *locales_dirs].map {|path| relative_path(path) }
|
@@ -27,7 +28,7 @@ module I18nJS
|
|
27
28
|
debug("Watching #{relative_paths.inspect}")
|
28
29
|
|
29
30
|
listener(config_file, locales_dirs.map(&:to_s), options).start
|
30
|
-
I18nJS.call(config_file: config_file)
|
31
|
+
I18nJS.call(config_file: config_file) if run_on_start
|
31
32
|
end
|
32
33
|
|
33
34
|
def self.relative_path(path)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "schema"
|
4
|
+
|
5
|
+
module I18nJS
|
6
|
+
def self.plugins
|
7
|
+
@plugins ||= []
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.register_plugin(plugin)
|
11
|
+
plugins << plugin
|
12
|
+
plugin.setup
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.plugin_files
|
16
|
+
Gem.find_files("i18n-js/*_plugin.rb")
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.load_plugins!
|
20
|
+
plugin_files.each do |path|
|
21
|
+
require path
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Plugin
|
26
|
+
def self.transform(translations:, config:) # rubocop:disable Lint/UnusedMethodArgument
|
27
|
+
translations
|
28
|
+
end
|
29
|
+
|
30
|
+
# Must raise I18nJS::SchemaInvalidError with the error message if schema
|
31
|
+
# validation has failed.
|
32
|
+
def self.validate_schema(config:)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.setup
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/i18n-js/schema.rb
CHANGED
@@ -4,14 +4,28 @@ module I18nJS
|
|
4
4
|
class Schema
|
5
5
|
InvalidError = Class.new(StandardError)
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
REQUIRED_CHECK_KEYS = %i[ignore].freeze
|
7
|
+
REQUIRED_LINT_TRANSLATIONS_KEYS = %i[ignore].freeze
|
8
|
+
REQUIRED_LINT_SCRIPTS_KEYS = %i[ignore patterns].freeze
|
10
9
|
REQUIRED_TRANSLATION_KEYS = %i[file patterns].freeze
|
11
10
|
TRANSLATION_KEYS = %i[file patterns].freeze
|
12
11
|
|
12
|
+
def self.root_keys
|
13
|
+
@root_keys ||= Set.new(%i[
|
14
|
+
translations
|
15
|
+
lint_translations
|
16
|
+
lint_scripts
|
17
|
+
check
|
18
|
+
])
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.required_root_keys
|
22
|
+
@required_root_keys ||= Set.new(%i[translations])
|
23
|
+
end
|
24
|
+
|
13
25
|
def self.validate!(target)
|
14
|
-
new(target)
|
26
|
+
schema = new(target)
|
27
|
+
schema.validate!
|
28
|
+
I18nJS.plugins.each {|plugin| plugin.validate_schema(config: target) }
|
15
29
|
end
|
16
30
|
|
17
31
|
attr_reader :target
|
@@ -23,20 +37,36 @@ module I18nJS
|
|
23
37
|
def validate!
|
24
38
|
expect_type(:root, target, Hash, target)
|
25
39
|
|
26
|
-
expect_required_keys(
|
27
|
-
reject_extraneous_keys(
|
40
|
+
expect_required_keys(self.class.required_root_keys, target)
|
41
|
+
reject_extraneous_keys(self.class.root_keys, target)
|
28
42
|
validate_translations
|
29
|
-
|
43
|
+
validate_lint_translations
|
44
|
+
validate_lint_scripts
|
30
45
|
end
|
31
46
|
|
32
|
-
def
|
33
|
-
|
47
|
+
def validate_lint_translations
|
48
|
+
key = :lint_translations
|
34
49
|
|
35
|
-
|
50
|
+
return unless target.key?(key)
|
36
51
|
|
37
|
-
|
38
|
-
|
39
|
-
expect_type(
|
52
|
+
config = target[key]
|
53
|
+
|
54
|
+
expect_type(key, config, Hash, target)
|
55
|
+
expect_required_keys(REQUIRED_LINT_TRANSLATIONS_KEYS, config)
|
56
|
+
expect_type(:ignore, config[:ignore], Array, config)
|
57
|
+
end
|
58
|
+
|
59
|
+
def validate_lint_scripts
|
60
|
+
key = :lint_scripts
|
61
|
+
|
62
|
+
return unless target.key?(key)
|
63
|
+
|
64
|
+
config = target[key]
|
65
|
+
|
66
|
+
expect_type(key, config, Hash, target)
|
67
|
+
expect_required_keys(REQUIRED_LINT_SCRIPTS_KEYS, config)
|
68
|
+
expect_type(:ignore, config[:ignore], Array, config)
|
69
|
+
expect_type(:patterns, config[:patterns], Array, config)
|
40
70
|
end
|
41
71
|
|
42
72
|
def validate_translations
|
@@ -63,6 +93,15 @@ module I18nJS
|
|
63
93
|
raise InvalidError, "#{error_message}#{node_json}"
|
64
94
|
end
|
65
95
|
|
96
|
+
def expect_enabled_config(config_key, value)
|
97
|
+
return if [TrueClass, FalseClass].include?(value.class)
|
98
|
+
|
99
|
+
actual_type = value.class
|
100
|
+
|
101
|
+
reject "Expected #{config_key}.enabled to be a boolean; " \
|
102
|
+
"got #{actual_type} instead"
|
103
|
+
end
|
104
|
+
|
66
105
|
def expect_type(attribute, value, expected_type, payload)
|
67
106
|
return if value.is_a?(expected_type)
|
68
107
|
|
@@ -94,7 +133,7 @@ module I18nJS
|
|
94
133
|
|
95
134
|
def reject_extraneous_keys(allowed_keys, value)
|
96
135
|
keys = value.keys.map(&:to_sym)
|
97
|
-
extraneous = keys - allowed_keys
|
136
|
+
extraneous = keys.to_a - allowed_keys.to_a
|
98
137
|
|
99
138
|
return if extraneous.empty?
|
100
139
|
|
data/lib/i18n-js/version.rb
CHANGED
data/lib/i18n-js.rb
CHANGED
@@ -6,9 +6,13 @@ require "yaml"
|
|
6
6
|
require "glob"
|
7
7
|
require "fileutils"
|
8
8
|
require "optparse"
|
9
|
+
require "erb"
|
10
|
+
require "set"
|
11
|
+
require "digest/md5"
|
9
12
|
|
10
13
|
require_relative "i18n-js/schema"
|
11
14
|
require_relative "i18n-js/version"
|
15
|
+
require_relative "i18n-js/plugin"
|
12
16
|
|
13
17
|
module I18nJS
|
14
18
|
MissingConfigError = Class.new(StandardError)
|
@@ -19,19 +23,27 @@ module I18nJS
|
|
19
23
|
"you must set either `config_file` or `config`"
|
20
24
|
end
|
21
25
|
|
22
|
-
|
26
|
+
load_plugins!
|
27
|
+
|
28
|
+
config = Glob::SymbolizeKeys.call(config || load_config_file(config_file))
|
29
|
+
|
23
30
|
Schema.validate!(config)
|
24
31
|
exported_files = []
|
25
32
|
|
26
33
|
config[:translations].each do |group|
|
27
|
-
exported_files += export_group(group)
|
34
|
+
exported_files += export_group(group, config)
|
28
35
|
end
|
29
36
|
|
30
37
|
exported_files
|
31
38
|
end
|
32
39
|
|
33
|
-
def self.export_group(group)
|
40
|
+
def self.export_group(group, config)
|
34
41
|
filtered_translations = Glob.filter(translations, group[:patterns])
|
42
|
+
filtered_translations =
|
43
|
+
plugins.reduce(filtered_translations) do |buffer, plugin|
|
44
|
+
plugin.transform(translations: buffer, config: config)
|
45
|
+
end
|
46
|
+
|
35
47
|
output_file_path = File.expand_path(group[:file])
|
36
48
|
exported_files = []
|
37
49
|
|
@@ -70,4 +82,9 @@ module I18nJS
|
|
70
82
|
translations
|
71
83
|
end
|
72
84
|
end
|
85
|
+
|
86
|
+
def self.load_config_file(config_file)
|
87
|
+
erb = ERB.new(File.read(config_file))
|
88
|
+
YAML.safe_load(erb.result(binding))
|
89
|
+
end
|
73
90
|
end
|
data/package.json
ADDED
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
|
4
|
+
version: 4.1.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-
|
11
|
+
date: 2022-12-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: glob
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 0.4.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 0.4.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: i18n
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: mocha
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
98
|
name: pry-meta
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -176,6 +190,7 @@ files:
|
|
176
190
|
- MIGRATING_FROM_V3_TO_V4.md
|
177
191
|
- README.md
|
178
192
|
- Rakefile
|
193
|
+
- bin/pack
|
179
194
|
- exe/i18n
|
180
195
|
- i18n-js.gemspec
|
181
196
|
- lib/guard/i18n-js.rb
|
@@ -187,11 +202,19 @@ files:
|
|
187
202
|
- lib/i18n-js/cli/command.rb
|
188
203
|
- lib/i18n-js/cli/export_command.rb
|
189
204
|
- lib/i18n-js/cli/init_command.rb
|
205
|
+
- lib/i18n-js/cli/lint_scripts_command.rb
|
206
|
+
- lib/i18n-js/cli/lint_translations_command.rb
|
207
|
+
- lib/i18n-js/cli/plugins_command.rb
|
190
208
|
- lib/i18n-js/cli/ui.rb
|
191
209
|
- lib/i18n-js/cli/version_command.rb
|
210
|
+
- lib/i18n-js/embed_fallback_translations_plugin.rb
|
211
|
+
- lib/i18n-js/lint.js
|
212
|
+
- lib/i18n-js/lint.ts
|
192
213
|
- lib/i18n-js/listen.rb
|
214
|
+
- lib/i18n-js/plugin.rb
|
193
215
|
- lib/i18n-js/schema.rb
|
194
216
|
- lib/i18n-js/version.rb
|
217
|
+
- package.json
|
195
218
|
homepage: https://github.com/fnando/i18n-js
|
196
219
|
licenses:
|
197
220
|
- MIT
|
@@ -199,10 +222,10 @@ metadata:
|
|
199
222
|
rubygems_mfa_required: 'true'
|
200
223
|
homepage_uri: https://github.com/fnando/i18n-js
|
201
224
|
bug_tracker_uri: https://github.com/fnando/i18n-js/issues
|
202
|
-
source_code_uri: https://github.com/fnando/i18n-js/tree/v4.0
|
203
|
-
changelog_uri: https://github.com/fnando/i18n-js/tree/v4.0
|
204
|
-
documentation_uri: https://github.com/fnando/i18n-js/tree/v4.0
|
205
|
-
license_uri: https://github.com/fnando/i18n-js/tree/v4.0
|
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
|
206
229
|
post_install_message:
|
207
230
|
rdoc_options: []
|
208
231
|
require_paths:
|
@@ -218,7 +241,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
218
241
|
- !ruby/object:Gem::Version
|
219
242
|
version: '0'
|
220
243
|
requirements: []
|
221
|
-
rubygems_version: 3.3.
|
244
|
+
rubygems_version: 3.3.26
|
222
245
|
signing_key:
|
223
246
|
specification_version: 4
|
224
247
|
summary: Export i18n translations and use them on JavaScript.
|