inertia_i18n 0.2.0 → 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: bc3b1063cb9d177edbf18129f392d70249ae7d15afedf9a933eeeba81ae8c9b6
4
- data.tar.gz: 573079aff7dc0032cf2d40036c026474343c9fb221e198b593920870444cbac5
3
+ metadata.gz: f6aedf82d5304993ab58a1dc1b6f1fedee2bd08328be799da83d0bafd50cba32
4
+ data.tar.gz: cfe3ce966e7359e38c3ec0752f4c902735f9f60c79e33b9eac903ef002e41ed0
5
5
  SHA512:
6
- metadata.gz: 11c00ed36396cd3d20cd7938fc34d0b7e98b89f7eeaaa5d4576865ea390685f6c7c3ffd1deac376c08252317b4c716b88da4cc3d265d58a6870df05b5980bba4
7
- data.tar.gz: d245a9bb60931d7d75f416497ef578640db298e06d31d1eeb1670b140089dd74e29b52bf496fbeb0501fe3c1a8186531f4844832e7f4466d85fcbc3159450ffc
6
+ metadata.gz: 249f0dc540b501b336b89a3f69bb32e294a005f510cbfc7ae81c5c1b2c40a40184f046342c2286cbbf561c8eb76d452983a60859b45f066e77db9fffbe745e66
7
+ data.tar.gz: cf6145d5310f3162d36dcdcdabdcd9760e8088b5507f874ef6e6b10f97ad8a6584fa17d609e72158f39fd30af4ab9c997f4d310bbfe777cdda6632ae22a7d0fd
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2026-01-19
4
+
5
+ ### Added
6
+
7
+ - Configuration options for tuning health checks: `key_properties`, `sibling_detection`, and`missing_key_filters`
8
+ - Support for extracting translation keys from object properties (e.g., `titleKey: "some.key"`)
9
+ - Support for detecting string concatenation patterns as dynamic keys (e.g., `t('prefix.' + var)`)
10
+
11
+ ### Improved
12
+
13
+ - Health Checker now intelligently handles i18next plural suffixes:
14
+ - Missing check: `t('key', { count: n })` won't report missing if `key_one`/`key_other` exist
15
+ - Unused check: `key_one`/`key_other` won't report unused if base `key` is used in code
16
+ - Added "sibling detection" to automatically handle enum-like keys (e.g., if `status.open` is used, all`status.*` are considered used)
17
+ - Added default filters for false-positive missing keys (ignores short keys, keys without dots, URLs,constants, incomplete keys ending with `.`)
18
+
3
19
  ## [0.2.0] - 2026-01-18
4
20
 
5
21
  ### Added
@@ -35,6 +35,15 @@ module InertiaI18n
35
35
  # Keys to ignore during missing check
36
36
  attr_accessor :ignore_missing
37
37
 
38
+ # Object properties that contain translation keys (e.g., titleKey: "some.key")
39
+ attr_accessor :key_properties
40
+
41
+ # Sibling detection settings for enum-like keys (status, types, etc.)
42
+ attr_accessor :sibling_detection
43
+
44
+ # Filters for false-positive missing keys
45
+ attr_accessor :missing_key_filters
46
+
38
47
  def initialize
39
48
  @source_paths = ["config/locales/frontend"]
40
49
  @target_path = "app/frontend/locales"
@@ -52,6 +61,21 @@ module InertiaI18n
52
61
  @dynamic_patterns = {}
53
62
  @ignore_unused = []
54
63
  @ignore_missing = []
64
+ @key_properties = %w[titleKey labelKey messageKey descriptionKey placeholderKey key]
65
+ @sibling_detection = {
66
+ enabled: true,
67
+ suffixes: %w[status statuses types type priorities priority]
68
+ }
69
+ @missing_key_filters = {
70
+ min_length: 4,
71
+ require_dot: true,
72
+ exclude_patterns: [
73
+ /^\/[\w\/-]*$/, # URL paths (/hr/applications)
74
+ /^[A-Z_]+$/, # Constants (HTTP, POST)
75
+ /^\w+_id$/, # ID fields (user_id, parent_id)
76
+ /^[a-z]{2}(-[A-Z]{2})?$/ # Locales (en, ru-RU)
77
+ ]
78
+ }
55
79
  end
