@lingual/i18n-check 0.4.0 → 0.5.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
@@ -63,7 +63,7 @@ node_modules/.bin/i18n-check
63
63
 
64
64
  ## General Usage
65
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`).
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`). Currently supported file formats are JSON and YAML.
67
67
 
68
68
  Example:
69
69
 
@@ -112,28 +112,28 @@ Hint: If you want to use the `--unused` flag, you should provide `react-intl` or
112
112
  yarn i18n:check --locales translations/i18NextMessageExamples -s en-US -f i18next
113
113
  ```
114
114
 
115
- ### --check, -c
115
+ ### --only, -o
116
116
 
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.
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 `-o` or `--only` option you can specify a specific check to run.
118
118
 
119
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.
120
120
 
121
121
  Check for missing keys:
122
122
 
123
123
  ```bash
124
- yarn i18n:check --locales translations/messageExamples -s en-US -c missingKeys
124
+ yarn i18n:check --locales translations/messageExamples -s en-US -o missingKeys
125
125
  ```
126
126
 
127
127
  Check for invalid keys:
128
128
 
129
129
  ```bash
130
- yarn i18n:check --locales translations/messageExamples -s en-US -c invalidKeys
130
+ yarn i18n:check --locales translations/messageExamples -s en-US -o invalidKeys
131
131
  ```
132
132
 
133
133
  Check for missing and invalid keys (which is the default):
134
134
 
135
135
  ```bash
136
- yarn i18n:check --locales translations/messageExamples -s en-US -c missingKeys,invalidKeys
136
+ yarn i18n:check --locales translations/messageExamples -s en-US -o missingKeys invalidKeys
137
137
  ```
138
138
 
139
139
  ### --unused, -u
@@ -192,6 +192,20 @@ yarn i18n:check --locales translations/folderExamples -s en-US -e translations/f
192
192
  The `--exclude` option also accepts a mix of files and folders, which follows the same pattern as above, i.e.
193
193
  `-e translations/folderExamples/fr/* translations/messageExamples/it.json`
194
194
 
195
+ ### --parser-component-functions
196
+
197
+ When using the `--unused` option, there will be situations where the i18next-parser will not be able to find components that wrap a `Trans` component.The component names for i18next-parser to match should be provided via the `--parser-component-functions` option. This option should onlybe used to define additional names for matching, a by default `Trans` will always be matched.
198
+
199
+ ```bash
200
+ yarn i18n:check --locales translations/i18NextMessageExamples -s en-US -f i18next
201
+ -u src --parser-component-functions WrappedTransComponent
202
+ ```
203
+
204
+ ```bash
205
+ yarn i18n:check --locales translations/i18NextMessageExamples -s en-US -f i18next
206
+ -u src --parser-component-functions WrappedTransComponent AnotherWrappedTransComponent
207
+ ```
208
+
195
209
  ## Examples
196
210
 
197
211
  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.
@@ -206,7 +220,7 @@ locales/
206
220
  de-de.json
207
221
  ```
208
222
 
209
- 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.
223
+ Use the `-l` or `--locales` 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.
210
224
 
211
225
  ```bash
212
226
  yarn i18n:check --locales locales -s locales/en-us.json
@@ -214,7 +228,7 @@ yarn i18n:check --locales locales -s locales/en-us.json
214
228
 
215
229
  ### Folder per locale
216
230
 
217
- If the locales are **organised as folders** containing a single json file:
231
+ If the locales are **organised as folders** containing a single JSON/YAML file:
218
232
 
219
233
  ```
220
234
  locales/
@@ -232,7 +246,7 @@ yarn i18n:check --locales locales -s en-US
232
246
 
233
247
  ### Folder per locale with multiple files
234
248
 
235
- If the locales are **organised as folders** containing multiple json files:
249
+ If the locales are **organised as folders** containing multiple JSON/YAML files:
236
250
 
237
251
  ```
238
252
  locales/
@@ -254,7 +268,7 @@ yarn i18n:check --locales locales -s en-US
254
268
 
255
269
  #### Multiple folders containing locales
256
270
 
257
- If the locales are **organised as folders** containing multiple json files:
271
+ If the locales are **organised as folders** containing multiple JSON/YAML files:
258
272
 
259
273
  ```
260
274
  - spaceOne
package/dist/bin/index.js CHANGED
@@ -13,32 +13,37 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
13
13
  return (mod && mod.__esModule) ? mod : { "default": mod };
14
14
  };
15
15
  Object.defineProperty(exports, "__esModule", { value: true });
16
- const glob_1 = require("glob");
16
+ const node_fs_1 = __importDefault(require("node:fs"));
17
+ const node_process_1 = require("node:process");
17
18
  const chalk_1 = __importDefault(require("chalk"));
18
- const fs_1 = __importDefault(require("fs"));
19
- const process_1 = require("process");
20
19
  const commander_1 = require("commander");
20
+ const glob_1 = require("glob");
21
+ const js_yaml_1 = __importDefault(require("js-yaml"));
21
22
  const __1 = require("..");
22
23
  const errorReporters_1 = require("../errorReporters");
23
24
  const flattenTranslations_1 = require("../utils/flattenTranslations");
25
+ const version = require("../../package.json").version;
24
26
  commander_1.program
25
- .version("0.3.0")
27
+ .version(version)
26
28
  .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
- .option("-f, --format [format type]", "define the specific format: i18next or react-intl")
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
- .option("-r, --reporter [error reporting style]", "define the reporting style: standard or summary")
29
+ .option("-s, --source <locale>", "the source locale to validate against")
30
+ .option("-f, --format <type>", "define the specific format: i18next or react-intl")
31
+ .option("-c, --check <checks...>", "this option is deprecated - use -o or --only instead")
32
+ .option("-o, --only <only...>", "define the specific checks you want to run: invalid, missing. By default the check will validate against missing and invalid keys, i.e. --only invalidKeys,missingKeys")
33
+ .option("-r, --reporter <style>", "define the reporting style: standard or summary")
31
34
  .option("-e, --exclude <exclude...>", "define the file(s) and/or folders(s) that should be excluded from the check")
32
- .option("-u, --unused [folder]", "define the source path to find all unused keys")
35
+ .option("-u, --unused <path>", "define the source path to find all unused keys")
36
+ .option("--parser-component-functions <components...>", "a list of component names to parse when using the --unused option")
33
37
  .parse();
34
38
  const getCheckOptions = () => {
35
- const checkOption = commander_1.program.getOptionValue("check");
39
+ const checkOption = commander_1.program.getOptionValue("only") || commander_1.program.getOptionValue("check");
40
+ if (commander_1.program.getOptionValue("check")) {
41
+ console.log(chalk_1.default.yellow("The --check option has been deprecated, use the --only option instead."));
42
+ }
36
43
  if (!checkOption) {
37
44
  return ["invalidKeys", "missingKeys"];
38
45
  }
39
- const checks = checkOption
40
- .split(",")
41
- .filter((check) => ["invalidKeys", "missingKeys"].includes(check.trim()));
46
+ const checks = checkOption.filter((check) => ["invalidKeys", "missingKeys"].includes(check.trim()));
42
47
  return checks.length > 0 ? checks : ["invalidKeys", "missingKeys"];
43
48
  };
44
49
  const isSource = (fileInfo, srcPath) => {
@@ -51,13 +56,14 @@ const main = () => __awaiter(void 0, void 0, void 0, function* () {
51
56
  const format = commander_1.program.getOptionValue("format");
52
57
  const exclude = commander_1.program.getOptionValue("exclude");
53
58
  const unusedSrcPath = commander_1.program.getOptionValue("unused");
59
+ const componentFunctions = commander_1.program.getOptionValue("parserComponentFunctions");
54
60
  if (!srcPath) {
55
61
  console.log(chalk_1.default.red("Source not found. Please provide a valid source locale, i.e. -s en-US"));
56
- (0, process_1.exit)(1);
62
+ (0, node_process_1.exit)(1);
57
63
  }
58
64
  if (!localePath || localePath.length === 0) {
59
65
  console.log(chalk_1.default.red("Locale file(s) not found. Please provide valid locale file(s), i.e. -locales translations/"));
60
- (0, process_1.exit)(1);
66
+ (0, node_process_1.exit)(1);
61
67
  }
62
68
  const excludedPaths = exclude !== null && exclude !== void 0 ? exclude : [];
63
69
  const localePathFolders = localePath;
@@ -65,8 +71,8 @@ const main = () => __awaiter(void 0, void 0, void 0, function* () {
65
71
  let srcFiles = [];
66
72
  let localeFiles = [];
67
73
  const pattern = isMultiFolders
68
- ? `{${localePath.join(",").trim()}}/**/*.json`
69
- : `${localePath.join(",").trim()}/**/*.json`;
74
+ ? `{${localePath.join(",").trim()}}/**/*.{json,yaml,yml}`
75
+ : `${localePath.join(",").trim()}/**/*.{json,yaml,yml}`;
70
76
  const files = yield (0, glob_1.glob)(pattern, {
71
77
  ignore: ["node_modules/**"].concat(excludedPaths),
72
78
  });
@@ -81,17 +87,25 @@ const main = () => __awaiter(void 0, void 0, void 0, function* () {
81
87
  };
82
88
  const fileInfos = [];
83
89
  files.sort().forEach((file) => {
84
- var _a;
90
+ var _a, _b;
85
91
  const path = file.split("/");
86
92
  const name = (_a = path.pop()) !== null && _a !== void 0 ? _a : "";
93
+ const extension = (_b = name.split(".").pop()) !== null && _b !== void 0 ? _b : "json";
87
94
  fileInfos.push({
95
+ extension,
88
96
  file,
89
- path,
90
97
  name,
98
+ path,
91
99
  });
92
100
  });
93
- fileInfos.forEach(({ file, name, path }) => {
94
- const rawContent = JSON.parse(fs_1.default.readFileSync(file, "utf-8"));
101
+ fileInfos.forEach(({ extension, file, name, path }) => {
102
+ let rawContent;
103
+ if (extension === "yaml") {
104
+ rawContent = js_yaml_1.default.load(node_fs_1.default.readFileSync(file, "utf-8"));
105
+ }
106
+ else {
107
+ rawContent = JSON.parse(node_fs_1.default.readFileSync(file, "utf-8"));
108
+ }
95
109
  const content = (0, flattenTranslations_1.flattenTranslations)(rawContent);
96
110
  if (isSource({ file, name, path }, srcPath)) {
97
111
  srcFiles.push({
@@ -141,32 +155,32 @@ const main = () => __awaiter(void 0, void 0, void 0, function* () {
141
155
  });
142
156
  if (srcFiles.length === 0) {
143
157
  console.log(chalk_1.default.red("Source not found. Please provide a valid source locale, i.e. -s en-US"));
144
- (0, process_1.exit)(1);
158
+ (0, node_process_1.exit)(1);
145
159
  }
146
160
  if (localeFiles.length === 0) {
147
161
  console.log(chalk_1.default.red("Locale file(s) not found. Please provide valid locale file(s), i.e. --locales translations/"));
148
- (0, process_1.exit)(1);
162
+ (0, node_process_1.exit)(1);
149
163
  }
150
164
  try {
151
165
  const result = (0, __1.checkTranslations)(srcFiles, localeFiles, options);
152
166
  printTranslationResult(result);
153
167
  if (unusedSrcPath) {
154
- const unusedKeys = yield (0, __1.checkUnusedKeys)(srcFiles, unusedSrcPath, options);
168
+ const unusedKeys = yield (0, __1.checkUnusedKeys)(srcFiles, unusedSrcPath, options, componentFunctions);
155
169
  printUnusedKeysResult({ unusedKeys });
156
170
  }
157
171
  const end = performance.now();
158
172
  console.log(chalk_1.default.green(`\nDone in ${Math.round(((end - start) * 100) / 1000) / 100}s.`));
159
173
  if ((result.missingKeys && Object.keys(result.missingKeys).length > 0) ||
160
174
  (result.invalidKeys && Object.keys(result.invalidKeys).length > 0)) {
161
- (0, process_1.exit)(1);
175
+ (0, node_process_1.exit)(1);
162
176
  }
163
177
  else {
164
- (0, process_1.exit)(0);
178
+ (0, node_process_1.exit)(0);
165
179
  }
166
180
  }
167
181
  catch (e) {
168
182
  console.log(chalk_1.default.red("\nError: Can't validate translations. Check if the format is supported or specify the translation format i.e. -f i18next"));
169
- (0, process_1.exit)(1);
183
+ (0, node_process_1.exit)(1);
170
184
  }
171
185
  });
172
186
  const printTranslationResult = ({ missingKeys, invalidKeys, }) => {
@@ -2,10 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const child_process_1 = require("child_process");
4
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
5
+ describe("JSON", () => {
6
+ it("should return the missing keys for single folder translations", (done) => {
7
+ (0, child_process_1.exec)("node dist/bin/index.js -s en-US -l translations/flattenExamples", (_error, stdout, _stderr) => {
8
+ const result = stdout.split("Done")[0];
9
+ expect(result).toEqual(`i18n translations checker
9
10
  Source: en-US
