i18n-tasks 1.1.2 → 1.2.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +321 -242
  3. data/Rakefile +5 -1
  4. data/benchmarks/bench_helper.rb +156 -0
  5. data/benchmarks/data_bench.rb +173 -0
  6. data/benchmarks/end_to_end_bench.rb +138 -0
  7. data/benchmarks/fixtures/generator.rb +257 -0
  8. data/benchmarks/results/baseline.json +250 -0
  9. data/benchmarks/run_all.rb +60 -0
  10. data/benchmarks/scanning_bench.rb +135 -0
  11. data/benchmarks/tree_bench.rb +205 -0
  12. data/config/locales/en.yml +10 -0
  13. data/config/locales/ru.yml +14 -0
  14. data/i18n-tasks.gemspec +1 -1
  15. data/lib/i18n/tasks/base_task.rb +2 -0
  16. data/lib/i18n/tasks/command/commands/check_prism.rb +67 -15
  17. data/lib/i18n/tasks/command/commands/prune.rb +57 -0
  18. data/lib/i18n/tasks/commands.rb +2 -0
  19. data/lib/i18n/tasks/data/adapter/json_adapter.rb +1 -1
  20. data/lib/i18n/tasks/data/file_formats.rb +4 -1
  21. data/lib/i18n/tasks/data/router/conservative_router.rb +13 -22
  22. data/lib/i18n/tasks/data/router/pattern_router.rb +26 -14
  23. data/lib/i18n/tasks/data/tree/node.rb +3 -3
  24. data/lib/i18n/tasks/data/tree/siblings.rb +11 -3
  25. data/lib/i18n/tasks/data/tree/traversal.rb +20 -14
  26. data/lib/i18n/tasks/interpolations.rb +61 -3
  27. data/lib/i18n/tasks/logging.rb +1 -2
  28. data/lib/i18n/tasks/missing_keys.rb +7 -38
  29. data/lib/i18n/tasks/plural_keys.rb +56 -0
  30. data/lib/i18n/tasks/prune.rb +18 -0
  31. data/lib/i18n/tasks/references.rb +9 -1
  32. data/lib/i18n/tasks/scanners/ast_matchers/base_matcher.rb +11 -2
  33. data/lib/i18n/tasks/scanners/ast_matchers/rails_model_matcher.rb +16 -2
  34. data/lib/i18n/tasks/scanners/erb_ast_scanner.rb +6 -3
  35. data/lib/i18n/tasks/scanners/file_scanner.rb +4 -1
  36. data/lib/i18n/tasks/scanners/files/file_finder.rb +29 -6
  37. data/lib/i18n/tasks/scanners/files/parse_cache.rb +24 -0
  38. data/lib/i18n/tasks/scanners/prism_scanners/arguments_visitor.rb +33 -0
  39. data/lib/i18n/tasks/scanners/prism_scanners/nodes.rb +57 -23
  40. data/lib/i18n/tasks/scanners/prism_scanners/visitor.rb +70 -16
  41. data/lib/i18n/tasks/scanners/relative_keys.rb +21 -11
  42. data/lib/i18n/tasks/scanners/ruby_scanner.rb +37 -5
  43. data/lib/i18n/tasks/split_key.rb +16 -6
  44. data/lib/i18n/tasks/translators/base_translator.rb +25 -23
  45. data/lib/i18n/tasks/translators/deepl_translator.rb +18 -15
  46. data/lib/i18n/tasks/translators/google_translator.rb +41 -14
  47. data/lib/i18n/tasks/translators/openai_translator.rb +31 -17
  48. data/lib/i18n/tasks/translators/watsonx_translator.rb +8 -10
  49. data/lib/i18n/tasks/translators/yandex_translator.rb +4 -0
  50. data/lib/i18n/tasks/unused_keys.rb +35 -5
  51. data/lib/i18n/tasks/used_keys.rb +6 -1
  52. data/lib/i18n/tasks/version.rb +1 -1
  53. data/script/report_test_results.rb +58 -0
  54. data/templates/config/i18n-tasks.yml +11 -3
  55. metadata +17 -7
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # i18n-tasks [![Build Status][badge-ci]][ci] [![Coverage Status][badge-coverage]][coverage] [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/glebm/i18n-tasks?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
1
+ # i18n-tasks [![Build Status][badge-ci]][ci] [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/glebm/i18n-tasks?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
2
2
 
