@lingual/i18n-check 0.2.0 → 0.3.1

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 CHANGED
@@ -1,15 +1,28 @@
1
1
  # Lingual i18n-check
2
2
 
3
- **i18n-check** will help with validating your translation files, including
4
- checking for missing and/or invalid/broken translations.
5
- It will compare the defined source language with all target translation files and try to find inconsistencies between source and target files.
3
+ **i18n-check** validates your [ICU](https://github.com/unicode-org/icu) and [i18next](https://www.i18next.com/) translation files and checks for missing and broken translations.
4
+ It compares the defined source language with all target translation files and finds inconsistencies between source and target files.
6
5
  You can run these checks as a pre-commit hook or on the CI depending on your use-case and setup.
7
6
 
8
- ![example 1](./assets/i18n-check-example-one.png)
7
+ ![example 1](./assets/i18n-check-screenshot-full.png)
9
8
 
10
- ![example 2](./assets/i18n-check-example-two.png)
9
+ ![example 2](./assets/i18n-check-screenshot-summary.png)
11
10
 
12
- ## Installation and Usage
11
+ ## Table of Contents
12
+
13
+ - [Installation](#installation)
14
+ - [General Usage](#general-usage)
15
+ - [CLI Options](#options)
16
+ - [Examples](#examples)
17
+ - [Single folder](#single-folder)
18
+ - [Folder per locale](#folder-per-locale)
19
+ - [Folder per locale with multiple files](#folder-per-locale-with-multiple-files)
20
+ - [Github Action](#as-github-action)
21
+ - [API](#api)
22
+ - [Development](#development)
23
+ - [Links](#links)
24
+
25
+ ## Installation
13
26
 
14
27
  Using **yarn**:
15
28
 
@@ -29,9 +42,9 @@ Using **pnpm**:
29
42
  pnpm add --save-dev @lingual/i18n-check
30
43
  ```
31
44
 
32
- After the installation, `i18n-check` can either be accessed via defining a command in the `package.json` file or directly in the CLI.
45
+ After the installation, i18n-check can either be accessed via defining a command in the `package.json` file or directly in the CLI.
33
46
 
34
- Update the your `package.json` and add a new command:
47
+ Update your `package.json` and add a new command:
35
48
 
36
49
  ```bash
37
50
  "scripts": {
@@ -48,172 +61,197 @@ Alternatively you can also access the library directly:
48
61
  node_modules/.bin/i18n-check
49
62
  ```
50
63
 
64
+ ## General Usage
65
+
66
+ For i18n-check to work you need to provide it at a minimum the source locale (`--source, -s`) for the primary language and the path to the locale translation files (`--locales, -l`).
67
+
68
+ Example:
69
+
70
+ ```bash
71
+ yarn i18n:check -s en-US --locales translations/
72
+ ```
73
+
74
+ Instead of a single source file you can also pass a directory:
75
+
76
+ ```bash
77
+ yarn i18n:check -s en-US --locales translations/
78
+ ```
79
+
80
+ See the [examples](#examples) for more details.
81
+
51
82
  ## Options
52
83
 
53
- ### --target
84
+ ### --locales, -l
54
85
 
55
- With the `-t` or `--target` option you define which folder or multiple folders you want to run the i18n checks against. It is a **required** option. `i18n-check` will try to find all target locale files and compare these files against the defined source file(s).
86
+ With the `-l` or `--locales` option you define which folder or multiple folders you want to run the i18n checks against. It is a **required** option. i18n-check will try to find all target locale files and compare these files against the defined source file(s).
56
87
  Check the [example](#examples) to see how different locale translation files are organised and how they can be addressed.
57
88
 
58
89
  ```bash
59
- yarn i18n:check -t translations/messageExamples -s translations/messageExamples/en-us.json
90
+ yarn i18n:check --locales translations/messageExamples -s en-US
60
91
  ```
61
92
 
62
- ### --source
93
+ ### --source, -s
63
94
 
64
- With the `-s` or `--source` option you define which file(s) or folder(s) you want to use as the source to compare all target files against. It is a **required** option. `i18n-check` will try to find all target locale files and compare these files against the defined source file(s).
65
- Check the [example](#examples) to see how different locale translation files are organised and how they can be addressed.
95
+ With the `-s` or `--source` option you define the source locale to compare all target files against. It is a **required** option. i18n-check will try to find all target locale files and compare these files against the applicable source file(s).
96
+ Check the [examples](#examples) to see how different locale translation files are organised and how they can be addressed.
66
97
 
67
98
  ```bash
68
- yarn i18n:check -t translations/messageExamples -s translations/messageExamples/en-us.json
99
+ yarn i18n:check --locales translations/messageExamples -s en-US
69
100
  ```
70
101
 
71
- ### --format
102
+ ### --format, -f
72
103
 
73
- By default `i18n-check` will validate against any [`icu`](https://github.com/unicode-org/icu) compliant translations.
104
+ By default i18n-check will validate against any [ICU](https://github.com/unicode-org/icu) compliant translations.
74
105
  Additionally the `i18next` format is supported and can be set via the `-f` or `--format` option.
75
106
 
76
- There are i18n libraries that have their own specific format, which might not be based on `icu` and therefore can not be validated against currently. On a side-note: there might be future support for more specific formats.
107
+ There are i18n libraries that have their own specific format, which might not be based on ICU and therefore can not be validated against currently. On a side-note: there might be future support for more specific formats.
77
108
 
78
- Hint: If you want to use the `--unused` flag, you should provide `react-intl` as the format. Also see the [`unused` section](#--unused) for more details.
109
+ Hint: If you want to use the `--unused` flag, you should provide react-intl as the format. Also see the [`unused` section](#--unused) for more details.
79
110
 
80
111
  ```bash
81
- yarn i18n:check -t translations/i18NextMessageExamples -s translations/i18NextMessageExamples/en-us.json -f i18next
112
+ yarn i18n:check --locales translations/i18NextMessageExamples -s en-US -f i18next
82
113
  ```
83
114
 
84
- ### --check
115
+ ### --check, -c
85
116
 
86
- By default the `i18n-check` will perform a validation against any **missing** and/or **invalid** keys. There are situations where only a specific check should run. By using the `-c` or `--check` option you can specify a specific check to run.
117
+ By default i18n-check will perform a validation against any **missing** and/or **invalid** keys. There are situations where only a specific check should run. By using the `-c` or `--check` option you can specify a specific check to run.
87
118
 
88
119
  The available options are `missingKeys`, which will check against any missing keys in the target files and `invalidKeys` will check for invalid keys, where the target translations has a different type then the one defined in the source file.
89
120
 
90
121
  Check for missing keys:
91
122
 
92
123
  ```bash
93
- yarn i18n:check -t translations/messageExamples -s translations/messageExamples/en-us.json -c missingKeys
124
+ yarn i18n:check --locales translations/messageExamples -s en-US -c missingKeys
94
125
  ```
95
126
 
96
127
  Check for invalid keys:
97
128
 
98
129
  ```bash
99
- yarn i18n:check -t translations/messageExamples -s translations/messageExamples/en-us.json -c invalidKeys
130
+ yarn i18n:check --locales translations/messageExamples -s en-US -c invalidKeys
100
131
  ```
101
132
 
102
- Check for missing an invalid keys (which is the default):
133
+ Check for missing and invalid keys (which is the default):
103
134
 
104
135
  ```bash
105
- yarn i18n:check -t translations/messageExamples -s translations/messageExamples/en-us.json -c missingKeys,invalidKeys
136
+ yarn i18n:check --locales translations/messageExamples -s en-US -c missingKeys,invalidKeys
106
137
  ```
107
138
 
108
- ### --unused
139
+ ### --unused, -u
109
140
 
110
- This feature is currently only supported for `react-intl` and is useful if you need to know which keys exist in your translation files but not in your codebase. Via the `-u` or `--unused` option you provide a source path to the code, which will be parsed to find all unused keys in the primay target language.
141
+ This feature is currently only supported for react-intl and is useful if you need to know which keys exist in your translation files but not in your codebase. Via the `-u` or `--unused` option you provide a source path to the code, which will be parsed to find all unused keys in the primary target language.
111
142
 
112
143
  It is important to note that you must also provide the `-f` or `--format` option with `react-intl` as value. See the [`format` section](#--format) for more information.
113
144
 
114
145
  ```bash
115
- yarn i18n:check -t translations/messageExamples -s translations/messageExamples/en-us.json -u client/ -f react-intl
146
+ yarn i18n:check --locales translations/messageExamples -s en-US -u client/ -f react-intl
116
147
  ```
117
148
 
118
- ### --reporter
149
+ ### --reporter, -r
119
150
 
120
151
  The standard reporting prints out all the missing or invalid keys.
121
152
  Using the `-r` or `--reporter` option enables to override the standard error reporting. Passing the `summary` option will print a summary of the missing or invalid keys.
122
153
 
123
154
  ```bash
124
- yarn i18n:check -t translations/messageExamples -s translations/messageExamples/en-us.json -r summary
155
+ yarn i18n:check --locales translations/messageExamples -s en-US -r summary
125
156
  ```
126
157
 
127
- ### --exclude
158
+ ### --exclude, -e
128
159
 
129
- There are situations where we want to exclude a single or multiple files or a single folder or a group of folders. A typical scenario would be that some keys are missing in a specific folder, as they are being work in progress for example. To exclude this or these files/folders you can use the `-e` or `--exclude` option. It expects a comma separated string of files and/or folders.
160
+ There are situations where we want to exclude a single or multiple files or a single folder or a group of folders. A typical scenario would be that some keys are missing in a specific folder, as they are being work in progress for example. To exclude this or these files/folders you can use the `-e` or `--exclude` option. It expects one or more files and/or folders.
130
161
 
131
162
  To exclude a single file:
132
163
 
133
164
  ```bash
134
- yarn i18n:check -t translations/messageExamples -s translations/messageExamples/en-us.json -e translations/messageExamples/fr-fr.json
165
+ yarn i18n:check --locales translations/messageExamples -s en-US -e translations/messageExamples/fr-fr.json
135
166
  ```
136
167
 
137
- To exclude multiple files, provide a comma-separated list:
168
+ To exclude multiple files provide all files:
138
169
 
139
170
  ```bash
140
- yarn i18n:check -t translations/messageExamples -s translations/messageExamples/en-us.json -e translations/messageExamples/fr-fr.json,translations/messageExamples/de-at.json
171
+ yarn i18n:check --locales translations/messageExamples -s en-US -e translations/messageExamples/fr-fr.json translations/messageExamples/de-at.json
141
172
  ```
142
173
 
143
174
  To exclude a single folder:
144
175
 
145
176
  ```bash
146
- yarn i18n:check -t translations/folderExamples -s translations/folderExamples/en -e translations/folderExamples/fr/*
177
+ yarn i18n:check --locales translations/folderExamples -s en-US -e translations/folderExamples/fr/*
147
178
  ```
148
179
 
149
- Alternatively you can exclude multiple folders by providing a comma-separated list of folders to be excluded:
180
+ Alternatively you can exclude multiple folders by providing the folders to be excluded:
150
181
 
151
182
  ```bash
152
- yarn i18n:check -t translations/folderExamples -s translations/folderExamples/en -e translations/folderExamples/fr/*,translations/folderExample/it/*
183
+ yarn i18n:check --locales translations/folderExamples -s en-US -e translations/folderExamples/fr/* translations/folderExample/it/*
153
184
  ```
154
185
 
155
- The `--exclude` option also accepts a mix of files and folders, which follows the same pattern as above and can be defined as a comma-separated list, i.e.
156
- `-e translations/folderExamples/fr/*,translations/messageExamples/it.json`
186
+ The `--exclude` option also accepts a mix of files and folders, which follows the same pattern as above, i.e.
187
+ `-e translations/folderExamples/fr/* translations/messageExamples/it.json`
157
188
 
158
189
  ## Examples
159
190
 
160
- `i18n-check` is able to load and validate against different locale folder structures. Depending on how the locale files are organized, there are different configuration options.
191
+ i18n-check is able to load and validate against different locale folder structures. Depending on how the locale files are organized, there are different configuration options.
161
192
 
162
- #### Single folder
193
+ ### Single folder
163
194
 
164
195
  If all the locales are organized in a **single folder**:
165
196
 
166
- - locales/
167
- - en-en.json
168
- - de-de.json
197
+ ```
198
+ locales/
199
+ en-en.json
200
+ de-de.json
201
+ ```
169
202
 
170
203
  Use the `t` or `target` option to define the directory that should be checked for target files. With the `s` or `source` option you can specify the base/reference file to compare the target files against.
171
204
 
172
205
  ```bash
173
- yarn i18n:check -t locales -s locales/en-us.json
206
+ yarn i18n:check --locales locales -s locales/en-us.json
174
207
  ```
175
208
 
176
- #### Folder per locale
209
+ ### Folder per locale
177
210
 
178
211
  If the locales are **organised as folders** containing a single json file:
179
212
 
180
- - locales/
181
- - en-US/
182
- - index.json
183
- - de-DE/
184
- - index.json
213
+ ```
214
+ locales/
215
+ en-US/
216
+ index.json
217
+ de-DE/
218
+ index.json
219
+ ```
185
220
 
186
221
  Define the `locales` folder as the directory to look for target files.
187
222
 
188
223
  ```bash
189
- yarn i18n:check -t locales -s locales/en-US/index.json
224
+ yarn i18n:check --locales locales -s en-US
190
225
  ```
191
226
 
192
- #### Folder per locale with multiple files
227
+ ### Folder per locale with multiple files
193
228
 
194
229
  If the locales are **organised as folders** containing multiple json files:
195
230
 
196
- - locales/
197
- - en-US/
198
- - one.json
199
- - two.json
200
- - three.json
201
- - de-DE/
202
- - one.json
203
- - two.json
204
- - three.json
231
+ ```
232
+ locales/
233
+ en-US/
234
+ one.json
235
+ two.json
236
+ three.json
237
+ de-DE/
238
+ one.json
239
+ two.json
240
+ three.json
241
+ ```
205
242
 
206
- Define the `locales` folder as the directory to look for target files and pass `locales/en-US/` as the `source` option. `i18n-check` will try to collect all the files in the provided source directory and compare each one against the corresponding files in the target locales.
243
+ Define the `locales` folder as the directory to look for target files and pass `locales/en-US/` as the `source` option. i18n-check will try to collect all the files in the provided source directory and compare each one against the corresponding files in the target locales.
207
244
 
208
245
  ```bash
209
- yarn i18n:check -t locales -s locales/en-US/
246
+ yarn i18n:check --locales locales -s en-US
210
247
  ```
211
248
 
212
249
  #### Multiple folders containing locales
213
250
 
214
251
  If the locales are **organised as folders** containing multiple json files:
215
252
 
216
- - dirOne
253
+ ```
254
+ - spaceOne
217
255
  - locales/
218
256
  - en-US/
219
257
  - one.json
@@ -223,28 +261,29 @@ If the locales are **organised as folders** containing multiple json files:
223
261
  - one.json
224
262
  - two.json
225
263
  - three.json
226
- - dirTwo
264
+ - spaceTwo
227
265
  - locales/
228
- - en/
266
+ - en-US/
229
267
  - one.json
230
268
  - two.json
231
269
  - three.json
232
- - de/
270
+ - de-DE/
233
271
  - one.json
234
272
  - two.json
235
273
  - three.json
274
+ ```
236
275
 
237
- Define the `locales` folder as the directory to look for target files and pass `locales/en-US/` as the `source` option. `i18n-check` will try to collect all the files in the provided source directory and compare each one against the corresponding files in the target locales.
276
+ Define the `locales` folder as the directory to look for target files and pass `en-US` as the `source` option. i18n-check will try to collect all the files in the provided source directory and compare each one against the corresponding files in the target locales.
238
277
 
239
278
  ```bash
240
- yarn i18n:check -t dirOne,dirTwo -s dirOne/en/,dirTwo/en
279
+ yarn i18n:check -l spaceOne spaceTwo -s en-US
241
280
  ```
242
281
 
243
282
  ## As Github Action
244
283
 
245
- We currently do not offer an explicit **Github Action** you can use out of the box, but if you have `i18n-check` already installed, you can define your own **YAML** file. The following example can be starting point that you can adapt to your current setup:
284
+ We currently do not offer an explicit **Github Action** you can use out of the box, but if you have i18n-check already installed, you can define your own **YAML** file. The following example can be seen as a starting point that you can adapt to your current setup:
246
285
 
247
- ```
286
+ ```yml
248
287
  name: i18n Check
249
288
  on:
250
289
  pull_request:
@@ -268,13 +307,17 @@ jobs:
268
307
 
269
308
  - name: yarn i18n-check
270
309
  run: |
271
- yarn i18n-check -t translations/messageExamples -s translations/messageExamples/en-us.json
310
+ yarn i18n-check --locales translations/messageExamples --source en-US
272
311
  ```
273
312
 
313
+ The above workflow will return any missing or invalid keys and the action would fail if missing/invalid keys are found:
314
+
315
+ ![i18n-check Github workflow example out](./assets/i18n-check-workflow-example.png)
316
+
274
317
  ## API
275
318
 
276
- Aside from using the CLI, `i18n-check` also exposes a set of check functions that can be accessed programmatically.
277
- Start by importing `i18n-check`:
319
+ Aside from using the CLI, i18n-check also exposes a set of check functions that can be accessed programmatically.
320
+ Start by importing i18n-check:
278
321
 
279
322
  ```ts
280
323
  import * as i18nCheck from "@lingual/i18n-check";
@@ -381,47 +424,56 @@ Run `yarn build`, `pnpm run build` or `npm run build` and then depending on the
381
424
  Basic icu translation example:
382
425
 
383
426
  ```bash
384
- node dist/bin/index.js -t translations/messageExamples -s translations/messageExamples/en-us.json
427
+ node dist/bin/index.js --locales translations/messageExamples -s en-US
385
428
  ```
386
429
 
387
430
  Flatted translation keys example:
388
431
 
389
432
  ```bash
390
- node dist/bin/index.js -t translations/flattenExamples -s translations/flattenExamples/en-us.json
433
+ node dist/bin/index.js --locales translations/flattenExamples -s en-US
391
434
  ```
392
435
 
393
436
  i18next translation example:
394
437
 
395
438
  ```bash
396
- node dist/bin/index.js -t translations/i18NextMessageExamples -s translations/i18NextMessageExamples/en-us.json -f i18next
439
+ node dist/bin/index.js --locales translations/i18NextMessageExamples -s en-US -f i18next
397
440
  ```
398
441
 
399
442
  Single file translation example:
400
443
 
401
444
  ```bash
402
- node dist/bin/index.js -t translations/folderExample -s translations/folderExample/en-US/
445
+ node dist/bin/index.js --locales translations/folderExample -s en-US
403
446
  ```
404
447
 
405
448
  Multiple files per folder translation example:
406
449
 
407
450
  ```bash
408
- node dist/bin/index.js -t translations/multipleFilesFolderExample/ -s translations/multipleFilesFolderExample/en-US/
451
+ node dist/bin/index.js --locales translations/multipleFilesFolderExample/ -s en-US
409
452
  ```
410
453
 
411
454
  Multiple folders containing locales translation example:
412
455
 
413
456
  ```bash
414
- node dist/bin/index.js -t translations/folderExample,translations/messageExamples -s translations/folderExample/en-US/,translations/messageExamples/en-us.json
457
+ node dist/bin/index.js --locales translations/folderExample,translations/messageExamples -s en-US
415
458
  ```
416
459
 
417
- ## Test
460
+ ### Tests
461
+
462
+ To run the tests use one of the following commands:
463
+
464
+ ```bash
465
+ pnpm test
466
+ ```
418
467
 
419
- To run the tests use the following command:
468
+ ```bash
469
+ yarn test
470
+ ```
420
471
 
421
472
  ```bash
422
- pnpm run test // yarn test or npm test
473
+ npm test
423
474
  ```
424
475
 
425
476
  ## Links
426
477
 
427
- [Twitter](https://twitter.com/lingualdev)
478
+ - [Introducing i18n-check](https://lingual.dev/blog/introducing-i18n-check/)
479
+ - [Twitter](https://twitter.com/lingualdev)
package/dist/bin/index.js CHANGED
@@ -22,13 +22,13 @@ const __1 = require("..");
22
22
  const errorReporters_1 = require("../errorReporters");
23
23
  const flattenTranslations_1 = require("../utils/flattenTranslations");
24
24
  commander_1.program
25
- .version("0.1.0")
26
- .option("-t, --target [directory]", "name of the directory containing the JSON files to validate")
27
- .option("-s, --source [source file(s)]", "path to the reference file(s)")
25
+ .version("0.3.0")
26
+ .option("-l, --locales <locales...>", "name of the directory containing the locales to validate")
27
+ .option("-s, --source [source locale]", "the source locale to validate against")
28
28
  .option("-f, --format [format type]", "define the specific format, i.e. i18next")
29
29
  .option("-c, --check [checks]", "define the specific checks you want to run: invalid, missing. By default the check will validate against missing and invalid keys, i.e. --check invalidKeys,missingKeys")
30
30
  .option("-r, --reporter [error reporting style]", "define the reporting style: standard or summary")
31
- .option("-e, --exclude [file(s), folder(s)]", "define the file(s) and/or folders(s) that should be excluded from the check")
31
+ .option("-e, --exclude <exclude...>", "define the file(s) and/or folders(s) that should be excluded from the check")
32
32
  .option("-u, --unused [folder]", "define the source path to find all unused keys")
33
33
  .parse();
34
34
  const getCheckOptions = () => {
@@ -41,55 +41,37 @@ const getCheckOptions = () => {
41
41
  .filter((check) => ["invalidKeys", "missingKeys"].includes(check.trim()));
42
42
  return checks.length > 0 ? checks : ["invalidKeys", "missingKeys"];
43
43
  };
44
- const getSourcePath = (sourcePaths, fileName) => {
45
- return sourcePaths.find((basePathName) => {
46
- return fileName.includes(basePathName);
47
- });
48
- };
49
- const getTargetPath = (paths, sourcePaths, fileName) => {
50
- const basePath = paths.find((path) => {
51
- return fileName.includes(path);
52
- });
53
- if (!basePath) {
54
- return null;
55
- }
56
- return sourcePaths.find((path) => path.includes(basePath));
57
- };
58
- const toArray = (input) => {
59
- return input
60
- .trim()
61
- .split(",")
62
- .filter((a) => a);
44
+ const isSource = (fileInfo, srcPath) => {
45
+ return (fileInfo.path.some((path) => path.toLowerCase() === srcPath.toLowerCase()) || fileInfo.name.toLowerCase().slice(0, -5) === srcPath.toLowerCase());
63
46
  };
64
47
  const main = () => __awaiter(void 0, void 0, void 0, function* () {
65
48
  const start = performance.now();
66
49
  const srcPath = commander_1.program.getOptionValue("source");
67
- const targetPath = commander_1.program.getOptionValue("target");
50
+ const localePath = commander_1.program.getOptionValue("locales");
68
51
  const format = commander_1.program.getOptionValue("format");
69
52
  const exclude = commander_1.program.getOptionValue("exclude");
70
53
  const unusedSrcPath = commander_1.program.getOptionValue("unused");
71
54
  if (!srcPath) {
72
- console.log(chalk_1.default.red("Source file(s) not found. Please provide valid source file(s), i.e. -s translations/en-us.json"));
55
+ console.log(chalk_1.default.red("Source not found. Please provide a valid source locale, i.e. -s en-US"));
73
56
  (0, process_1.exit)(1);
74
57
  }
75
- if (!targetPath) {
76
- console.log(chalk_1.default.red("Target file(s) not found. Please provide valid target file(s), i.e. -t translations/"));
58
+ if (!localePath || localePath.length === 0) {
59
+ console.log(chalk_1.default.red("Locale file(s) not found. Please provide valid locale file(s), i.e. -locales translations/"));
77
60
  (0, process_1.exit)(1);
78
61
  }
79
- const excludedPaths = exclude ? toArray(exclude) : [];
80
- const targetPathFolders = toArray(targetPath);
81
- const srcPaths = toArray(srcPath);
82
- const isMultiFolders = targetPathFolders.length > 1;
62
+ const excludedPaths = exclude !== null && exclude !== void 0 ? exclude : [];
63
+ const localePathFolders = localePath;
64
+ const isMultiFolders = localePathFolders.length > 1;
83
65
  let srcFiles = [];
84
- let targetFiles = [];
66
+ let localeFiles = [];
85
67
  const pattern = isMultiFolders
86
- ? `{${targetPath.trim()}}/**/*.json`
87
- : `${targetPath.trim()}/**/*.json`;
68
+ ? `{${localePath.join(",").trim()}}/**/*.json`
69
+ : `${localePath.join(",").trim()}/**/*.json`;
88
70
  const files = yield (0, glob_1.glob)(pattern, {
89
71
  ignore: ["node_modules/**"].concat(excludedPaths),
90
72
  });
91
73
  console.log("i18n translations checker");
92
- console.log(chalk_1.default.gray(`Source file(s): ${srcPath}`));
74
+ console.log(chalk_1.default.gray(`Source: ${srcPath}`));
93
75
  if (format) {
94
76
  console.log(chalk_1.default.blackBright(`Selected format is: ${format}`));
95
77
  }
@@ -97,38 +79,76 @@ const main = () => __awaiter(void 0, void 0, void 0, function* () {
97
79
  checks: getCheckOptions(),
98
80
  format: format !== null && format !== void 0 ? format : undefined,
99
81
  };
100
- files.forEach((file) => {
101
- const content = JSON.parse(fs_1.default.readFileSync(file, "utf-8"));
102
- const sourcePath = getSourcePath(srcPaths, file);
103
- if (sourcePath) {
82
+ const fileInfos = [];
83
+ files.sort().forEach((file) => {
84
+ var _a;
85
+ const path = file.split("/");
86
+ const name = (_a = path.pop()) !== null && _a !== void 0 ? _a : "";
87
+ fileInfos.push({
88
+ file,
89
+ path,
90
+ name,
91
+ });
92
+ });
93
+ fileInfos.forEach(({ file, name, path }) => {
94
+ const rawContent = JSON.parse(fs_1.default.readFileSync(file, "utf-8"));
95
+ const content = (0, flattenTranslations_1.flattenTranslations)(rawContent);
96
+ if (isSource({ file, name, path }, srcPath)) {
104
97
  srcFiles.push({
105
98
  reference: null,
106
99
  name: file,
107
- content: (0, flattenTranslations_1.flattenTranslations)(content),
100
+ content,
108
101
  });
109
102
  }
110
103
  else {
111
- const targetPath = getTargetPath(targetPathFolders, srcPaths, file);
112
- const reference = (targetPath === null || targetPath === void 0 ? void 0 : targetPath.includes(".json"))
113
- ? targetPath
114
- : `${targetPath}${file.split("/").pop()}`;
115
- targetFiles.push({
116
- reference,
117
- name: file,
118
- content: (0, flattenTranslations_1.flattenTranslations)(content),
104
+ const fullPath = path.join("-");
105
+ const reference = fileInfos.find((fileInfo) => {
106
+ if (!isSource(fileInfo, srcPath)) {
107
+ return false;
108
+ }
109
+ if (fileInfo.path.join("-") === fullPath) {
110
+ return true;
111
+ }
112
+ // Check if the folder path matches - ignoring the last folder
113
+ // Then check if the file names are the same
114
+ // Example folder structure:
115
+ // path/to/locales/
116
+ // en-US/
117
+ // one.json
118
+ // two.json
119
+ // three.json
120
+ // de-DE/
121
+ // one.json
122
+ // two.json
123
+ // three.json
124
+ //
125
+ // Referencing: `path/to/locales/en-US/one.json`, `path/to/locales/de-DE/one.json`
126
+ // Non Referencing: `path/to/locales/en-US/one.json`, `path/to/other/locales/de-DE/one.json`
127
+ if (fileInfo.path.slice(0, fileInfo.path.length - 1).join("-") ===
128
+ path.slice(0, path.length - 1).join("-")) {
129
+ return fileInfo.name === name;
130
+ }
131
+ return false;
119
132
  });
133
+ if (reference) {
134
+ localeFiles.push({
135
+ reference: reference.file,
136
+ name: file,
137
+ content,
138
+ });
139
+ }
120
140
  }
121
141
  });
122
142
  if (srcFiles.length === 0) {
123
- console.log(chalk_1.default.red("Source file(s) not found. Please provide valid source file(s), i.e. -s translations/en-us.json"));
143
+ console.log(chalk_1.default.red("Source not found. Please provide a valid source locale, i.e. -s en-US"));
124
144
  (0, process_1.exit)(1);
125
145
  }
126
- if (targetFiles.length === 0) {
127
- console.log(chalk_1.default.red("Target file(s) not found. Please provide valid target file(s), i.e. -t translations/"));
146
+ if (localeFiles.length === 0) {
147
+ console.log(chalk_1.default.red("Locale file(s) not found. Please provide valid locale file(s), i.e. --locales translations/"));
128
148
  (0, process_1.exit)(1);
129
149
  }
130
150
  try {
131
- const result = (0, __1.checkTranslations)(srcFiles, targetFiles, options);
151
+ const result = (0, __1.checkTranslations)(srcFiles, localeFiles, options);
132
152
  printTranslationResult(result);
133
153
  if (unusedSrcPath) {
134
154
  const unusedKeys = yield (0, __1.checkUnusedKeys)(srcFiles, unusedSrcPath, options);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,266 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const child_process_1 = require("child_process");
4
+ describe("CLI", () => {
5
+ it("should return the missing keys for single folder translations", (done) => {
6
+ (0, child_process_1.exec)("node dist/bin/index.js -s en-US -l translations/flattenExamples", (_error, stdout, _stderr) => {
7
+ const result = stdout.split("Done")[0];
8
+ expect(result).toEqual(`i18n translations checker
9
+ Source: en-US
10
+
11
+ Found missing keys!
12
+ ┌───────────────────────────────────────────┬────────────────────────────────┐
13
+ │ file │ key │
14
+ ├───────────────────────────────────────────┼────────────────────────────────┤
15
+ │ translations/flattenExamples/de-de.json │ other.nested.three │
16
+ │ translations/flattenExamples/de-de.json │ other.nested.deep.more.final │
17
+ └───────────────────────────────────────────┴────────────────────────────────┘
18
+
19
+ No invalid translations found!
20
+
21
+ `);
22
+ done();
23
+ });
24
+ });
25
+ it("should return the missing/invalid keys for folder per locale with single file", (done) => {
26
+ (0, child_process_1.exec)("node dist/bin/index.js -l translations/folderExample/ -s en-US", (_error, stdout, _stderr) => {
27
+ const result = stdout.split("Done")[0];
28
+ expect(result).toEqual(`i18n translations checker
29
+ Source: en-US
30
+
31
+ Found missing keys!
32
+ ┌───────────────────────────────────────────────┬───────────────────────┐
33
+ │ file │ key │
34
+ ├───────────────────────────────────────────────┼───────────────────────┤
35
+ │ translations/folderExample/de-DE/index.json │ message.text-format │
36
+ └───────────────────────────────────────────────┴───────────────────────┘
37
+
38
+ Found invalid keys!
39
+ ┌───────────────────────────────────────────────┬──────────────────┐
40
+ │ file │ key │
41
+ ├───────────────────────────────────────────────┼──────────────────┤
42
+ │ translations/folderExample/de-DE/index.json │ message.select │
43
+ └───────────────────────────────────────────────┴──────────────────┘
44
+
45
+ `);
46
+ done();
47
+ });
48
+ });
49
+ it("should return the missing/invalid keys for folder per locale with multiple files", (done) => {
50
+ (0, child_process_1.exec)("node dist/bin/index.js -l translations/multipleFilesFolderExample/ -s en-US", (_error, stdout, _stderr) => {
51
+ const result = stdout.split("Done")[0];
52
+ expect(result).toEqual(`i18n translations checker
53
+ Source: en-US
54
+
55
+ Found missing keys!
56
+ ┌──────────────────────────────────────────────────────────┬───────────────────────┐
57
+ │ file │ key │
58
+ ├──────────────────────────────────────────────────────────┼───────────────────────┤
59
+ │ translations/multipleFilesFolderExample/de-DE/one.json │ message.text-format │
60
+ │ translations/multipleFilesFolderExample/de-DE/two.json │ test.drive.four │
61
+ └──────────────────────────────────────────────────────────┴───────────────────────┘
62
+
63
+ Found invalid keys!
64
+ ┌────────────────────────────────────────────────────────────┬─────────────────────┐
65
+ │ file │ key │
66
+ ├────────────────────────────────────────────────────────────┼─────────────────────┤
67
+ │ translations/multipleFilesFolderExample/de-DE/one.json │ message.select │
68
+ │ translations/multipleFilesFolderExample/de-DE/three.json │ multipleVariables │
69
+ └────────────────────────────────────────────────────────────┴─────────────────────┘
70
+
71
+ `);
72
+ done();
73
+ });
74
+ });
75
+ it("should return the missing/invalid keys for folder containing multiple locale folders", (done) => {
76
+ (0, child_process_1.exec)("node dist/bin/index.js -l translations/multipleFoldersExample -s en-US", (_error, stdout, _stderr) => {
77
+ const result = stdout.split("Done")[0];
78
+ expect(result).toEqual(`i18n translations checker
79
+ Source: en-US
80
+
81
+ Found missing keys!
82
+ ┌───────────────────────────────────────────────────────────────────────┬───────────────────────┐
83
+ │ file │ key │
84
+ ├───────────────────────────────────────────────────────────────────────┼───────────────────────┤
85
+ │ translations/multipleFoldersExample/spaceOne/locales/de-DE/one.json │ message.text-format │
86
+ │ translations/multipleFoldersExample/spaceOne/locales/de-DE/two.json │ test.drive.four │
87
+ │ translations/multipleFoldersExample/spaceTwo/locales/de-DE/one.json │ message.plural │
88
+ │ translations/multipleFoldersExample/spaceTwo/locales/de-DE/two.json │ test.drive.two │
89
+ └───────────────────────────────────────────────────────────────────────┴───────────────────────┘
90
+
91
+ Found invalid keys!
92
+ ┌─────────────────────────────────────────────────────────────────────────┬───────────────────────┐
93
+ │ file │ key │
94
+ ├─────────────────────────────────────────────────────────────────────────┼───────────────────────┤
95
+ │ translations/multipleFoldersExample/spaceOne/locales/de-DE/one.json │ message.select │
96
+ │ translations/multipleFoldersExample/spaceOne/locales/de-DE/three.json │ multipleVariables │
97
+ │ translations/multipleFoldersExample/spaceTwo/locales/de-DE/one.json │ message.text-format │
98
+ │ translations/multipleFoldersExample/spaceTwo/locales/de-DE/three.json │ numberFormat │
99
+ └─────────────────────────────────────────────────────────────────────────┴───────────────────────┘
100
+
101
+ `);
102
+ done();
103
+ });
104
+ });
105
+ it("should return the missing/invalid keys for multiple locale folders", (done) => {
106
+ (0, child_process_1.exec)("node dist/bin/index.js -l translations/multipleFoldersExample/spaceOne translations/multipleFoldersExample/spaceTwo -s en-US", (_error, stdout, _stderr) => {
107
+ const result = stdout.split("Done")[0];
108
+ expect(result).toEqual(`i18n translations checker
109
+ Source: en-US
110
+
111
+ Found missing keys!
112
+ ┌───────────────────────────────────────────────────────────────────────┬───────────────────────┐
113
+ │ file │ key │
114
+ ├───────────────────────────────────────────────────────────────────────┼───────────────────────┤
115
+ │ translations/multipleFoldersExample/spaceOne/locales/de-DE/one.json │ message.text-format │
116
+ │ translations/multipleFoldersExample/spaceOne/locales/de-DE/two.json │ test.drive.four │
117
+ │ translations/multipleFoldersExample/spaceTwo/locales/de-DE/one.json │ message.plural │
118
+ │ translations/multipleFoldersExample/spaceTwo/locales/de-DE/two.json │ test.drive.two │
119
+ └───────────────────────────────────────────────────────────────────────┴───────────────────────┘
120
+
121
+ Found invalid keys!
122
+ ┌─────────────────────────────────────────────────────────────────────────┬───────────────────────┐
123
+ │ file │ key │
124
+ ├─────────────────────────────────────────────────────────────────────────┼───────────────────────┤
125
+ │ translations/multipleFoldersExample/spaceOne/locales/de-DE/one.json │ message.select │
126
+ │ translations/multipleFoldersExample/spaceOne/locales/de-DE/three.json │ multipleVariables │
127
+ │ translations/multipleFoldersExample/spaceTwo/locales/de-DE/one.json │ message.text-format │
128
+ │ translations/multipleFoldersExample/spaceTwo/locales/de-DE/three.json │ numberFormat │
129
+ └─────────────────────────────────────────────────────────────────────────┴───────────────────────┘
130
+
131
+ `);
132
+ done();
133
+ });
134
+ });
135
+ it("should return the missing/invalid keys for all files in the provided locale folders", (done) => {
136
+ (0, child_process_1.exec)("node dist/bin/index.js --source en-US --locales translations/flattenExamples translations/messageExamples", (_error, stdout, _stderr) => {
137
+ const result = stdout.split("Done")[0];
138
+ expect(result).toEqual(`i18n translations checker
139
+ Source: en-US
140
+
141
+ Found missing keys!
142
+ ┌───────────────────────────────────────────┬────────────────────────────────┐
143
+ │ file │ key │
144
+ ├───────────────────────────────────────────┼────────────────────────────────┤
145
+ │ translations/flattenExamples/de-de.json │ other.nested.three │
146
+ │ translations/flattenExamples/de-de.json │ other.nested.deep.more.final │
147
+ │ translations/messageExamples/de-de.json │ richText │
148
+ │ translations/messageExamples/de-de.json │ yo │
149
+ │ translations/messageExamples/de-de.json │ nesting1 │
150
+ │ translations/messageExamples/de-de.json │ nesting2 │
151
+ │ translations/messageExamples/de-de.json │ nesting3 │
152
+ │ translations/messageExamples/de-de.json │ key1 │
153
+ └───────────────────────────────────────────┴────────────────────────────────┘
154
+
155
+ Found invalid keys!
156
+ ┌───────────────────────────────────────────┬─────────────────────┐
157
+ │ file │ key │
158
+ ├───────────────────────────────────────────┼─────────────────────┤
159
+ │ translations/messageExamples/de-de.json │ multipleVariables │
160
+ └───────────────────────────────────────────┴─────────────────────┘
161
+
162
+ `);
163
+ done();
164
+ });
165
+ });
166
+ it("should return the missing/invalid keys for all files with source matching folder and source matching file", (done) => {
167
+ (0, child_process_1.exec)("node dist/bin/index.js -l translations/multipleFilesFolderExample translations/flattenExamples -s en-US", (_error, stdout, _stderr) => {
168
+ const result = stdout.split("Done")[0];
169
+ expect(result).toEqual(`i18n translations checker
170
+ Source: en-US
171
+
172
+ Found missing keys!
173
+ ┌──────────────────────────────────────────────────────────┬────────────────────────────────┐
174
+ │ file │ key │
175
+ ├──────────────────────────────────────────────────────────┼────────────────────────────────┤
176
+ │ translations/flattenExamples/de-de.json │ other.nested.three │
177
+ │ translations/flattenExamples/de-de.json │ other.nested.deep.more.final │
178
+ │ translations/multipleFilesFolderExample/de-DE/one.json │ message.text-format │
179
+ │ translations/multipleFilesFolderExample/de-DE/two.json │ test.drive.four │
180
+ └──────────────────────────────────────────────────────────┴────────────────────────────────┘
181
+
182
+ Found invalid keys!
183
+ ┌────────────────────────────────────────────────────────────┬─────────────────────┐
184
+ │ file │ key │
185
+ ├────────────────────────────────────────────────────────────┼─────────────────────┤
186
+ │ translations/multipleFilesFolderExample/de-DE/one.json │ message.select │
187
+ │ translations/multipleFilesFolderExample/de-DE/three.json │ multipleVariables │
188
+ └────────────────────────────────────────────────────────────┴─────────────────────┘
189
+
190
+ `);
191
+ done();
192
+ });
193
+ });
194
+ it("should ignore the excluded file", (done) => {
195
+ (0, child_process_1.exec)("node dist/bin/index.js --source en-US --locales translations/flattenExamples translations/messageExamples --exclude translations/flattenExamples/de-de.json", (_error, stdout, _stderr) => {
196
+ const result = stdout.split("Done")[0];
197
+ expect(result).toEqual(`i18n translations checker
198
+ Source: en-US
199
+
200
+ Found missing keys!
201
+ ┌───────────────────────────────────────────┬────────────┐
202
+ │ file │ key │
203
+ ├───────────────────────────────────────────┼────────────┤
204
+ │ translations/messageExamples/de-de.json │ richText │
205
+ │ translations/messageExamples/de-de.json │ yo │
206
+ │ translations/messageExamples/de-de.json │ nesting1 │
207
+ │ translations/messageExamples/de-de.json │ nesting2 │
208
+ │ translations/messageExamples/de-de.json │ nesting3 │
209
+ │ translations/messageExamples/de-de.json │ key1 │
210
+ └───────────────────────────────────────────┴────────────┘
211
+
212
+ Found invalid keys!
213
+ ┌───────────────────────────────────────────┬─────────────────────┐
214
+ │ file │ key │
215
+ ├───────────────────────────────────────────┼─────────────────────┤
216
+ │ translations/messageExamples/de-de.json │ multipleVariables │
217
+ └───────────────────────────────────────────┴─────────────────────┘
218
+
219
+ `);
220
+ done();
221
+ });
222
+ });
223
+ it("should ignore the excluded folder", (done) => {
224
+ (0, child_process_1.exec)("node dist/bin/index.js --source en-US --locales translations/flattenExamples translations/messageExamples --exclude translations/flattenExamples/*", (_error, stdout, _stderr) => {
225
+ const result = stdout.split("Done")[0];
226
+ expect(result).toEqual(`i18n translations checker
227
+ Source: en-US
228
+
229
+ Found missing keys!
230
+ ┌───────────────────────────────────────────┬────────────┐
231
+ │ file │ key │
232
+ ├───────────────────────────────────────────┼────────────┤
233
+ │ translations/messageExamples/de-de.json │ richText │
234
+ │ translations/messageExamples/de-de.json │ yo │
235
+ │ translations/messageExamples/de-de.json │ nesting1 │
236
+ │ translations/messageExamples/de-de.json │ nesting2 │
237
+ │ translations/messageExamples/de-de.json │ nesting3 │
238
+ │ translations/messageExamples/de-de.json │ key1 │
239
+ └───────────────────────────────────────────┴────────────┘
240
+
241
+ Found invalid keys!
242
+ ┌───────────────────────────────────────────┬─────────────────────┐
243
+ │ file │ key │
244
+ ├───────────────────────────────────────────┼─────────────────────┤
245
+ │ translations/messageExamples/de-de.json │ multipleVariables │
246
+ └───────────────────────────────────────────┴─────────────────────┘
247
+
248
+ `);
249
+ done();
250
+ });
251
+ });
252
+ it("should ignore the excluded multiple files", (done) => {
253
+ (0, child_process_1.exec)("node dist/bin/index.js --source en-US --locales translations/flattenExamples translations/messageExamples --exclude translations/flattenExamples/de-de.json translations/messageExamples/de-de.json", (_error, stdout, _stderr) => {
254
+ const result = stdout.split("Done")[0];
255
+ expect(result).toEqual(`i18n translations checker
256
+ Source: en-US
257
+
258
+ No missing keys found!
259
+
260
+ No invalid translations found!
261
+
262
+ `);
263
+ done();
264
+ });
265
+ });
266
+ });
@@ -35,6 +35,6 @@ const createTable = (input) => {
35
35
  .replace(/^└─*┴/, "└")
36
36
  .replace(/'/g, " ")}\n`;
37
37
  }
38
- return output;
38
+ return output.replace(/\n\n$/, "");
39
39
  };
40
40
  exports.createTable = createTable;
package/dist/index.js CHANGED
@@ -50,9 +50,9 @@ const checkTranslations = (source, targets, options = { format: "icu", checks: [
50
50
  };
51
51
  };
52
52
  exports.checkTranslations = checkTranslations;
53
- const checkUnusedKeys = (source, codebaseSrc, options = {
53
+ const checkUnusedKeys = (source_1, codebaseSrc_1, ...args_1) => __awaiter(void 0, [source_1, codebaseSrc_1, ...args_1], void 0, function* (source, codebaseSrc, options = {
54
54
  format: "react-intl",
55
- }) => __awaiter(void 0, void 0, void 0, function* () {
55
+ }) {
56
56
  if (!options.format || !["react-intl", "i18next"].includes(options.format)) {
57
57
  return undefined;
58
58
  }
package/dist/types.d.ts CHANGED
@@ -5,3 +5,8 @@ export type TranslationFile = {
5
5
  name: string;
6
6
  content: Translation;
7
7
  };
8
+ export type FileInfo = {
9
+ file: string;
10
+ name: string;
11
+ path: string[];
12
+ };
@@ -65,9 +65,6 @@ const hasDiff = (a, b) => {
65
65
  if (formatElementA.type !== formatElementB.type) {
66
66
  return true;
67
67
  }
68
- if (formatElementA.type === "text" && formatElementB.type === "text") {
69
- return false;
70
- }
71
68
  if (formatElementA.type === "tag" && formatElementB.type === "tag") {
72
69
  return (formatElementA.raw !== formatElementB.raw ||
73
70
  formatElementA.voidElement !== formatElementB.voidElement);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lingual/i18n-check",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "i18n translation messages check",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -11,25 +11,26 @@
11
11
  "types": "dist/index.d.ts",
12
12
  "scripts": {
13
13
  "build": "tsc",
14
- "test": "jest"
14
+ "test": "jest src/ --testPathIgnorePatterns src/bin/*",
15
+ "test:cli": "tsc && jest src/bin/index.test.ts"
15
16
  },
16
17
  "files": [
17
18
  "dist/"
18
19
  ],
19
20
  "dependencies": {
20
21
  "@formatjs/cli-lib": "^6.4.2",
21
- "@formatjs/icu-messageformat-parser": "^2.7.6",
22
+ "@formatjs/icu-messageformat-parser": "^2.7.8",
22
23
  "chalk": "^4.1.2",
23
- "commander": "^11.0.0",
24
- "glob": "^10.3.10",
25
- "typescript": "^5.2.2"
24
+ "commander": "^12.1.0",
25
+ "glob": "^11.0.0",
26
+ "typescript": "^5.6.2"
26
27
  },
27
28
  "devDependencies": {
28
- "@types/jest": "^29.5.12",
29
- "@types/node": "^20.12.12",
30
- "braces": ">=3.0.3",
29
+ "@types/jest": "^29.5.13",
30
+ "@types/node": "^22.5.5",
31
+ "braces": "^3.0.3",
31
32
  "jest": "^29.7.0",
32
- "ts-jest": "^29.1.2"
33
+ "ts-jest": "^29.2.5"
33
34
  },
34
35
  "repository": {
35
36
  "type": "git",