10
11
 
11
12
  Found missing keys!
@@ -19,13 +20,13 @@ Found missing keys!
19
20
  No invalid translations found!
20
21
 
21
22
  `);
22
- done();
23
+ done();
24
+ });
23
25
  });
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
26
+ it("should return the missing/invalid keys for folder per locale with single file", (done) => {
27
+ (0, child_process_1.exec)("node dist/bin/index.js -l translations/folderExample/ -s en-US", (_error, stdout, _stderr) => {
28
+ const result = stdout.split("Done")[0];
29
+ expect(result).toEqual(`i18n translations checker
29
30
  Source: en-US
30
31
 
31
32
  Found missing keys!
@@ -43,13 +44,13 @@ Found invalid keys!
43
44
  └───────────────────────────────────────────────┴──────────────────┘
44
45
 
45
46
  `);
46
- done();
47
+ done();
48
+ });
47
49
  });
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
50
+ it("should return the missing/invalid keys for folder per locale with multiple files", (done) => {
51
+ (0, child_process_1.exec)("node dist/bin/index.js -l translations/multipleFilesFolderExample/ -s en-US", (_error, stdout, _stderr) => {
52
+ const result = stdout.split("Done")[0];
53
+ expect(result).toEqual(`i18n translations checker
53
54
  Source: en-US
54
55
 
55
56
  Found missing keys!
@@ -69,13 +70,13 @@ Found invalid keys!
69
70
  └────────────────────────────────────────────────────────────┴─────────────────────┘
70
71
 
