@lingual/i18n-check 0.1.0 → 0.1.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/package.json +9 -6
- package/.github/workflows/tests.yaml +0 -31
- package/jest.config.js +0 -6
- package/src/bin/index.ts +0 -244
- package/src/errorReporters.ts +0 -18
- package/src/index.ts +0 -68
- package/src/types.ts +0 -9
- package/src/utils/findInvalidTranslations.test.ts +0 -78
- package/src/utils/findInvalidTranslations.ts +0 -120
- package/src/utils/findInvalidi18nTranslations.test.ts +0 -213
- package/src/utils/findInvalidi18nTranslations.ts +0 -137
- package/src/utils/findMissingKeys.test.ts +0 -60
- package/src/utils/findMissingKeys.ts +0 -27
- package/src/utils/flattenTranslations.test.ts +0 -32
- package/src/utils/flattenTranslations.ts +0 -42
- package/src/utils/i18NextParser.test.ts +0 -169
- package/src/utils/i18NextParser.ts +0 -149
- package/translations/de-de.json +0 -6
- package/translations/en-us.json +0 -6
- package/translations/flattenExamples/de-de.json +0 -6
- package/translations/flattenExamples/en-us.json +0 -18
- package/translations/folderExample/de-DE/index.json +0 -7
- package/translations/folderExample/en-US/index.json +0 -8
- package/translations/i18NextMessageExamples/de-de.json +0 -73
- package/translations/i18NextMessageExamples/en-us.json +0 -90
- package/translations/largeFileExamples/de-de.json +0 -5272
- package/translations/largeFileExamples/en-us.json +0 -5278
- package/translations/largeFileExamples/fr-fr.json +0 -871
- package/translations/messageExamples/de-de.json +0 -24
- package/translations/messageExamples/en-us.json +0 -30
- package/translations/multipleFilesFolderExample/de-DE/one.json +0 -7
- package/translations/multipleFilesFolderExample/de-DE/three.json +0 -8
- package/translations/multipleFilesFolderExample/de-DE/two.json +0 -5
- package/translations/multipleFilesFolderExample/en-US/one.json +0 -8
- package/translations/multipleFilesFolderExample/en-US/three.json +0 -8
- package/translations/multipleFilesFolderExample/en-US/two.json +0 -6
- package/tsconfig.json +0 -113
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lingual/i18n-check",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -8,10 +8,9 @@
|
|
|
8
8
|
"i18n-check": "./dist/bin/index.js"
|
|
9
9
|
},
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/"
|
|
13
|
+
],
|
|
15
14
|
"dependencies": {
|
|
16
15
|
"@formatjs/icu-messageformat-parser": "^2.7.6",
|
|
17
16
|
"chalk": "^4.1.2",
|
|
@@ -24,5 +23,9 @@
|
|
|
24
23
|
"@types/node": "^20.12.12",
|
|
25
24
|
"jest": "^29.7.0",
|
|
26
25
|
"ts-jest": "^29.1.2"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc",
|
|
29
|
+
"test": "jest"
|
|
27
30
|
}
|
|
28
|
-
}
|
|
31
|
+
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
name: Test
|
|
2
|
-
on:
|
|
3
|
-
pull_request:
|
|
4
|
-
branches:
|
|
5
|
-
- main
|
|
6
|
-
push:
|
|
7
|
-
branches:
|
|
8
|
-
- main
|
|
9
|
-
|
|
10
|
-
jobs:
|
|
11
|
-
test:
|
|
12
|
-
runs-on: ubuntu-latest
|
|
13
|
-
|
|
14
|
-
steps:
|
|
15
|
-
- uses: actions/checkout@master
|
|
16
|
-
|
|
17
|
-
- name: yarn install & test
|
|
18
|
-
run: |
|
|
19
|
-
yarn install
|
|
20
|
-
yarn test
|
|
21
|
-
|
|
22
|
-
test-build:
|
|
23
|
-
runs-on: ubuntu-latest
|
|
24
|
-
|
|
25
|
-
steps:
|
|
26
|
-
- uses: actions/checkout@master
|
|
27
|
-
|
|
28
|
-
- name: yarn install & build
|
|
29
|
-
run: |
|
|
30
|
-
yarn install
|
|
31
|
-
yarn build
|
package/jest.config.js
DELETED
package/src/bin/index.ts
DELETED
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
#! /usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { glob } from "glob";
|
|
4
|
-
import chalk from "chalk";
|
|
5
|
-
import fs from "fs";
|
|
6
|
-
import { exit } from "process";
|
|
7
|
-
import { program } from "commander";
|
|
8
|
-
import { CheckResult, TranslationFile } from "../types";
|
|
9
|
-
import { checkTranslations } from "..";
|
|
10
|
-
import { Context, standardReporter, summaryReporter } from "../errorReporters";
|
|
11
|
-
import { flattenTranslations } from "../utils/flattenTranslations";
|
|
12
|
-
|
|
13
|
-
program
|
|
14
|
-
.version("0.1.0")
|
|
15
|
-
.option(
|
|
16
|
-
"-t, --target [directory]",
|
|
17
|
-
"name of the directory containing the JSON files to validate"
|
|
18
|
-
)
|
|
19
|
-
.option("-s, --source [source file(s)]", "path to the reference file(s)")
|
|
20
|
-
.option(
|
|
21
|
-
"-f, --format [format type]",
|
|
22
|
-
"define the specific format, i.e. i18next"
|
|
23
|
-
)
|
|
24
|
-
.option(
|
|
25
|
-
"-c, --check [checks]",
|
|
26
|
-
"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"
|
|
27
|
-
)
|
|
28
|
-
.option(
|
|
29
|
-
"-r, --reporter [error reporting style]",
|
|
30
|
-
"define the reporting style: standard or summary"
|
|
31
|
-
)
|
|
32
|
-
.option(
|
|
33
|
-
"-e, --exclude [file(s), folder(s)]",
|
|
34
|
-
"define the file(s) and/or folders(s) that should be excluded from the check"
|
|
35
|
-
)
|
|
36
|
-
.parse();
|
|
37
|
-
|
|
38
|
-
const getCheckOptions = (): Context[] => {
|
|
39
|
-
const checkOption = program.getOptionValue("check");
|
|
40
|
-
|
|
41
|
-
if (!checkOption) {
|
|
42
|
-
return ["invalidKeys", "missingKeys"];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const checks = checkOption
|
|
46
|
-
.split(",")
|
|
47
|
-
.filter((check: string) =>
|
|
48
|
-
["invalidKeys", "missingKeys"].includes(check.trim())
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
return checks.length > 0 ? checks : ["invalidKeys", "missingKeys"];
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const getSourcePath = (sourcePaths: string[], fileName: string) => {
|
|
55
|
-
return sourcePaths.find((basePathName: string) => {
|
|
56
|
-
return fileName.includes(basePathName);
|
|
57
|
-
});
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const getTargetPath = (
|
|
61
|
-
paths: string[],
|
|
62
|
-
sourcePaths: string[],
|
|
63
|
-
fileName: string
|
|
64
|
-
) => {
|
|
65
|
-
const basePath = paths.find((path: string) => {
|
|
66
|
-
return fileName.includes(path);
|
|
67
|
-
});
|
|
68
|
-
if (!basePath) {
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return sourcePaths.find((path) => path.includes(basePath));
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const toArray = (input: string): string[] => {
|
|
76
|
-
return input
|
|
77
|
-
.trim()
|
|
78
|
-
.split(",")
|
|
79
|
-
.filter((a: string) => a);
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const main = async () => {
|
|
83
|
-
const start = performance.now();
|
|
84
|
-
const srcPath = program.getOptionValue("source");
|
|
85
|
-
const targetPath = program.getOptionValue("target");
|
|
86
|
-
const format = program.getOptionValue("format");
|
|
87
|
-
const exclude = program.getOptionValue("exclude");
|
|
88
|
-
|
|
89
|
-
if (!srcPath) {
|
|
90
|
-
console.log(
|
|
91
|
-
chalk.red(
|
|
92
|
-
"Source file(s) not found. Please provide valid source file(s), i.e. -s translations/en-us.json"
|
|
93
|
-
)
|
|
94
|
-
);
|
|
95
|
-
exit(1);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (!targetPath) {
|
|
99
|
-
console.log(
|
|
100
|
-
chalk.red(
|
|
101
|
-
"Target file(s) not found. Please provide valid target file(s), i.e. -t translations/"
|
|
102
|
-
)
|
|
103
|
-
);
|
|
104
|
-
exit(1);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const excludedPaths = exclude ? toArray(exclude) : [];
|
|
108
|
-
const targetPathFolders: string[] = toArray(targetPath);
|
|
109
|
-
const srcPaths: string[] = toArray(srcPath);
|
|
110
|
-
|
|
111
|
-
const isMultiFolders = targetPathFolders.length > 1;
|
|
112
|
-
|
|
113
|
-
let srcFiles: TranslationFile[] = [];
|
|
114
|
-
let targetFiles: TranslationFile[] = [];
|
|
115
|
-
|
|
116
|
-
const pattern = isMultiFolders
|
|
117
|
-
? `{${targetPath.trim()}}/**/*.json`
|
|
118
|
-
: `${targetPath.trim()}/**/*.json`;
|
|
119
|
-
|
|
120
|
-
const files = await glob(pattern, {
|
|
121
|
-
ignore: ["node_modules/**"].concat(excludedPaths),
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
console.log(chalk.blue("i18n translations checker"));
|
|
125
|
-
console.log(chalk.blackBright(`Source file(s): ${srcPath}`));
|
|
126
|
-
|
|
127
|
-
if (format) {
|
|
128
|
-
console.log(chalk.blackBright(`Selected format is: ${format}`));
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const options = {
|
|
132
|
-
checks: getCheckOptions(),
|
|
133
|
-
format: format ?? undefined,
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
files.forEach((file) => {
|
|
137
|
-
const content = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
138
|
-
const sourcePath = getSourcePath(srcPaths, file);
|
|
139
|
-
if (sourcePath) {
|
|
140
|
-
srcFiles.push({
|
|
141
|
-
reference: null,
|
|
142
|
-
name: file,
|
|
143
|
-
content: flattenTranslations(content),
|
|
144
|
-
});
|
|
145
|
-
} else {
|
|
146
|
-
const targetPath = getTargetPath(targetPathFolders, srcPaths, file);
|
|
147
|
-
const reference = targetPath?.includes(".json")
|
|
148
|
-
? targetPath
|
|
149
|
-
: `${targetPath}${file.split("/").pop()}`;
|
|
150
|
-
|
|
151
|
-
targetFiles.push({
|
|
152
|
-
reference,
|
|
153
|
-
name: file,
|
|
154
|
-
content: flattenTranslations(content),
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
if (srcFiles.length === 0) {
|
|
160
|
-
console.log(
|
|
161
|
-
chalk.red(
|
|
162
|
-
"Source file(s) not found. Please provide valid source file(s), i.e. -s translations/en-us.json"
|
|
163
|
-
)
|
|
164
|
-
);
|
|
165
|
-
exit(1);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (targetFiles.length === 0) {
|
|
169
|
-
console.log(
|
|
170
|
-
chalk.red(
|
|
171
|
-
"Target file(s) not found. Please provide valid target file(s), i.e. -t translations/"
|
|
172
|
-
)
|
|
173
|
-
);
|
|
174
|
-
exit(1);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
try {
|
|
178
|
-
const result = checkTranslations(srcFiles, targetFiles, options);
|
|
179
|
-
|
|
180
|
-
print(result);
|
|
181
|
-
|
|
182
|
-
const end = performance.now();
|
|
183
|
-
|
|
184
|
-
console.log(
|
|
185
|
-
chalk.green(
|
|
186
|
-
`\nDone in ${Math.round(((end - start) * 100) / 1000) / 100}s.`
|
|
187
|
-
)
|
|
188
|
-
);
|
|
189
|
-
if (
|
|
190
|
-
(result.missingKeys && Object.keys(result.missingKeys).length > 0) ||
|
|
191
|
-
(result.invalidKeys && Object.keys(result.invalidKeys).length > 0)
|
|
192
|
-
) {
|
|
193
|
-
exit(1);
|
|
194
|
-
} else {
|
|
195
|
-
exit(0);
|
|
196
|
-
}
|
|
197
|
-
} catch (e) {
|
|
198
|
-
console.log(
|
|
199
|
-
chalk.red(
|
|
200
|
-
"\nError: Can't validate translations. Check if the format is supported or specify the translation format i.e. -f i18next"
|
|
201
|
-
)
|
|
202
|
-
);
|
|
203
|
-
exit(1);
|
|
204
|
-
}
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
const print = ({
|
|
208
|
-
missingKeys,
|
|
209
|
-
invalidKeys,
|
|
210
|
-
}: {
|
|
211
|
-
missingKeys: CheckResult | undefined;
|
|
212
|
-
invalidKeys: CheckResult | undefined;
|
|
213
|
-
}) => {
|
|
214
|
-
const reporter = program.getOptionValue("reporter");
|
|
215
|
-
|
|
216
|
-
const errorReporter =
|
|
217
|
-
reporter === "summary" ? summaryReporter : standardReporter;
|
|
218
|
-
|
|
219
|
-
if (missingKeys && Object.keys(missingKeys).length > 0) {
|
|
220
|
-
console.log(chalk.bgRed(chalk.white("\nFound missing keys!")));
|
|
221
|
-
for (const [lang, missingMessageKeys] of Object.entries<string[]>(
|
|
222
|
-
missingKeys
|
|
223
|
-
)) {
|
|
224
|
-
console.log(chalk.red(`\nIn ${lang}:\n`));
|
|
225
|
-
console.log(chalk.red(errorReporter(missingMessageKeys, "missingKeys")));
|
|
226
|
-
}
|
|
227
|
-
} else if (missingKeys) {
|
|
228
|
-
console.log(chalk.green("\nNo missing keys found!"));
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (invalidKeys && Object.keys(invalidKeys).length > 0) {
|
|
232
|
-
console.log(chalk.bgRed(chalk.white("\nFound invalid keys!")));
|
|
233
|
-
for (const [lang, invalidMessageKeys] of Object.entries<string[]>(
|
|
234
|
-
invalidKeys
|
|
235
|
-
)) {
|
|
236
|
-
console.log(chalk.red(`\nIn ${lang}:\n`));
|
|
237
|
-
console.log(chalk.red(errorReporter(invalidMessageKeys, "invalidKeys")));
|
|
238
|
-
}
|
|
239
|
-
} else if (invalidKeys) {
|
|
240
|
-
console.log(chalk.green("\nNo invalid translations found!"));
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
main();
|
package/src/errorReporters.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export type Context = "missingKeys" | "invalidKeys";
|
|
2
|
-
export type ErrorReporter = (result: string[], context: Context) => void;
|
|
3
|
-
|
|
4
|
-
export const contextMapping: Record<Context, string> = {
|
|
5
|
-
invalidKeys: "invalid",
|
|
6
|
-
missingKeys: "missing",
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export const standardReporter: ErrorReporter = (result) => {
|
|
10
|
-
return result.map((key) => `◯ ${key}`).join("\n");
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export const summaryReporter: ErrorReporter = (result, context) => {
|
|
14
|
-
const count = result.length;
|
|
15
|
-
return `Found ${count} ${contextMapping[context]} ${
|
|
16
|
-
count === 1 ? "key" : "keys"
|
|
17
|
-
}.`;
|
|
18
|
-
};
|
package/src/index.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { findMissingKeys } from "./utils/findMissingKeys";
|
|
2
|
-
import { CheckResult, Translation, TranslationFile } from "./types";
|
|
3
|
-
import { findInvalidTranslations } from "./utils/findInvalidTranslations";
|
|
4
|
-
import { findInvalid18nTranslations } from "./utils/findInvalidi18nTranslations";
|
|
5
|
-
import { Context } from "./errorReporters";
|
|
6
|
-
|
|
7
|
-
export type Options = {
|
|
8
|
-
format?: "icu" | "i18next";
|
|
9
|
-
checks?: Context[];
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export const checkInvalidTranslations = (
|
|
13
|
-
source: Translation,
|
|
14
|
-
targets: Record<string, Translation>,
|
|
15
|
-
options: Options = { format: "icu" }
|
|
16
|
-
): CheckResult => {
|
|
17
|
-
return options.format === "i18next"
|
|
18
|
-
? findInvalid18nTranslations(source, targets)
|
|
19
|
-
: findInvalidTranslations(source, targets);
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export const checkMissingTranslations = (
|
|
23
|
-
source: Translation,
|
|
24
|
-
targets: Record<string, Translation>
|
|
25
|
-
): CheckResult => {
|
|
26
|
-
return findMissingKeys(source, targets);
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export const checkTranslations = (
|
|
30
|
-
source: TranslationFile[],
|
|
31
|
-
targets: TranslationFile[],
|
|
32
|
-
options: Options = { format: "icu", checks: ["invalidKeys", "missingKeys"] }
|
|
33
|
-
): {
|
|
34
|
-
missingKeys: CheckResult | undefined;
|
|
35
|
-
invalidKeys: CheckResult | undefined;
|
|
36
|
-
} => {
|
|
37
|
-
const { checks = ["invalidKeys", "missingKeys"] } = options;
|
|
38
|
-
|
|
39
|
-
let missingKeys = {};
|
|
40
|
-
let invalidKeys = {};
|
|
41
|
-
|
|
42
|
-
const hasMissingKeys = checks.includes("missingKeys");
|
|
43
|
-
const hasInvalidKeys = checks.includes("invalidKeys");
|
|
44
|
-
|
|
45
|
-
source.forEach(({ name, content }) => {
|
|
46
|
-
const files = targets
|
|
47
|
-
.filter(({ reference }) => reference === name)
|
|
48
|
-
.reduce((obj, { name: key, content }) => {
|
|
49
|
-
return Object.assign(obj, { [key]: content });
|
|
50
|
-
}, {});
|
|
51
|
-
|
|
52
|
-
if (hasMissingKeys) {
|
|
53
|
-
Object.assign(missingKeys, checkMissingTranslations(content, files));
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (hasInvalidKeys) {
|
|
57
|
-
Object.assign(
|
|
58
|
-
invalidKeys,
|
|
59
|
-
checkInvalidTranslations(content, files, options)
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
missingKeys: hasMissingKeys ? missingKeys : undefined,
|
|
66
|
-
invalidKeys: hasInvalidKeys ? invalidKeys : undefined,
|
|
67
|
-
};
|
|
68
|
-
};
|
package/src/types.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
compareTranslationFiles,
|
|
3
|
-
findInvalidTranslations,
|
|
4
|
-
} from "./findInvalidTranslations";
|
|
5
|
-
import { flattenTranslations } from "./flattenTranslations";
|
|
6
|
-
|
|
7
|
-
const sourceFile = require("../../translations/messageExamples/en-us.json");
|
|
8
|
-
const secondaryFile = require("../../translations/messageExamples/de-de.json");
|
|
9
|
-
|
|
10
|
-
describe("findInvalidTranslations:compareTranslationFiles", () => {
|
|
11
|
-
it("should return empty array if files are identical", () => {
|
|
12
|
-
expect(
|
|
13
|
-
compareTranslationFiles(
|
|
14
|
-
flattenTranslations(sourceFile),
|
|
15
|
-
flattenTranslations(sourceFile)
|
|
16
|
-
)
|
|
17
|
-
).toEqual([]);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it("should return the invalid keys in the target file", () => {
|
|
21
|
-
expect(
|
|
22
|
-
compareTranslationFiles(
|
|
23
|
-
flattenTranslations({
|
|
24
|
-
...sourceFile,
|
|
25
|
-
"ten.eleven.twelve": "ten eleven twelve",
|
|
26
|
-
}),
|
|
27
|
-
flattenTranslations(secondaryFile)
|
|
28
|
-
)
|
|
29
|
-
).toEqual(["multipleVariables"]);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("should return empty array if placeholders are identical but in different positions", () => {
|
|
33
|
-
expect(
|
|
34
|
-
compareTranslationFiles(
|
|
35
|
-
{
|
|
36
|
-
basic: "added {this} and {that} should work.",
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
basic: "It is {this} with different position {that}",
|
|
40
|
-
}
|
|
41
|
-
)
|
|
42
|
-
).toEqual([]);
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
describe("findInvalidTranslations", () => {
|
|
47
|
-
it("should return an empty object if all files have no invalid keys", () => {
|
|
48
|
-
expect(findInvalidTranslations(sourceFile, { de: sourceFile })).toEqual({});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("should return an object containing the keys for the missing language", () => {
|
|
52
|
-
expect(
|
|
53
|
-
findInvalidTranslations(
|
|
54
|
-
{ ...sourceFile, "ten.eleven.twelve": "ten eleven twelve" },
|
|
55
|
-
{ de: secondaryFile }
|
|
56
|
-
)
|
|
57
|
-
).toEqual({ de: ["multipleVariables"] });
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it("should return an object containing the keys for every language with missing key", () => {
|
|
61
|
-
expect(
|
|
62
|
-
findInvalidTranslations(
|
|
63
|
-
{ ...sourceFile, "ten.eleven.twelve": "ten eleven twelve" },
|
|
64
|
-
{
|
|
65
|
-
de: secondaryFile,
|
|
66
|
-
fr: {
|
|
67
|
-
"four.five.six": "four five six",
|
|
68
|
-
"seven.eight.nine": "seven eight nine",
|
|
69
|
-
"message.text-format": "yo,<p><b>John</b></p>!",
|
|
70
|
-
},
|
|
71
|
-
}
|
|
72
|
-
)
|
|
73
|
-
).toEqual({
|
|
74
|
-
de: ["multipleVariables"],
|
|
75
|
-
fr: ["message.text-format"],
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
});
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
MessageFormatElement,
|
|
3
|
-
isLiteralElement,
|
|
4
|
-
isPluralElement,
|
|
5
|
-
isPoundElement,
|
|
6
|
-
isSelectElement,
|
|
7
|
-
isTagElement,
|
|
8
|
-
parse,
|
|
9
|
-
} from "@formatjs/icu-messageformat-parser";
|
|
10
|
-
import { Translation } from "../types";
|
|
11
|
-
|
|
12
|
-
export const findInvalidTranslations = (
|
|
13
|
-
source: Translation,
|
|
14
|
-
files: Record<string, Translation>
|
|
15
|
-
) => {
|
|
16
|
-
let differences = {};
|
|
17
|
-
if (Object.keys(files).length === 0) {
|
|
18
|
-
return differences;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
for (const [lang, file] of Object.entries(files)) {
|
|
22
|
-
const result = compareTranslationFiles(source, file);
|
|
23
|
-
|
|
24
|
-
if (result.length > 0) {
|
|
25
|
-
differences = Object.assign(differences, { [lang]: result });
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return differences;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const sortParsedKeys = (a: MessageFormatElement, b: MessageFormatElement) => {
|
|
33
|
-
if (a.type === b.type) {
|
|
34
|
-
return !isPoundElement(a) && !isPoundElement(b)
|
|
35
|
-
? a.value < b.value
|
|
36
|
-
? -1
|
|
37
|
-
: 1
|
|
38
|
-
: -1;
|
|
39
|
-
}
|
|
40
|
-
return a.type - b.type;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export const compareTranslationFiles = (a: Translation, b: Translation) => {
|
|
44
|
-
let diffs = [];
|
|
45
|
-
for (const key in a) {
|
|
46
|
-
if (
|
|
47
|
-
b[key] !== undefined &&
|
|
48
|
-
hasDiff(parse(String(a[key])), parse(String(b[key])))
|
|
49
|
-
) {
|
|
50
|
-
diffs.push(key);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return diffs;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
export const hasDiff = (
|
|
58
|
-
a: MessageFormatElement[],
|
|
59
|
-
b: MessageFormatElement[]
|
|
60
|
-
) => {
|
|
61
|
-
const compA = a
|
|
62
|
-
.filter((element) => !isLiteralElement(element))
|
|
63
|
-
.sort(sortParsedKeys);
|
|
64
|
-
|
|
65
|
-
const compB = b
|
|
66
|
-
.filter((element) => !isLiteralElement(element))
|
|
67
|
-
.sort(sortParsedKeys);
|
|
68
|
-
if (compA.length !== compB.length) {
|
|
69
|
-
return true;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const hasErrors = compA.some((formatElementA, index) => {
|
|
73
|
-
const formatElementB = compB[index];
|
|
74
|
-
|
|
75
|
-
if (
|
|
76
|
-
formatElementA.type !== formatElementB.type ||
|
|
77
|
-
formatElementA.location !== formatElementB.location
|
|
78
|
-
) {
|
|
79
|
-
return true;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (
|
|
83
|
-
(isLiteralElement(formatElementA) && isLiteralElement(formatElementB)) ||
|
|
84
|
-
(isPoundElement(formatElementA) && isPoundElement(formatElementB))
|
|
85
|
-
) {
|
|
86
|
-
return false;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// @ts-ignore
|
|
90
|
-
if (formatElementA.value !== formatElementB.value) {
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (isTagElement(formatElementA) && isTagElement(formatElementB)) {
|
|
95
|
-
return hasDiff(formatElementA.children, formatElementB.children);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (
|
|
99
|
-
(isSelectElement(formatElementA) && isSelectElement(formatElementB)) ||
|
|
100
|
-
(isPluralElement(formatElementA) && isPluralElement(formatElementB))
|
|
101
|
-
) {
|
|
102
|
-
const optionsA = Object.keys(formatElementA.options).sort();
|
|
103
|
-
const optionsB = Object.keys(formatElementA.options).sort();
|
|
104
|
-
|
|
105
|
-
if (optionsA.join("-") !== optionsB.join("-")) {
|
|
106
|
-
return true;
|
|
107
|
-
}
|
|
108
|
-
return optionsA.some((key) => {
|
|
109
|
-
return hasDiff(
|
|
110
|
-
formatElementA.options[key].value,
|
|
111
|
-
formatElementB.options[key].value
|
|
112
|
-
);
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return false;
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
return hasErrors;
|
|
120
|
-
};
|