handlebars-i18n-cli 2.0.2 → 2.0.4
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.
- package/README.md +42 -85
- package/README_programmatic.md +4 -4
- package/package.json +15 -15
- package/test/handlebars-i18n-cli.test.mjs +37 -34
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ If you do not link the package, you may run into the error `bash: i18n-collect:
|
|
|
25
25
|
|
|
26
26
|
## General Use
|
|
27
27
|
|
|
28
|
-
### 1. Language Key Extraction:
|
|
28
|
+
### 1. Language Key Extraction: i18n-collect
|
|
29
29
|
|
|
30
30
|
#### Syntax:
|
|
31
31
|
|
|
@@ -39,10 +39,10 @@ Generate a file `translations.json` holding the translations for `de`, `fr`, and
|
|
|
39
39
|
names intended for i18next translation from all html files in your project:
|
|
40
40
|
|
|
41
41
|
```shell
|
|
42
|
-
i18n-collect
|
|
42
|
+
i18n-collect src/**/*.hbs src/translations.json --lng de,en,fr
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
From a very simple template like this …
|
|
45
|
+
From a very simple handlebars template like this …
|
|
46
46
|
|
|
47
47
|
```html
|
|
48
48
|
<!DOCTYPE html>
|
|
@@ -56,7 +56,7 @@ From a very simple template like this …
|
|
|
56
56
|
</html>
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
… the generated translations.json would be:
|
|
59
|
+
… the generated `translations.json` would be:
|
|
60
60
|
|
|
61
61
|
```json
|
|
62
62
|
{
|
|
@@ -83,7 +83,7 @@ From a very simple template like this …
|
|
|
83
83
|
}
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
-
### 2. Automatic Translation via DeepL API:
|
|
86
|
+
### 2. Automatic Translation via DeepL API: i18n-deepl
|
|
87
87
|
|
|
88
88
|
#### Syntax:
|
|
89
89
|
|
|
@@ -97,7 +97,7 @@ i18n-deepl translate <source> <target> <targetLang> <options...>
|
|
|
97
97
|
i18n-deepl translate en.json fi.json fi
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
Runs the file `en.json` against DeepL API. From this …
|
|
101
101
|
|
|
102
102
|
```
|
|
103
103
|
{
|
|
@@ -107,7 +107,7 @@ Will run the file en.json against DeepL API. From this file
|
|
|
107
107
|
}
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
-
…
|
|
110
|
+
… the Finish translation `fi.json` will be generated:
|
|
111
111
|
|
|
112
112
|
```
|
|
113
113
|
{
|
|
@@ -130,11 +130,10 @@ template, the carry to the according language JSON is done by the CLI. You then
|
|
|
130
130
|
In case a translation string expects variables for replacement, these variables will be added to your json template.
|
|
131
131
|
|
|
132
132
|
If you are not using [handlebars-i18n](https://github.com/fwalzel/handlebars-i18n.git) for translation but a custom integration of i18next into handlebars.js, you
|
|
133
|
-
might be able to appropriate this cli by using the option --translFunc (
|
|
134
|
-
see below).
|
|
133
|
+
might be able to appropriate this cli by using the option --translFunc (see below).
|
|
135
134
|
|
|
136
|
-
Also `handlebars-i18n-cli` allows you to **auto-translate** a JSON file with an existing translation to another language
|
|
137
|
-
via the DeepL API, while the original key names and
|
|
135
|
+
Also `handlebars-i18n-cli` allows you to **auto-translate** a JSON file with an existing translation to another language
|
|
136
|
+
via the DeepL API, while the original key names and JSON structure are kept.
|
|
138
137
|
|
|
139
138
|
## Example
|
|
140
139
|
|
|
@@ -164,7 +163,7 @@ You can use `handlebars-i18n-cli` in a programmatical way too. For import and in
|
|
|
164
163
|
|
|
165
164
|
* The source files can be passed in as [glob](https://www.npmjs.com/package/glob) pattern.
|
|
166
165
|
* i18n-collect is agnostic against the data type of the template(s) you want to extract translations keys from. It works
|
|
167
|
-
with `.html` as well as `.js` files.
|
|
166
|
+
with `.hbs`, `.html` as well as `.js` files.
|
|
168
167
|
|
|
169
168
|
`<target>`
|
|
170
169
|
|
|
@@ -177,36 +176,22 @@ i18next.init({
|
|
|
177
176
|
});
|
|
178
177
|
```
|
|
179
178
|
|
|
180
|
-
###
|
|
179
|
+
### Options
|
|
181
180
|
|
|
182
|
-
`--alphabetical` or `-a
|
|
181
|
+
* `--alphabetical` or `-a`: Order the keys to the translation strings alphabetically in the generated json file(s). When the flag
|
|
182
|
+
is not set, the keys appear in order as within the template(s).
|
|
183
|
+
* `--dryRun` or `-dr`: For simulation: Logs the result to console, but does not write out the file(s) to disk.
|
|
184
|
+
* `--empty` or `-e`: Create empty value strings for the translations in the json files(s). When the flag is not set, the
|
|
185
|
+
value strings contain current language and key name. Example:
|
|
183
186
|
|
|
184
|
-
|
|
185
|
-
--alphabetical is not set the keys appear in order as within the template(s).
|
|
186
|
-
|
|
187
|
-
---
|
|
188
|
-
|
|
189
|
-
`--dryRun` or `-dr`
|
|
190
|
-
|
|
191
|
-
For simulation: Logs the result to console, but does not write out the file(s) to disk.
|
|
192
|
-
|
|
193
|
-
---
|
|
194
|
-
|
|
195
|
-
`--empty` or `-e`
|
|
196
|
-
|
|
197
|
-
Create empty value strings for the translations in the json files(s). When the flag --empty is not set the
|
|
198
|
-
value strings contain current language and key name.
|
|
199
|
-
|
|
200
|
-
Example:
|
|
201
|
-
|
|
202
|
-
The template
|
|
187
|
+
The template …
|
|
203
188
|
|
|
204
189
|
```html
|
|
205
190
|
<h1>{{__ headline userName="Frank"}}</h1>
|
|
206
191
|
<p>{{__ paragraph}}</p>
|
|
207
192
|
```
|
|
208
193
|
|
|
209
|
-
would become
|
|
194
|
+
… would become …
|
|
210
195
|
|
|
211
196
|
```json
|
|
212
197
|
{
|
|
@@ -219,7 +204,7 @@ would become
|
|
|
219
204
|
}
|
|
220
205
|
```
|
|
221
206
|
|
|
222
|
-
instead of
|
|
207
|
+
… instead of:
|
|
223
208
|
|
|
224
209
|
```json
|
|
225
210
|
{
|
|
@@ -232,71 +217,43 @@ instead of
|
|
|
232
217
|
}
|
|
233
218
|
```
|
|
234
219
|
|
|
235
|
-
---
|
|
236
|
-
|
|
237
|
-
`--lng language1,language2,...languageN`
|
|
238
|
-
|
|
239
|
-
The list of language shortcodes you want to be generated with an own set in the json. Arguments are comma separated (no
|
|
240
|
-
blank space between, no quotation marks around).
|
|
241
|
-
If no language is defined, "en" is the default.
|
|
242
|
-
|
|
243
|
-
---
|
|
244
|
-
|
|
245
|
-
`--log` or `-l`
|
|
246
|
-
|
|
247
|
-
Logs the final result that is written to the json files(s) into the console as well.
|
|
248
220
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
`--
|
|
252
|
-
|
|
253
|
-
|
|
221
|
+
* `--lng <languges>`: The list of language shortcodes you want to be generated with an own set in the json. Arguments are comma separated (no
|
|
222
|
+
blank space between, no quotation marks around). Example: `--lng de,fi,en,es`. If no language is defined, `en` is the default.
|
|
223
|
+
* `--log` or `-l`: Logs the final result that is written to the json files(s) into the console as well.
|
|
224
|
+
* `--separateLngFiles` or `-sf`: Write each language in a separate json file instead of a single one. (By default,
|
|
225
|
+
all translations are written to a single json file). This will generate three json files
|
|
226
|
+
*translation.de.json*, *translation.en.json*, and *translation.fn.json*, each
|
|
227
|
+
holding only the translation for their respective language:
|
|
254
228
|
|
|
255
229
|
```bash
|
|
256
|
-
i18n-collect
|
|
230
|
+
i18n-collect template.hbs translation.json --lng de,en,fn --separateLngFiles
|
|
257
231
|
```
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
---
|
|
263
|
-
|
|
264
|
-
`--translFunc=yourCustomFunctionName`
|
|
265
|
-
|
|
266
|
-
If you are not using handlebars-i18n for translations but a custom handlebars helper, you might be able to use
|
|
267
|
-
i18n-collect as well.Say your translation function has the name *t* instead of handlebars-i18n’s *__* (double
|
|
268
|
-
underscore) and your template usage would look like
|
|
232
|
+
* `--translFunc <yourCustomFunctionName>`: Substitutes the query name for the handlebars function, performing the translation.
|
|
233
|
+
Say your translation function has the name *t* instead of handlebars-i18n’s *__* (double underscore) and your template
|
|
234
|
+
usage would look like:
|
|
269
235
|
|
|
270
236
|
```html
|
|
271
|
-
{{t myKeyNameToTranslation}}
|
|
237
|
+
<p> {{t myKeyNameToTranslation}} </p>
|
|
272
238
|
```
|
|
273
239
|
|
|
274
|
-
|
|
240
|
+
You can then do …
|
|
275
241
|
|
|
276
242
|
```bash
|
|
277
|
-
i18n-collect my-project/template.html my-project/translation.json --translFunc
|
|
243
|
+
i18n-collect my-project/template.html my-project/translation.json --translFunc t
|
|
278
244
|
```
|
|
279
245
|
|
|
280
|
-
|
|
246
|
+
… this substitutes the default *__* with a search for the handlebars function named *t*.
|
|
281
247
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
Update an existing .json file with new translations. All keys in the existing .json are kept, new ones from the template
|
|
287
|
-
will be added.
|
|
288
|
-
|
|
289
|
-
Works also with the option --separateLngFiles:
|
|
248
|
+
* `--update` or `-u`: Update an existing .json file with new translations. All keys in the existing .json are kept, new ones from the template
|
|
249
|
+
will be added. Works also with the option `--separateLngFiles`. Leave out the language ending and json file extension and give only the base name for <target>. In this example case
|
|
250
|
+
handlebars-i18n-cli would look for *translation.de.json*, *translation.en.json*, and *translation.en.json* to update
|
|
251
|
+
them. A language file that does not exist yet will be generated.
|
|
290
252
|
|
|
291
253
|
```bash
|
|
292
254
|
i18n-collect my-project/**/*.html my-project/translation --update --lng de,en,fr --separateLngFiles
|
|
293
255
|
```
|
|
294
256
|
|
|
295
|
-
Leave out the language ending and json file extension and give only the base name for <target>. In this example case
|
|
296
|
-
handlebars-i18n-cli would look for *translation.de.json*, *translation.en.json*, and *translation.en.json* to update
|
|
297
|
-
them. A language file that does not exist yet will be generated.
|
|
298
|
-
|
|
299
|
-
|
|
300
257
|
## Detailed Description for _i18n-deepl_ Commands
|
|
301
258
|
|
|
302
259
|
i18n-deepl is a command-line tool to translate i18next JSON files using the DeepL API. Below is a detailed guide to its
|
|
@@ -403,10 +360,10 @@ Translation complete. See ./output.json for your results.
|
|
|
403
360
|
## Run tests
|
|
404
361
|
|
|
405
362
|
```bash
|
|
406
|
-
npm
|
|
363
|
+
npm test
|
|
407
364
|
```
|
|
408
365
|
|
|
409
366
|
## License
|
|
410
367
|
|
|
411
|
-
Copyright (c) 2022
|
|
412
|
-
|
|
368
|
+
MIT License, Copyright (c) 2022–25 Florian Walzel
|
|
369
|
+
|
package/README_programmatic.md
CHANGED
|
@@ -26,10 +26,10 @@ existing keys, and generating separate files for each language.
|
|
|
26
26
|
|
|
27
27
|
**Positional Arguments**
|
|
28
28
|
|
|
29
|
-
| **Name** | **Type** | **Description**
|
|
30
|
-
|
|
31
|
-
| `source` | `string` | Path to the source
|
|
32
|
-
| `target` | `string` | Path to the target
|
|
29
|
+
| **Name** | **Type** | **Description** | **Example** |
|
|
30
|
+
|-----------|---------------|------------------------------------------|---------------------------|
|
|
31
|
+
| `source` | `string` | Path to the source file(s). Glob allowed | `./src/**/*.html` |
|
|
32
|
+
| `target` | `string` | Path to the target file(s) or directory. | `./src/translations.json` |
|
|
33
33
|
|
|
34
34
|
**Optional `options` Object Properties**
|
|
35
35
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "handlebars-i18n-cli",
|
|
3
|
-
"version": "2.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.0.4",
|
|
4
|
+
"description": "Extract translation keys from handlebars templates and form JSON from it, generate automatic translations via DeepL",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"types": "types/index.d.ts",
|
|
@@ -41,21 +41,21 @@
|
|
|
41
41
|
"homepage": "https://github.com/fwalzel/handlebars-i18n-cli#readme",
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"async-file-tried": "^1.2.1",
|
|
44
|
-
"axios": "^1.
|
|
45
|
-
"commander": "^
|
|
46
|
-
"deepl-node": "^1.
|
|
47
|
-
"dotenv": "^
|
|
48
|
-
"glob": "^11.0.
|
|
44
|
+
"axios": "^1.12.2",
|
|
45
|
+
"commander": "^14.0.1",
|
|
46
|
+
"deepl-node": "^1.19.1",
|
|
47
|
+
"dotenv": "^17.2.2",
|
|
48
|
+
"glob": "^11.0.3"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"c8": "^10.1.
|
|
52
|
-
"chai": "^5.
|
|
53
|
-
"chai-as-promised": "^8.0.
|
|
54
|
-
"coveralls-next": "^
|
|
55
|
-
"mocha": "^
|
|
56
|
-
"sinon": "^
|
|
57
|
-
"sinon-chai": "^4.0.
|
|
51
|
+
"c8": "^10.1.3",
|
|
52
|
+
"chai": "^5.3.3",
|
|
53
|
+
"chai-as-promised": "^8.0.2",
|
|
54
|
+
"coveralls-next": "^5.0.0",
|
|
55
|
+
"mocha": "^11.7.2",
|
|
56
|
+
"sinon": "^21.0.0",
|
|
57
|
+
"sinon-chai": "^4.0.1",
|
|
58
58
|
"test-console": "^2.0.0",
|
|
59
|
-
"typescript": "^5.
|
|
59
|
+
"typescript": "^5.9.2"
|
|
60
60
|
}
|
|
61
61
|
}
|
|
@@ -117,7 +117,7 @@ describe('i18n-deepl getSupportedLanguages', function () {
|
|
|
117
117
|
* translateTexts
|
|
118
118
|
****************************************/
|
|
119
119
|
describe('i18n-deepl translateTexts', () => {
|
|
120
|
-
let authKey, texts, sourceLang, targetLang, options,
|
|
120
|
+
let authKey, texts, sourceLang, targetLang, options, translateTextStub;
|
|
121
121
|
|
|
122
122
|
beforeEach(() => {
|
|
123
123
|
authKey = 'valid-auth-key';
|
|
@@ -126,50 +126,53 @@ describe('i18n-deepl translateTexts', () => {
|
|
|
126
126
|
targetLang = 'FR';
|
|
127
127
|
options = {formality: 'informal'};
|
|
128
128
|
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
sinon.stub(deepl, '
|
|
129
|
+
// Stub the 'translateText' method on the prototype
|
|
130
|
+
// This will affect all future instances of deepl.Translator
|
|
131
|
+
translateTextStub = sinon.stub(deepl.Translator.prototype, 'translateText');
|
|
132
132
|
});
|
|
133
133
|
|
|
134
134
|
afterEach(() => {
|
|
135
|
-
sinon.restore(); //
|
|
135
|
+
sinon.restore(); // This correctly restores the original prototype method
|
|
136
136
|
});
|
|
137
137
|
|
|
138
138
|
it('[C-1] translateTexts should throw an error if authKey is not a string', async () => {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
expect(error.message).to.equal('Invalid argument authKey provided.');
|
|
143
|
-
}
|
|
139
|
+
// This test doesn't even need the stub, but the setup is now correct
|
|
140
|
+
await expect(translateTexts(12345, texts, sourceLang, targetLang, options))
|
|
141
|
+
.to.be.rejectedWith('Invalid argument authKey provided.');
|
|
144
142
|
});
|
|
145
143
|
|
|
146
|
-
it('[C-2]
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
await translateTexts(authKey, texts, sourceLang, targetLang, options);
|
|
144
|
+
it('[C-2] should call the translator once with all texts and return the aggregated results', async () => {
|
|
145
|
+
// The expected result from your function
|
|
146
|
+
const expectedTranslations = ['Bonjour', 'Monde'];
|
|
150
147
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
148
|
+
// This is what the deepl-node library would return for a batch request
|
|
149
|
+
const mockApiResult = [
|
|
150
|
+
'Bonjour', 'Monde'
|
|
151
|
+
];
|
|
154
152
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
translatorStub.translateText.resolves(translatedTexts);
|
|
153
|
+
// CORRECT: Configure the stub to resolve ONCE with an array of result objects.
|
|
154
|
+
translateTextStub.resolves(mockApiResult);
|
|
158
155
|
|
|
159
156
|
const result = await translateTexts(authKey, texts, sourceLang, targetLang, options);
|
|
160
157
|
|
|
161
|
-
|
|
158
|
+
// CORRECT: Verify the stub was called only ONCE.
|
|
159
|
+
// The first argument should be the entire `texts` array.
|
|
160
|
+
// The `options` object is also being passed, so we include it in the check.
|
|
161
|
+
expect(translateTextStub).to.have.been.calledOnceWith(texts, sourceLang, targetLang, options);
|
|
162
|
+
|
|
163
|
+
// The assertion for the final output remains the same and should now pass.
|
|
164
|
+
expect(result).to.deep.equal(expectedTranslations);
|
|
162
165
|
});
|
|
163
166
|
|
|
164
|
-
it('[C-
|
|
165
|
-
const
|
|
166
|
-
translatorStub.translateText.rejects(error);
|
|
167
|
+
it('[C-3] should throw an error when the translation API fails', async () => {
|
|
168
|
+
const apiError = new Error('API Error');
|
|
167
169
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
// CORRECT: Configure the stub to reject the promise directly.
|
|
171
|
+
translateTextStub.rejects(apiError);
|
|
172
|
+
|
|
173
|
+
// Use chai-as-promised for cleaner async error checking.
|
|
174
|
+
await expect(translateTexts(authKey, texts, sourceLang, targetLang, options))
|
|
175
|
+
.to.be.rejectedWith(apiError);
|
|
173
176
|
});
|
|
174
177
|
});
|
|
175
178
|
|
|
@@ -381,9 +384,9 @@ describe('i18n-collect', () => {
|
|
|
381
384
|
}
|
|
382
385
|
inspect.restore();
|
|
383
386
|
assert.deepEqual(inspect.output, [
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
+
`Now processing test/test-assets/simple.html\n`,
|
|
388
|
+
`{\n \"translations\": {\n \"en\": {\n \"myKey\": \"en of myKey with variables {{myVar}}\"\n }\n }\n}\n`
|
|
389
|
+
],
|
|
387
390
|
'The logged output did not match the expected result.');
|
|
388
391
|
});
|
|
389
392
|
|
|
@@ -459,8 +462,8 @@ describe('i18n-collect', () => {
|
|
|
459
462
|
const inspect = stdout.inspect();
|
|
460
463
|
try {
|
|
461
464
|
await i18nCollect(templSimple, `test/test-generated/test-${fileNo}.json`, {update: true, log: true});
|
|
462
|
-
} catch(e) {
|
|
463
|
-
console.log
|
|
465
|
+
} catch (e) {
|
|
466
|
+
console.log(e)
|
|
464
467
|
}
|
|
465
468
|
inspect.restore();
|
|
466
469
|
assert.deepEqual(inspect.output, [
|