inertia_i18n 0.6.2 → 0.7.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: bd6963fa4cee963f25abe89fd9b5d2c85fdd215818dc20758a58fe78bd1ef0fc
4
- data.tar.gz: 570e11fd4df5a71af1885f576fd16bdd8bdd4a6dd63b96e70e26e341def65bee
3
+ metadata.gz: 4e612133c6b6af1e6269626c3af83a4c27c846b50f2b75f1a12d51559425a3d5
4
+ data.tar.gz: f2524dc747a6314a7a482566b086cc854b1a1276ae048b04cb8002946d5a4910
5
5
  SHA512:
6
- metadata.gz: e03446783627997094d0e4c33b16b68719ee417167b76b2b370960354414c274c55ff366167499d4c4081c04ed49b9185e1937d93985f1fbe3f600949bc7d5fd
7
- data.tar.gz: fcc6c743a74b8aef68f4c7a9cdf7d35a88492c543600ba89bc1ae68219a57ca42d6286441647f4d91ac0f9e563be8965746ddbfa7dcfb25539edc01a231a44ec
6
+ metadata.gz: e2675724ebf16a84ce0a13c3b7d09cc7d16b4c587f79c5ba05840100e796f16d86193e590011f08c6685d557926f63966191456c7bb72d4345397013d4fee80f
7
+ data.tar.gz: 166504db3b51629f77c9fb112c53f5ba87e661d1558f72beb22f2849996ded50dbee7c94a8879cfe07fd54590291df22ac97bd87e28a6a39c956b5e1e19a323a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.7.0] - 2026-02-27
4
+
5
+ ### Added
6
+
7
+ - **Dynamic Keys Configuration**: Added `dynamic_keys` configuration property to expand dynamic patterns into explicit static keys. This allows the scanner to check for missing variants of dynamic keys (e.g. `status.active`, `status.inactive`) and mark them as used. Supported in both Ruby initializer and YAML configuration formats.
8
+
3
9
  ## [0.6.2] - 2026-02-23
4
10
 
5
11
  ### Fixed
data/README.md CHANGED
@@ -231,6 +231,38 @@ Handles:
231
231
  - Template literals: `t(\`user.\${type}.title\`)` (flagged for review)
232
232
  - Dynamic patterns: `t(keyVariable)` (flagged for review)
233
233
 
234
+ ### Dynamic Keys Configuration
235
+
236
+ When you use template literals or dynamic variables for translations, the scanner might flag them as unused or missing. You can configure exact mappings for these dynamic keys using the `dynamic_keys` setting.
237
+
238
+ ```ruby
239
+ # config/initializers/inertia_i18n.rb
240
+ config.dynamic_keys = {
241
+ "status." => ["active", "inactive", "pending"]
242
+ }
243
+ ```
244
+
245
+ **Option B: YAML config**
246
+
247
+ ```yaml
248
+ # config/inertia_i18n.yml
249
+ dynamic_keys:
250
+ "status.":
251
+ - active
252
+ - inactive
253
+ - pending
254
+ ```
255
+
256
+ Now, when the scanner sees `t(\`status.\${statusKey}\`)`, it will automatically expand it to `status.active`, `status.inactive`, and `status.pending`, ensuring they are checked for existence and marked as used.
257
+
258
+ Alternatively, you can just ignore the prefix if the possible values are not known using `dynamic_patterns`:
259
+
260
+ ```ruby
261
+ config.dynamic_patterns = {
262
+ "status." => "Dynamic status keys"
263
+ }
264
+ ```
265
+
234
266
  ### Magic Comments
235
267
 
236
268
  Use magic comments to mark dynamic keys as used or suppress warnings. Both `inertia-i18n-use` and `i18n-tasks-use` are supported.
@@ -416,6 +448,12 @@ InertiaI18n.configure do |config|
416
448
  # "status." => "Dynamic status keys"
417
449
  }
418
450
 
451
+ # Dynamic keys exact mapping (prefix => array of possible values)
452
+ # These keys will be automatically expanded and checked for existence
453
+ config.dynamic_keys = {
454
+ # "status." => ["active", "inactive", "pending"]
455
+ }
456
+
419
457
  # Keys to ignore during unused/missing checks
420
458
  config.ignore_unused = []
421
459
  config.ignore_missing = []
@@ -467,6 +505,11 @@ translation_functions:
467
505
  - i18n.t
468
506
  # dynamic_patterns:
469
507
  # "status.": Dynamic status keys
508
+ dynamic_keys:
509
+ "status.":
510
+ - active
511
+ - inactive
512
+ - pending
470
513
  ignore_unused: []
471
514
  ignore_missing: []
472
515
  key_properties:
