neeto-translate-cli 0.2.21 → 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: 894f4af5c559b178153908e1798e8f1d407ea8f3ad663a49acc87a7a63d3a8fd
4
- data.tar.gz: d246bd195c78def54c6e826208394f0ec55ab35435c7ed5d2b37b206151f0a96
3
+ metadata.gz: 2843b446edd2c585c87d15b48cc8b950e80dab86bac2d6eaf928478ceb4fd8ba
4
+ data.tar.gz: 0dd014fabdbb035100109d4abf5a6d9f27412def945e5f40780839bb43672581
5
5
  SHA512:
6
- metadata.gz: 843092224cc6c4661b66001e7018adf9086487231285bdeeb3af192263162e2f5474ba06eac8b9b1f5aabae26492734151e5e6c447d7ac8ed48ef67976005abd
7
- data.tar.gz: 690dcfcda6636b87f23550367bbac54ed39ae11b859c15ebd9c7a59702265e10d538b28ba0303344b1a0cd37196cc2f9e0281e7a4614794abf63995db3113937
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: { en: en_json_flattened, missing_keys: find_missing_keys_for(:frontend) },
18
- backend: { en: en_yml_flattened, missing_keys: find_missing_keys_for(: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
- payload[:frontend][:missing_keys].values.all?(&:empty?) && payload[:backend][:missing_keys].values.all?(&:empty?)
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,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NeetoTranslateCli
4
- VERSION = "0.2.21"
4
+ VERSION = "0.3.0"
5
5
  end
@@ -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.2.21
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-09-10 00:00:00.000000000 Z
11
+ date: 2026-11-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday