@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 +32 -18
- package/dist/bin/index.js +61 -57
- package/dist/bin/index.test.js +321 -289
- package/dist/errorReporters.d.ts +1 -1
- package/dist/errorReporters.js +21 -21
- package/dist/errorReporters.test.js +39 -39
- package/dist/index.d.ts +4 -3
- package/dist/index.js +82 -47
- package/dist/utils/findInvalidTranslations.d.ts +2 -2
- package/dist/utils/findInvalidTranslations.js +20 -19
- package/dist/utils/findInvalidTranslations.test.js +30 -30
- package/dist/utils/findInvalidi18nTranslations.d.ts +2 -2
- package/dist/utils/findInvalidi18nTranslations.js +35 -35
- package/dist/utils/findInvalidi18nTranslations.test.js +72 -72
- package/dist/utils/findMissingKeys.d.ts +1 -1
- package/dist/utils/findMissingKeys.js +2 -2
- package/dist/utils/findMissingKeys.test.js +20 -20
- package/dist/utils/flattenTranslations.d.ts +1 -1
- package/dist/utils/flattenTranslations.js +3 -3
- package/dist/utils/flattenTranslations.test.js +13 -13
- package/dist/utils/i18NextParser.d.ts +6 -6
- package/dist/utils/i18NextParser.js +29 -29
- package/dist/utils/i18NextParser.test.js +104 -104
- package/dist/utils/nextIntlSrcParser.js +11 -11
- package/dist/utils/nextIntlSrcParser.test.js +156 -156
- package/package.json +14 -4
|
@@ -62,8 +62,9 @@ const hasDiff = (a, b) => {
|
|
|
62
62
|
((0, icu_messageformat_parser_1.isPoundElement)(formatElementA) && (0, icu_messageformat_parser_1.isPoundElement)(formatElementB))) {
|
|
63
63
|
return false;
|
|
64
64
|
}
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
if (!(0, icu_messageformat_parser_1.isPoundElement)(formatElementA) &&
|
|
66
|
+
!(0, icu_messageformat_parser_1.isPoundElement)(formatElementB) &&
|
|
67
|
+
formatElementA.value !== formatElementB.value) {
|
|
67
68
|
return true;
|
|
68
69
|
}
|
|
69
70
|
if ((0, icu_messageformat_parser_1.isTagElement)(formatElementA) && (0, icu_messageformat_parser_1.isTagElement)(formatElementB)) {
|
|
@@ -72,7 +73,7 @@ const hasDiff = (a, b) => {
|
|
|
72
73
|
if ((0, icu_messageformat_parser_1.isSelectElement)(formatElementA) && (0, icu_messageformat_parser_1.isSelectElement)(formatElementB)) {
|
|
73
74
|
const optionsA = Object.keys(formatElementA.options).sort();
|
|
74
75
|
const optionsB = Object.keys(formatElementB.options).sort();
|
|
75
|
-
if (optionsA.join(
|
|
76
|
+
if (optionsA.join('-') !== optionsB.join('-')) {
|
|
76
77
|
return true;
|
|
77
78
|
}
|
|
78
79
|
return optionsA.some((key) => {
|
|
@@ -133,7 +134,7 @@ const getErrorMessage = (a, b) => {
|
|
|
133
134
|
}
|
|
134
135
|
if ((0, icu_messageformat_parser_1.isSelectElement)(formatElementA) && (0, icu_messageformat_parser_1.isSelectElement)(formatElementB)) {
|
|
135
136
|
const optionsA = Object.keys(formatElementA.options).sort();
|
|
136
|
-
|
|
137
|
+
const elementErrors = [];
|
|
137
138
|
optionsA.forEach((key) => {
|
|
138
139
|
if (formatElementB.options[key]) {
|
|
139
140
|
elementErrors.push(getErrorMessage(formatElementA.options[key].value, formatElementB.options[key].value));
|
|
@@ -141,12 +142,12 @@ const getErrorMessage = (a, b) => {
|
|
|
141
142
|
});
|
|
142
143
|
acc.push(`Error in select: ${elementErrors
|
|
143
144
|
.flatMap((elementError) => elementError)
|
|
144
|
-
.join(
|
|
145
|
+
.join(', ')}`);
|
|
145
146
|
return acc;
|
|
146
147
|
}
|
|
147
148
|
if ((0, icu_messageformat_parser_1.isPluralElement)(formatElementA) && (0, icu_messageformat_parser_1.isPluralElement)(formatElementB)) {
|
|
148
149
|
const optionsA = Object.keys(formatElementA.options).sort();
|
|
149
|
-
|
|
150
|
+
const elementErrors = [];
|
|
150
151
|
optionsA.forEach((key) => {
|
|
151
152
|
if (formatElementB.options[key]) {
|
|
152
153
|
elementErrors.push(getErrorMessage(formatElementA.options[key].value, formatElementB.options[key].value));
|
|
@@ -154,7 +155,7 @@ const getErrorMessage = (a, b) => {
|
|
|
154
155
|
});
|
|
155
156
|
acc.push(`Error in plural: ${elementErrors
|
|
156
157
|
.flatMap((elementError) => elementError)
|
|
157
|
-
.join(
|
|
158
|
+
.join(', ')}`);
|
|
158
159
|
return acc;
|
|
159
160
|
}
|
|
160
161
|
return acc;
|
|
@@ -166,19 +167,19 @@ const getErrorMessage = (a, b) => {
|
|
|
166
167
|
acc.push(`Unexpected ${typeLookup[formatElementB.type]} element`);
|
|
167
168
|
return acc;
|
|
168
169
|
}, [])
|
|
169
|
-
.join(
|
|
170
|
-
return [...errors, unexpectedElements].join(
|
|
170
|
+
.join(', ');
|
|
171
|
+
return [...errors, unexpectedElements].join(', ');
|
|
171
172
|
}
|
|
172
|
-
return errors.join(
|
|
173
|
+
return errors.join(', ');
|
|
173
174
|
};
|
|
174
175
|
const typeLookup = {
|
|
175
|
-
0:
|
|
176
|
-
1:
|
|
177
|
-
2:
|
|
178
|
-
3:
|
|
179
|
-
4:
|
|
180
|
-
5:
|
|
181
|
-
6:
|
|
182
|
-
7:
|
|
183
|
-
8:
|
|
176
|
+
0: 'literal',
|
|
177
|
+
1: 'argument',
|
|
178
|
+
2: 'number',
|
|
179
|
+
3: 'date',
|
|
180
|
+
4: 'time',
|
|
181
|
+
5: 'select',
|
|
182
|
+
6: 'plural',
|
|
183
|
+
7: 'pound',
|
|
184
|
+
8: 'tag',
|
|
184
185
|
};
|
|
@@ -2,81 +2,81 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const findInvalidTranslations_1 = require("./findInvalidTranslations");
|
|
4
4
|
const flattenTranslations_1 = require("./flattenTranslations");
|
|
5
|
-
const sourceFile = require(
|
|
6
|
-
const secondaryFile = require(
|
|
7
|
-
describe(
|
|
8
|
-
it(
|
|
5
|
+
const sourceFile = require('../../translations/messageExamples/en-us.json');
|
|
6
|
+
const secondaryFile = require('../../translations/messageExamples/de-de.json');
|
|
7
|
+
describe('findInvalidTranslations:compareTranslationFiles', () => {
|
|
8
|
+
it('should return empty array if files are identical', () => {
|
|
9
9
|
expect((0, findInvalidTranslations_1.compareTranslationFiles)((0, flattenTranslations_1.flattenTranslations)(sourceFile), (0, flattenTranslations_1.flattenTranslations)(sourceFile))).toEqual([]);
|
|
10
10
|
});
|
|
11
|
-
it(
|
|
11
|
+
it('should return the invalid keys in the target file', () => {
|
|
12
12
|
expect((0, findInvalidTranslations_1.compareTranslationFiles)((0, flattenTranslations_1.flattenTranslations)({
|
|
13
13
|
...sourceFile,
|
|
14
|
-
|
|
15
|
-
}), (0, flattenTranslations_1.flattenTranslations)(secondaryFile))).toEqual([{ key:
|
|
14
|
+
'ten.eleven.twelve': 'ten eleven twelve',
|
|
15
|
+
}), (0, flattenTranslations_1.flattenTranslations)(secondaryFile))).toEqual([{ key: 'multipleVariables', msg: 'Unexpected date element' }]);
|
|
16
16
|
});
|
|
17
|
-
it(
|
|
17
|
+
it('should return empty array if placeholders are identical but in different positions', () => {
|
|
18
18
|
expect((0, findInvalidTranslations_1.compareTranslationFiles)({
|
|
19
|
-
basic:
|
|
19
|
+
basic: 'added {this} and {that} should work.',
|
|
20
20
|
}, {
|
|
21
|
-
basic:
|
|
21
|
+
basic: 'It is {this} with different position {that}',
|
|
22
22
|
})).toEqual([]);
|
|
23
23
|
});
|
|
24
24
|
});
|
|
25
|
-
describe(
|
|
26
|
-
it(
|
|
25
|
+
describe('findInvalidTranslations', () => {
|
|
26
|
+
it('should return an empty object if all files have no invalid keys', () => {
|
|
27
27
|
expect((0, findInvalidTranslations_1.findInvalidTranslations)(sourceFile, { de: sourceFile })).toEqual({});
|
|
28
28
|
});
|
|
29
|
-
it(
|
|
30
|
-
expect((0, findInvalidTranslations_1.findInvalidTranslations)({ ...sourceFile,
|
|
31
|
-
de: [{ key:
|
|
29
|
+
it('should return an object containing the keys for the missing language', () => {
|
|
30
|
+
expect((0, findInvalidTranslations_1.findInvalidTranslations)({ ...sourceFile, 'ten.eleven.twelve': 'ten eleven twelve' }, { de: secondaryFile })).toEqual({
|
|
31
|
+
de: [{ key: 'multipleVariables', msg: 'Unexpected date element' }],
|
|
32
32
|
});
|
|
33
33
|
});
|
|
34
|
-
it(
|
|
35
|
-
expect((0, findInvalidTranslations_1.findInvalidTranslations)({ ...sourceFile,
|
|
34
|
+
it('should return an object containing the keys for every language with missing key', () => {
|
|
35
|
+
expect((0, findInvalidTranslations_1.findInvalidTranslations)({ ...sourceFile, 'ten.eleven.twelve': 'ten eleven twelve' }, {
|
|
36
36
|
de: secondaryFile,
|
|
37
37
|
fr: {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
'four.five.six': 'four five six',
|
|
39
|
+
'seven.eight.nine': 'seven eight nine',
|
|
40
|
+
'message.text-format': 'yo,<p><b>John</b></p>!',
|
|
41
41
|
},
|
|
42
42
|
})).toEqual({
|
|
43
|
-
de: [{ key:
|
|
43
|
+
de: [{ key: 'multipleVariables', msg: 'Unexpected date element' }],
|
|
44
44
|
fr: [
|
|
45
45
|
{
|
|
46
|
-
key:
|
|
46
|
+
key: 'message.text-format',
|
|
47
47
|
msg: 'Expected tag to contain "b" but received "p"',
|
|
48
48
|
},
|
|
49
49
|
],
|
|
50
50
|
});
|
|
51
51
|
});
|
|
52
|
-
it(
|
|
52
|
+
it('should allow for different types of keys per locale', () => {
|
|
53
53
|
expect((0, findInvalidTranslations_1.findInvalidTranslations)(sourceFile, {
|
|
54
54
|
de: {
|
|
55
55
|
...secondaryFile,
|
|
56
|
-
|
|
56
|
+
'message.plural': '{count, plural, other {# of {total} items}}',
|
|
57
57
|
},
|
|
58
58
|
})).toEqual({
|
|
59
59
|
de: [
|
|
60
60
|
{
|
|
61
|
-
key:
|
|
62
|
-
msg:
|
|
61
|
+
key: 'multipleVariables',
|
|
62
|
+
msg: 'Unexpected date element',
|
|
63
63
|
},
|
|
64
64
|
],
|
|
65
65
|
});
|
|
66
66
|
});
|
|
67
|
-
it(
|
|
67
|
+
it('should fail if a variable is changed in one of the translations', () => {
|
|
68
68
|
expect((0, findInvalidTranslations_1.findInvalidTranslations)(sourceFile, {
|
|
69
69
|
de: {
|
|
70
70
|
...secondaryFile,
|
|
71
|
-
|
|
71
|
+
'message.plural': '{count, plural, other {# of {cargado} items}}',
|
|
72
72
|
},
|
|
73
73
|
})).toEqual({
|
|
74
74
|
de: [
|
|
75
75
|
{
|
|
76
|
-
key:
|
|
76
|
+
key: 'message.plural',
|
|
77
77
|
msg: 'Error in plural: Expected argument to contain "total" but received "cargado"',
|
|
78
78
|
},
|
|
79
|
-
{ key:
|
|
79
|
+
{ key: 'multipleVariables', msg: 'Unexpected date element' },
|
|
80
80
|
],
|
|
81
81
|
});
|
|
82
82
|
});
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
*
|
|
6
6
|
*/
|
|
7
|
-
import { MessageFormatElement } from
|
|
8
|
-
import { InvalidTranslationEntry, InvalidTranslationsResult, Translation } from
|
|
7
|
+
import { MessageFormatElement } from './i18NextParser';
|
|
8
|
+
import { InvalidTranslationEntry, InvalidTranslationsResult, Translation } from '../types';
|
|
9
9
|
export declare const findInvalid18nTranslations: (source: Translation, targets: Record<string, Translation>) => InvalidTranslationsResult;
|
|
10
10
|
export declare const compareTranslationFiles: (a: Translation, b: Translation) => InvalidTranslationEntry[];
|
|
11
11
|
export declare const hasDiff: (a: MessageFormatElement[], b: MessageFormatElement[]) => boolean;
|
|
@@ -9,7 +9,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
9
9
|
exports.hasDiff = exports.compareTranslationFiles = exports.findInvalid18nTranslations = void 0;
|
|
10
10
|
const i18NextParser_1 = require("./i18NextParser");
|
|
11
11
|
const findInvalid18nTranslations = (source, targets) => {
|
|
12
|
-
|
|
12
|
+
const differences = {};
|
|
13
13
|
if (Object.keys(targets).length === 0) {
|
|
14
14
|
return differences;
|
|
15
15
|
}
|
|
@@ -23,7 +23,7 @@ const findInvalid18nTranslations = (source, targets) => {
|
|
|
23
23
|
};
|
|
24
24
|
exports.findInvalid18nTranslations = findInvalid18nTranslations;
|
|
25
25
|
const compareTranslationFiles = (a, b) => {
|
|
26
|
-
|
|
26
|
+
const diffs = [];
|
|
27
27
|
for (const key in a) {
|
|
28
28
|
if (b[key] === undefined) {
|
|
29
29
|
continue;
|
|
@@ -47,20 +47,20 @@ const lookUp = {
|
|
|
47
47
|
tag: 5,
|
|
48
48
|
};
|
|
49
49
|
const sortParsedKeys = (a, b) => {
|
|
50
|
-
if (a.type === b.type && a.type !==
|
|
50
|
+
if (a.type === b.type && a.type !== 'tag' && b.type !== 'tag') {
|
|
51
51
|
return a.content < b.content ? -1 : 1;
|
|
52
52
|
}
|
|
53
|
-
if (a.type ===
|
|
53
|
+
if (a.type === 'tag' && b.type === 'tag') {
|
|
54
54
|
return a.raw < b.raw ? -1 : 1;
|
|
55
55
|
}
|
|
56
56
|
return lookUp[a.type] - lookUp[b.type];
|
|
57
57
|
};
|
|
58
58
|
const hasDiff = (a, b) => {
|
|
59
59
|
const compA = a
|
|
60
|
-
.filter((element) => element.type !==
|
|
60
|
+
.filter((element) => element.type !== 'text')
|
|
61
61
|
.sort(sortParsedKeys);
|
|
62
62
|
const compB = b
|
|
63
|
-
.filter((element) => element.type !==
|
|
63
|
+
.filter((element) => element.type !== 'text')
|
|
64
64
|
.sort(sortParsedKeys);
|
|
65
65
|
if (compA.length !== compB.length) {
|
|
66
66
|
return true;
|
|
@@ -70,28 +70,28 @@ const hasDiff = (a, b) => {
|
|
|
70
70
|
if (formatElementA.type !== formatElementB.type) {
|
|
71
71
|
return true;
|
|
72
72
|
}
|
|
73
|
-
if (formatElementA.type ===
|
|
73
|
+
if (formatElementA.type === 'tag' && formatElementB.type === 'tag') {
|
|
74
74
|
return (formatElementA.raw !== formatElementB.raw ||
|
|
75
75
|
formatElementA.voidElement !== formatElementB.voidElement);
|
|
76
76
|
}
|
|
77
|
-
if ((formatElementA.type ===
|
|
78
|
-
formatElementB.type ===
|
|
79
|
-
(formatElementA.type ===
|
|
80
|
-
formatElementB.type ===
|
|
81
|
-
(formatElementA.type ===
|
|
82
|
-
formatElementB.type ===
|
|
83
|
-
(formatElementA.type ===
|
|
77
|
+
if ((formatElementA.type === 'interpolation' &&
|
|
78
|
+
formatElementB.type === 'interpolation') ||
|
|
79
|
+
(formatElementA.type === 'interpolation_unescaped' &&
|
|
80
|
+
formatElementB.type === 'interpolation_unescaped') ||
|
|
81
|
+
(formatElementA.type === 'nesting' &&
|
|
82
|
+
formatElementB.type === 'nesting') ||
|
|
83
|
+
(formatElementA.type === 'plural' && formatElementB.type === 'plural')) {
|
|
84
84
|
const optionsA = formatElementA.variable
|
|
85
|
-
.split(
|
|
85
|
+
.split(',')
|
|
86
86
|
.map((value) => value.trim())
|
|
87
87
|
.sort()
|
|
88
|
-
.join(
|
|
88
|
+
.join('-')
|
|
89
89
|
.trim();
|
|
90
90
|
const optionsB = formatElementB.variable
|
|
91
|
-
.split(
|
|
91
|
+
.split(',')
|
|
92
92
|
.map((value) => value.trim())
|
|
93
93
|
.sort()
|
|
94
|
-
.join(
|
|
94
|
+
.join('-')
|
|
95
95
|
.trim();
|
|
96
96
|
if (optionsA !== optionsB) {
|
|
97
97
|
return true;
|
|
@@ -110,10 +110,10 @@ const hasDiff = (a, b) => {
|
|
|
110
110
|
exports.hasDiff = hasDiff;
|
|
111
111
|
const getErrorMessage = (a, b) => {
|
|
112
112
|
const compA = a
|
|
113
|
-
.filter((element) => element.type !==
|
|
113
|
+
.filter((element) => element.type !== 'text')
|
|
114
114
|
.sort(sortParsedKeys);
|
|
115
115
|
const compB = b
|
|
116
|
-
.filter((element) => element.type !==
|
|
116
|
+
.filter((element) => element.type !== 'text')
|
|
117
117
|
.sort(sortParsedKeys);
|
|
118
118
|
const errors = compA.reduce((acc, formatElementA, index) => {
|
|
119
119
|
const formatElementB = compB[index];
|
|
@@ -125,7 +125,7 @@ const getErrorMessage = (a, b) => {
|
|
|
125
125
|
acc.push(`Expected element of type "${formatElementA.type}" but received "${formatElementB.type}"`);
|
|
126
126
|
return acc;
|
|
127
127
|
}
|
|
128
|
-
if (formatElementA.type ===
|
|
128
|
+
if (formatElementA.type === 'tag' && formatElementB.type === 'tag') {
|
|
129
129
|
if (formatElementA.raw !== formatElementB.raw) {
|
|
130
130
|
acc.push(`Expected tag "${formatElementA.raw}" but received "${formatElementB.raw}"`);
|
|
131
131
|
}
|
|
@@ -140,13 +140,13 @@ const getErrorMessage = (a, b) => {
|
|
|
140
140
|
return acc;
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
|
-
if ((formatElementA.type ===
|
|
144
|
-
formatElementB.type ===
|
|
145
|
-
(formatElementA.type ===
|
|
146
|
-
formatElementB.type ===
|
|
147
|
-
(formatElementA.type ===
|
|
148
|
-
formatElementB.type ===
|
|
149
|
-
(formatElementA.type ===
|
|
143
|
+
if ((formatElementA.type === 'interpolation' &&
|
|
144
|
+
formatElementB.type === 'interpolation') ||
|
|
145
|
+
(formatElementA.type === 'interpolation_unescaped' &&
|
|
146
|
+
formatElementB.type === 'interpolation_unescaped') ||
|
|
147
|
+
(formatElementA.type === 'nesting' &&
|
|
148
|
+
formatElementB.type === 'nesting') ||
|
|
149
|
+
(formatElementA.type === 'plural' && formatElementB.type === 'plural')) {
|
|
150
150
|
if (formatElementA.prefix !== formatElementA.prefix) {
|
|
151
151
|
acc.push(`Error in ${formatElementA.type}: Expected prefix "${formatElementA.prefix}" but received "${formatElementB.prefix}"`);
|
|
152
152
|
return acc;
|
|
@@ -156,14 +156,14 @@ const getErrorMessage = (a, b) => {
|
|
|
156
156
|
return acc;
|
|
157
157
|
}
|
|
158
158
|
const optionsA = formatElementA.variable
|
|
159
|
-
.split(
|
|
159
|
+
.split(',')
|
|
160
160
|
.map((value) => value.trim())
|
|
161
161
|
.sort();
|
|
162
162
|
const optionsB = formatElementB.variable
|
|
163
|
-
.split(
|
|
163
|
+
.split(',')
|
|
164
164
|
.map((value) => value.trim())
|
|
165
165
|
.sort();
|
|
166
|
-
|
|
166
|
+
const elementErrors = [];
|
|
167
167
|
optionsA.forEach((key, index) => {
|
|
168
168
|
if (key !== optionsB[index]) {
|
|
169
169
|
elementErrors.push(`Expected ${key} but received ${optionsB[index]}`);
|
|
@@ -172,7 +172,7 @@ const getErrorMessage = (a, b) => {
|
|
|
172
172
|
if (elementErrors.length > 0) {
|
|
173
173
|
acc.push(`Error in ${formatElementA.type}: ${elementErrors
|
|
174
174
|
.flatMap((elementError) => elementError)
|
|
175
|
-
.join(
|
|
175
|
+
.join(', ')}`);
|
|
176
176
|
}
|
|
177
177
|
return acc;
|
|
178
178
|
}
|
|
@@ -185,8 +185,8 @@ const getErrorMessage = (a, b) => {
|
|
|
185
185
|
acc.push(`Unexpected ${formatElementB.type} element`);
|
|
186
186
|
return acc;
|
|
187
187
|
}, [])
|
|
188
|
-
.join(
|
|
189
|
-
return [...errors, unexpectedElements].join(
|
|
188
|
+
.join(', ');
|
|
189
|
+
return [...errors, unexpectedElements].join(', ');
|
|
190
190
|
}
|
|
191
|
-
return errors.join(
|
|
191
|
+
return errors.join(', ');
|
|
192
192
|
};
|