56
80
 
57
81
  def primary_locale
@@ -42,6 +42,12 @@ module InertiaI18n
42
42
 
43
43
  missing = used_keys - available_keys
44
44
 
45
+ # Apply built-in filters for false positives
46
+ missing = apply_missing_filters(missing)
47
+
48
+ # Filter out keys that have i18next plural suffixes (e.g., key_one, key_other)
49
+ missing = filter_plural_keys(missing, available_keys)
50
+
45
51
  # Filter out keys covered by ignore_missing config
46
52
  missing = filter_ignored_keys(missing, @config.ignore_missing)
47
53
 
@@ -72,6 +78,18 @@ module InertiaI18n
72
78
  dynamic_prefixes.any? { |prefix| key.start_with?(prefix) }
73
79
  end
74
80
 
81
+ # Apply sibling detection for enum-like keys (status, types, etc.)
82
+ if @config.sibling_detection[:enabled]
83
+ sibling_prefixes = detect_sibling_prefixes(used_keys)
84
+ unused = unused.reject do |key|
85
+ sibling_prefixes.any? { |prefix| key.start_with?(prefix) }
86
+ end
87
+ end
88
+
89
+ # Filter out i18next plural variant keys when base key is used
90
+ # e.g., if "key" is used, don't mark "key_one", "key_other" as unused
91
+ unused = filter_plural_variant_keys(unused, used_keys)
92
+
75
93
  # Filter out keys covered by ignore_unused config
76
94
  unused = filter_ignored_keys(unused, @config.ignore_unused)
77
95
 
@@ -128,5 +146,93 @@ module InertiaI18n
128
146
  end
129
147
  end
130
148
  end
149
+
150
+ # Filter out keys that have i18next pluralization suffixes present
151
+ # e.g., if "key_one" or "key_other" exists, consider "key" as present
152
+ def filter_plural_keys(keys, available_keys)
153
+ # i18next plural suffixes
154
+ plural_suffixes = %w[_zero _one _two _few _many _other]
155
+
156
+ keys.reject do |key|
157
+ plural_suffixes.any? do |suffix|
158
+ available_keys.include?("#{key}#{suffix}")
159
+ end
160
+ end
161
+ end
162
+
163
+ # Filter out i18next plural variant keys when base key is used
164
+ # e.g., if "key" is used in code, don't mark "key_one", "key_other" as unused
165
+ def filter_plural_variant_keys(keys, used_keys)
166
+ plural_suffixes = %w[_zero _one _two _few _many _other]
167
+
168
+ keys.reject do |key|
169
+ # Check if this key ends with a plural suffix
170
+ plural_suffixes.any? do |suffix|
171
+ next false unless key.end_with?(suffix)
172
+
173
+ # Extract base key and check if it's used
174
+ base_key = key.chomp(suffix)
175
+ used_keys.include?(base_key)
176
+ end
177
+ end
178
+ end
179
+
180
+ # Filter out false-positive missing keys based on configured filters
181
+ def apply_missing_filters(keys)
182
+ filters = @config.missing_key_filters
183
+ return keys if filters.nil? || filters.empty?
184
+
185
+ keys.reject do |key|
186
+ # Filter by minimum length
187
+ if filters[:min_length] && key.length < filters[:min_length]
188
+ next true
189
+ end
190
+
191
+ # Require at least one dot (namespace separator)
192
+ if filters[:require_dot] && !key.include?(".")
193
+ next true
194
+ end
195
+
196
+ # Filter out incomplete keys ending with dot (string concatenation patterns)
197
+ if key.end_with?(".")
198
+ next true
199
+ end
200
+
201
+ # Apply exclusion patterns
202
+ if filters[:exclude_patterns]
203
+ next true if filters[:exclude_patterns].any? { |p| key.match?(p) }
204
+ end
205
+
206
+ false
207
+ end
208
+ end
209
+
210
+ # Detect sibling prefixes from used keys for enum-like namespaces
211
+ # If code uses "hr.vacancies.status.open", consider all "hr.vacancies.status.*" as potentially used
212
+ def detect_sibling_prefixes(used_keys)
213
+ prefixes = Set.new
214
+ suffixes = @config.sibling_detection[:suffixes] || []
215
+
216
+ used_keys.each do |key|
217
+ parts = key.split(".")
218
+ next if parts.length < 2
219
+
220
+ # Check if any suffix matches a part of the key
221
+ suffixes.each do |suffix|
222
+ # Find index of suffix in key parts (handle both singular and plural)
223
+ idx = parts.index(suffix) ||
224
+ parts.index("#{suffix}s") ||
225
+ parts.index(suffix.chomp("s"))
226
+
227
+ # If found and there's at least one more segment after it
228
+ if idx && idx < parts.length - 1
229
+ prefix = parts[0..idx].join(".") + "."
230
+ prefixes.add(prefix)
231
+ end
232
+ end
233
+ end
234
+
235
+ prefixes.to_a
236
+ end
131
237
  end
