@lingual/i18n-check 0.8.2 → 0.8.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 CHANGED
@@ -223,6 +223,28 @@ yarn i18n:check --locales translations/folderExamples -s en-US -e translations/f
223
223
  The `--exclude` option also accepts a mix of files and folders, which follows the same pattern as above, i.e.
224
224
  `-e translations/folderExamples/fr/* translations/messageExamples/it.json`
225
225
 
226
+ ### --ignore, -i
227
+
228
+ There can be situations where we only want to translate a feature for a specific region and therefore need to ignore any missing key checks against non supported locales. Another scenario is that we know of the missing keys and want to be able to skip these missing keys when running checks. For these aforementioned scenarios, by using the `--ignore` or `-i` option you can specify which keys to ignore, additionally also being able to define ignoring all keys inside a defined path, i.e. `some.keys.path.*`.
229
+
230
+ To ignore regular keys:
231
+
232
+ ```bash
233
+ yarn i18n:check --locales translations/folderExamples -s en-US -i some.key.to.ignore other.key.to.ignore
234
+ ```
235
+
236
+ To ignore all keys within a provided path:
237
+
238
+ ```bash
239
+ yarn i18n:check --locales translations/folderExamples -s en-US -i "some.path.to.keys.*"
240
+ ```
241
+
242
+ A mix of regular keys and paths:
243
+
244
+ ```bash
245
+ yarn i18n:check --locales translations/folderExamples -s en-US -i "some.path.to.keys.*" some.key.to.ignore other.key.to.ignore
246
+ ```
247
+
226
248
  ### --parser-component-functions
227
249
 
228
250
  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.
@@ -371,7 +393,7 @@ Aside from using the CLI, i18n-check also exposes a set of check functions that
371
393
  Start by importing i18n-check:
372
394
 
373
395
  ```ts
374
- import * as i18nCheck from "@lingual/i18n-check";
396
+ import * as i18nCheck from '@lingual/i18n-check';
375
397
  ```
376
398
 
377
399
  ### `i18nCheck.checkTranslations(source, targets [, options])`
@@ -379,10 +401,10 @@ import * as i18nCheck from "@lingual/i18n-check";
379
401
  `checkTranslations` expects the base and comparison or target files and returns an object containing the missing and invalid keys. The optional `options` objects can be provided as a third argument to define the format style via the `format` property, this is useful if you want to validate `i18next` specific translations.
380
402
 
381
403
  ```ts
382
- import { checkTranslations } from "@lingual/i18n-check";
404
+ import { checkTranslations } from '@lingual/i18n-check';
383
405
 
384
406
  const options = {
385
- format: "i18next",
407
+ format: 'i18next',
386
408
  };
387
409
 
388
410
  const { invalidKeys, missingKeys } = checkTranslations(
@@ -395,11 +417,11 @@ const { invalidKeys, missingKeys } = checkTranslations(
395
417
  Additionally the `options` object enables to also define which checks should run via the `checks` property, f.e. if you only want to check for missing or invalid keys only.
396
418
 
397
419
  ```ts
398
- import { checkTranslations } from "@lingual/i18n-check";
420
+ import { checkTranslations } from '@lingual/i18n-check';
399
421
 
400
422
  const options = {
401
- format: "icu",
402
- checks: ["invalidKeys"],
423
+ format: 'icu',
424
+ checks: ['invalidKeys'],
403
425
  };
404
426
 
405
427
  const { invalidKeys } = checkTranslations(source, targets, options);
@@ -433,7 +455,7 @@ The result for `missingKeys` as well as `invalidKeys` is an object containing th
433
455
  `checkMissingTranslations` checks for any missing keys in the target files. All files are compared against the source file.
434
456
 
435
457
  ```ts
436
- import { checkMissingTranslations } from "@lingual/i18n-check";
458
+ import { checkMissingTranslations } from '@lingual/i18n-check';
437
459
 
438
460
  const result = checkMissingTranslations(source, targets);
439
461
 
@@ -450,10 +472,10 @@ The result is an object containing the provided locales and their corresponding
450
472
  `checkInvalidTranslations` checks if there are any invalid keys in the target files. All files are compared against the source file.
451
473
 
452
474
  ```ts
