@lingual/i18n-check 0.5.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 +20 -6
- package/dist/bin/index.js +27 -22
- package/dist/bin/index.test.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +21 -6
- package/dist/utils/findInvalidi18nTranslations.test.js +5 -0
- package/dist/utils/i18NextParser.js +1 -1
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -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
|
-
### --
|
|
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 `-
|
|
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 -
|
|
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 -
|
|
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 -
|
|
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 `
|
|
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
|
package/dist/bin/index.js
CHANGED
|
@@ -13,33 +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 node_fs_1 = __importDefault(require("node:fs"));
|
|
17
|
+
const node_process_1 = require("node:process");
|
|
16
18
|
const chalk_1 = __importDefault(require("chalk"));
|
|
17
19
|
const commander_1 = require("commander");
|
|
18
|
-
const fs_1 = __importDefault(require("fs"));
|
|
19
20
|
const glob_1 = require("glob");
|
|
20
21
|
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
21
|
-
const process_1 = require("process");
|
|
22
22
|
const __1 = require("..");
|
|
23
23
|
const errorReporters_1 = require("../errorReporters");
|
|
24
24
|
const flattenTranslations_1 = require("../utils/flattenTranslations");
|
|
25
|
+
const version = require("../../package.json").version;
|
|
25
26
|
commander_1.program
|
|
26
|
-
.version(
|
|
27
|
+
.version(version)
|
|
27
28
|
.option("-l, --locales <locales...>", "name of the directory containing the locales to validate")
|
|
28
|
-
.option("-s, --source
|
|
29
|
-
.option("-f, --format
|
|
30
|
-
.option("-c, --check
|
|
31
|
-
.option("-
|
|
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")
|
|
32
34
|
.option("-e, --exclude <exclude...>", "define the file(s) and/or folders(s) that should be excluded from the check")
|
|
33
|
-
.option("-u, --unused
|
|
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")
|
|
34
37
|
.parse();
|
|
35
38
|
const getCheckOptions = () => {
|
|
36
|
-
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
|
+
}
|
|
37
43
|
if (!checkOption) {
|
|
38
44
|
return ["invalidKeys", "missingKeys"];
|
|
39
45
|
}
|
|
40
|
-
const checks = checkOption
|
|
41
|
-
.split(",")
|
|
42
|
-
.filter((check) => ["invalidKeys", "missingKeys"].includes(check.trim()));
|
|
46
|
+
const checks = checkOption.filter((check) => ["invalidKeys", "missingKeys"].includes(check.trim()));
|
|
43
47
|
return checks.length > 0 ? checks : ["invalidKeys", "missingKeys"];
|
|
44
48
|
};
|
|
45
49
|
const isSource = (fileInfo, srcPath) => {
|
|
@@ -52,13 +56,14 @@ const main = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
52
56
|
const format = commander_1.program.getOptionValue("format");
|
|
53
57
|
const exclude = commander_1.program.getOptionValue("exclude");
|
|
54
58
|
const unusedSrcPath = commander_1.program.getOptionValue("unused");
|
|
59
|
+
const componentFunctions = commander_1.program.getOptionValue("parserComponentFunctions");
|
|
55
60
|
if (!srcPath) {
|
|
56
61
|
console.log(chalk_1.default.red("Source not found. Please provide a valid source locale, i.e. -s en-US"));
|
|
57
|
-
(0,
|
|
62
|
+
(0, node_process_1.exit)(1);
|
|
58
63
|
}
|
|
59
64
|
if (!localePath || localePath.length === 0) {
|
|
60
65
|
console.log(chalk_1.default.red("Locale file(s) not found. Please provide valid locale file(s), i.e. -locales translations/"));
|
|
61
|
-
(0,
|
|
66
|
+
(0, node_process_1.exit)(1);
|
|
62
67
|
}
|
|
63
68
|
const excludedPaths = exclude !== null && exclude !== void 0 ? exclude : [];
|
|
64
69
|
const localePathFolders = localePath;
|
|
@@ -96,10 +101,10 @@ const main = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
96
101
|
fileInfos.forEach(({ extension, file, name, path }) => {
|
|
97
102
|
let rawContent;
|
|
98
103
|
if (extension === "yaml") {
|
|
99
|
-
rawContent = js_yaml_1.default.load(
|
|
104
|
+
rawContent = js_yaml_1.default.load(node_fs_1.default.readFileSync(file, "utf-8"));
|
|
100
105
|
}
|
|
101
106
|
else {
|
|
102
|
-
rawContent = JSON.parse(
|
|
107
|
+
rawContent = JSON.parse(node_fs_1.default.readFileSync(file, "utf-8"));
|
|
103
108
|
}
|
|
104
109
|
const content = (0, flattenTranslations_1.flattenTranslations)(rawContent);
|
|
105
110
|
if (isSource({ file, name, path }, srcPath)) {
|
|
@@ -150,32 +155,32 @@ const main = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
150
155
|
});
|
|
151
156
|
if (srcFiles.length === 0) {
|
|
152
157
|
console.log(chalk_1.default.red("Source not found. Please provide a valid source locale, i.e. -s en-US"));
|
|
153
|
-
(0,
|
|
158
|
+
(0, node_process_1.exit)(1);
|
|
154
159
|
}
|
|
155
160
|
if (localeFiles.length === 0) {
|
|
156
161
|
console.log(chalk_1.default.red("Locale file(s) not found. Please provide valid locale file(s), i.e. --locales translations/"));
|
|
157
|
-
(0,
|
|
162
|
+
(0, node_process_1.exit)(1);
|
|
158
163
|
}
|
|
159
164
|
try {
|
|
160
165
|
const result = (0, __1.checkTranslations)(srcFiles, localeFiles, options);
|
|
161
166
|
printTranslationResult(result);
|
|
162
167
|
if (unusedSrcPath) {
|
|
163
|
-
const unusedKeys = yield (0, __1.checkUnusedKeys)(srcFiles, unusedSrcPath, options);
|
|
168
|
+
const unusedKeys = yield (0, __1.checkUnusedKeys)(srcFiles, unusedSrcPath, options, componentFunctions);
|
|
164
169
|
printUnusedKeysResult({ unusedKeys });
|
|
165
170
|
}
|
|
166
171
|
const end = performance.now();
|
|
167
172
|
console.log(chalk_1.default.green(`\nDone in ${Math.round(((end - start) * 100) / 1000) / 100}s.`));
|
|
168
173
|
if ((result.missingKeys && Object.keys(result.missingKeys).length > 0) ||
|
|
169
174
|
(result.invalidKeys && Object.keys(result.invalidKeys).length > 0)) {
|
|
170
|
-
(0,
|
|
175
|
+
(0, node_process_1.exit)(1);
|
|
171
176
|
}
|
|
172
177
|
else {
|
|
173
|
-
(0,
|
|
178
|
+
(0, node_process_1.exit)(0);
|
|
174
179
|
}
|
|
175
180
|
}
|
|
176
181
|
catch (e) {
|
|
177
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"));
|
|
178
|
-
(0,
|
|
183
|
+
(0, node_process_1.exit)(1);
|
|
179
184
|
}
|
|
180
185
|
});
|
|
181
186
|
const printTranslationResult = ({ missingKeys, invalidKeys, }) => {
|
package/dist/bin/index.test.js
CHANGED
|
@@ -265,7 +265,7 @@ No invalid translations found!
|
|
|
265
265
|
});
|
|
266
266
|
});
|
|
267
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", (_error, stdout, _stderr) => {
|
|
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
269
|
const result = stdout.split("Done")[0];
|
|
270
270
|
expect(result).toEqual(`i18n translations checker
|
|
271
271
|
Source: en
|
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
|
-
:
|
|
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
|
|
72
|
+
const unusedKeysFiles = (0, glob_1.globSync)(codebaseSrc, {
|
|
73
73
|
ignore: ["node_modules/**"],
|
|
74
74
|
});
|
|
75
|
-
const extracted = yield (0, cli_lib_1.extract)(
|
|
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
|
|
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.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "i18n translation messages check",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -40,5 +40,8 @@
|
|
|
40
40
|
"repository": {
|
|
41
41
|
"type": "git",
|
|
42
42
|
"url": "https://github.com/lingualdev/i18n-check.git"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=20"
|
|
43
46
|
}
|
|
44
47
|
}
|