132
238
  end
@@ -37,6 +37,14 @@ module InertiaI18n
37
37
  end
38
38
  end
39
39
 
40
+ # Extract keys from object properties (e.g., titleKey: "some.key")
41
+ config.key_properties.each do |prop|
42
+ # Matches: titleKey: "some.key" or titleKey: 'some.key'
43
+ content.scan(/#{Regexp.escape(prop)}\s*:\s*(['"])([^'"]+)\1/) do |_quote, key|
44
+ keys << key if looks_like_i18n_key?(key)
45
+ end
46
+ end
47
+
40
48
  keys
41
49
  end
42
50
 
@@ -49,18 +57,34 @@ module InertiaI18n
49
57
  escaped_func = Regexp.escape(func)
50
58
 
51
59
  # Matches t(`prefix.${var}`)
52
-
53
60
  content.scan(/(?<!\w)#{escaped_func}\(\s*`([^`]*\$\{.+?)`/) do |match|
54
61
  template = match[0]
55
-
56
62
  prefix = template.split("${").first
57
-
58
63
  patterns << {pattern: prefix, type: :template_literal, raw: template}
59
64
  end
65
+
66
+ # Matches t('prefix.' + var) or t("prefix." + var) - string concatenation
67
+ content.scan(/(?<!\w)#{escaped_func}\(\s*(['"])([^'"]+\.)\1\s*\+/) do |match|
68
+ prefix = match[1]
69
+ patterns << {pattern: prefix, type: :string_concat, raw: "#{prefix} + ..."}
70
+ end
60
71
  end
61
72
 
62
73
  patterns
63
74
  end
75
+
76
+ private
77
+
78
+ # Check if a string looks like an i18n key (has dots, proper length, not a URL)
79
+ def looks_like_i18n_key?(key)
80
+ return false if key.nil? || key.empty?
81
+ return false if key.length < 4
82
+ return false unless key.include?(".")
83
+ return false if key.start_with?("/")
84
+ return false if key.match?(/^https?:/)
85
+ return false if key.match?(/\s/) # Contains whitespace
86
+ true
87
+ end
64
88
  end
65
89
  end
66
90
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module InertiaI18n
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.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.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexey Poimtsev
@@ -121,6 +121,20 @@ dependencies:
121
121
  - - ">="
122
122
  - !ruby/object:Gem::Version
123
123
  version: '0'
124
+ - !ruby/object:Gem::Dependency
125
+ name: lefthook
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
124
138
  description: Convert Rails YAML locales to i18next JSON, scan frontend code for translation
125
139
  usage, detect missing/unused keys, and check locale synchronization. Supports Svelte,
126
140
  React, and Vue frontends.