3
3
  [![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://stand-with-ukraine.pp.ua/)
4
4
 
@@ -8,75 +8,140 @@ i18n-tasks helps you find and manage missing and unused translations.
8
8
 
9
9
  This gem analyses code statically for key usages, such as `I18n.t('some.key')`, in order to:
10
10
 
11
- * Report keys that are missing or unused.
12
- * Pre-fill missing keys, optionally from Google Translate or DeepL Pro.
13
- * Remove unused keys.
11
+ - Report keys that are missing or unused.
12
+ - Pre-fill missing keys, optionally from Google Translate or DeepL (Pro or Free).
13
+ - Remove unused keys.
14
14
 
15
15
  Thus addressing the two main problems of [i18n gem][i18n-gem] design:
16
16
 
17
- * Missing keys only blow up at runtime.
18
- * Keys no longer in use may accumulate and introduce overhead, without you knowing it.
19
-
20
- ## Installation
17
+ - Missing keys only blow up at runtime.
18
+ - Keys no longer in use may accumulate and introduce overhead, without you knowing it.
19
+
20
+ ## Table of Contents
21
+
22
+ - [Quick Start](#quick-start)
23
+ - [Commands](#commands)
24
+ - [Check health](#check-health)
25
+ - [Find usages](#find-usages)
26
+ - [Add missing keys](#add-missing-keys)
27
+ - [Translate missing keys](#translate-missing-keys)
28
+ - [Remove unused keys](#remove-unused-keys)
29
+ - [Prune](#prune)
30
+ - [Normalize](#normalize)
31
+ - [Move / rename / merge keys](#move--rename--merge-keys)
32
+ - [Delete keys](#delete-keys)
33
+ - [Compose tasks](#compose-tasks)
34
+ - [Configuration](#configuration)
35
+ - [Locales](#locales)
36
+ - [Storage & locale files](#storage--locale-files)
37
+ - [Pattern router](#pattern-router)
38
+ - [Conservative router](#conservative-router)
39
+ - [Isolating router](#isolating-router)
40
+ - [Key pattern syntax](#key-pattern-syntax)
41
+ - [Custom adapters](#custom-adapters)
42
+ - [Rails credentials](#rails-credentials)
43
+ - [Usage search](#usage-search)
44
+ - [Prism-based scanner](#prism-based-scanner)
45
+ - [Fine-tuning](#fine-tuning)
46
+ - [Environment variables & dotenv](#environment-variables--dotenv)
47
+ - [Translation backends](#translation-backends)
48
+ - [Google Translate](#google-translate)
49
+ - [DeepL](#deepl)
50
+ - [Yandex](#yandex)
51
+ - [OpenAI](#openai)
52
+ - [watsonx](#watsonx)
53
+ - [Features & limitations](#features--limitations)
54
+ - [Relative keys](#relative-keys)
55
+ - [Plural keys](#plural-keys)
56
+ - [Reference keys](#reference-keys)
57
+ - [Dynamic keys](#dynamic-keys)
58
+ - [I18n.localize](#i18nlocalize)
59
+ - [`t()` keyword arguments](#t-keyword-arguments)
60
+ - [Unexpected normalization](#unexpected-normalization)
61
+ - [Advanced](#advanced)
62
+ - [Interactive console](#interactive-console)
63
+ - [CSV import / export](#csv-import--export)
64
+ - [Add custom tasks](#add-custom-tasks)
65
+ - [Development](#development)
66
+
67
+ ## Quick Start
21
68
 
22
69
  i18n-tasks can be used with any project using the ruby [i18n gem][i18n-gem] (default in Rails).
23
70
 
24
- Add i18n-tasks to the Gemfile:
71
+ 1. Add to your `Gemfile`:
25
72
 
26
- ```ruby
27
- gem 'i18n-tasks', '~> 1.1.2', group: :development
28
- ```
73
+ ```ruby
74
+ gem 'i18n-tasks', '~> 1.2.0', group: :development
75
+ ```
29
76
 
30
- Copy the default [configuration file](#configuration):
77
+ 2. Copy the default [configuration file](#configuration):
31
78
 
32
- ```console
33
- $ cp $(i18n-tasks gem-path)/templates/config/i18n-tasks.yml config/
34
- ```
79
+ ```sh
80
+ $ cp $(bundle exec i18n-tasks gem-path)/templates/config/i18n-tasks.yml config/
81
+ ```
35
82
 
36
- Copy rspec test to test for missing and unused translations as part of the suite (optional):
83
+ 3. Run your first health check:
37
84
 
38
- ```console
39
- $ cp $(i18n-tasks gem-path)/templates/rspec/i18n_spec.rb spec/
40
- ```
85
+ ```sh
86
+ $ bundle exec i18n-tasks health
87
+ ```
88
+
89
+ That's it. See [Commands](#commands) for the full list of tasks, or [Configuration](#configuration) to tailor the setup to your project.
41
90
 
42
- Or for minitest:
91
+ **Optional:** copy a test that checks for missing/unused translations on every CI run:
92
+
93
+ ```sh
94
+ # RSpec
95
+ $ cp $(bundle exec i18n-tasks gem-path)/templates/rspec/i18n_spec.rb spec/
43
96
 
44
- ```console
45
- $ cp $(i18n-tasks gem-path)/templates/minitest/i18n_test.rb test/
97
+ # Minitest
98
+ $ cp $(bundle exec i18n-tasks gem-path)/templates/minitest/i18n_test.rb test/
46
99
  ```
47
100
 
48
- ## Usage
101
+ ## Commands
49
102
 
50
103
  Run `bundle exec i18n-tasks` to get the list of all the tasks with short descriptions.
51
104
 
52
105
  ### Check health
53
106
 
54
- `i18n-tasks health` checks if any keys are missing or not used,
55
- that interpolations variables are consistent across locales,
107
+ `bundle exec i18n-tasks health` checks if any keys are missing or not used,
108
+ that interpolation variables are consistent across locales,
56
109
  and that all the locale files are normalized (auto-formatted):
57
110
 
58
- ```console
59
- $ i18n-tasks health
111
+ ```sh
112
+ $ bundle exec i18n-tasks health
60
113
  ```
61
114
 
115
+ ### Find usages
116
+
117
+ See where the keys are used with `bundle exec i18n-tasks find`:
118
+
119
+ ```sh
120
+ $ bundle exec i18n-tasks find common.help
121
+ $ bundle exec i18n-tasks find 'auth.*'
122
+ $ bundle exec i18n-tasks find '{number,currency}.format.*'
123
+ ```
124
+
125
+ <img width="437" height="129" src="https://i.imgur.com/VxBrSfY.png">
126
+
62
127
  ### Add missing keys
63
128
 
64
129
  Add missing keys with placeholders (base value or humanized key):
65
130
 
66
- ```console
67
- $ i18n-tasks add-missing
131
+ ```sh
132
+ $ bundle exec i18n-tasks add-missing
68
133
  ```
69
134
 
70
135
  This and other tasks accept arguments:
71
136
 
72
- ```console
73
- $ i18n-tasks add-missing -v 'TRME %{value}' fr
137
+ ```sh
138
+ $ bundle exec i18n-tasks add-missing -v 'TRME %{value}' fr
74
139
  ```
75
140
 
76
141
  Pass `--help` for more information:
77
142
 
78
- ```console
79
- $ i18n-tasks add-missing --help
143
+ ```sh
144
+ $ bundle exec i18n-tasks add-missing --help
80
145
  Usage: i18n-tasks add-missing [options] [locale ...]
81
146
  -l, --locales Comma-separated list of locale(s) to process. Default: all. Special: base.
82
147
  -f, --format Output format: terminal-table, yaml, json, keys, inspect. Default: terminal-table.
@@ -84,41 +149,30 @@ Usage: i18n-tasks add-missing [options] [locale ...]
84
149
  -h, --help Display this help message.
85
150
  ```
86
151
 
87
- ### Translate Missing Keys
152
+ ### Translate missing keys
88
153
 
89
154
  Translate missing keys using a backend service of your choice.
90
155
 
91
- ```console
92
- $ i18n-tasks translate-missing
156
+ ```sh
157
+ $ bundle exec i18n-tasks translate-missing
93
158
 
94
159
  # accepts backend, from and locales options
95
- $ i18n-tasks translate-missing --from=base es fr --backend=google
160
+ $ bundle exec i18n-tasks translate-missing --from=base es fr --backend=google
96
161
  ```
97
162
 
98
163
  Available backends:
99
- - `google` - [Google Translate](#google-translation-config)
100
- - `deepl` - [DeepL Pro](#deepl-translation-config)
101
- - `yandex` - [Yandex Translate](#yandex-translation-config)
102
- - `openai` - [OpenAI](#openai-translation-config)
103
- - `watsonx` - [watsonx](#watsonx-translation-config)
104
164
 
105
- ### Find usages
106
-
107
- See where the keys are used with `i18n-tasks find`:
108
-
109
- ```bash
110
- $ i18n-tasks find common.help
111
- $ i18n-tasks find 'auth.*'
112
- $ i18n-tasks find '{number,currency}.format.*'
113
- ```
114
-
115
- <img width="437" height="129" src="https://i.imgur.com/VxBrSfY.png">
165
+ - `google` – [Google Translate](#google-translate)
166
+ - `deepl` – [DeepL](#deepl)
167
+ - `yandex` – [Yandex](#yandex)
168
+ - `openai` – [OpenAI](#openai)
169
+ - `watsonx` – [watsonx](#watsonx)
116
170
 
117
171
  ### Remove unused keys
118
172
 
119
- ```bash
120
- $ i18n-tasks unused
121
- $ i18n-tasks remove-unused
173
+ ```sh
174
+ $ bundle exec i18n-tasks unused
175
+ $ bundle exec i18n-tasks remove-unused
122
176
  ```
123
177
 
124
178
  These tasks can infer [dynamic keys](#dynamic-keys) such as `t("category.\#{category.name}")` if you set
@@ -127,62 +181,72 @@ These tasks can infer [dynamic keys](#dynamic-keys) such as `t("category.\#{cate
127
181
  If you want to keep the ordering from the original language file when using remove-unused, pass
128
182
  `-k` or `--keep-order`.
129
183
 
130
- ### Normalize data
184
+ ### Prune keys not in base locale
185
+
186
+ Remove keys from non-base locales that are absent in the base locale:
187
+
188
+ ```bash
189
+ $ i18n-tasks prune
190
+ ```
191
+
192
+ Pass `-k` or `--keep-order` to preserve the original key ordering in the locale files.
193
+
194
+ ### Normalize
131
195
 
132
196
  Sort the keys:
133
197
 
134
- ```console
135
- $ i18n-tasks normalize
198
+ ```sh
199
+ $ bundle exec i18n-tasks normalize
136
200
  ```
137
201
 
138
- Sort the keys, and move them to the respective files as defined by [`config.write`](#multiple-locale-files):
202
+ Sort the keys, and move them to the respective files as defined by [`config.write`](#storage--locale-files):
139
203
 
140
- ```console
141
- $ i18n-tasks normalize -p
204
+ ```sh
205
+ $ bundle exec i18n-tasks normalize -p
142
206
  ```
143
207
 
144
208
  ### Move / rename / merge keys
145
209
 
146
- `i18n-tasks mv <pattern> <target>` is a versatile task to move or delete keys matching the given pattern.
210
+ `bundle exec i18n-tasks mv <pattern> <target>` is a versatile task to move or delete keys matching the given pattern.
147
211
 
148
212
  All nodes (leafs or subtrees) matching [`<pattern>`](#key-pattern-syntax) are merged together and moved to `<target>`.
149
213
 
150
214
  Rename a node (leaf or subtree):
151
215
 
152
- ``` console
153
- $ i18n-tasks mv user account
216
+ ```sh
217
+ $ bundle exec i18n-tasks mv user account
154
218
  ```
155
219
 
156
220
  Move a node:
157
221
 
158
- ``` console
159
- $ i18n-tasks mv user_alerts user.alerts
222
+ ```sh
223
+ $ bundle exec i18n-tasks mv user_alerts user.alerts
160
224
  ```
161
225
 
162
226
  Move the children one level up:
163
227
 
164
- ``` console
165
- $ i18n-tasks mv 'alerts.{:}' '\1'
228
+ ```sh
229
+ $ bundle exec i18n-tasks mv 'alerts.{:}' '\1'
166
230
  ```
167
231
 
168
232
  Merge-move multiple nodes:
169
233
 
170
- ``` console
171
- $ i18n-tasks mv '{user,profile}' account
234
+ ```sh
235
+ $ bundle exec i18n-tasks mv '{user,profile}' account
172
236
  ```
173
237
 
174
238
  Merge (non-leaf) nodes into parent:
175
239
 
176
- ``` console
177
- $ i18n-tasks mv '{pages}.{a,b}' '\1'
240
+ ```sh
241
+ $ bundle exec i18n-tasks mv '{pages}.{a,b}' '\1'
178
242
  ```
179
243
 
180
244
  ### Delete keys
181
245
 
182
246
  Delete the keys by using the `rm` task:
183
247
 
184
- ```console
185
- $ i18n-tasks rm 'user.{old_profile,old_title}' another_key
248
+ ```sh
249
+ $ bundle exec i18n-tasks rm 'user.{old_profile,old_title}' another_key
186
250
  ```
187
251
 
188
252
  ### Compose tasks
@@ -190,94 +254,34 @@ $ i18n-tasks rm 'user.{old_profile,old_title}' another_key
190
254
  `i18n-tasks` also provides composable tasks for reading, writing and manipulating locale data. Examples below.
191
255
 
192
256
  `add-missing` implemented with `missing`, `tree-set-value` and `data-merge`:
193
- ```console
194
- $ i18n-tasks missing -f yaml fr | i18n-tasks tree-set-value 'TRME %{value}' | i18n-tasks data-merge
257
+
258
+ ```sh
259
+ $ bundle exec i18n-tasks missing -f yaml fr | bundle exec i18n-tasks tree-set-value 'TRME %{value}' | bundle exec i18n-tasks data-merge
195
260
  ```
196
261
 
197
262
  `remove-unused` implemented with `unused` and `data-remove` (sans the confirmation):
198
- ```console
199
- $ i18n-tasks unused -f yaml | i18n-tasks data-remove
200
- ```
201
263
 
202
- Remove all keys from `fr` that do not exist in `en`. Do not change `en`:
203
- ```console
204
- $ i18n-tasks missing -t diff -f yaml en | i18n-tasks tree-mv en fr | i18n-tasks data-remove
264
+ ```sh
265
+ $ bundle exec i18n-tasks unused -f yaml | bundle exec i18n-tasks data-remove
205
266
  ```
206
267
 
207
- See the full list of tasks with `i18n-tasks --help`.
208
-
209
- ### Features and limitations
210
-
211
- `i18n-tasks` uses an AST scanner for `.rb` and `.html.erb` files, and a regexp-based scanner for other files, such as `.haml`.
212
-
213
- #### Relative keys
214
-
215
- `i18n-tasks` offers support for relative keys, such as `t '.title'`.
216
-
217
- ✔ Keys relative to the file path they are used in (see [relative roots configuration](#usage-search)) are supported.
218
-
219
- ✔ Keys relative to `controller.action_name` in Rails controllers are supported. The closest `def` name is used.
220
-
221
- #### Plural keys
222
-
223
- ✔ Plural keys, such as `key.{one,many,other,...}` are fully supported.
224
-
225
- #### Reference keys
226
-
227
- ✔ Reference keys (keys with `:symbol` values) are fully supported. These keys are copied as-is in
228
- `add/translate-missing`, and can be looked up by reference or value in `find`.
268
+ Remove all keys from `fr` that do not exist in `en`. Do not change `en`:
229
269
 
230
- #### Unexpected normalization
231
- `i18n-tasks` uses a yaml parser and emitter called `Psych` under the hood. `Psych` has it's own heuristic on when
232
- to use `|`, `>`, or `""` for multi-line strings. This can have some unexpected consequences, eg when normalizing:
233
- ```yaml
234
- a: |
235
- Lorem ipsum dolor sit amet, consectetur
236
- Lorem ipsum dolor sit amet, consectetur
237
- b: |
238
- Lorem ipsum dolor sit amet, consectetur
239
- Lorem ipsum dolor sit amet, consectetur
240
- ```
241
- we get the result
242
- ```yaml
243
- a: |
244
- Lorem ipsum dolor sit amet, consectetur
245
- Lorem ipsum dolor sit amet, consectetur
246
- b: "Lorem ipsum dolor sit amet, consectetur \nLorem ipsum dolor sit amet, consectetur\n"
270
+ ```sh
271
+ $ bundle exec i18n-tasks missing -t diff -f yaml en | bundle exec i18n-tasks tree-mv en fr | bundle exec i18n-tasks data-remove
247
272
  ```
248
- The only difference between `a` and `b` is that `b` has an extra trailing space in each line.
249
- This is an unfortunate side effect of `i18n-tasks` using `Psych`.
250
-
251
- #### `t()` keyword arguments
252
-
253
- ✔ `scope` keyword argument is fully supported by the AST scanner, and also by the Regexp scanner but only when it is the first argument.
254
-
255
- ✔ `default` argument can be used to pre-fill locale files (AST scanner only).
256
-
257
- #### Dynamic keys
258
-
259
- By default, dynamic keys such as `t "cats.#{cat}.name"` are not recognized.
260
- I encourage you to mark these with [i18n-tasks-use hints](#fine-tuning).
261
-
262
- Alternatively, you can enable dynamic key inference by setting `search.strict` to `false` in the config. In this case,
263
- all the dynamic parts of the key will be considered used, e.g. `cats.tenderlove.name` would not be reported as unused.
264
- Note that only one section of the key is treated as a wildcard for each string interpolation; i.e. in this example,
265
- `cats.tenderlove.special.name` *will* be reported as unused.
266
273
 
267
- #### I18n.localize
268
-
269
- `I18n.localize` is not supported, use [i18n-tasks-use hints](#fine-tuning).
270
- This is because the key generated by `I18n.localize` depends on the type of the object passed in and thus cannot be inferred statically.
274
+ See the full list of tasks with `bundle exec i18n-tasks --help`.
271
275
 
272
276
  ## Configuration
273
277
 
274
278
  Configuration is read from `config/i18n-tasks.yml` or `config/i18n-tasks.yml.erb`.
275
- Inspect the configuration with `i18n-tasks config`.
279
+ Inspect the configuration with `bundle exec i18n-tasks config`.
276
280
 
277
281
  Install the [default config file][config] with:
278
282
 
279
- ```console
280
- $ cp $(i18n-tasks gem-path)/templates/config/i18n-tasks.yml config/
283
+ ```sh
284
+ $ cp $(bundle exec i18n-tasks gem-path)/templates/config/i18n-tasks.yml config/
281
285
  ```
282
286
 
283
287
  Settings are compatible with Rails by default.
@@ -287,53 +291,51 @@ Settings are compatible with Rails by default.
287
291
  By default, `base_locale` is set to `en` and `locales` are inferred from the paths to data files.
288
292
  You can override these in the [config][config].
289
293
 
290
- ### Storage
294
+ ### Storage & locale files
291
295
 
292
296
  The default data adapter supports YAML and JSON files.
293
297
 
294
- #### Multiple locale files
295
-
296
298
  i18n-tasks can manage multiple translation files and read translations from other gems.
297
299
  To find out more see the `data` options in the [config][config].
298
300
  NB: By default, only `%{locale}.yml` files are read, not `namespace.%{locale}.yml`. Make sure to check the config.
299
301
 
300
- For writing to locale files i18n-tasks provides 2 options.
302
+ For writing to locale files i18n-tasks provides three routers.
301
303
 
302
- ##### Pattern router
304
+ #### Pattern router
303
305
 
304
306
  Pattern router organizes keys based on a list of key patterns, as in the example below:
305
307
 
306
- ```
308
+ ```yaml
307
309
  data:
308
310
  router: pattern_router
309
311
  # a list of {key pattern => file} routes, matched top to bottom
310
312
  write:
311
313
  # write models.* and views.* keys to the respective files
312
- - ['{models,views}.*', 'config/locales/\1.%{locale}.yml']
314
+ - ["{models,views}.*", 'config/locales/\1.%{locale}.yml']
313
315
  # or, write every top-level key namespace to its own file
314
- - ['{:}.*', 'config/locales/\1.%{locale}.yml']
316
+ - ["{:}.*", 'config/locales/\1.%{locale}.yml']
315
317
  # default, sugar for ['*', path]
316
- - 'config/locales/%{locale}.yml'
318
+ - "config/locales/%{locale}.yml"
317
319
  ```
318
320
 
319
- ##### Conservative router
321
+ #### Conservative router
320
322
 
321
323
  Conservative router keeps the keys where they are found, or infers the path from base locale.
322
324
  If the key is completely new, conservative router will fall back to pattern router behaviour.
323
325
  Conservative router is the **default** router.
324
326
 
325
- ```
327
+ ```yaml
326
328
  data:
327
329
  router: conservative_router
328
330
  write:
329
- - ['devise.*', 'config/locales/devise.%{locale}.yml']
330
- - 'config/locales/%{locale}.yml'
331
+ - ["devise.*", "config/locales/devise.%{locale}.yml"]
332
+ - "config/locales/%{locale}.yml"
331
333
  ```
332
334
 
333
335
  If you want to have i18n-tasks reorganize your existing keys using `data.write`, either set the router to
334
- `pattern_router` as above, or run `i18n-tasks normalize -p` (forcing the use of the pattern router for that run).
336
+ `pattern_router` as above, or run `bundle exec i18n-tasks normalize -p` (forcing the use of the pattern router for that run).
335
337
 
336
- ##### Isolating router
338
+ #### Isolating router
337
339
 
338
340
  Isolating router assumes each YAML file is independent and can contain similar keys.
339
341
 
@@ -345,31 +347,32 @@ This can be useful for example when using [ViewComponent sidecars](https://viewc
345
347
  (ViewComponent assigns an implicit scope to each sidecar YAML file but `i18n-tasks` is not aware of
346
348
  that logic, resulting in collisions):
347
349
 
348
- * `app/components/movies_component.en.yml`:
349
- ```yaml
350
- en:
351
- title: Movies
352
- ```
350
+ - `app/components/movies_component.en.yml`:
353
351
 
354
- * `app/components/games_component.en.yml`
355
- ```yaml
356
- en:
357
- title: Games
358
- ```
352
+ ```yaml
353
+ en:
354
+ title: Movies
355
+ ```
356
+
357
+ - `app/components/games_component.en.yml`
358
+ ```yaml
359
+ en:
360
+ title: Games
361
+ ```
359
362
 
360
363
  This router has a limitation, though: it does not support detecting missing keys from code usage
361
364
  (since it is not aware of the implicit scope logic).
362
365
 
363
- ##### Key pattern syntax
366
+ #### Key pattern syntax
364
367
 
365
368
  A special syntax similar to file glob patterns is used throughout i18n-tasks to match translation keys:
366
369
 
367
- | syntax | description |
368
- |:------------:|:----------------------------------------------------------|
369
- | `*` | matches everything |
370
- | `:` | matches a single key |
371
- | `*:` | matches part of a single key |
372
- | `{a, b.c}` | match any in set, can use `:` and `*`, match is captured |
370
+ | syntax | description |
371
+ | :--------: | :------------------------------------------------------- |
372
+ | `*` | matches everything |
373
+ | `:` | matches a single key |
374
+ | `*:` | matches part of a single key |
375
+ | `{a, b.c}` | match any in set, can use `:` and `*`, match is captured |
373
376
 
374
377
  Example of usage:
375
378
 
@@ -389,7 +392,7 @@ If you have implemented a custom adapter please share it on [the wiki][wiki].
389
392
 
390
393
  #### Rails credentials
391
394
 
392
- If you use Rails credentials and want to load e.g. credentials for translation backends, convert your `i18n-tasks.yml`to `i18n-tasks.yml.erb` and add
395
+ If you use Rails credentials and want to load e.g. credentials for translation backends, convert your `i18n-tasks.yml` to `i18n-tasks.yml.erb` and add
393
396
  a `require "./config/application"` line to load Rails.
394
397
 
395
398
  ```yaml
@@ -411,6 +414,47 @@ New scanners can be added easily: please refer to [this example](https://github.
411
414
  See the `search` section in the [config file][config] for all available configuration options.
412
415
  NB: By default, only the `app/` directory is searched.
413
416
 
417
+ #### Prism-based scanner
418
+
419
+ There is a scanner based on [Prism](https://github.com/ruby/prism) usable in two different modes.
420
+
421
+ - `rails` mode parses Rails code and handles context such as controllers, before_actions, model translations and more.
422
+ - `ruby` mode parses Ruby code only, and works similar to the existing whitequark/parser-implementation.
423
+ - The parser is used for both ruby and ERB files.
424
+
425
+ ##### `rails` mode
426
+
427
+ It handles the following cases:
428
+
429
+ - Translations called in `before_actions`
430
+ - Translations called in nested methods
431
+ - `Model.human_attribute_name` calls
432
+ - `Model.model_name.human` calls
433
+
434
+ Enable it by adding the following to your `config/i18n-tasks.yml`:
435
+
436
+ ```yaml
437
+ search:
438
+ prism: "rails"
439
+ ```
440
+
441
+ ##### `ruby` mode
442
+
443
+ It finds all `I18n.t`, `I18n.translate`, `t` and `translate` calls in Ruby code. Enable it by adding:
444
+
445
+ ```yaml
446
+ search:
447
+ prism: "ruby"
448
+ ```
449
+
450
+ The goal is to replace the whitequark/parser-based scanner with this one in the future.
451
+
452
+ ##### Help us out with testing
453
+
454
+ Please install the latest version of the gem and run `bundle exec i18n-tasks check-prism` which will parse everything with the whitequark/parser-based scanner and then everything with the Prism-scanner and try to compare the results.
455
+
456
+ Open up issues with any parser crashes, missed translations or false positives.
457
+
414
458
  ### Fine-tuning
415
459
 
416
460
  Add hints to static analysis with magic comment hints (lines starting with `(#|/) i18n-tasks-use` by default):
@@ -429,7 +473,7 @@ For more complex cases, you can implement a [custom scanner][custom-scanner-docs
429
473
 
430
474
  See the [config file][config] to find out more.
431
475
 
432
- ### Environment Variables and Dotenv
476
+ ### Environment variables & dotenv
433
477
 
434
478
  i18n-tasks supports loading environment variables from `.env` files using the [dotenv](https://github.com/bkeepers/dotenv) gem.
435
479
  This is particularly useful for storing translation API keys and other sensitive configuration.
@@ -437,25 +481,26 @@ This is particularly useful for storing translation API keys and other sensitive
437
481
  If you have `dotenv` in your Gemfile, i18n-tasks will automatically load environment variables from `.env` files
438
482
  before executing commands. This means you can store your API keys in a `.env` file:
439
483
 
440
- ```bash
484
+ ```sh
441
485
  # .env
442
486
  GOOGLE_TRANSLATE_API_KEY=your_google_api_key
443
487
  DEEPL_AUTH_KEY=your_deepl_api_key
444
488
  OPENAI_API_KEY=your_openai_api_key
445
489
  ```
446
490
 
447
- The dotenv integration works seamlessly - no additional configuration is required. If `dotenv` is not available,
491
+ The dotenv integration works seamlessly – no additional configuration is required. If `dotenv` is not available,
448
492
  i18n-tasks will continue to work normally using system environment variables.
449
493
 
450
- <a name="google-translation-config"></a>
494
+ ## Translation backends
495
+
451
496
  ### Google Translate
452
497
 
453
498
  `i18n-tasks translate-missing` requires a Google Translate API key, get it at [Google API Console](https://code.google.com/apis/console).
454
499
 
455
500
  Where this key is depends on your Google API console:
456
501
 
457
- * Old console: API Access -> Simple API Access -> Key for server apps.
458
- * New console: Nav Menu -> APIs & Services -> Credentials -> Create Credentials -> API Keys -> Restrict Key -> Cloud Translation API
502
+ - Old console: API Access -> Simple API Access -> Key for server apps.
503
+ - New console: Nav Menu -> APIs & Services -> Credentials -> Create Credentials -> API Keys -> Restrict Key -> Cloud Translation API
459
504
 
460
505
  In both cases, you may need to create the key if it doesn't exist.
461
506
 
@@ -470,25 +515,23 @@ translation:
470
515
 
471
516
  or via environment variable:
472
517
 
473
- ```bash
518
+ ```sh
474
519
  GOOGLE_TRANSLATE_API_KEY=<Google Translate API key>
475
520
  ```
476
521
 
477
- <a name="deepl-translation-config"></a>
478
- ### DeepL Pro Translate
522
+ ### DeepL
479
523
 
480
- `i18n-tasks translate-missing` requires a DeepL Pro API key, get it at [DeepL](https://www.deepl.com/pro). You can specify alias locales if you only use the simple locales internally.
524
+ `i18n-tasks translate-missing` requires a DeepL API key. DeepL offers both a Pro plan and a [Free plan](https://www.deepl.com/en/pro#api) (limited to 500,000 characters/month). Get your API key at [DeepL](https://www.deepl.com/en/pro#api). You can specify alias locales if you only use the simple locales internally.
481
525
 
482
526
  ```yaml
483
527
  # config/i18n-tasks.yml
484
528
  translation:
485
529
  backend: deepl
486
- deepl_api_key: <DeepL Pro API key>
487
- deepl_host: <optional>
530
+ deepl_api_key: <DeepL API key>
531
+ deepl_host: <optional, see note below>
488
532
  deepl_version: <optional>
489
533
  deepl_glossary_ids:
490
- - f28106eb-0e06-489e-82c6-8215d6f95089
491
- - 2c6415be-1852-4f54-9e1b-d800463496b4
534
+ - uuid
492
535
  deepl_options:
493
536
  formality: prefer_less
494
537
  deepl_locale_aliases:
@@ -499,13 +542,21 @@ translation:
499
542
  or via environment variables:
500
543
 
501
544
  ```bash
502
- DEEPL_AUTH_KEY=<DeepL Pro API key>
503
- DEEPL_HOST=<optional>
545
+ DEEPL_AUTH_KEY=<DeepL API key>
546
+ DEEPL_HOST=<optional, see note below>
504
547
  DEEPL_VERSION=<optional>
505
548
  ```
506
549
 
507
- <a name="yandex-translation-config"></a>
508
- ### Yandex Translate
550
+ > **Free API:** If you are using a DeepL Free account, set `deepl_host` to `https://api-free.deepl.com` (or set the `DEEPL_HOST` environment variable).
551
+ >
552
+ > ```yaml
553
+ > translation:
554
+ > deepl_api_key: "your-free-api-key"
555
+ > deepl_host: "https://api-free.deepl.com"
556
+ > deepl_version: "v2"
557
+ > ```
558
+
559
+ ### Yandex
509
560
 
510
561
  `i18n-tasks translate-missing` requires a Yandex API key, get it at [Yandex](https://tech.yandex.com/translate).
511
562
 
@@ -518,14 +569,13 @@ translation:
518
569
 
519
570
  or via environment variable:
520
571
 
521
- ```bash
572
+ ```sh
522
573
  YANDEX_API_KEY=<Yandex API key>
523
574
  ```
524
575
 
525
- <a name="openai-translation-config"></a>
526
- ### OpenAI Translate
576
+ ### OpenAI
527
577
 
528
- `i18n-tasks translate-missing` requires a OpenAI API key, get it at [OpenAI](https://openai.com/).
578
+ `i18n-tasks translate-missing` requires an OpenAI API key, get it at [OpenAI](https://openai.com/).
529
579
 
530
580
  ```yaml
531
581
  # config/i18n-tasks.yml
@@ -533,19 +583,22 @@ translation:
533
583
  backend: openai
534
584
  openai_api_key: <OpenAI API key>
535
585
  openai_model: <optional>
586
+ openai_reasoning_effort: <optional>
587
+ openai_temperature: <optional>
536
588
  ```
537
589
 
538
590
  or via environment variable:
539
591
 
540
- ```bash
592
+ ```sh
541
593
  OPENAI_API_KEY=<OpenAI API key>
542
594
  OPENAI_MODEL=<optional>
543
595
  ```
544
596
 
545
- <a name="watsonx-translation-config"></a>
546
- ### watsonx Translate
597
+ The translator sends temperature as usual when `openai_reasoning_effort` is unset or `none`. With other reasoning efforts, it omits temperature; do not configure `openai_temperature` at the same time.
598
+
599
+ ### watsonx
547
600
 
548
- `i18n-tasks translate-missing` requires a watsonx project and api key, get it at [IBM watsonx](https://www.ibm.com/watsonx/).
601
+ `i18n-tasks translate-missing` requires a watsonx project and an API key, get it at [IBM watsonx](https://www.ibm.com/watsonx/).
549
602
 
550
603
  ```yaml
551
604
  # config/i18n-tasks.yml
@@ -558,83 +611,109 @@ translation:
558
611
 
559
612
  or via environment variable:
560
613
 
561
- ```bash
614
+ ```sh
562
615
  WATSONX_API_KEY=<watsonx API key>
563
616
  WATSONX_PROJECT_ID=<watsonx project id>
564
617
  WATSONX_MODEL=<optional>
565
618
  ```
566
619
 
567
- ### Prism-based scanner for Ruby and ERB files
620
+ ## Features & limitations
568
621
 
569
- There is a scanner based on [Prism](https://github.com/ruby/prism) usable in two different modes.
622
+ `i18n-tasks` uses an AST scanner for `.rb` and `.html.erb` files, and a regexp-based scanner for other files, such as `.haml`.
570
623
 
571
- - `rails` mode parses Rails code and handles context such as controllers, before_actions, model translations and more.
572
- - `ruby` mode parses Ruby code only, and works similar to the existing whitequark/parser-implementation.
573
- - The parser is used for both ruby and ERB files.
624
+ ### Relative keys
574
625
 
575
- #### `rails` mode
626
+ `i18n-tasks` offers support for relative keys, such as `t '.title'`.
576
627
 
577
- It handles the following cases:
578
- - Translations called in `before_actions`
579
- - Translations called in nested methods
580
- - `Model.human_attribute_name` calls
581
- - `Model.model_name.human` calls
628
+ ✔ Keys relative to the file path they are used in (see [Usage search](#usage-search)) are supported.
582
629
 
583
- Enabled it by adding:
584
- ```yaml
585
- search:
586
- prism: "rails"
587
- ```
630
+ ✔ Keys relative to `controller.action_name` in Rails controllers are supported. The closest `def` name is used.
631
+
632
+ ### Plural keys
633
+
634
+ ✔ Plural keys, such as `key.{one,many,other,...}` are fully supported.
635
+
636
+ ### Reference keys
637
+
638
+ ✔ Reference keys (keys with `:symbol` values) are fully supported. These keys are copied as-is in
639
+ `add/translate-missing`, and can be looked up by reference or value in `find`.
640
+
641
+ ### Dynamic keys
642
+
643
+ By default, dynamic keys such as `t "cats.#{cat}.name"` are not recognized.
644
+ I encourage you to mark these with [i18n-tasks-use hints](#fine-tuning).
645
+
646
+ Alternatively, you can enable dynamic key inference by setting `search.strict` to `false` in the config. In this case,
647
+ all the dynamic parts of the key will be considered used, e.g. `cats.tenderlove.name` would not be reported as unused.
648
+ Note that only one section of the key is treated as a wildcard for each string interpolation; i.e. in this example,
649
+ `cats.tenderlove.special.name` _will_ be reported as unused.
588
650
 
589
- to your `config/i18n-tasks.yml` file.
651
+ ### I18n.localize
590
652
 
591
- #### `ruby` mode
653
+ `I18n.localize` is not supported, use [i18n-tasks-use hints](#fine-tuning).
654
+ This is because the key generated by `I18n.localize` depends on the type of the object passed in and thus cannot be inferred statically.
655
+
656
+ ### `t()` keyword arguments
657
+
658
+ ✔ `scope` keyword argument is fully supported by the AST scanner, and also by the Regexp scanner but only when it is the first argument.
659
+
660
+ ✔ `default` argument can be used to pre-fill locale files (AST scanner only).
592
661
 
593
- It finds all `I18n.t`, `I18n.translate`, `t` and `translate` calls in Ruby code. Enabled it by adding:
662
+ ### Unexpected normalization
663
+
664
+ `i18n-tasks` uses a YAML parser and emitter called `Psych` under the hood. `Psych` has its own heuristic on when
665
+ to use `|`, `>`, or `""` for multi-line strings. This can have some unexpected consequences, e.g. when normalizing:
594
666
 
595
667
  ```yaml
596
- search:
597
- prism: "ruby"
668
+ a: |
669
+ Lorem ipsum dolor sit amet, consectetur
670
+ Lorem ipsum dolor sit amet, consectetur
671
+ b: |
672
+ Lorem ipsum dolor sit amet, consectetur
673
+ Lorem ipsum dolor sit amet, consectetur
598
674
  ```
599
675
 
600
- The goal is to replace the whitequark/parser-based scanner with this one in the future.
676
+ we get the result:
601
677
 
602
- #### Help us out with testing
678
+ ```yaml
679
+ a: |
680
+ Lorem ipsum dolor sit amet, consectetur
681
+ Lorem ipsum dolor sit amet, consectetur
682
+ b: "Lorem ipsum dolor sit amet, consectetur \nLorem ipsum dolor sit amet, consectetur\n"
683
+ ```
603
684
 
604
- Please install the latest version of the gem and run `i18n-tasks check-prism` which will parse everything with the whitequark/parser-based scanner and then everything with the Prism-scanner and try to compare the results.
685
+ The only difference between `a` and `b` is that `b` has an extra trailing space in each line.
686
+ This is an unfortunate side effect of `i18n-tasks` using `Psych`.
605
687
 
606
- Open up issues with any parser crashes, missed translations or false positives.
688
+ ## Advanced
607
689
 
608
- ## Interactive console
690
+ ### Interactive console
609
691
 
610
- `i18n-tasks irb` starts an IRB session in i18n-tasks context. Type `guide` for more information.
692
+ `bundle exec i18n-tasks irb` starts an IRB session in i18n-tasks context. Type `guide` for more information.
611
693
 
612
- ## Import / export to a CSV spreadsheet
694
+ ### CSV import / export
613
695
 
614
696
  See [i18n-tasks wiki: CSV import and export tasks](https://github.com/glebm/i18n-tasks/wiki/Custom-CSV-import-and-export-tasks).
615
697
 
616
- ## Add new tasks
698
+ ### Add custom tasks
617
699
 
618
700
  Tasks that come with the gem are defined in [lib/i18n/tasks/command/commands](lib/i18n/tasks/command/commands).
619
701
  Custom tasks can be added easily, see the examples [on the wiki](https://github.com/glebm/i18n-tasks/wiki#custom-tasks).
620
702
 
621
- # Development
703
+ ## Development
622
704
 
623
705
  - Install dependencies using `bundle install`
624
706
  - Run tests using `bundle exec rspec`
625
707
  - Install [Overcommit](https://github.com/sds/overcommit) by running `overcommit --install`
626
708
 
627
- ## Skip Overcommit-hooks
709
+ ### Skip Overcommit hooks
628
710
 
629
711
  - `SKIP=RuboCop git commit`
630
712
  - `OVERCOMMIT_DISABLE=1 git commit`
631
713
 
632
-
633
714
  [MIT license]: /LICENSE.txt
634
715
  [ci]: https://github.com/glebm/i18n-tasks/actions/workflows/tests.yml
635
716
  [badge-ci]: https://github.com/glebm/i18n-tasks/actions/workflows/tests.yml/badge.svg
636
- [coverage]: https://codeclimate.com/github/glebm/i18n-tasks
637
- [badge-coverage]: https://api.codeclimate.com/v1/badges/5d173e90ada8df07cedc/test_coverage
638
717
  [config]: https://github.com/glebm/i18n-tasks/blob/main/templates/config/i18n-tasks.yml
639
718
  [wiki]: https://github.com/glebm/i18n-tasks/wiki "i18n-tasks wiki"
640
719
  [i18n-gem]: https://github.com/svenfuchs/i18n "svenfuchs/i18n on Github"