@@ -34,6 +34,12 @@ InertiaI18n.configure do |config|
34
34
  # "status." => "Dynamic status keys"
35
35
  }
36
36
 
37
+ # Dynamic keys (prefix => array of possible values)
38
+ # These keys will be automatically expanded and checked for existence
39
+ config.dynamic_keys = {
40
+ # "status." => ["active", "inactive", "pending"]
41
+ }
42
+
37
43
  # Keys to ignore during unused check
38
44
  # "common" namespace is generated by inertia_i18n:install as sample data
39
45
  config.ignore_unused = ["common"]
@@ -205,6 +205,12 @@ module InertiaI18n
205
205
  # "status." => "Dynamic status keys"
206
206
  }
207
207
 
208
+ # Dynamic keys (prefix => array of possible values)
209
+ # These keys will be automatically expanded and checked for existence
210
+ config.dynamic_keys = {
211
+ # "status." => ["active", "inactive", "pending"]
212
+ }
213
+
208
214
  # Keys to ignore during unused check
209
215
  config.ignore_unused = []
210
216
 
@@ -253,6 +259,14 @@ module InertiaI18n
253
259
  # dynamic_patterns:
254
260
  # "status.": Dynamic status keys
255
261
 
262
+ # Dynamic keys (prefix => array of possible values)
263
+ # These keys will be automatically expanded and checked for existence
264
+ # dynamic_keys:
265
+ # "status.":
266
+ # - active
267
+ # - inactive
268
+ # - pending
269
+
256
270
  # Keys to ignore during unused check
257
271
  ignore_unused: []
258
272
 
@@ -35,6 +35,9 @@ module InertiaI18n
35
35
  # Dynamic key patterns (prefix => regex)
36
36
  attr_accessor :dynamic_patterns
37
37
 
38
+ # Dynamic keys exact mapping (prefix => array of possible values)
39
+ attr_accessor :dynamic_keys
40
+
38
41
  # Keys to ignore during unused check
39
42
  attr_accessor :ignore_unused
40
43
 
@@ -65,6 +68,7 @@ module InertiaI18n
65
68
  }
66
69
  @translation_functions = %w[t $t i18n.t]
67
70
  @dynamic_patterns = {}
71
+ @dynamic_keys = {}
68
72
  @ignore_unused = []
69
73
  @ignore_missing = []
70
74
  @key_properties = %w[titleKey labelKey messageKey descriptionKey placeholderKey key]
@@ -104,6 +108,7 @@ module InertiaI18n
104
108
 
105
109
  load_hash_attribute(:interpolation, data)
106
110
  self.dynamic_patterns = data["dynamic_patterns"] || {} if data.key?("dynamic_patterns")
111
+ self.dynamic_keys = data["dynamic_keys"] || {} if data.key?("dynamic_keys")
107
112
  load_sibling_detection(data) if data.key?("sibling_detection")
108
113
  load_missing_key_filters(data) if data.key?("missing_key_filters")
109
114
  end
@@ -79,6 +79,7 @@ module InertiaI18n
79
79
 
80
80
  # Add configured dynamic patterns
81
81
  dynamic_prefixes.concat(@config.dynamic_patterns&.keys || [])
82
+ dynamic_prefixes.concat(@config.dynamic_keys&.keys || [])
82
83
 
83
84
  unused = available_keys - used_keys
84
85
 
@@ -102,11 +103,14 @@ module InertiaI18n
102
103
  # Filter out keys covered by ignore_unused config
103
104
  unused = filter_ignored_keys(unused, @config.ignore_unused)
104
105
 
105
- unused.each do |key|
106
+ normalized_unused = normalize_keys_for_sync(unused)
107
+
108
+ normalized_unused.each do |key|
109
+ display_key = key.sub("_[plural]", " (plural forms)")
106
110
  @issues[:unused] << {
107
- key: key,
111
+ key: display_key,
108
112
  severity: :warning,
109
- message: "Key '#{key}' exists in #{primary_locale}.json but is not used in code"
113
+ message: "Key '#{display_key}' exists in #{primary_locale}.json but is not used in code"
110
114
  }
111
115
  end
112
116
  end
@@ -114,34 +118,53 @@ module InertiaI18n
114
118
  def check_locale_sync
115
119
  primary_locale = @config.primary_locale
116
120
  primary_keys = Set.new(LocaleLoader.extract_keys(@locales[primary_locale] || {}))
121
+ normalized_primary = normalize_keys_for_sync(primary_keys)
117
122
 
118
123
  @config.secondary_locales.each do |locale|
119
124
  locale_data = @locales[locale] || {}
120
125
  locale_keys = Set.new(LocaleLoader.extract_keys(locale_data))
126
+ normalized_locale = normalize_keys_for_sync(locale_keys)
121
127
 
