neeto-translate-cli 0.2.22 → 0.3.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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2843b446edd2c585c87d15b48cc8b950e80dab86bac2d6eaf928478ceb4fd8ba
|
|
4
|
+
data.tar.gz: 0dd014fabdbb035100109d4abf5a6d9f27412def945e5f40780839bb43672581
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6b6f0aa12230ed0e04f997051c5275a866745f0407c4dd940fbc76919a2fe36401bfae67d65fbaffebd6ccfd9368fbb9b9ed10985ea871873e57de2f2552b347
|
|
7
|
+
data.tar.gz: eda8a84c5c70b9d5d690f1aedf53e939bbdc51bb81530c2f389b7bfa04354a64359a02916cb727de98e0a298beadf58fa5f6ac051f6d16eacde553e40131480e
|
data/README.md
CHANGED
|
@@ -87,11 +87,12 @@ The following environment variables can be configured:
|
|
|
87
87
|
### How It Works
|
|
88
88
|
|
|
89
89
|
1. **Extraction**: The tool reads your English translation files (`en.json` for frontend, `en.yml` for backend)
|
|
90
|
-
2. **Analysis**: It compares with other language files to identify missing translations
|
|
90
|
+
2. **Analysis**: It compares with other language files to identify missing translations, and keys that are no longer in the English files (plural forms other languages need, like `_few` or `.many`, are kept)
|
|
91
91
|
3. **Change Detection**: It detects keys that have been updated since the last automated translation run
|
|
92
92
|
4. **API Request**: Sends a payload to the neeto-translate API with:
|
|
93
93
|
- Current English translations
|
|
94
94
|
- Missing translation keys for each language
|
|
95
|
+
- Keys removed from the English files, for each language
|
|
95
96
|
- Updated keys that need re-translation
|
|
96
97
|
- Repository metadata
|
|
97
98
|
|
|
@@ -4,6 +4,12 @@ require_relative "git_helper"
|
|
|
4
4
|
|
|
5
5
|
module NeetoTranslateCli
|
|
6
6
|
class PayloadBuilder
|
|
7
|
+
TYPES = %i[frontend backend].freeze
|
|
8
|
+
KEY_KINDS = %i[missing_keys removed_keys].freeze
|
|
9
|
+
# Same plural rule as Github::CreatePullRequestService on the server.
|
|
10
|
+
PLURAL_FORM_REGEX = /\A(?<base>.+)(?<separator>[._])(?:zero|one|two|few|many|other)\z/
|
|
11
|
+
PLURAL_BASE_FORMS = %w[one other].freeze
|
|
12
|
+
|
|
7
13
|
attr_reader :options, :updated_keys
|
|
8
14
|
|
|
9
15
|
def initialize(options)
|
|
@@ -14,8 +20,8 @@ module NeetoTranslateCli
|
|
|
14
20
|
|
|
15
21
|
def process!
|
|
16
22
|
{
|
|
17
|
-
frontend:
|
|
18
|
-
backend:
|
|
23
|
+
frontend: payload_for(:frontend),
|
|
24
|
+
backend: payload_for(:backend),
|
|
19
25
|
metadata: {
|
|
20
26
|
repo: options[:repo],
|
|
21
27
|
frontend: options[:frontend],
|
|
@@ -26,11 +32,21 @@ module NeetoTranslateCli
|
|
|
26
32
|
end
|
|
27
33
|
|
|
28
34
|
def no_new_changes?(payload)
|
|
29
|
-
|
|
35
|
+
TYPES.all? do |type|
|
|
36
|
+
KEY_KINDS.all? { |kind| payload[type][kind].to_h.values.all?(&:empty?) }
|
|
37
|
+
end
|
|
30
38
|
end
|
|
31
39
|
|
|
32
40
|
private
|
|
33
41
|
|
|
42
|
+
def payload_for(type)
|
|
43
|
+
{
|
|
44
|
+
en: type == :frontend ? en_json_flattened : en_yml_flattened,
|
|
45
|
+
missing_keys: find_missing_keys_for(type),
|
|
46
|
+
removed_keys: find_removed_keys_for(type)
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
34
50
|
def git_commit_id
|
|
35
51
|
GitHelper.find_neeto_translate_commit
|
|
36
52
|
end
|
|
@@ -94,6 +110,36 @@ module NeetoTranslateCli
|
|
|
94
110
|
end
|
|
95
111
|
end
|
|
96
112
|
|
|
113
|
+
def find_removed_keys_for(type)
|
|
114
|
+
base_keys = type == :frontend ? en_json_flattened.keys : en_yml_flattened.keys
|
|
115
|
+
|
|
116
|
+
languages.each_with_object({}) do |lang, removed|
|
|
117
|
+
translations = load_translations(type, lang)
|
|
118
|
+
next unless translations
|
|
119
|
+
|
|
120
|
+
removed[lang] = (flatten_hash(translations).keys - base_keys).reject { |key| plural_form_of?(key, base_keys) }
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def load_translations(type, lang)
|
|
125
|
+
if type == :frontend
|
|
126
|
+
file_path = File.expand_path("#{lang}.json", options[:frontend])
|
|
127
|
+
load_json(file_path) if File.file?(file_path)
|
|
128
|
+
else
|
|
129
|
+
file_path = File.expand_path("#{lang}.yml", options[:backend])
|
|
130
|
+
load_yml(file_path)[lang].to_h if File.file?(file_path)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Languages like ar and ru need plural forms that en doesn't have (e.g. `item_few`, `items.many`),
|
|
135
|
+
# so a plural form of an en key is not treated as removed.
|
|
136
|
+
def plural_form_of?(key, base_keys)
|
|
137
|
+
match = key.match(PLURAL_FORM_REGEX)
|
|
138
|
+
return false unless match
|
|
139
|
+
|
|
140
|
+
PLURAL_BASE_FORMS.any? { |form| base_keys.include?("#{match[:base]}#{match[:separator]}#{form}") }
|
|
141
|
+
end
|
|
142
|
+
|
|
97
143
|
def load_json(file_path)
|
|
98
144
|
return {} unless File.file?(file_path)
|
|
99
145
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
require "test_helper"
|
|
2
|
+
require "fileutils"
|
|
3
|
+
require "tmpdir"
|
|
2
4
|
|
|
3
5
|
class NeetoTranslateCli::PayloadBuilderTest < Minitest::Test
|
|
4
6
|
def setup
|
|
@@ -170,8 +172,71 @@ class NeetoTranslateCli::PayloadBuilderTest < Minitest::Test
|
|
|
170
172
|
end
|
|
171
173
|
end
|
|
172
174
|
|
|
175
|
+
def test_removed_keys_are_the_keys_missing_from_en
|
|
176
|
+
with_locale_files do |payload_builder|
|
|
177
|
+
payload = payload_builder.process!
|
|
178
|
+
|
|
179
|
+
assert_equal %w[common.deleted dashboard.stats.removed], payload.dig(:frontend, :removed_keys, "es").sort
|
|
180
|
+
assert_equal %w[common.deleted], payload.dig(:backend, :removed_keys, "es")
|
|
181
|
+
assert_empty payload.dig(:frontend, :removed_keys, "de")
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def test_removed_keys_keep_plural_forms_of_en_keys
|
|
186
|
+
with_locale_files do |payload_builder|
|
|
187
|
+
payload = payload_builder.process!
|
|
188
|
+
|
|
189
|
+
refute_includes payload.dig(:frontend, :removed_keys, "es"), "common.item_many"
|
|
190
|
+
refute_includes payload.dig(:backend, :removed_keys, "es"), "common.items.few"
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def test_no_new_changes_is_false_when_keys_were_only_removed
|
|
195
|
+
with_locale_files do |payload_builder|
|
|
196
|
+
payload = payload_builder.process!
|
|
197
|
+
|
|
198
|
+
assert payload[:frontend][:missing_keys].values.all?(&:empty?)
|
|
199
|
+
assert payload[:backend][:missing_keys].values.all?(&:empty?)
|
|
200
|
+
refute payload_builder.no_new_changes?(payload)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
173
204
|
private
|
|
174
205
|
|
|
206
|
+
def with_locale_files
|
|
207
|
+
Dir.mktmpdir do |dir|
|
|
208
|
+
frontend = File.join(dir, "frontend")
|
|
209
|
+
backend = File.join(dir, "backend")
|
|
210
|
+
FileUtils.mkdir_p([frontend, backend])
|
|
211
|
+
|
|
212
|
+
en_json = { "common" => { "ok" => "OK", "item_one" => "Item", "item_other" => "Items" } }
|
|
213
|
+
es_json = {
|
|
214
|
+
"common" => { "ok" => "Aceptar", "item_one" => "Elemento", "item_other" => "Elementos",
|
|
215
|
+
"item_many" => "Elementos", "deleted" => "Borrado" },
|
|
216
|
+
"dashboard" => { "stats" => { "removed" => "Eliminado" } }
|
|
217
|
+
}
|
|
218
|
+
File.write(File.join(frontend, "en.json"), JSON.generate(en_json))
|
|
219
|
+
File.write(File.join(frontend, "es.json"), JSON.generate(es_json))
|
|
220
|
+
File.write(File.join(frontend, "de.json"), JSON.generate(en_json))
|
|
221
|
+
|
|
222
|
+
en_yml = { "en" => { "common" => { "ok" => "OK", "items" => { "one" => "Item", "other" => "Items" } } } }
|
|
223
|
+
es_yml = {
|
|
224
|
+
"es" => {
|
|
225
|
+
"common" => { "ok" => "Aceptar", "deleted" => "Borrado",
|
|
226
|
+
"items" => { "one" => "Elemento", "few" => "Elementos", "other" => "Elementos" } }
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
File.write(File.join(backend, "en.yml"), en_yml.to_yaml)
|
|
230
|
+
File.write(File.join(backend, "es.yml"), es_yml.to_yaml)
|
|
231
|
+
File.write(File.join(backend, "de.yml"), { "de" => en_yml["en"] }.to_yaml)
|
|
232
|
+
|
|
233
|
+
options = @options.merge(frontend:, backend:)
|
|
234
|
+
NeetoTranslateCli::GitHelper.stub :find_neeto_translate_commit, "" do
|
|
235
|
+
yield NeetoTranslateCli::PayloadBuilder.new(options)
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
175
240
|
def flatten_hash(hash, prefix = nil)
|
|
176
241
|
hash.each_with_object({}) do |(key, value), result|
|
|
177
242
|
current_key = prefix ? "#{prefix}.#{key}" : key.to_s
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: neeto-translate-cli
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Neeraj Singh
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-11-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|