inertia_i18n 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c5d21515efb4733bc47d4a1f7b4964768893478366e101bfd4209c4cd45f3fee
4
+ data.tar.gz: fd44f198a5d66b793459eac5ab9b0b43487e3c4d4abc9b5f1e2145a995ceacd3
5
+ SHA512:
6
+ metadata.gz: a54935ae8c875ba3b9baf3fadab5bc7033150da97eb72ed7c453b7dd14e315809df3cfc7ac58cf5f34eb0a426b1343a9cfe206da45e897a93e4ac20edc957b12
7
+ data.tar.gz: 02f8b5b053cf02be28186c79a137935d83fd55c82c7e27f1c7a9531c46803e16a5aab8d8748f4c46576800f7322ce755c99154d26ae2c350360d7efbf95f6011
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2026-01-06
4
+
5
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Alexey Poimtsev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,418 @@
1
+ # InertiaI18n
2
+
3
+ Translation management for Inertia.js applications with Rails backend
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/inertia_i18n.svg)](https://badge.fury.io/rb/inertia_i18n)
6
+ [![Build Status](https://github.com/alec-c4/inertia_i18n/workflows/CI/badge.svg)](https://github.com/alec-c4/inertia_i18n/actions)
7
+
8
+ ## The Problem
9
+
10
+ Inertia.js applications have a split architecture:
11
+
12
+ - **Backend (Rails):** Uses YAML locale files (`config/locales/*.yml`)
13
+ - **Frontend (React/Svelte/Vue):** Uses i18next JSON files
14
+
15
+ This creates several challenges:
16
+
17
+ 1. **Duplicate management:** Maintaining translations in two formats
18
+ 2. **Sync issues:** Keys in YAML but missing in JSON (or vice versa)
19
+ 3. **No usage tracking:** Unused translation keys accumulate
20
+ 4. **Manual process:** Converting YAML → JSON by hand is error-prone
21
+
22
+ Existing tools like [i18n-tasks](https://github.com/glebm/i18n-tasks) only handle Rails/backend translations.
23
+
24
+ ## The Solution
25
+
26
+ InertiaI18n provides:
27
+
28
+ - **YAML → JSON conversion** with interpolation mapping (`%{var}` → `{{var}}`)
29
+ - **AST-based scanning** to find translation usage in `.svelte`, `.tsx`, `.vue` files
30
+ - **Health checks** to detect missing, unused, and unsynchronized keys
31
+ - **Watch mode** for automatic regeneration during development
32
+ - **Rails integration** via initializers and rake tasks
33
+
34
+ **One source of truth:** Rails YAML files, with JSON auto-generated.
35
+
36
+ ---
37
+
38
+ ## Installation
39
+
40
+ Add to your Gemfile:
41
+
42
+ ```ruby
43
+ gem 'inertia_i18n'
44
+ ```
45
+
46
+ Run the installer:
47
+
48
+ ```bash
49
+ rails generate inertia_i18n:install
50
+ ```
51
+
52
+ This generator will:
53
+
54
+ 1. Create the locale directory structure (`config/locales/frontend`, `config/locales/backend`).
55
+ 2. Generate the configuration file (`config/initializers/inertia_i18n.rb`).
56
+ 3. Create a sample locale file.
57
+ 4. Detect your frontend framework (React, Vue, or Svelte) and add necessary dependencies (e.g., `react-i18next`) to your `package.json`.
58
+
59
+ ---
60
+
61
+ ## Recommended Directory Structure
62
+
63
+ To avoid conflicts between backend and frontend translation keys, it is recommended to separate your locale files into subdirectories:
64
+
65
+ ```
66
+ config/
67
+ └── locales/
68
+ ├── backend/ # Rails-specific translations
69
+ │ ├── en.yml
70
+ │ └── ru.yml
71
+ ├── frontend/ # Frontend-specific translations
72
+ │ ├── common.en.yml
73
+ │ ├── pages.en.yml
74
+ │ └── pages.ru.yml
75
+ └── en.yml # Optional: shared or legacy keys
76
+ ```
77
+
78
+ By default, InertiaI18n will look for YAML files in `config/locales/frontend`. You can customize this using the `source_paths` configuration.
79
+
80
+ ---
81
+
82
+ ## Quick Start
83
+
84
+ ### 1. Configure
85
+
86
+ The installer creates a default configuration file. You can customize it in `config/initializers/inertia_i18n.rb`.
87
+
88
+ ```ruby
89
+ # config/initializers/inertia_i18n.rb
90
+ InertiaI18n.configure do |config|
91
+ # Recommended: point to a dedicated frontend folder
92
+ config.source_paths = [Rails.root.join('config', 'locales', 'frontend')]
93
+
94
+ config.target_path = Rails.root.join('app', 'frontend', 'locales')
95
+ config.locales = [:en, :ru]
96
+
97
+ # Scan paths are automatically set based on your detected framework
98
+ config.scan_paths = [
99
+ Rails.root.join('app', 'frontend', '**', '*.{svelte,tsx,vue}')
100
+ ]
101
+ end
102
+ ```
103
+
104
+ ### 2. Convert YAML to JSON
105
+
106
+ ```bash
107
+ # One-time conversion
108
+ bundle exec rake inertia_i18n:convert
109
+
110
+ # Watch mode (auto-convert on YAML changes)
111
+ bundle exec rake inertia_i18n:watch
112
+ ```
113
+
114
+ ### 3. Check Translation Health
115
+
116
+ The recommended way to check translation health is by running the generated test as part of your test suite. See the [CI Integration](#ci-integration) section for details.
117
+
118
+ You can also run a manual check from the command line:
119
+
120
+ ```bash
121
+ # Find missing, unused, and out-of-sync keys
122
+ bundle exec rake inertia_i18n:health
123
+ ```
124
+
125
+ ---
126
+
127
+ ## CLI Usage
128
+
129
+ All CLI commands load the Rails environment, so they have access to your application's configuration and behave identically to the `rake` tasks.
130
+
131
+ ```bash
132
+ # Generate a new configuration file
133
+ inertia_i18n init
134
+
135
+ # Convert YAML to JSON
136
+ inertia_i18n convert
137
+
138
+ # Convert specific locale
139
+ inertia_i18n convert --locale=ru
140
+
141
+ # Scan frontend code for translation usage
142
+ inertia_i18n scan
143
+
144
+ # Check translation health
145
+ inertia_i18n health
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Features
151
+
152
+ ### YAML → JSON Conversion
153
+
154
+ **Input (Rails YAML):**
155
+
156
+ ```yaml
157
+ # config/locales/en.yml
158
+ en:
159
+ user:
160
+ greeting: "Hello, %{name}!"
161
+ items:
162
+ one: "1 item"
163
+ other: "%{count} items"
164
+ ```
165
+
166
+ **Output (i18next JSON):**
167
+
168
+ ```json
169
+ {
170
+ "user": {
171
+ "greeting": "Hello, {{name}}!",
172
+ "items_one": "1 item",
173
+ "items_other": "{{count}} items"
174
+ }
175
+ }
176
+ ```
177
+
178
+ ### Smart Scanning
179
+
180
+ Detects translation usage in:
181
+
182
+ - Svelte: `{t('key')}` and `t('key')` in `<script>`
183
+ - React: `{t('key')}` in JSX
184
+ - Vue: `{{ t('key') }}` and `t('key')` in script
185
+
186
+ Handles:
187
+
188
+ - Static keys: `t('user.greeting')`
189
+ - Template literals: `t(\`user.\${type}.title\`)` (flagged for review)
190
+ - Dynamic patterns: `t(keyVariable)` (flagged for review)
191
+
192
+ ### Health Checks
193
+
194
+ | Check | Description |
195
+ | ---------------- | ------------------------------------------------ |
196
+ | **Missing Keys** | Used in code but not in JSON (breaks app) |
197
+ | **Unused Keys** | In JSON but never used (bloat) |
198
+ | **Locale Sync** | Key exists in `en.json` but missing in `ru.json` |
199
+
200
+ ### Watch Mode
201
+
202
+ Auto-regenerates JSON when YAML files change:
203
+
204
+ ```bash
205
+ bundle exec rake inertia_i18n:watch
206
+
207
+ # Output:
208
+ 👀 Watching config/locales for YAML changes...
209
+ 📝 Detected locale file changes...
210
+ Changed: config/locales/hr.en.yml
211
+ 🔄 Regenerating JSON files...
212
+ ✅ Done!
213
+ ```
214
+
215
+ ---
216
+
217
+ ## CI Integration
218
+
219
+ The best way to ensure your translations stay healthy is to check them in your Continuous Integration (CI) pipeline.
220
+
221
+ ### Test-based Health Check (Recommended)
222
+
223
+ Generate a dedicated test file that runs the health check as part of your test suite:
224
+
225
+ ```bash
226
+ # For RSpec
227
+ rails g inertia_i18n:test
228
+ # Creates spec/inertia_i18n_health_spec.rb
229
+
230
+ # For Minitest
231
+ # Creates test/inertia_i18n_health_test.rb
232
+ ```
233
+
234
+ Now, your existing CI command will automatically catch translation issues:
235
+
236
+ ```bash
237
+ # Run your full test suite
238
+ bundle exec rspec
239
+ # or
240
+ bundle exec rails test
241
+ ```
242
+
243
+ When issues are found, the test will fail with a detailed report:
244
+
245
+ ```
246
+ Failure/Error: fail message.join("\n")
247
+
248
+ RuntimeError:
249
+
250
+ Translation health check failed!
251
+
252
+ Missing Keys (1):
253
+ - home.title
254
+
255
+ Unused Keys (1):
256
+ - unused.key
257
+
258
+ Locale Synchronization Issues (2):
259
+ - unused.key (in ru)
260
+ - home.title (in ru)
261
+ ```
262
+
263
+ ### GitHub Actions Example
264
+
265
+ ```yaml
266
+ # .github/workflows/ci.yml
267
+ name: CI
268
+
269
+ on: [push, pull_request]
270
+
271
+ jobs:
272
+ test:
273
+ runs-on: ubuntu-latest
274
+ steps:
275
+ - uses: actions/checkout@v3
276
+ - uses: ruby/setup-ruby@v1
277
+ with:
278
+ bundler-cache: true
279
+
280
+ # Run the full test suite, which now includes the translation health check
281
+ - name: Run tests
282
+ run: bundle exec rspec
283
+ ```
284
+
285
+ ---
286
+
287
+ ## Compatibility with i18n-tasks
288
+
289
+ If you use [i18n-tasks](https://github.com/glebm/i18n-tasks) for your backend translations, it might flag your frontend keys as "unused" or "missing". To prevent this, configure `i18n-tasks` to ignore the frontend locale directory.
290
+
291
+ Add this to your `config/i18n-tasks.yml`:
292
+
293
+ ```yaml
294
+ # config/i18n-tasks.yml
295
+ data:
296
+ read:
297
+ - "config/locales/backend/**/*.yml" # Read only backend locales
298
+ - "config/locales/*.yml" # Optional: shared keys
299
+ ```
300
+
301
+ Alternatively, you can exclude the frontend directory:
302
+
303
+ ```yaml
304
+ # config/i18n-tasks.yml
305
+ ignore:
306
+ - "frontend.*" # Ignore all keys starting with "frontend." (if namespaced)
307
+ ```
308
+
309
+ ---
310
+
311
+ ## Configuration Reference
312
+
313
+ ```ruby
314
+ InertiaI18n.configure do |config|
315
+ # Source directories for your frontend YAML files.
316
+ # Default: ['config/locales/frontend']
317
+ config.source_paths = [
318
+ 'config/locales/frontend',
319
+ 'config/locales/common'
320
+ ]
321
+ config.source_pattern = '**/*.{yml,yaml}'
322
+
323
+ # Target: i18next JSON files
324
+ config.target_path = 'app/frontend/locales'
325
+
326
+ # Locales to process
327
+ config.locales = [:en, :ru, :de]
328
+
329
+ # Frontend paths to scan
330
+ config.scan_paths = [
331
+ 'app/frontend/**/*.{js,ts,jsx,tsx,svelte,vue}'
332
+ ]
333
+
334
+ # Interpolation conversion
335
+ config.interpolation = { from: '%{', to: '{{' }
336
+
337
+ # Flatten nested keys (default: false)
338
+ config.flatten_keys = false
339
+
340
+ # Ignore patterns (don't scan these files)
341
+ config.ignore_patterns = [
342
+ '**/node_modules/**',
343
+ '**/vendor/**',
344
+ '**/*.test.{js,ts}'
345
+ ]
346
+ end
347
+ ```
348
+
349
+ ---
350
+
351
+ ## Comparison with Alternatives
352
+
353
+ | Feature | InertiaI18n | i18n-tasks | i18next-parser |
354
+ | ----------------------- | ----------- | ----------------- | -------------------- |
355
+ | Rails YAML support | ✅ | ✅ | ❌ |
356
+ | i18next JSON support | ✅ | ❌ | ✅ |
357
+ | YAML → JSON conversion | ✅ | ❌ | ❌ |
358
+ | Frontend usage scanning | ✅ | ❌ | ✅ (extraction only) |
359
+ | Missing keys detection | ✅ | ✅ (backend only) | ✅ (frontend only) |
360
+ | Unused keys detection | ✅ | ✅ (backend only) | ❌ |
361
+ | Locale sync check | ✅ | ✅ | ✅ |
362
+ | Watch mode | ✅ | ❌ | ✅ |
363
+ | Rails integration | ✅ | ✅ | ❌ |
364
+ | Inertia.js specific | ✅ | ❌ | ❌ |
365
+
366
+ **InertiaI18n = i18n-tasks + i18next-parser + YAML↔JSON bridge**
367
+
368
+ ---
369
+
370
+ ## Development
371
+
372
+ ```bash
373
+ # Clone repository
374
+ git clone https://github.com/alec-c4/inertia_i18n.git
375
+ cd inertia_i18n
376
+
377
+ # Install dependencies
378
+ bundle install
379
+
380
+ # Run tests
381
+ bundle exec rspec
382
+
383
+ # Run locally in your Rails app
384
+ # Add to Gemfile:
385
+ gem 'inertia_i18n', path: '../inertia_i18n'
386
+ ```
387
+
388
+ ---
389
+
390
+ ## Contributing
391
+
392
+ 1. Fork the repository
393
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
394
+ 3. Write tests (TDD approach)
395
+ 4. Implement the feature
396
+ 5. Commit your changes (`git commit -m 'Add amazing feature'`)
397
+ 6. Push to the branch (`git push origin feature/amazing-feature`)
398
+ 7. Open a Pull Request
399
+
400
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
401
+
402
+ ---
403
+
404
+ ## License
405
+
406
+ MIT License - see [LICENSE.txt](LICENSE.txt)
407
+
408
+ ---
409
+
410
+ ## Credits
411
+
412
+ Created by [Alexey Poimtsev](https://alec-c4.com)
413
+
414
+ Inspired by:
415
+
416
+ - [i18n-tasks](https://github.com/glebm/i18n-tasks) - Rails i18n management
417
+ - [i18next-parser](https://github.com/i18next/i18next-parser) - Frontend key extraction
418
+ - Real-world pain from managing translations in Inertia.js apps
data/exe/inertia_i18n ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "zeitwerk"
4
+ loader = Zeitwerk::Loader.new
5
+ loader.push_dir(File.expand_path("../lib", __dir__))
6
+ loader.setup
7
+
8
+ # Load Rails environment if available
9
+ if File.exist?("config/environment.rb")
10
+ require "rails"
11
+ require File.expand_path("config/environment.rb")
12
+ end
13
+
14
+ require "inertia_i18n/cli"
15
+
16
+ InertiaI18n::Cli.start(ARGV)
@@ -0,0 +1,102 @@
1
+ require "rails/generators"
2
+ require "json"
3
+
4
+ module InertiaI18n
5
+ module Generators
6
+ class InstallGenerator < Rails::Generators::Base
7
+ source_root File.expand_path("templates", __dir__)
8
+
9
+ desc "Installs InertiaI18n, creates directory structure, and adds frontend dependencies."
10
+
11
+ def check_dependencies
12
+ @framework = detect_framework
13
+ end
14
+
15
+ def create_directory_structure
16
+ say "Creating locale directory structure...", :green
17
+ empty_directory "config/locales/frontend"
18
+ empty_directory "config/locales/backend"
19
+ empty_directory "app/frontend/locales"
20
+ end
21
+
22
+ def move_existing_locales
23
+ # Move standard Rails locales to backend if they exist in the root
24
+ %w[en.yml ru.yml].each do |file|
25
+ source = "config/locales/#{file}"
26
+ if File.exist?(source)
27
+ say "Moving #{file} to config/locales/backend/", :yellow
28
+ rename_file source, "config/locales/backend/#{file}"
29
+ end
30
+ end
31
+ end
32
+
33
+ def create_sample_locales
34
+ copy_file "common.en.yml", "config/locales/frontend/common.en.yml"
35
+ end
36
+
37
+ def create_initializer
38
+ template "inertia_i18n.rb.tt", "config/initializers/inertia_i18n.rb"
39
+ end
40
+
41
+ def add_frontend_dependencies
42
+ packages_to_add = ["i18next"]
43
+
44
+ case @framework
45
+ when :react
46
+ say "Detected React. Adding react-i18next...", :green
47
+ packages_to_add << "react-i18next"
48
+ when :vue
49
+ say "Detected Vue. Adding i18next-vue...", :green
50
+ packages_to_add << "i18next-vue"
51
+ when :svelte
52
+ say "Detected Svelte.", :green
53
+ end
54
+
55
+ return unless File.exist?("package.json")
56
+
57
+ # Detect package manager
58
+ cmd = if File.exist?("yarn.lock")
59
+ "yarn add"
60
+ elsif File.exist?("bun.lockb")
61
+ "bun add"
62
+ else
63
+ "npm install"
64
+ end
65
+
66
+ run "#{cmd} #{packages_to_add.join(" ")}"
67
+ end
68
+
69
+ def show_post_install_info
70
+ say "\n✅ InertiaI18n installed successfully!", :green
71
+ say "\nNext steps:", :bold
72
+ say "1. Import and initialize i18next in your frontend application (e.g., app/frontend/i18n.js)."
73
+ say "2. Use the generated locales in config/locales/frontend/."
74
+ say "3. Run `bundle exec inertia_i18n convert` to generate the initial JSON files."
75
+ say "4. Start your server and happy translating!\n\n"
76
+ end
77
+
78
+ private
79
+
80
+ def detect_framework
81
+ return unless File.exist?("package.json")
82
+
83
+ package_json = JSON.parse(File.read("package.json"))
84
+ dependencies = package_json["dependencies"] || {}
85
+ dev_dependencies = package_json["devDependencies"] || {}
86
+ all_deps = dependencies.merge(dev_dependencies)
87
+
88
+ if all_deps.key?("react")
89
+ :react
90
+ elsif all_deps.key?("vue")
91
+ :vue
92
+ elsif all_deps.key?("svelte")
93
+ :svelte
94
+ end
95
+ end
96
+
97
+ def rename_file(old_name, new_name)
98
+ FileUtils.mv(old_name, new_name)
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,6 @@
1
+ en:
2
+ common:
3
+ welcome: "Welcome to InertiaI18n!"
4
+ actions:
5
+ save: "Save"
6
+ cancel: "Cancel"
@@ -0,0 +1,33 @@
1
+ require "rails_helper"
2
+ require "inertia_i18n/health_checker"
3
+ require "inertia_i18n/file_converter"
4
+
5
+ RSpec.describe InertiaI18n::HealthChecker do
6
+ it "has healthy translations" do
7
+ # Ensure JSON files are up-to-date before checking
8
+ InertiaI18n::FileConverter.convert_all
9
+
10
+ checker = described_class.new.check!
11
+
12
+ return if checker.healthy?
13
+
14
+ message = ["\nTranslation health check failed!"]
15
+
16
+ if checker.issues[:missing].any?
17
+ message << "\nMissing Keys (#{checker.issues[:missing].count}):"
18
+ checker.issues[:missing].each { |i| message << " - #{i[:key]}" }
19
+ end
20
+
21
+ if checker.issues[:unused].any?
22
+ message << "\nUnused Keys (#{checker.issues[:unused].count}):"
23
+ checker.issues[:unused].each { |i| message << " - #{i[:key]}" }
24
+ end
25
+
26
+ if checker.issues[:unsync].any?
27
+ message << "\nLocale Synchronization Issues (#{checker.issues[:unsync].count}):"
28
+ checker.issues[:unsync].each { |i| message << " - #{i[:key]} (in #{i[:locale]})" }
29
+ end
30
+
31
+ expect(checker.healthy?).to be(true), message.join("\n")
32
+ end
33
+ end
@@ -0,0 +1,31 @@
1
+ require "test_helper"
2
+ require "inertia_i18n/health_checker"
3
+ require "inertia_i18n/file_converter"
4
+
5
+ class I18nTest < ActiveSupport::TestCase
6
+ test "translations are healthy" do
7
+ # Ensure JSON files are up-to-date before checking
8
+ InertiaI18n::FileConverter.convert_all
9
+
10
+ checker = InertiaI18n::HealthChecker.new.check!
11
+
12
+ message = ["\nTranslation health check failed!"]
13
+
14
+ if checker.issues[:missing].any?
15
+ message << "\nMissing Keys (#{checker.issues[:missing].count}):"
16
+ checker.issues[:missing].each { |i| message << " - #{i[:key]}" }
17
+ end
18
+
19
+ if checker.issues[:unused].any?
20
+ message << "\nUnused Keys (#{checker.issues[:unused].count}):"
21
+ checker.issues[:unused].each { |i| message << " - #{i[:key]}" }
22
+ end
23
+
24
+ if checker.issues[:unsync].any?
25
+ message << "\nLocale Synchronization Issues (#{checker.issues[:unsync].count}):"
26
+ checker.issues[:unsync].each { |i| message << " - #{i[:key]} (in #{i[:locale]})" }
27
+ end
28
+
29
+ assert checker.healthy?, message.join("\n")
30
+ end
31
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ InertiaI18n.configure do |config|
4
+ # Source directories for your frontend YAML files.
5
+ config.source_paths = [Rails.root.join("config", "locales", "frontend")]
6
+
7
+ # Target path for generated i18next JSON files
8
+ config.target_path = Rails.root.join("app", "frontend", "locales")
9
+
10
+ # Locales to process (first is primary)
11
+ config.locales = %i[en]
12
+
13
+ # YAML file pattern to match
14
+ config.source_pattern = "**/*.{yml,yaml}"
15
+
16
+ # Paths to scan for translation usage
17
+ <% extensions = case @framework
18
+ when :react then "js,ts,jsx,tsx"
19
+ when :vue then "js,ts,vue"
20
+ when :svelte then "js,ts,svelte"
21
+ else "js,ts,jsx,tsx,svelte,vue"
22
+ end %>
23
+ config.scan_paths = [
24
+ "app/frontend/**/*.{<%= extensions %>}"
25
+ ]
26
+
27
+ # Interpolation conversion (Rails %{var} -> i18next {{var}})
28
+ config.interpolation = { from: "%{", to: "{{" }
29
+
30
+ # Dynamic key patterns (prefix => description)
31
+ # Keys matching these prefixes won't be marked as unused
32
+ config.dynamic_patterns = {
33
+ # "status." => "Dynamic status keys"
34
+ }
35
+
36
+ # Keys to ignore during unused check
37
+ config.ignore_unused = []
38
+
39
+ # Keys to ignore during missing check
40
+ config.ignore_missing = []
41
+ end