122
- missing_in_locale = primary_keys - locale_keys
123
- extra_in_locale = locale_keys - primary_keys
128
+ missing_in_locale = normalized_primary - normalized_locale
129
+ extra_in_locale = normalized_locale - normalized_primary
124
130
 
125
- missing_in_locale.each do |key|
131
+ missing_in_locale.each do |n_key|
132
+ display_key = n_key.sub("_[plural]", " (plural forms)")
126
133
  @issues[:unsync] << {
127
- key: key,
134
+ key: display_key,
128
135
  locale: locale,
129
136
  severity: :error,
130
- message: "Key '#{key}' exists in #{primary_locale}.json but missing from #{locale}.json"
137
+ message: "Key '#{display_key}' exists in #{primary_locale}.json but missing from #{locale}.json"
131
138
  }
132
139
  end
133
140
 
134
- extra_in_locale.each do |key|
141
+ extra_in_locale.each do |n_key|
142
+ display_key = n_key.sub("_[plural]", " (plural forms)")
135
143
  @issues[:unsync] << {
136
- key: key,
144
+ key: display_key,
137
145
  locale: locale,
138
146
  severity: :warning,
139
- message: "Key '#{key}' exists in #{locale}.json but missing from #{primary_locale}.json"
147
+ message: "Key '#{display_key}' exists in #{locale}.json but missing from #{primary_locale}.json"
140
148
  }
141
149
  end
142
150
  end
143
151
  end
144
152
 
153
+ def normalize_keys_for_sync(keys)
154
+ plural_suffixes = %w[_zero _one _two _few _many _other]
155
+
156
+ normalized = Set.new
157
+ keys.each do |key|
158
+ suffix = plural_suffixes.find { |s| key.end_with?(s) }
159
+ if suffix
160
+ normalized.add(key.chomp(suffix) + "_[plural]")
161
+ else
162
+ normalized.add(key)
163
+ end
164
+ end
165
+ normalized
166
+ end
167
+
145
168
  def filter_ignored_keys(keys, ignore_patterns)
146
169
  return keys if ignore_patterns.empty?
147
170
 
@@ -83,13 +83,15 @@ module InertiaI18n
83
83
  content.scan(/(?<!\w)#{escaped_func}\(\s*`([^`]*\$\{.+?)`/) do |match|
84
84
  template = match[0]
85
85
  prefix = template.split("${").first
86
- patterns << {pattern: prefix, type: :template_literal, raw: template}
86
+ line = content[0..Regexp.last_match.begin(0)].count("\n") + 1
87
+ patterns << {pattern: prefix, type: :template_literal, raw: template, line: line}
87
88
  end
88
89
 
89
90
  # Matches t('prefix.' + var) or t("prefix." + var) - string concatenation
90
91
  content.scan(/(?<!\w)#{escaped_func}\(\s*(['"])([^'"]+\.)\1\s*\+/) do |match|
91
92
  prefix = match[1]
92
- patterns << {pattern: prefix, type: :string_concat, raw: "#{prefix} + ..."}
93
+ line = content[0..Regexp.last_match.begin(0)].count("\n") + 1
94
+ patterns << {pattern: prefix, type: :string_concat, raw: "#{prefix} + ...", line: line}
93
95
  end
94
96
  end
95
97
 
@@ -26,7 +26,22 @@ module InertiaI18n
26
26
  end
27
27
  end
28
28
 
29
- @dynamic_patterns.concat(keys[:dynamic])
29
+ config = InertiaI18n.configuration
30
+ dynamic_keys_config = config.dynamic_keys || {}
31
+
32
+ keys[:dynamic].each do |dynamic|
33
+ pattern = dynamic[:pattern]
34
+ @dynamic_patterns << dynamic
35
+
36
+ if dynamic_keys_config.key?(pattern)
37
+ dynamic_keys_config[pattern].each do |value|
38
+ # Append value to pattern (e.g. "status." + "active" -> "status.active")
39
+ expanded_key = "#{pattern}#{value}"
40
+ @static_keys.add(expanded_key)
41
+ @occurrences[expanded_key] << {file: file, line: dynamic[:line]}
42
+ end
43
+ end
44
+ end
30
45
  end
31
46
 
32
47
  def used_keys
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module InertiaI18n
4
- VERSION = "0.6.2"
4
+ VERSION = "0.7.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inertia_i18n
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexey Poimtsev
@@ -198,7 +198,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
198
198
  - !ruby/object:Gem::Version
199
199
  version: '0'
200
200
  requirements: []
201
- rubygems_version: 4.0.6
201
+ rubygems_version: 4.0.7
202
202
  specification_version: 4
203
203
  summary: Translation management for Inertia.js applications
204
204
  test_files: []