71
72
  `);
72
- done();
73
+ done();
74
+ });
73
75
  });
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
76
+ it("should return the missing/invalid keys for folder containing multiple locale folders", (done) => {
77
+ (0, child_process_1.exec)("node dist/bin/index.js -l translations/multipleFoldersExample -s en-US", (_error, stdout, _stderr) => {
78
+ const result = stdout.split("Done")[0];
79
+ expect(result).toEqual(`i18n translations checker
79
80
  Source: en-US
80
81
 
81
82
  Found missing keys!
@@ -99,13 +100,13 @@ Found invalid keys!
99
100
  └─────────────────────────────────────────────────────────────────────────┴───────────────────────┘
100
101
 
101
102
  `);
102
- done();
103
+ done();
104
+ });
103
105
  });
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
106
+ it("should return the missing/invalid keys for multiple locale folders", (done) => {
107
+ (0, child_process_1.exec)("node dist/bin/index.js -l translations/multipleFoldersExample/spaceOne translations/multipleFoldersExample/spaceTwo -s en-US", (_error, stdout, _stderr) => {
108
+ const result = stdout.split("Done")[0];
109
+ expect(result).toEqual(`i18n translations checker
109
110
  Source: en-US
110
111
 
111
112
  Found missing keys!
@@ -129,13 +130,13 @@ Found invalid keys!
129
130
  └─────────────────────────────────────────────────────────────────────────┴───────────────────────┘
130
131
 
131
132
  `);
132
- done();
133
+ done();
134
+ });
133
135
  });
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
136
+ it("should return the missing/invalid keys for all files in the provided locale folders", (done) => {
137
+ (0, child_process_1.exec)("node dist/bin/index.js --source en-US --locales translations/flattenExamples translations/messageExamples", (_error, stdout, _stderr) => {
138
+ const result = stdout.split("Done")[0];
139
+ expect(result).toEqual(`i18n translations checker
139
140
  Source: en-US
140
141
 
141
142
  Found missing keys!
@@ -160,13 +161,13 @@ Found invalid keys!
160
161
  └───────────────────────────────────────────┴─────────────────────┘
161
162
 
162
163
  `);
163
- done();
164
+ done();
165
+ });
164
166
  });
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
167
+ it("should return the missing/invalid keys for all files with source matching folder and source matching file", (done) => {
168
+ (0, child_process_1.exec)("node dist/bin/index.js -l translations/multipleFilesFolderExample translations/flattenExamples -s en-US", (_error, stdout, _stderr) => {
169
+ const result = stdout.split("Done")[0];
170
+ expect(result).toEqual(`i18n translations checker
170
171
  Source: en-US
171
172
 
172
173
  Found missing keys!
@@ -188,13 +189,13 @@ Found invalid keys!
188
189
  └────────────────────────────────────────────────────────────┴─────────────────────┘
189
190
 
190
191
  `);
191
- done();
192
+ done();
193
+ });
192
194
  });
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
195
+ it("should ignore the excluded file", (done) => {
196
+ (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) => {
197
+ const result = stdout.split("Done")[0];
198
+ expect(result).toEqual(`i18n translations checker
198
199
  Source: en-US
199
200
 
200
201
  Found missing keys!
@@ -217,13 +218,13 @@ Found invalid keys!
217
218
  └───────────────────────────────────────────┴─────────────────────┘
218
219
 
219
220
  `);
220
- done();
221
+ done();
222
+ });
221
223
  });
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
224
+ it("should ignore the excluded folder", (done) => {
225
+ (0, child_process_1.exec)("node dist/bin/index.js --source en-US --locales translations/flattenExamples translations/messageExamples --exclude translations/flattenExamples/*", (_error, stdout, _stderr) => {
226
+ const result = stdout.split("Done")[0];
227
+ expect(result).toEqual(`i18n translations checker
227
228
  Source: en-US
228
229
 
229
230
  Found missing keys!
@@ -246,13 +247,13 @@ Found invalid keys!
246
247
  └───────────────────────────────────────────┴─────────────────────┘
247
248
 
248
249
  `);
249
- done();
250
+ done();
251
+ });
250
252
  });
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
253
+ it("should ignore the excluded multiple files", (done) => {
254
+ (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) => {
255
+ const result = stdout.split("Done")[0];
256
+ expect(result).toEqual(`i18n translations checker
256
257
  Source: en-US
257
258
 
258
259
  No missing keys found!
@@ -260,13 +261,13 @@ No missing keys found!
260
261
  No invalid translations found!
261
262
 
