@lingual/i18n-check 0.8.1 → 0.8.3

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
@@ -371,7 +371,7 @@ Aside from using the CLI, i18n-check also exposes a set of check functions that
371
371
  Start by importing i18n-check:
372
372
 
373
373
  ```ts
374
- import * as i18nCheck from "@lingual/i18n-check";
374
+ import * as i18nCheck from '@lingual/i18n-check';
375
375
  ```
376
376
 
377
377
  ### `i18nCheck.checkTranslations(source, targets [, options])`
@@ -379,10 +379,10 @@ import * as i18nCheck from "@lingual/i18n-check";
379
379
  `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
380
 
381
381
  ```ts
382
- import { checkTranslations } from "@lingual/i18n-check";
382
+ import { checkTranslations } from '@lingual/i18n-check';
383
383
 
384
384
  const options = {
385
- format: "i18next",
385
+ format: 'i18next',
386
386
  };
387
387
 
388
388
  const { invalidKeys, missingKeys } = checkTranslations(
@@ -395,11 +395,11 @@ const { invalidKeys, missingKeys } = checkTranslations(
395
395
  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
396
 
397
397
  ```ts
398
- import { checkTranslations } from "@lingual/i18n-check";
398
+ import { checkTranslations } from '@lingual/i18n-check';
399
399
 
400
400
  const options = {
401
- format: "icu",
402
- checks: ["invalidKeys"],
401
+ format: 'icu',
402
+ checks: ['invalidKeys'],
403
403
  };
404
404
 
405
405
  const { invalidKeys } = checkTranslations(source, targets, options);
@@ -433,7 +433,7 @@ The result for `missingKeys` as well as `invalidKeys` is an object containing th
433
433
  `checkMissingTranslations` checks for any missing keys in the target files. All files are compared against the source file.
434
434
 
435
435
  ```ts
436
- import { checkMissingTranslations } from "@lingual/i18n-check";
436
+ import { checkMissingTranslations } from '@lingual/i18n-check';
437
437
 
438
438
  const result = checkMissingTranslations(source, targets);
439
439
 
@@ -450,10 +450,10 @@ The result is an object containing the provided locales and their corresponding
450
450
  `checkInvalidTranslations` checks if there are any invalid keys in the target files. All files are compared against the source file.
451
451
 
452
452
  ```ts
453
- import { checkInvalidTranslations } from "@lingual/i18n-check";
453
+ import { checkInvalidTranslations } from '@lingual/i18n-check';
454
454
 
455
455
  const options = {
456
- format: "i18next",
456
+ format: 'i18next',
457
457
  };
458
458
 
459
459
  const result = checkInvalidTranslations(source, targets, options);
@@ -470,7 +470,7 @@ The result is an object containing the provided locales and their corresponding
470
470
 
471
471
  If you want to checkout and run the code, you need to run the `build` command first.
472
472
 
473
- Run `yarn build`, `pnpm run build` or `npm run build` and then depending on the scenario one of the following commands.
473
+ Run `pnpm run build` and then depending on the scenario one of the following commands.
474
474
 
475
475
  Basic icu translation example:
476
476
 
@@ -516,14 +516,6 @@ To run the tests use one of the following commands:
516
516
  pnpm test
517
517
  ```
518
518
 
519
- ```bash
520
- yarn test
521
- ```
522
-
523
- ```bash
524
- npm test
525
- ```
526
-
527
519
  ## Links
528
520
 
529
521
  - [Introducing i18n-check](https://lingual.dev/blog/introducing-i18n-check/)
package/dist/bin/index.js CHANGED
@@ -13,23 +13,24 @@ const js_yaml_1 = __importDefault(require("js-yaml"));
13
13
  const __1 = require("..");
14
14
  const errorReporters_1 = require("../errorReporters");
15
15
  const flattenTranslations_1 = require("../utils/flattenTranslations");
16
- const version = require("../../package.json").version;
16
+ const node_path_1 = __importDefault(require("node:path"));
17
+ const version = require('../../package.json').version;
17
18
  commander_1.program
18
19
  .version(version)
19
- .option("-l, --locales <locales...>", "name of the directory containing the locales to validate")
20
- .option("-s, --source <locale>", "the source locale to validate against")
21
- .option("-f, --format <type>", "define the specific format: i18next, react-intl or next-intl")
22
- .option("-c, --check <checks...>", "this option is deprecated - use -o or --only instead")
23
- .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")
24
- .option("-r, --reporter <style>", "define the reporting style: standard or summary")
25
- .option("-e, --exclude <exclude...>", "define the file(s) and/or folders(s) that should be excluded from the check")
26
- .option("-u, --unused <paths...>", "define the source path(s) to find all unused and undefined keys")
27
- .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('-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')
28
29
  .parse();
29
30
  const getCheckOptions = () => {
30
- const checkOption = commander_1.program.getOptionValue("only") || commander_1.program.getOptionValue("check");
31
- if (commander_1.program.getOptionValue("check")) {
32
- console.log(chalk_1.default.yellow("The --check option has been deprecated, use the --only option instead."));
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.'));
33
34
  }
34
35
  if (!checkOption) {
35
36
  return errorReporters_1.CheckOptions;
@@ -42,32 +43,33 @@ const isSource = (fileInfo, srcPath) => {
42
43
  };
43
44
  const main = async () => {
44
45
  const start = performance.now();
45
- const srcPath = commander_1.program.getOptionValue("source");
46
- const localePath = commander_1.program.getOptionValue("locales");
47
- const format = commander_1.program.getOptionValue("format");
48
- const exclude = commander_1.program.getOptionValue("exclude");
49
- const unusedSrcPath = commander_1.program.getOptionValue("unused");
50
- const componentFunctions = commander_1.program.getOptionValue("parserComponentFunctions");
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');
51
52
  if (!srcPath) {
52
- console.log(chalk_1.default.red("Source not found. Please provide a valid source locale, i.e. -s en-US"));
53
+ console.log(chalk_1.default.red('Source not found. Please provide a valid source locale, i.e. -s en-US'));
53
54
  (0, node_process_1.exit)(1);
54
55
  }
55
56
  if (!localePath || localePath.length === 0) {
56
- console.log(chalk_1.default.red("Locale file(s) not found. Please provide valid locale file(s), i.e. -locales translations/"));
57
+ console.log(chalk_1.default.red('Locale file(s) not found. Please provide valid locale file(s), i.e. -locales translations/'));
57
58
  (0, node_process_1.exit)(1);
58
59
  }
59
60
  const excludedPaths = exclude ?? [];
60
61
  const localePathFolders = localePath;
61
62
  const isMultiFolders = localePathFolders.length > 1;
62
- let srcFiles = [];
63
- let targetFiles = [];
63
+ const srcFiles = [];
64
+ const targetFiles = [];
64
65
  const pattern = isMultiFolders
65
- ? `{${localePath.join(",").trim()}}/**/*.{json,yaml,yml}`
66
- : `${localePath.join(",").trim()}/**/*.{json,yaml,yml}`;
66
+ ? `{${localePath.join(',').trim()}}/**/*.{json,yaml,yml}`
67
+ : `${localePath.join(',').trim()}/**/*.{json,yaml,yml}`;
67
68
  const files = await (0, glob_1.glob)(pattern, {
68
- ignore: ["node_modules/**"].concat(excludedPaths),
69
+ ignore: ['node_modules/**'].concat(excludedPaths),
70
+ windowsPathsNoEscape: true,
69
71
  });
70
- console.log("i18n translations checker");
72
+ console.log('i18n translations checker');
71
73
  console.log(chalk_1.default.gray(`Source: ${srcPath}`));
72
74
  if (format) {
73
75
  console.log(chalk_1.default.blackBright(`Selected format is: ${format}`));
@@ -78,23 +80,23 @@ const main = async () => {
78
80
  };
79
81
  const fileInfos = [];
80
82
  files.sort().forEach((file) => {
81
- const path = file.split("/");
82
- const name = path.pop() ?? "";
83
- const extension = name.split(".").pop() ?? "json";
83
+ const filePath = file.split(node_path_1.default.sep);
84
+ const name = filePath.pop() ?? '';
85
+ const extension = name.split('.').pop() ?? 'json';
84
86
  fileInfos.push({
85
87
  extension,
86
88
  file,
87
89
  name,
88
- path,
90
+ path: filePath,
89
91
  });
90
92
  });
91
93
  fileInfos.forEach(({ extension, file, name, path }) => {
92
94
  let rawContent;
93
- if (extension === "yaml") {
94
- rawContent = js_yaml_1.default.load(node_fs_1.default.readFileSync(file, "utf-8"));
95
+ if (extension === 'yaml') {
96
+ rawContent = js_yaml_1.default.load(node_fs_1.default.readFileSync(file, 'utf-8'));
95
97
  }
96
98
  else {
97
- rawContent = JSON.parse(node_fs_1.default.readFileSync(file, "utf-8"));
99
+ rawContent = JSON.parse(node_fs_1.default.readFileSync(file, 'utf-8'));
98
100
  }
99
101
  const content = (0, flattenTranslations_1.flattenTranslations)(rawContent);
100
102
  if (isSource({ file, name, path }, srcPath)) {
@@ -105,12 +107,12 @@ const main = async () => {
105
107
  });
106
108
  }
107
109
  else {
108
- const fullPath = path.join("-");
110
+ const fullPath = path.join('-');
109
111
  const reference = fileInfos.find((fileInfo) => {
110
112
  if (!isSource(fileInfo, srcPath)) {
111
113
  return false;
112
114
  }
113
- if (fileInfo.path.join("-") === fullPath) {
115
+ if (fileInfo.path.join('-') === fullPath) {
114
116
  return true;
115
117
  }
116
118
  // Check if the folder path matches - ignoring the last folder
@@ -128,8 +130,8 @@ const main = async () => {
128
130
  //
129
131
  // Referencing: `path/to/locales/en-US/one.json`, `path/to/locales/de-DE/one.json`
130
132
  // Non Referencing: `path/to/locales/en-US/one.json`, `path/to/other/locales/de-DE/one.json`
131
- if (fileInfo.path.slice(0, fileInfo.path.length - 1).join("-") ===
132
- path.slice(0, path.length - 1).join("-")) {
133
+ if (fileInfo.path.slice(0, fileInfo.path.length - 1).join('-') ===
134
+ path.slice(0, path.length - 1).join('-')) {
133
135
  return fileInfo.name === name;
134
136
  }
135
137
  return false;
@@ -144,13 +146,13 @@ const main = async () => {
144
146
  }
145
147
  });
146
148
  if (srcFiles.length === 0) {
147
- console.log(chalk_1.default.red("Source not found. Please provide a valid source locale, i.e. -s en-US"));
149
+ console.log(chalk_1.default.red('Source not found. Please provide a valid source locale, i.e. -s en-US'));
148
150
  (0, node_process_1.exit)(1);
149
151
  }
150
- if ((options.checks.includes("missingKeys") ||
151
- options.checks.includes("invalidKeys")) &&
152
+ if ((options.checks.includes('missingKeys') ||
153
+ options.checks.includes('invalidKeys')) &&
152
154
  targetFiles.length === 0) {
153
- console.log(chalk_1.default.red("Locale file(s) not found. Please provide valid locale file(s), i.e. --locales translations/"));
155
+ console.log(chalk_1.default.red('Locale file(s) not found. Please provide valid locale file(s), i.e. --locales translations/'));
154
156
  (0, node_process_1.exit)(1);
155
157
  }
156
158
  try {
@@ -159,10 +161,11 @@ const main = async () => {
159
161
  if (unusedSrcPath) {
160
162
  const isMultiUnusedFolders = unusedSrcPath.length > 1;
161
163
  const pattern = isMultiUnusedFolders
162
- ? `{${unusedSrcPath.join(",").trim()}}/**/*.{ts,tsx}`
163
- : `${unusedSrcPath.join(",").trim()}/**/*.{ts,tsx}`;
164
+ ? `{${unusedSrcPath.join(',').trim()}}/**/*.{ts,tsx}`
165
+ : `${unusedSrcPath.join(',').trim()}/**/*.{ts,tsx}`;
164
166
  const filesToParse = (0, glob_1.globSync)(pattern, {
165
- ignore: ["node_modules/**"],
167
+ ignore: ['node_modules/**'],
168
+ windowsPathsNoEscape: true,
166
169
  });
167
170
  const unusedKeys = await (0, __1.checkUnusedKeys)(srcFiles, filesToParse, options, componentFunctions);
168
171
  printUnusedKeysResult({ unusedKeys });
@@ -180,6 +183,7 @@ const main = async () => {
180
183
  else {
181
184
  (0, node_process_1.exit)(0);
182
185
  }
186
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
183
187
  }
184
188
  catch (e) {
185
189
  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"));
@@ -187,97 +191,67 @@ const main = async () => {
187
191
  }
188
192
  };
189
193
  const printTranslationResult = ({ missingKeys, invalidKeys, }) => {
190
- const reporter = commander_1.program.getOptionValue("reporter");
191
- const isSummary = reporter === "summary";
194
+ const reporter = commander_1.program.getOptionValue('reporter');
195
+ const isSummary = reporter === 'summary';
192
196
  if (missingKeys && Object.keys(missingKeys).length > 0) {
193
- console.log(chalk_1.default.red("\nFound missing keys!"));
197
+ console.log(chalk_1.default.red('\nFound missing keys!'));
194
198
  if (isSummary) {
195
- console.log(chalk_1.default.red((0, errorReporters_1.summaryReporter)(getSummaryRows(missingKeys))));
199
+ console.log(chalk_1.default.red((0, errorReporters_1.formatSummaryTable)(missingKeys)));
196
200
  }
197
201
  else {
198
- console.log(chalk_1.default.red((0, errorReporters_1.standardReporter)(getStandardRows(missingKeys))));
202
+ const table = (0, errorReporters_1.formatCheckResultTable)(missingKeys);
203
+ console.log(chalk_1.default.red(table));
199
204
  }
200
205
  }
201
206
  else if (missingKeys) {
202
- console.log(chalk_1.default.green("\nNo missing keys found!"));
207
+ console.log(chalk_1.default.green('\nNo missing keys found!'));
203
208
  }
204
209
  if (invalidKeys && Object.keys(invalidKeys).length > 0) {
205
- console.log(chalk_1.default.red("\nFound invalid keys!"));
210
+ console.log(chalk_1.default.red('\nFound invalid keys!'));
206
211
  if (isSummary) {
207
- console.log(chalk_1.default.red((0, errorReporters_1.summaryReporter)(getSummaryRows(invalidKeys))));
212
+ console.log(chalk_1.default.red((0, errorReporters_1.formatSummaryTable)(invalidKeys)));
208
213
  }
209
214
  else {
210
- console.log(chalk_1.default.red((0, errorReporters_1.standardReporter)(getStandardRows(invalidKeys), true)));
215
+ const table = (0, errorReporters_1.formatInvalidTranslationsResultTable)(invalidKeys);
216
+ console.log(chalk_1.default.red(table));
211
217
  }
212
218
  }
213
219
  else if (invalidKeys) {
214
- console.log(chalk_1.default.green("\nNo invalid translations found!"));
220
+ console.log(chalk_1.default.green('\nNo invalid translations found!'));
215
221
  }
216
222
  };
217
223
  const printUnusedKeysResult = ({ unusedKeys, }) => {
218
- const reporter = commander_1.program.getOptionValue("reporter");
219
- const isSummary = reporter === "summary";
224
+ const reporter = commander_1.program.getOptionValue('reporter');
225
+ const isSummary = reporter === 'summary';
220
226
  if (unusedKeys && hasKeys(unusedKeys)) {
221
- console.log(chalk_1.default.red("\nFound unused keys!"));
227
+ console.log(chalk_1.default.red('\nFound unused keys!'));
222
228
  if (isSummary) {
223
- console.log(chalk_1.default.red((0, errorReporters_1.summaryReporter)(getSummaryRows(unusedKeys))));
229
+ console.log(chalk_1.default.red((0, errorReporters_1.formatSummaryTable)(unusedKeys)));
224
230
  }
225
231
  else {
226
- console.log(chalk_1.default.red((0, errorReporters_1.standardReporter)(getStandardRows(unusedKeys))));
232
+ console.log(chalk_1.default.red((0, errorReporters_1.formatCheckResultTable)(unusedKeys)));
227
233
  }
228
234
  }
229
235
  else if (unusedKeys) {
230
- console.log(chalk_1.default.green("\nNo unused keys found!"));
236
+ console.log(chalk_1.default.green('\nNo unused keys found!'));
231
237
  }
232
238
  };
233
239
  const printUndefinedKeysResult = ({ undefinedKeys, }) => {
234
- const reporter = commander_1.program.getOptionValue("reporter");
235
- const isSummary = reporter === "summary";
240
+ const reporter = commander_1.program.getOptionValue('reporter');
241
+ const isSummary = reporter === 'summary';
236
242
  if (undefinedKeys && hasKeys(undefinedKeys)) {
237
- console.log(chalk_1.default.red("\nFound undefined keys!"));
243
+ console.log(chalk_1.default.red('\nFound undefined keys!'));
238
244
  if (isSummary) {
239
- console.log(chalk_1.default.red((0, errorReporters_1.summaryReporter)(getSummaryRows(undefinedKeys))));
245
+ console.log(chalk_1.default.red((0, errorReporters_1.formatSummaryTable)(undefinedKeys)));
240
246
  }
241
247
  else {
242
- console.log(chalk_1.default.red((0, errorReporters_1.standardReporter)(getStandardRows(undefinedKeys))));
248
+ console.log(chalk_1.default.red((0, errorReporters_1.formatCheckResultTable)(undefinedKeys)));
243
249
  }
244
250
  }
245
251
  else if (undefinedKeys) {
246
- console.log(chalk_1.default.green("\nNo undefined keys found!"));
252
+ console.log(chalk_1.default.green('\nNo undefined keys found!'));
247
253
  }
248
254
  };
249
- const truncate = (chars, len = 80) => chars.length > 80 ? `${chars.substring(0, len)}...` : chars;
250
- const getSummaryRows = (checkResult) => {
251
- const formattedRows = [];
252
- for (const [file, keys] of Object.entries(checkResult)) {
253
- formattedRows.push({
254
- file: truncate(file),
255
- total: keys.length,
256
- });
257
- }
258
- return formattedRows;
259
- };
260
- const getStandardRows = (checkResult) => {
261
- const formattedRows = [];
262
- for (const [file, keys] of Object.entries(checkResult)) {
263
- for (const entry of keys) {
264
- if (typeof entry === "object") {
265
- formattedRows.push({
266
- file: truncate(file),
267
- key: truncate(entry.key),
268
- msg: truncate(entry.msg, 120),
269
- });
270
- }
271
- else {
272
- formattedRows.push({
273
- file: truncate(file),
274
- key: truncate(entry),
275
- });
276
- }
277
- }
278
- }
279
- return formattedRows;
280
- };
281
255
  const hasKeys = (checkResult) => {
282
256
  for (const [_, keys] of Object.entries(checkResult)) {
283
257
  if (keys.length > 0) {