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 +4 -4
- data/CHANGELOG.md +16 -0
- data/lib/inertia_i18n/configuration.rb +24 -0
- data/lib/inertia_i18n/health_checker.rb +106 -0
- data/lib/inertia_i18n/parsers/javascript_parsing.rb +27 -3
- data/lib/inertia_i18n/version.rb +1 -1
- metadata +15 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f6aedf82d5304993ab58a1dc1b6f1fedee2bd08328be799da83d0bafd50cba32
|
|
4
|
+
data.tar.gz: cfe3ce966e7359e38c3ec0752f4c902735f9f60c79e33b9eac903ef002e41ed0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
data/lib/inertia_i18n/version.rb
CHANGED
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.
|
|
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.
|