262
263
  `);
263
- done();
264
+ done();
265
+ });
264
266
  });
265
- });
266
- it("should find unused keys for react-i18next applications", (done) => {
267
- (0, child_process_1.exec)("node dist/bin/index.js --source en --locales translations/codeExamples/reacti18next/locales -f i18next -u translations/codeExamples/reacti18next/src", (_error, stdout, _stderr) => {
268
- const result = stdout.split("Done")[0];
269
- expect(result).toEqual(`i18n translations checker
267
+ it("should find unused keys for react-i18next applications", (done) => {
268
+ (0, child_process_1.exec)("node dist/bin/index.js --source en --locales translations/codeExamples/reacti18next/locales -f i18next -u translations/codeExamples/reacti18next/src --parser-component-functions WrappedTransComponent", (_error, stdout, _stderr) => {
269
+ const result = stdout.split("Done")[0];
270
+ expect(result).toEqual(`i18n translations checker
270
271
  Source: en
271
272
  Selected format is: i18next
272
273
 
@@ -283,7 +284,199 @@ Found unused keys!
283
284
  └──────────────────────────────────────────────────────────────────────┴──────────────────┘
284
285
 
285
286
  `);
286
- done();
287
+ done();
288
+ });
289
+ });
290
+ });
291
+ describe("YAML", () => {
292
+ it("should return the missing keys for single folder translations", (done) => {
293
+ (0, child_process_1.exec)("node dist/bin/index.js -s en-US -l translations/yaml/flattenExamples", (_error, stdout, _stderr) => {
294
+ const result = stdout.split("Done")[0];
295
+ expect(result).toEqual(`i18n translations checker
296
+ Source: en-US
297
+
298
+ Found missing keys!
299
+ ┌────────────────────────────────────────────────┬────────────────────────────────┐
300
+ │ file │ key │
301
+ ├────────────────────────────────────────────────┼────────────────────────────────┤
302
+ │ translations/yaml/flattenExamples/de-de.yaml │ other.nested.three │
303
+ │ translations/yaml/flattenExamples/de-de.yaml │ other.nested.deep.more.final │
304
+ └────────────────────────────────────────────────┴────────────────────────────────┘
305
+
306
+ No invalid translations found!
307
+
308
+ `);
309
+ done();
310
+ });
311
+ });
312
+ it("should return the missing/invalid keys for folder per locale with single file", (done) => {
313
+ (0, child_process_1.exec)("node dist/bin/index.js -l translations/yaml/folderExample/ -s en-US", (_error, stdout, _stderr) => {
314
+ const result = stdout.split("Done")[0];
315
+ expect(result).toEqual(`i18n translations checker
316
+ Source: en-US
317
+
318
+ Found missing keys!
319
+ ┌────────────────────────────────────────────────────┬───────────────────────┐
320
+ │ file │ key │
321
+ ├────────────────────────────────────────────────────┼───────────────────────┤
322
+ │ translations/yaml/folderExample/de-DE/index.yaml │ message.text-format │
323
+ └────────────────────────────────────────────────────┴───────────────────────┘
324
+
325
+ Found invalid keys!
326
+ ┌────────────────────────────────────────────────────┬──────────────────┐
327
+ │ file │ key │
328
+ ├────────────────────────────────────────────────────┼──────────────────┤
329
+ │ translations/yaml/folderExample/de-DE/index.yaml │ message.select │
330
+ └────────────────────────────────────────────────────┴──────────────────┘
331
+
332
+ `);
333
+ done();
334
+ });
335
+ });
336
+ it("should return the missing/invalid keys for folder per locale with multiple files", (done) => {
337
+ (0, child_process_1.exec)("node dist/bin/index.js -l translations/yaml/multipleFilesFolderExample/ -s en-US", (_error, stdout, _stderr) => {
338
+ const result = stdout.split("Done")[0];
339
+ expect(result).toEqual(`i18n translations checker
340
+ Source: en-US
341
+
342
+ Found missing keys!
343
+ ┌───────────────────────────────────────────────────────────────┬───────────────────────┐
344
+ │ file │ key │
345
+ ├───────────────────────────────────────────────────────────────┼───────────────────────┤
346
+ │ translations/yaml/multipleFilesFolderExample/de-DE/one.yaml │ message.text-format │
347
+ │ translations/yaml/multipleFilesFolderExample/de-DE/two.yaml │ test.drive.four │
348
+ └───────────────────────────────────────────────────────────────┴───────────────────────┘
349
+
350
+ Found invalid keys!
351
+ ┌─────────────────────────────────────────────────────────────────┬─────────────────────┐
352
+ │ file │ key │
353
+ ├─────────────────────────────────────────────────────────────────┼─────────────────────┤
354
+ │ translations/yaml/multipleFilesFolderExample/de-DE/one.yaml │ message.select │
355
+ │ translations/yaml/multipleFilesFolderExample/de-DE/three.yaml │ multipleVariables │
356
+ └─────────────────────────────────────────────────────────────────┴─────────────────────┘
357
+
358
+ `);
359
+ done();
360
+ });
361
+ });
362
+ it("should return the missing/invalid keys for folder containing multiple locale folders", (done) => {
363
+ (0, child_process_1.exec)("node dist/bin/index.js -l translations/yaml/multipleFoldersExample -s en-US", (_error, stdout, _stderr) => {
364
+ const result = stdout.split("Done")[0];
365
+ expect(result).toEqual(`i18n translations checker
366
+ Source: en-US
367
+
368
+ Found missing keys!
369
+ ┌────────────────────────────────────────────────────────────────────────────┬───────────────────────┐
370
+ │ file │ key │
371
+ ├────────────────────────────────────────────────────────────────────────────┼───────────────────────┤
372
+ │ translations/yaml/multipleFoldersExample/spaceOne/locales/de-DE/one.yaml │ message.text-format │
373
+ │ translations/yaml/multipleFoldersExample/spaceOne/locales/de-DE/two.yaml │ test.drive.four │
374
+ │ translations/yaml/multipleFoldersExample/spaceTwo/locales/de-DE/one.yaml │ message.plural │
375
+ │ translations/yaml/multipleFoldersExample/spaceTwo/locales/de-DE/two.yaml │ test.drive.two │
376
+ └────────────────────────────────────────────────────────────────────────────┴───────────────────────┘
377
+
378
+ Found invalid keys!
379
+ ┌──────────────────────────────────────────────────────────────────────────────┬───────────────────────┐
380
+ │ file │ key │
381
+ ├──────────────────────────────────────────────────────────────────────────────┼───────────────────────┤
382
+ │ translations/yaml/multipleFoldersExample/spaceOne/locales/de-DE/one.yaml │ message.select │
383
+ │ translations/yaml/multipleFoldersExample/spaceOne/locales/de-DE/three.yaml │ multipleVariables │
384
+ │ translations/yaml/multipleFoldersExample/spaceTwo/locales/de-DE/one.yaml │ message.text-format │
385
+ │ translations/yaml/multipleFoldersExample/spaceTwo/locales/de-DE/three.yaml │ numberFormat │
386
+ └──────────────────────────────────────────────────────────────────────────────┴───────────────────────┘
387
+
388
+ `);
389
+ done();
390
+ });
391
+ });
392
+ it("should return the missing/invalid keys for multiple locale folders", (done) => {
393
+ (0, child_process_1.exec)("node dist/bin/index.js -l translations/yaml/multipleFoldersExample/spaceOne translations/yaml/multipleFoldersExample/spaceTwo -s en-US", (_error, stdout, _stderr) => {
394
+ const result = stdout.split("Done")[0];
395
+ expect(result).toEqual(`i18n translations checker
396
+ Source: en-US
397
+
398
+ Found missing keys!
399
+ ┌────────────────────────────────────────────────────────────────────────────┬───────────────────────┐
400
+ │ file │ key │
401
+ ├────────────────────────────────────────────────────────────────────────────┼───────────────────────┤
402
+ │ translations/yaml/multipleFoldersExample/spaceOne/locales/de-DE/one.yaml │ message.text-format │
403
+ │ translations/yaml/multipleFoldersExample/spaceOne/locales/de-DE/two.yaml │ test.drive.four │
404
+ │ translations/yaml/multipleFoldersExample/spaceTwo/locales/de-DE/one.yaml │ message.plural │
405
+ │ translations/yaml/multipleFoldersExample/spaceTwo/locales/de-DE/two.yaml │ test.drive.two │
406
+ └────────────────────────────────────────────────────────────────────────────┴───────────────────────┘
407
+
408
+ Found invalid keys!
409
+ ┌──────────────────────────────────────────────────────────────────────────────┬───────────────────────┐
410
+ │ file │ key │
411
+ ├──────────────────────────────────────────────────────────────────────────────┼───────────────────────┤
412
+ │ translations/yaml/multipleFoldersExample/spaceOne/locales/de-DE/one.yaml │ message.select │
413
+ │ translations/yaml/multipleFoldersExample/spaceOne/locales/de-DE/three.yaml │ multipleVariables │
414
+ │ translations/yaml/multipleFoldersExample/spaceTwo/locales/de-DE/one.yaml │ message.text-format │
415
+ │ translations/yaml/multipleFoldersExample/spaceTwo/locales/de-DE/three.yaml │ numberFormat │
416
+ └──────────────────────────────────────────────────────────────────────────────┴───────────────────────┘
417
+
418
+ `);
419
+ done();
420
+ });
421
+ });
422
+ it("should return the missing/invalid keys for all files in the provided locale folders", (done) => {
423
+ (0, child_process_1.exec)("node dist/bin/index.js --source en-US --locales translations/yaml/flattenExamples translations/yaml/messageExamples", (_error, stdout, _stderr) => {
424
+ const result = stdout.split("Done")[0];
425
+ expect(result).toEqual(`i18n translations checker
426
+ Source: en-US
427
+
428
+ Found missing keys!
429
+ ┌────────────────────────────────────────────────┬────────────────────────────────┐
430
+ │ file │ key │
431
+ ├────────────────────────────────────────────────┼────────────────────────────────┤
432
+ │ translations/yaml/flattenExamples/de-de.yaml │ other.nested.three │
433
+ │ translations/yaml/flattenExamples/de-de.yaml │ other.nested.deep.more.final │
434
+ │ translations/yaml/messageExamples/de-de.yaml │ richText │
435
+ │ translations/yaml/messageExamples/de-de.yaml │ yo │
436
+ │ translations/yaml/messageExamples/de-de.yaml │ nesting1 │
437
+ │ translations/yaml/messageExamples/de-de.yaml │ nesting2 │
438
+ │ translations/yaml/messageExamples/de-de.yaml │ nesting3 │
439
+ │ translations/yaml/messageExamples/de-de.yaml │ key1 │
440
+ └────────────────────────────────────────────────┴────────────────────────────────┘
441
+
442
+ Found invalid keys!
443
+ ┌────────────────────────────────────────────────┬─────────────────────┐
444
+ │ file │ key │
445
+ ├────────────────────────────────────────────────┼─────────────────────┤
446
+ │ translations/yaml/messageExamples/de-de.yaml │ multipleVariables │
447
+ └────────────────────────────────────────────────┴─────────────────────┘
448
+
449
+ `);
450
+ done();
451
+ });
452
+ });
453
+ it("should return the missing/invalid keys for all files with source matching folder and source matching file", (done) => {
454
+ (0, child_process_1.exec)("node dist/bin/index.js -l translations/yaml/multipleFilesFolderExample translations/yaml/flattenExamples -s en-US", (_error, stdout, _stderr) => {
455
+ const result = stdout.split("Done")[0];
456
+ expect(result).toEqual(`i18n translations checker
457
+ Source: en-US
458
+
459
+ Found missing keys!
460
+ ┌───────────────────────────────────────────────────────────────┬────────────────────────────────┐
461
+ │ file │ key │
462
+ ├───────────────────────────────────────────────────────────────┼────────────────────────────────┤
463
+ │ translations/yaml/flattenExamples/de-de.yaml │ other.nested.three │
464
+ │ translations/yaml/flattenExamples/de-de.yaml │ other.nested.deep.more.final │
465
+ │ translations/yaml/multipleFilesFolderExample/de-DE/one.yaml │ message.text-format │
466
+ │ translations/yaml/multipleFilesFolderExample/de-DE/two.yaml │ test.drive.four │
467
+ └───────────────────────────────────────────────────────────────┴────────────────────────────────┘
468
+
469
+ Found invalid keys!
470
+ ┌─────────────────────────────────────────────────────────────────┬─────────────────────┐
471
+ │ file │ key │
472
+ ├─────────────────────────────────────────────────────────────────┼─────────────────────┤
473
+ │ translations/yaml/multipleFilesFolderExample/de-DE/one.yaml │ message.select │
474
+ │ translations/yaml/multipleFilesFolderExample/de-DE/three.yaml │ multipleVariables │
475
+ └─────────────────────────────────────────────────────────────────┴─────────────────────┘
476
+
477
+ `);
478
+ done();
479
+ });
287
480
  });
288
481
  });