453
- import { checkInvalidTranslations } from "@lingual/i18n-check";
475
+ import { checkInvalidTranslations } from '@lingual/i18n-check';
454
476
 
455
477
  const options = {
456
- format: "i18next",
478
+ format: 'i18next',
457
479
  };
458
480
 
459
481
  const result = checkInvalidTranslations(source, targets, options);
@@ -470,7 +492,7 @@ The result is an object containing the provided locales and their corresponding
470
492
 
471
493
  If you want to checkout and run the code, you need to run the `build` command first.
472
494
 
473
- Run `yarn build`, `pnpm run build` or `npm run build` and then depending on the scenario one of the following commands.
495
+ Run `pnpm run build` and then depending on the scenario one of the following commands.
474
496
 
475
497
  Basic icu translation example:
476
498
 
@@ -516,14 +538,6 @@ To run the tests use one of the following commands:
516
538
  pnpm test
517
539
  ```
518
540
 
519
- ```bash
520
- yarn test
521
- ```
522
-
523
- ```bash
524
- npm test
525
- ```
526
-
527
541
  ## Links
528
542
 
529
543
  - [Introducing i18n-check](https://lingual.dev/blog/introducing-i18n-check/)
package/dist/bin/index.js CHANGED
@@ -14,23 +14,24 @@ const __1 = require("..");
14
14
  const errorReporters_1 = require("../errorReporters");
15
15
  const flattenTranslations_1 = require("../utils/flattenTranslations");
16
16
  const node_path_1 = __importDefault(require("node:path"));
17
- const version = require("../../package.json").version;
17
+ const version = require('../../package.json').version;
18
18
  commander_1.program
19
19
  .version(version)
20
- .option("-l, --locales <locales...>", "name of the directory containing the locales to validate")
21
- .option("-s, --source <locale>", "the source locale to validate against")
22
- .option("-f, --format <type>", "define the specific format: i18next, react-intl or next-intl")
23
- .option("-c, --check <checks...>", "this option is deprecated - use -o or --only instead")
24
- .option("-o, --only <only...>", "define the specific checks you want to run: invalidKeys, missingKeys, unused, undefined. By default the check will validate against missing and invalid keys, i.e. --only invalidKeys,missingKeys")
25
- .option("-r, --reporter <style>", "define the reporting style: standard or summary")
26
- .option("-e, --exclude <exclude...>", "define the file(s) and/or folders(s) that should be excluded from the check")
27
- .option("-u, --unused <paths...>", "define the source path(s) to find all unused and undefined keys")
28
- .option("--parser-component-functions <components...>", "a list of component names to parse when using the --unused option")
20
+ .option('-l, --locales <locales...>', 'name of the directory containing the locales to validate')
21
+ .option('-s, --source <locale>', 'the source locale to validate against')
22
+ .option('-f, --format <type>', 'define the specific format: i18next, react-intl or next-intl')
23
+ .option('-c, --check <checks...>', 'this option is deprecated - use -o or --only instead')
24
+ .option('-o, --only <only...>', 'define the specific checks you want to run: invalidKeys, missingKeys, unused, undefined. By default the check will validate against missing and invalid keys, i.e. --only invalidKeys,missingKeys')
25
+ .option('-r, --reporter <style>', 'define the reporting style: standard or summary')
26
+ .option('-e, --exclude <exclude...>', 'define the file(s) and/or folders(s) that should be excluded from the check')
27
+ .option('-i, --ignore <ignore...>', 'define the key(s) or group of keys (i.e. `some.namespace.*`) that should be excluded from the check')
28
+ .option('-u, --unused <paths...>', 'define the source path(s) to find all unused and undefined keys')
29
+ .option('--parser-component-functions <components...>', 'a list of component names to parse when using the --unused option')
29
30
  .parse();
30
31
  const getCheckOptions = () => {
31
- const checkOption = commander_1.program.getOptionValue("only") || commander_1.program.getOptionValue("check");
32
- if (commander_1.program.getOptionValue("check")) {
33
- console.log(chalk_1.default.yellow("The --check option has been deprecated, use the --only option instead."));
32
+ const checkOption = commander_1.program.getOptionValue('only') || commander_1.program.getOptionValue('check');
33
+ if (commander_1.program.getOptionValue('check')) {
34
+ console.log(chalk_1.default.yellow('The --check option has been deprecated, use the --only option instead.'));
34
35
  }
35
36
  if (!checkOption) {
36
37
  return errorReporters_1.CheckOptions;
@@ -43,33 +44,34 @@ const isSource = (fileInfo, srcPath) => {
43
44
  };
44
45
  const main = async () => {
45
46
  const start = performance.now();
46
- const srcPath = commander_1.program.getOptionValue("source");
47
- const localePath = commander_1.program.getOptionValue("locales");
48
- const format = commander_1.program.getOptionValue("format");
49
- const exclude = commander_1.program.getOptionValue("exclude");
50
- const unusedSrcPath = commander_1.program.getOptionValue("unused");
51
- const componentFunctions = commander_1.program.getOptionValue("parserComponentFunctions");
47
+ const srcPath = commander_1.program.getOptionValue('source');
48
+ const localePath = commander_1.program.getOptionValue('locales');
49
+ const format = commander_1.program.getOptionValue('format');
50
+ const exclude = commander_1.program.getOptionValue('exclude');
51
+ const ignore = commander_1.program.getOptionValue('ignore');
52
+ const unusedSrcPath = commander_1.program.getOptionValue('unused');
53
+ const componentFunctions = commander_1.program.getOptionValue('parserComponentFunctions');
52
54
  if (!srcPath) {
53
- console.log(chalk_1.default.red("Source not found. Please provide a valid source locale, i.e. -s en-US"));
55
+ console.log(chalk_1.default.red('Source not found. Please provide a valid source locale, i.e. -s en-US'));
54
56
  (0, node_process_1.exit)(1);
55
57
  }
56
58
  if (!localePath || localePath.length === 0) {
57
- console.log(chalk_1.default.red("Locale file(s) not found. Please provide valid locale file(s), i.e. -locales translations/"));
59
+ console.log(chalk_1.default.red('Locale file(s) not found. Please provide valid locale file(s), i.e. -locales translations/'));
58
60
  (0, node_process_1.exit)(1);
59
61
  }
60
62
  const excludedPaths = exclude ?? [];
61
63
  const localePathFolders = localePath;
62
64
  const isMultiFolders = localePathFolders.length > 1;
63
- let srcFiles = [];
64
- let targetFiles = [];
65
+ const srcFiles = [];
66
+ const targetFiles = [];
65
67
  const pattern = isMultiFolders
66
- ? `{${localePath.join(",").trim()}}/**/*.{json,yaml,yml}`
67
- : `${localePath.join(",").trim()}/**/*.{json,yaml,yml}`;
68
+ ? `{${localePath.join(',').trim()}}/**/*.{json,yaml,yml}`
69
+ : `${localePath.join(',').trim()}/**/*.{json,yaml,yml}`;
68
70
  const files = await (0, glob_1.glob)(pattern, {
69
- ignore: ["node_modules/**"].concat(excludedPaths),
71
+ ignore: ['node_modules/**'].concat(excludedPaths),
70
72
  windowsPathsNoEscape: true,
71
73
  });
72
- console.log("i18n translations checker");
74
+ console.log('i18n translations checker');
73
75
  console.log(chalk_1.default.gray(`Source: ${srcPath}`));
74
76
  if (format) {
75
77
  console.log(chalk_1.default.blackBright(`Selected format is: ${format}`));
@@ -77,12 +79,13 @@ const main = async () => {
77
79
  const options = {
78
80
  checks: getCheckOptions(),
79
81
  format: format ?? undefined,
82
+ ignore,
80
83
  };
81
84
  const fileInfos = [];
82
85
  files.sort().forEach((file) => {
83
86
  const filePath = file.split(node_path_1.default.sep);
84
- const name = filePath.pop() ?? "";
85
- const extension = name.split(".").pop() ?? "json";
87
+ const name = filePath.pop() ?? '';
88
+ const extension = name.split('.').pop() ?? 'json';
86
89
  fileInfos.push({
87
90
  extension,
88
91
  file,
@@ -92,11 +95,11 @@ const main = async () => {
92
95
  });
93
96
  fileInfos.forEach(({ extension, file, name, path }) => {
94
97
  let rawContent;
95
- if (extension === "yaml") {
96
- rawContent = js_yaml_1.default.load(node_fs_1.default.readFileSync(file, "utf-8"));
98
+ if (extension === 'yaml') {
99
+ rawContent = js_yaml_1.default.load(node_fs_1.default.readFileSync(file, 'utf-8'));
97
100
  }
98
101
  else {
99
- rawContent = JSON.parse(node_fs_1.default.readFileSync(file, "utf-8"));
102
+ rawContent = JSON.parse(node_fs_1.default.readFileSync(file, 'utf-8'));
100
103
  }
101
104
  const content = (0, flattenTranslations_1.flattenTranslations)(rawContent);
102
105
  if (isSource({ file, name, path }, srcPath)) {
@@ -107,12 +110,12 @@ const main = async () => {
107
110
  });
108
111
  }
109
112
  else {
110
- const fullPath = path.join("-");
113
+ const fullPath = path.join('-');
111
114
  const reference = fileInfos.find((fileInfo) => {
112
115
  if (!isSource(fileInfo, srcPath)) {
113
116
  return false;
114
117
  }
115
- if (fileInfo.path.join("-") === fullPath) {
118
+ if (fileInfo.path.join('-') === fullPath) {
116
119
  return true;
117
120
  }
118
121
  // Check if the folder path matches - ignoring the last folder
@@ -130,8 +133,8 @@ const main = async () => {
130
133
  //
131
134
  // Referencing: `path/to/locales/en-US/one.json`, `path/to/locales/de-DE/one.json`
132
135
  // Non Referencing: `path/to/locales/en-US/one.json`, `path/to/other/locales/de-DE/one.json`
133
- if (fileInfo.path.slice(0, fileInfo.path.length - 1).join("-") ===
134
- path.slice(0, path.length - 1).join("-")) {
136
+ if (fileInfo.path.slice(0, fileInfo.path.length - 1).join('-') ===
137
+ path.slice(0, path.length - 1).join('-')) {
135
138
  return fileInfo.name === name;
136
139
  }
137
140
  return false;
@@ -146,13 +149,13 @@ const main = async () => {
146
149
  }
147
150
  });
148
151
  if (srcFiles.length === 0) {
149
- console.log(chalk_1.default.red("Source not found. Please provide a valid source locale, i.e. -s en-US"));
152
+ console.log(chalk_1.default.red('Source not found. Please provide a valid source locale, i.e. -s en-US'));
150
153
  (0, node_process_1.exit)(1);
151
154
  }
152
- if ((options.checks.includes("missingKeys") ||
153
- options.checks.includes("invalidKeys")) &&
155
+ if ((options.checks.includes('missingKeys') ||
156
+ options.checks.includes('invalidKeys')) &&
154
157
  targetFiles.length === 0) {
155
- console.log(chalk_1.default.red("Locale file(s) not found. Please provide valid locale file(s), i.e. --locales translations/"));
158
+ console.log(chalk_1.default.red('Locale file(s) not found. Please provide valid locale file(s), i.e. --locales translations/'));
156
159
  (0, node_process_1.exit)(1);
157
160
  }
158
161
  try {
@@ -161,10 +164,10 @@ const main = async () => {
161
164
  if (unusedSrcPath) {
162
165
  const isMultiUnusedFolders = unusedSrcPath.length > 1;
163
166
  const pattern = isMultiUnusedFolders
164
- ? `{${unusedSrcPath.join(",").trim()}}/**/*.{ts,tsx}`
165
- : `${unusedSrcPath.join(",").trim()}/**/*.{ts,tsx}`;
167
+ ? `{${unusedSrcPath.join(',').trim()}}/**/*.{ts,tsx}`
168
+ : `${unusedSrcPath.join(',').trim()}/**/*.{ts,tsx}`;
166
169
  const filesToParse = (0, glob_1.globSync)(pattern, {
167
- ignore: ["node_modules/**"],
170
+ ignore: ['node_modules/**'],
168
171
  windowsPathsNoEscape: true,
169
172
  });
170
173
  const unusedKeys = await (0, __1.checkUnusedKeys)(srcFiles, filesToParse, options, componentFunctions);
@@ -183,6 +186,7 @@ const main = async () => {
183
186
  else {
184
187
  (0, node_process_1.exit)(0);
185
188
  }
189
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
186
190
  }
187
191
  catch (e) {
188
192
  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"));
@@ -190,10 +194,10 @@ const main = async () => {
190
194
  }
191
195
  };
192
196
  const printTranslationResult = ({ missingKeys, invalidKeys, }) => {
193
- const reporter = commander_1.program.getOptionValue("reporter");
194
- const isSummary = reporter === "summary";
197
+ const reporter = commander_1.program.getOptionValue('reporter');
198
+ const isSummary = reporter === 'summary';
195
199
  if (missingKeys && Object.keys(missingKeys).length > 0) {
196
- console.log(chalk_1.default.red("\nFound missing keys!"));
200
+ console.log(chalk_1.default.red('\nFound missing keys!'));
197
201
  if (isSummary) {
198
202
  console.log(chalk_1.default.red((0, errorReporters_1.formatSummaryTable)(missingKeys)));
199
203
  }
@@ -203,10 +207,10 @@ const printTranslationResult = ({ missingKeys, invalidKeys, }) => {
203
207
  }
204
208
  }
205
209
  else if (missingKeys) {
206
- console.log(chalk_1.default.green("\nNo missing keys found!"));
210
+ console.log(chalk_1.default.green('\nNo missing keys found!'));
207
211
  }
208
212
  if (invalidKeys && Object.keys(invalidKeys).length > 0) {
209
- console.log(chalk_1.default.red("\nFound invalid keys!"));
213
+ console.log(chalk_1.default.red('\nFound invalid keys!'));
210
214
  if (isSummary) {
211
215
  console.log(chalk_1.default.red((0, errorReporters_1.formatSummaryTable)(invalidKeys)));
212
216
  }
@@ -216,14 +220,14 @@ const printTranslationResult = ({ missingKeys, invalidKeys, }) => {
216
220
  }
217
221
  }
218
222
  else if (invalidKeys) {
219
- console.log(chalk_1.default.green("\nNo invalid translations found!"));
223
+ console.log(chalk_1.default.green('\nNo invalid translations found!'));
220
224
  }
221
225
  };
222
226
  const printUnusedKeysResult = ({ unusedKeys, }) => {
223
- const reporter = commander_1.program.getOptionValue("reporter");
224
- const isSummary = reporter === "summary";
227
+ const reporter = commander_1.program.getOptionValue('reporter');
228
+ const isSummary = reporter === 'summary';
225
229
  if (unusedKeys && hasKeys(unusedKeys)) {
226
- console.log(chalk_1.default.red("\nFound unused keys!"));
230
+ console.log(chalk_1.default.red('\nFound unused keys!'));
227
231
  if (isSummary) {
228
232
  console.log(chalk_1.default.red((0, errorReporters_1.formatSummaryTable)(unusedKeys)));
229
233
  }
@@ -232,14 +236,14 @@ const printUnusedKeysResult = ({ unusedKeys, }) => {
232
236
  }
233
237
  }
234
238
  else if (unusedKeys) {
235
- console.log(chalk_1.default.green("\nNo unused keys found!"));
239
+ console.log(chalk_1.default.green('\nNo unused keys found!'));
236
240
  }
237
241
  };
238
242
  const printUndefinedKeysResult = ({ undefinedKeys, }) => {
239
- const reporter = commander_1.program.getOptionValue("reporter");
240
- const isSummary = reporter === "summary";
243
+ const reporter = commander_1.program.getOptionValue('reporter');
244
+ const isSummary = reporter === 'summary';
241
245
  if (undefinedKeys && hasKeys(undefinedKeys)) {
242
- console.log(chalk_1.default.red("\nFound undefined keys!"));
246
+ console.log(chalk_1.default.red('\nFound undefined keys!'));
243
247
  if (isSummary) {
244
248
  console.log(chalk_1.default.red((0, errorReporters_1.formatSummaryTable)(undefinedKeys)));
245
249
  }
@@ -248,7 +252,7 @@ const printUndefinedKeysResult = ({ undefinedKeys, }) => {
248
252
  }
249
253
  }
250
254
  else if (undefinedKeys) {
251
- console.log(chalk_1.default.green("\nNo undefined keys found!"));
255
+ console.log(chalk_1.default.green('\nNo undefined keys found!'));
252
256
  }
253
257
  };
254
258
  const hasKeys = (checkResult) => {