inertia_i18n 0.5.0 → 0.6.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 +15 -0
- data/README.md +30 -1
- data/exe/inertia-i18n +1 -0
- data/lib/inertia_i18n/cli.rb +13 -0
- data/lib/inertia_i18n/health_checker.rb +10 -1
- data/lib/inertia_i18n/i18n_tasks/scanner.rb +39 -0
- data/lib/inertia_i18n/parsers/javascript_parsing.rb +30 -11
- data/lib/inertia_i18n/parsers/vue_parser.rb +2 -2
- data/lib/inertia_i18n/scan_results.rb +17 -3
- data/lib/inertia_i18n/scanner.rb +6 -6
- data/lib/inertia_i18n/version.rb +1 -1
- data/lib/inertia_i18n.rb +4 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 34e9a2d18070430f9210ad477789b8d179c7feb234d9de11f5a6358ce4eda012
|
|
4
|
+
data.tar.gz: 95bb612b926a1a9eaf6b7cbe2d86fb53dfd06dcc62d9b8d7be8f7a70c5a66057
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2ac2dc6afe2112afdb8cac7a14a11b55880964d0ea86aab494fbb31350096ce502caa64b28b7b9570faa3d833cdadb8b7c1be3a4f95cb382d065c91244103954
|
|
7
|
+
data.tar.gz: 62a53052bfb31864001da174ed667ad6fbdab6d36461b47726e373515a621c7c98d8d45d68ee7dd82931f0d7373acf920c1a967230f6383624c09e0b48888029
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.6.0] - 2026-02-09
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Context Awareness**: `missing` and `unused` commands now report the file path and line number where the key was found (e.g., `app/frontend/User.vue:42`).
|
|
8
|
+
- **Magic Comments**: Support for `// inertia-i18n-use key.name` and `/* inertia-i18n-use key.name */` to mark dynamic keys as used.
|
|
9
|
+
- **i18n-tasks Compatibility**:
|
|
10
|
+
- Support for `// i18n-tasks-use key.name` comments (interoperability).
|
|
11
|
+
- `InertiaI18n::I18nTasks::Scanner` adapter class to allow `i18n-tasks` to use `inertia_i18n`'s frontend scanning logic.
|
|
12
|
+
- **Robust CLI**: `inertia-i18n` command now correctly loads the library in non-Rails environments.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Scanner adapter now returns data in the format `i18n-tasks` expects (including source location).
|
|
17
|
+
|
|
3
18
|
## [0.5.0] - 2026-02-06
|
|
4
19
|
|
|
5
20
|
### Added
|
data/README.md
CHANGED
|
@@ -231,6 +231,16 @@ Handles:
|
|
|
231
231
|
- Template literals: `t(\`user.\${type}.title\`)` (flagged for review)
|
|
232
232
|
- Dynamic patterns: `t(keyVariable)` (flagged for review)
|
|
233
233
|
|
|
234
|
+
### Magic Comments
|
|
235
|
+
|
|
236
|
+
Use magic comments to mark dynamic keys as used or suppress warnings. Both `inertia-i18n-use` and `i18n-tasks-use` are supported.
|
|
237
|
+
|
|
238
|
+
```javascript
|
|
239
|
+
// inertia-i18n-use status.active
|
|
240
|
+
// inertia-i18n-use status.inactive
|
|
241
|
+
const statusLabel = t(`status.${status}`)
|
|
242
|
+
```
|
|
243
|
+
|
|
234
244
|
### Health Checks
|
|
235
245
|
|
|
236
246
|
| Check | Description |
|
|
@@ -328,7 +338,26 @@ jobs:
|
|
|
328
338
|
|
|
329
339
|
## Compatibility with i18n-tasks
|
|
330
340
|
|
|
331
|
-
|
|
341
|
+
InertiaI18n works seamlessly with [i18n-tasks](https://github.com/glebm/i18n-tasks).
|
|
342
|
+
|
|
343
|
+
### 1. Integrating Scanners (Recommended)
|
|
344
|
+
|
|
345
|
+
You can configure `i18n-tasks` to use the `inertia_i18n` scanner. This allows `i18n-tasks` to correctly detect frontend key usage, so you can use `i18n-tasks unused` for your entire project (backend + frontend).
|
|
346
|
+
|
|
347
|
+
Add this to your `config/i18n-tasks.yml`:
|
|
348
|
+
|
|
349
|
+
```yaml
|
|
350
|
+
<% require 'inertia_i18n/i18n_tasks/scanner' %>
|
|
351
|
+
search:
|
|
352
|
+
scanners:
|
|
353
|
+
- InertiaI18n::I18nTasks::Scanner
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Now `i18n-tasks` will "see" keys used in your Vue/React/Svelte files.
|
|
357
|
+
|
|
358
|
+
### 2. Separating Concerns
|
|
359
|
+
|
|
360
|
+
If you prefer to keep them separate, configure `i18n-tasks` to ignore the frontend locale directory so it doesn't flag frontend keys as "unused" or "missing".
|
|
332
361
|
|
|
333
362
|
Add this to your `config/i18n-tasks.yml`:
|
|
334
363
|
|
data/exe/inertia-i18n
CHANGED
data/lib/inertia_i18n/cli.rb
CHANGED
|
@@ -363,7 +363,20 @@ module InertiaI18n
|
|
|
363
363
|
if verbose
|
|
364
364
|
issues.each do |issue|
|
|
365
365
|
puts " - #{issue[:key]}"
|
|
366
|
+
if issue[:occurrences]&.any?
|
|
367
|
+
issue[:occurrences].each do |loc|
|
|
368
|
+
puts " ↳ #{loc[:file]}:#{loc[:line]}"
|
|
369
|
+
end
|
|
370
|
+
end
|
|
366
371
|
end
|
|
372
|
+
else
|
|
373
|
+
# In non-verbose mode, show first occurrence for context
|
|
374
|
+
issues.take(5).each do |issue|
|
|
375
|
+
loc = issue[:occurrences]&.first
|
|
376
|
+
loc_str = loc ? " (#{loc[:file]}:#{loc[:line]})" : ""
|
|
377
|
+
puts " - #{issue[:key]}#{loc_str}"
|
|
378
|
+
end
|
|
379
|
+
puts " ... and #{issues.count - 5} more (use --verbose to see all)" if issues.count > 5
|
|
367
380
|
end
|
|
368
381
|
puts
|
|
369
382
|
end
|
|
@@ -52,10 +52,19 @@ module InertiaI18n
|
|
|
52
52
|
missing = filter_ignored_keys(missing, @config.ignore_missing)
|
|
53
53
|
|
|
54
54
|
missing.each do |key|
|
|
55
|
+
locations = @scan_results.occurrences[key]
|
|
56
|
+
message = "Key '#{key}' is used in code but missing from #{primary_locale}.json"
|
|
57
|
+
|
|
58
|
+
if locations&.any?
|
|
59
|
+
first_loc = locations.first
|
|
60
|
+
message += " (found in #{first_loc[:file]}:#{first_loc[:line]})"
|
|
61
|
+
end
|
|
62
|
+
|
|
55
63
|
@issues[:missing] << {
|
|
56
64
|
key: key,
|
|
57
65
|
severity: :error,
|
|
58
|
-
message:
|
|
66
|
+
message: message,
|
|
67
|
+
occurrences: locations
|
|
59
68
|
}
|
|
60
69
|
end
|
|
61
70
|
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "i18n/tasks/scanners/file_scanner"
|
|
4
|
+
require "i18n/tasks/scanners/results/occurrence"
|
|
5
|
+
|
|
6
|
+
module InertiaI18n
|
|
7
|
+
module I18nTasks
|
|
8
|
+
class Scanner < ::I18n::Tasks::Scanners::FileScanner
|
|
9
|
+
def scan_file(path)
|
|
10
|
+
# Scan file using InertiaI18n logic
|
|
11
|
+
scanner = InertiaI18n::Scanner.new
|
|
12
|
+
parser = scanner.parser_for_file(path)
|
|
13
|
+
return [] unless parser
|
|
14
|
+
return [] if parser.instance_of?(InertiaI18n::Parsers::BaseParser)
|
|
15
|
+
|
|
16
|
+
# Extract keys: {static: [{key: 'foo', line: 1}], dynamic: [...]}
|
|
17
|
+
results = parser.extract_keys(path)
|
|
18
|
+
|
|
19
|
+
# Convert to i18n-tasks format: [ [key, Occurrence] ]
|
|
20
|
+
results[:static].map do |usage|
|
|
21
|
+
key = usage[:key]
|
|
22
|
+
|
|
23
|
+
# Return the format i18n-tasks expects for key occurrences
|
|
24
|
+
[
|
|
25
|
+
key,
|
|
26
|
+
::I18n::Tasks::Scanners::Results::Occurrence.new(
|
|
27
|
+
path: path,
|
|
28
|
+
line_num: usage[:line],
|
|
29
|
+
pos: 1,
|
|
30
|
+
line_pos: 1,
|
|
31
|
+
line: "",
|
|
32
|
+
raw_key: key
|
|
33
|
+
)
|
|
34
|
+
]
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -9,11 +9,33 @@ module InertiaI18n
|
|
|
9
9
|
|
|
10
10
|
def extract_javascript_keys(content)
|
|
11
11
|
{
|
|
12
|
-
static: extract_static_keys(content).uniq,
|
|
12
|
+
static: (extract_static_keys(content) + extract_magic_comments(content)).uniq { |k| k[:key] },
|
|
13
13
|
dynamic: extract_dynamic_patterns(content).uniq { |p| p[:pattern] }
|
|
14
14
|
}
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
def extract_magic_comments(content)
|
|
18
|
+
keys = []
|
|
19
|
+
|
|
20
|
+
# Regex to match both 'inertia-i18n-use' and 'i18n-tasks-use'
|
|
21
|
+
# Group 1: key
|
|
22
|
+
comment_pattern = /(?:inertia-i18n-use|i18n-tasks-use)\s+([a-zA-Z0-9_.]+)/
|
|
23
|
+
|
|
24
|
+
# Single line comments: // inertia-i18n-use key.name
|
|
25
|
+
content.scan(%r{//\s*#{comment_pattern}}) do |match|
|
|
26
|
+
line = content[0..Regexp.last_match.begin(0)].count("\n") + 1
|
|
27
|
+
keys << {key: match[0], line: line}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Block comments: /* inertia-i18n-use key.name */
|
|
31
|
+
content.scan(%r{/\*\s*#{comment_pattern}\s*\*/}) do |match|
|
|
32
|
+
line = content[0..Regexp.last_match.begin(0)].count("\n") + 1
|
|
33
|
+
keys << {key: match[0], line: line}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
keys
|
|
37
|
+
end
|
|
38
|
+
|
|
17
39
|
def extract_static_keys(content)
|
|
18
40
|
keys = []
|
|
19
41
|
|
|
@@ -23,25 +45,22 @@ module InertiaI18n
|
|
|
23
45
|
escaped_func = Regexp.escape(func)
|
|
24
46
|
|
|
25
47
|
# Matches t('key'), t("key"), t(`key`)
|
|
26
|
-
|
|
27
|
-
# Uses a lookahead to ensure it's a translation function call
|
|
28
|
-
# Uses lookbehind (?<![\w]) to ensure we don't match end of other words (e.g. split)
|
|
29
48
|
content.scan(/(?<!\w)#{escaped_func}\(\s*(['"`])([^'"`]+)\1/) do |match|
|
|
30
49
|
quote_type, key = match
|
|
31
|
-
|
|
32
|
-
# If it's a backtick, ensure it's not a template literal with interpolation
|
|
33
|
-
|
|
34
50
|
next if quote_type == "`" && key.include?("${")
|
|
35
51
|
|
|
36
|
-
|
|
52
|
+
line = content[0..Regexp.last_match.begin(0)].count("\n") + 1
|
|
53
|
+
keys << {key: key, line: line}
|
|
37
54
|
end
|
|
38
55
|
end
|
|
39
56
|
|
|
40
|
-
# Extract keys from object properties
|
|
57
|
+
# Extract keys from object properties
|
|
41
58
|
config.key_properties.each do |prop|
|
|
42
|
-
# Matches: titleKey: "some.key" or titleKey: 'some.key'
|
|
43
59
|
content.scan(/#{Regexp.escape(prop)}\s*:\s*(['"])([^'"]+)\1/) do |_quote, key|
|
|
44
|
-
|
|
60
|
+
if looks_like_i18n_key?(key)
|
|
61
|
+
line = content[0..Regexp.last_match.begin(0)].count("\n") + 1
|
|
62
|
+
keys << {key: key, line: line}
|
|
63
|
+
end
|
|
45
64
|
end
|
|
46
65
|
end
|
|
47
66
|
|
|
@@ -31,9 +31,9 @@ module InertiaI18n
|
|
|
31
31
|
keys = []
|
|
32
32
|
|
|
33
33
|
# v-t directive: v-t="'key'" or v-t='"key"'
|
|
34
|
-
|
|
35
34
|
content.scan(/v-t\s*=\s*(["'])['"]([^'"]+)['"]\1/) do |match|
|
|
36
|
-
|
|
35
|
+
line = content[0..Regexp.last_match.begin(0)].count("\n") + 1
|
|
36
|
+
keys << {key: match[1], line: line}
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
keys
|
|
@@ -2,17 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
module InertiaI18n
|
|
4
4
|
class ScanResults
|
|
5
|
-
attr_reader :files, :static_keys, :dynamic_patterns
|
|
5
|
+
attr_reader :files, :static_keys, :dynamic_patterns, :occurrences
|
|
6
6
|
|
|
7
7
|
def initialize
|
|
8
8
|
@files = {}
|
|
9
9
|
@static_keys = Set.new
|
|
10
10
|
@dynamic_patterns = []
|
|
11
|
+
@occurrences = Hash.new { |h, k| h[k] = [] }
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
def add_file(file, keys)
|
|
14
15
|
@files[file] = keys
|
|
15
|
-
|
|
16
|
+
|
|
17
|
+
keys[:static].each do |usage|
|
|
18
|
+
# Handle both legacy string keys and new usage objects
|
|
19
|
+
if usage.is_a?(Hash)
|
|
20
|
+
key = usage[:key]
|
|
21
|
+
@static_keys.add(key)
|
|
22
|
+
@occurrences[key] << {file: file, line: usage[:line]}
|
|
23
|
+
else
|
|
24
|
+
@static_keys.add(usage)
|
|
25
|
+
@occurrences[usage] << {file: file, line: nil}
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
16
29
|
@dynamic_patterns.concat(keys[:dynamic])
|
|
17
30
|
end
|
|
18
31
|
|
|
@@ -24,7 +37,8 @@ module InertiaI18n
|
|
|
24
37
|
{
|
|
25
38
|
files: @files,
|
|
26
39
|
static_keys: @static_keys.to_a,
|
|
27
|
-
dynamic_patterns: @dynamic_patterns
|
|
40
|
+
dynamic_patterns: @dynamic_patterns,
|
|
41
|
+
occurrences: @occurrences
|
|
28
42
|
}
|
|
29
43
|
end
|
|
30
44
|
end
|
data/lib/inertia_i18n/scanner.rb
CHANGED
|
@@ -18,12 +18,6 @@ module InertiaI18n
|
|
|
18
18
|
results
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
private
|
|
22
|
-
|
|
23
|
-
def frontend_files
|
|
24
|
-
@config.scan_paths.flat_map { |pattern| Dir.glob(pattern) }
|
|
25
|
-
end
|
|
26
|
-
|
|
27
21
|
def parser_for_file(file)
|
|
28
22
|
ext = File.extname(file).downcase
|
|
29
23
|
|
|
@@ -40,5 +34,11 @@ module InertiaI18n
|
|
|
40
34
|
Parsers::BaseParser.new
|
|
41
35
|
end
|
|
42
36
|
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def frontend_files
|
|
41
|
+
@config.scan_paths.flat_map { |pattern| Dir.glob(pattern) }
|
|
42
|
+
end
|
|
43
43
|
end
|
|
44
44
|
end
|
data/lib/inertia_i18n/version.rb
CHANGED
data/lib/inertia_i18n.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.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alexey Poimtsev
|
|
@@ -161,6 +161,7 @@ files:
|
|
|
161
161
|
- lib/inertia_i18n/converter.rb
|
|
162
162
|
- lib/inertia_i18n/file_converter.rb
|
|
163
163
|
- lib/inertia_i18n/health_checker.rb
|
|
164
|
+
- lib/inertia_i18n/i18n_tasks/scanner.rb
|
|
164
165
|
- lib/inertia_i18n/locale_loader.rb
|
|
165
166
|
- lib/inertia_i18n/normalizer.rb
|
|
166
167
|
- lib/inertia_i18n/parsers/base_parser.rb
|