289
482
  });
package/dist/index.d.ts CHANGED
@@ -10,4 +10,4 @@ export declare const checkTranslations: (source: TranslationFile[], targets: Tra
10
10
  missingKeys: CheckResult | undefined;
11
11
  invalidKeys: CheckResult | undefined;
12
12
  };
13
- export declare const checkUnusedKeys: (source: TranslationFile[], codebaseSrc: string, options?: Options) => Promise<CheckResult | undefined>;
13
+ export declare const checkUnusedKeys: (source: TranslationFile[], codebaseSrc: string, options?: Options, componentFunctions?: never[]) => Promise<CheckResult | undefined>;
package/dist/index.js CHANGED
@@ -57,22 +57,22 @@ const checkTranslations = (source, targets, options = { format: "icu", checks: [
57
57
  exports.checkTranslations = checkTranslations;
58
58
  const checkUnusedKeys = (source_1, codebaseSrc_1, ...args_1) => __awaiter(void 0, [source_1, codebaseSrc_1, ...args_1], void 0, function* (source, codebaseSrc, options = {
59
59
  format: "react-intl",
60
- }) {
60
+ }, componentFunctions = []) {
61
61
  if (!options.format || !["react-intl", "i18next"].includes(options.format)) {
62
62
  return undefined;
63
63
  }
64
64
  return options.format === "react-intl"
65
65
  ? findUnusedReactIntlTranslations(source, codebaseSrc)
66
- : findUnusedi18NextTranslations(source, codebaseSrc);
66
+ : findUnusedI18NextTranslations(source, codebaseSrc, componentFunctions);
67
67
  });
68
68
  exports.checkUnusedKeys = checkUnusedKeys;
69
69
  const findUnusedReactIntlTranslations = (source, codebaseSrc) => __awaiter(void 0, void 0, void 0, function* () {
70
70
  let unusedKeys = {};
71
71
  // find any unused keys in a react-intl code base
72
- const unsuedKeysFiles = (0, glob_1.globSync)(codebaseSrc, {
72
+ const unusedKeysFiles = (0, glob_1.globSync)(codebaseSrc, {
73
73
  ignore: ["node_modules/**"],
74
74
  });
75
- const extracted = yield (0, cli_lib_1.extract)(unsuedKeysFiles, {});
75
+ const extracted = yield (0, cli_lib_1.extract)(unusedKeysFiles, {});
76
76
  const extractedResultSet = new Set(Object.keys(JSON.parse(extracted)));
77
77
  source.forEach(({ name, content }) => {
78
78
  const keysInSource = Object.keys(content);
@@ -86,7 +86,7 @@ const findUnusedReactIntlTranslations = (source, codebaseSrc) => __awaiter(void
86
86
  });
87
87
  return unusedKeys;
88
88
  });
89
- const findUnusedi18NextTranslations = (source, codebaseSrc) => __awaiter(void 0, void 0, void 0, function* () {
89
+ const findUnusedI18NextTranslations = (source_1, codebaseSrc_1, ...args_1) => __awaiter(void 0, [source_1, codebaseSrc_1, ...args_1], void 0, function* (source, codebaseSrc, componentFunctions = []) {
90
90
  let unusedKeys = {};
91
91
  // find any unused keys in a react-i18next code base
92
92
  const unusedKeysFiles = (0, glob_1.globSync)(`${codebaseSrc}/**/*.tsx`, {
@@ -97,7 +97,22 @@ const findUnusedi18NextTranslations = (source, codebaseSrc) => __awaiter(void 0,
97
97
  const { transform } = yield import("i18next-parser");
98
98
  unusedKeysFiles.forEach((file) => {
99
99
  const rawContent = fs_1.default.readFileSync(file);
100
- const i18nextParser = new transform();
100
+ const i18nextParser = new transform({
101
+ lexers: {
102
+ jsx: [
103
+ {
104
+ lexer: "JsxLexer",
105
+ componentFunctions: componentFunctions.concat(["Trans"]),
106
+ },
107
+ ],
108
+ tsx: [
109
+ {
110
+ lexer: "JsxLexer",
111
+ componentFunctions: componentFunctions.concat(["Trans"]),
112
+ },
113
+ ],
114
+ },
115
+ });
101
116
  i18nextParser.once("data", (file) => {
102
117
  extractedResult = extractedResult.concat(Object.keys(flatten(JSON.parse(file.contents))));
103
118
  });
@@ -11,6 +11,11 @@ describe("findInvalid18nTranslations:compareTranslationFiles", () => {
11
11
  it("should return the invalid keys in the target file", () => {
12
12
  expect((0, findInvalidi18nTranslations_1.compareTranslationFiles)((0, flattenTranslations_1.flattenTranslations)(Object.assign(Object.assign({}, sourceFile), { "ten.eleven.twelve": "ten eleven twelve" })), (0, flattenTranslations_1.flattenTranslations)(targetFile))).toEqual(["key_with_broken_de", "intlNumber_broken_de"]);
13
13
  });
14
+ it("should return an empty array if the strings contain paranthesis that have different content", () => {
15
+ expect((0, findInvalidi18nTranslations_1.compareTranslationFiles)((0, flattenTranslations_1.flattenTranslations)({
16
+ keyText: "Key(s)",
17
+ }), (0, flattenTranslations_1.flattenTranslations)({ keyText: "Taste(n)" }))).toEqual([]);
18
+ });
14
19
  it("should return empty array if placeholders are identical but in different positions", () => {
15
20
  expect((0, findInvalidi18nTranslations_1.compareTranslationFiles)({
16
21
  basic: "added {{this}} and {{that}} should work.",
@@ -2,7 +2,7 @@
2
2
  // Based on https://github.com/i18next/i18next-translation-parser/blob/v1.0.0/src/parse.js
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.parse = void 0;
5
- const REGEXP = new RegExp("({{[^}]+}}|\\$t{[^}]+}|\\$t\\([^\\)]+\\)|\\([0-9\\-inf]+\\)|<[^>]+>)", "g");
5
+ const REGEXP = new RegExp("({{[^}]+}}|\\$t{[^}]+}|\\$t\\([^\\)]+\\)|\\([0-9\\-inf]+\\)(?=\\[)|<[^>]+>)", "g");
6
6
  const DOUBLE_BRACE = "{{";
7
7
  const $_T_BRACE = "$t{";
8
8
  const $_T_PARENTHESIS = "$t(";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lingual/i18n-check",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "i18n translation messages check",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -24,19 +24,24 @@
24
24
  "commander": "^12.1.0",
25
25
  "glob": "^11.0.0",
26
26
  "i18next-parser": "^9.0.2",
27
- "typescript": "^5.7.2",
27
+ "js-yaml": "^4.1.0",
28
28
  "vinyl": "^3.0.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/jest": "^29.5.14",
32
+ "@types/js-yaml": "^4.0.9",
32
33
  "@types/node": "^22.10.1",
33
34
  "@types/vinyl": "^2.0.12",
34
35
  "braces": "^3.0.3",
35
36
  "jest": "^29.7.0",
36
- "ts-jest": "^29.2.5"
37
+ "ts-jest": "^29.2.5",
38
+ "typescript": "^5.7.3"
37
39
  },
38
40
  "repository": {
39
41
  "type": "git",
40
42
  "url": "https://github.com/lingualdev/i18n-check.git"
43
+ },
44
+ "engines": {
45
+ "node": ">=20"
41
46
  }
42
47
  }