@lingual/i18n-check 0.9.0 → 0.9.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/dist/bin/index.d.ts +2 -0
- package/dist/bin/index.js +286 -0
- package/dist/errorReporters.d.ts +13 -0
- package/dist/errorReporters.js +78 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +345 -0
- package/dist/types.d.ts +23 -0
- package/dist/types.js +2 -0
- package/dist/utils/constants.d.ts +1 -0
- package/dist/utils/constants.js +12 -0
- package/dist/utils/findInvalidI18NextTranslations.d.ts +11 -0
- package/dist/utils/findInvalidI18NextTranslations.js +192 -0
- package/dist/utils/findInvalidTranslations.d.ts +16 -0
- package/dist/utils/findInvalidTranslations.js +239 -0
- package/dist/utils/findMissingKeys.d.ts +4 -0
- package/dist/utils/findMissingKeys.js +55 -0
- package/dist/utils/flattenTranslations.d.ts +3 -0
- package/dist/utils/flattenTranslations.js +33 -0
- package/dist/utils/i18NextParser.d.ts +37 -0
- package/dist/utils/i18NextParser.js +104 -0
- package/dist/utils/i18NextSrcParser.d.ts +35 -0
- package/dist/utils/i18NextSrcParser.js +718 -0
- package/dist/utils/nextIntlSrcParser.d.ts +8 -0
- package/dist/utils/nextIntlSrcParser.js +432 -0
- package/package.json +3 -2
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hasDiff = exports.compareTranslationFiles = exports.findInvalidTranslations = exports.CheckError = void 0;
|
|
4
|
+
const icu_messageformat_parser_1 = require("@formatjs/icu-messageformat-parser");
|
|
5
|
+
class CheckError extends Error {
|
|
6
|
+
}
|
|
7
|
+
exports.CheckError = CheckError;
|
|
8
|
+
function isLocation(value) {
|
|
9
|
+
return (typeof value === 'object' &&
|
|
10
|
+
value != null &&
|
|
11
|
+
'start' in value &&
|
|
12
|
+
typeof value.start === 'object' &&
|
|
13
|
+
value.start != null &&
|
|
14
|
+
'line' in value.start &&
|
|
15
|
+
typeof value.start.line === 'number' &&
|
|
16
|
+
'column' in value.start &&
|
|
17
|
+
typeof value.start.column === 'number');
|
|
18
|
+
}
|
|
19
|
+
const findInvalidTranslations = (source, files) => {
|
|
20
|
+
const differences = {};
|
|
21
|
+
if (Object.keys(files).length === 0) {
|
|
22
|
+
return differences;
|
|
23
|
+
}
|
|
24
|
+
for (const [lang, file] of Object.entries(files)) {
|
|
25
|
+
try {
|
|
26
|
+
const result = (0, exports.compareTranslationFiles)(source, file);
|
|
27
|
+
if (result.length > 0) {
|
|
28
|
+
differences[lang] = result;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
// Re-throw with file context
|
|
33
|
+
const enhancedError = new CheckError(`Error in translation file "${lang}": ${error instanceof Error ? error.message : String(error)}`);
|
|
34
|
+
if (error instanceof Error) {
|
|
35
|
+
if ('location' in error && isLocation(error.location)) {
|
|
36
|
+
enhancedError.location = error.location;
|
|
37
|
+
}
|
|
38
|
+
if ('originalMessage' in error &&
|
|
39
|
+
typeof error.originalMessage === 'string') {
|
|
40
|
+
enhancedError.originalMessage = error.originalMessage;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
throw enhancedError;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return differences;
|
|
47
|
+
};
|
|
48
|
+
exports.findInvalidTranslations = findInvalidTranslations;
|
|
49
|
+
const sortParsedKeys = (a, b) => {
|
|
50
|
+
if (a.type === b.type) {
|
|
51
|
+
return !(0, icu_messageformat_parser_1.isPoundElement)(a) && !(0, icu_messageformat_parser_1.isPoundElement)(b)
|
|
52
|
+
? a.value < b.value
|
|
53
|
+
? -1
|
|
54
|
+
: 1
|
|
55
|
+
: -1;
|
|
56
|
+
}
|
|
57
|
+
return a.type - b.type;
|
|
58
|
+
};
|
|
59
|
+
const compareTranslationFiles = (a, b) => {
|
|
60
|
+
const diffs = [];
|
|
61
|
+
for (const key in a) {
|
|
62
|
+
if (b[key] === undefined) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const parsedTranslationA = (0, icu_messageformat_parser_1.parse)(String(a[key]));
|
|
67
|
+
const parsedTranslationB = (0, icu_messageformat_parser_1.parse)(String(b[key]));
|
|
68
|
+
if ((0, exports.hasDiff)(parsedTranslationA, parsedTranslationB)) {
|
|
69
|
+
const msg = getErrorMessage(parsedTranslationA, parsedTranslationB);
|
|
70
|
+
diffs.push({ key, msg });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
// Re-throw with key context and preserve location/originalMessage
|
|
75
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
76
|
+
const enhancedError = new CheckError(`Failed to parse translation key "${key}": ${errorMessage === 'INVALID_TAG' ? 'Invalid ICU message format tags found in translation content' : errorMessage}`);
|
|
77
|
+
if (error instanceof Error) {
|
|
78
|
+
if ('location' in error && isLocation(error.location)) {
|
|
79
|
+
enhancedError.location = error.location;
|
|
80
|
+
}
|
|
81
|
+
if ('originalMessage' in error &&
|
|
82
|
+
typeof error.originalMessage === 'string') {
|
|
83
|
+
enhancedError.originalMessage = error.originalMessage;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
throw enhancedError;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return diffs;
|
|
90
|
+
};
|
|
91
|
+
exports.compareTranslationFiles = compareTranslationFiles;
|
|
92
|
+
const hasDiff = (a, b) => {
|
|
93
|
+
const compA = a
|
|
94
|
+
.filter((element) => !(0, icu_messageformat_parser_1.isLiteralElement)(element))
|
|
95
|
+
.sort(sortParsedKeys);
|
|
96
|
+
const compB = b
|
|
97
|
+
.filter((element) => !(0, icu_messageformat_parser_1.isLiteralElement)(element))
|
|
98
|
+
.sort(sortParsedKeys);
|
|
99
|
+
if (compA.length !== compB.length) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
const hasErrors = compA.some((formatElementA, index) => {
|
|
103
|
+
const formatElementB = compB[index];
|
|
104
|
+
if ((0, icu_messageformat_parser_1.isPluralElement)(formatElementA) || (0, icu_messageformat_parser_1.isPluralElement)(formatElementB)) {
|
|
105
|
+
const optionsA = (0, icu_messageformat_parser_1.isPluralElement)(formatElementA)
|
|
106
|
+
? formatElementA.options
|
|
107
|
+
: {};
|
|
108
|
+
const optionsB = (0, icu_messageformat_parser_1.isPluralElement)(formatElementB)
|
|
109
|
+
? formatElementB.options
|
|
110
|
+
: {};
|
|
111
|
+
return Object.keys(optionsA)
|
|
112
|
+
.sort()
|
|
113
|
+
.some((key) => {
|
|
114
|
+
// We can only compare translations that have the same plural keys.
|
|
115
|
+
// In English, we might have "one", "other", but in German, we might have "one", "few", "other".
|
|
116
|
+
// Or, in Arabic it might just be "other".
|
|
117
|
+
// So, we'll have to skip over the ones that don't have a one-to-one match.
|
|
118
|
+
if (!optionsB[key]) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
return (0, exports.hasDiff)(optionsA[key].value, optionsB[key].value);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (formatElementA.type !== formatElementB.type ||
|
|
125
|
+
formatElementA.location !== formatElementB.location) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
if (((0, icu_messageformat_parser_1.isLiteralElement)(formatElementA) && (0, icu_messageformat_parser_1.isLiteralElement)(formatElementB)) ||
|
|
129
|
+
((0, icu_messageformat_parser_1.isPoundElement)(formatElementA) && (0, icu_messageformat_parser_1.isPoundElement)(formatElementB))) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
if (!(0, icu_messageformat_parser_1.isPoundElement)(formatElementA) &&
|
|
133
|
+
!(0, icu_messageformat_parser_1.isPoundElement)(formatElementB) &&
|
|
134
|
+
formatElementA.value !== formatElementB.value) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
if ((0, icu_messageformat_parser_1.isTagElement)(formatElementA) && (0, icu_messageformat_parser_1.isTagElement)(formatElementB)) {
|
|
138
|
+
return (0, exports.hasDiff)(formatElementA.children, formatElementB.children);
|
|
139
|
+
}
|
|
140
|
+
if ((0, icu_messageformat_parser_1.isSelectElement)(formatElementA) && (0, icu_messageformat_parser_1.isSelectElement)(formatElementB)) {
|
|
141
|
+
const optionsA = Object.keys(formatElementA.options).sort();
|
|
142
|
+
const optionsB = Object.keys(formatElementB.options).sort();
|
|
143
|
+
if (optionsA.join('-') !== optionsB.join('-')) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
return optionsA.some((key) => {
|
|
147
|
+
return (0, exports.hasDiff)(formatElementA.options[key].value, formatElementB.options[key].value);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
});
|
|
152
|
+
return hasErrors;
|
|
153
|
+
};
|
|
154
|
+
exports.hasDiff = hasDiff;
|
|
155
|
+
const getErrorMessage = (a, b) => {
|
|
156
|
+
const compA = a
|
|
157
|
+
.filter((element) => !(0, icu_messageformat_parser_1.isLiteralElement)(element))
|
|
158
|
+
.sort(sortParsedKeys);
|
|
159
|
+
const compB = b
|
|
160
|
+
.filter((element) => !(0, icu_messageformat_parser_1.isLiteralElement)(element))
|
|
161
|
+
.sort(sortParsedKeys);
|
|
162
|
+
const errors = compA.reduce((acc, formatElementA, index) => {
|
|
163
|
+
const formatElementB = compB[index];
|
|
164
|
+
if (!formatElementB) {
|
|
165
|
+
acc.push(`Missing element ${typeLookup[formatElementA.type]}`);
|
|
166
|
+
return acc;
|
|
167
|
+
}
|
|
168
|
+
if (formatElementA.type !== formatElementB.type) {
|
|
169
|
+
acc.push(`Expected element of type "${typeLookup[formatElementA.type]}" but received "${typeLookup[formatElementB.type]}"`);
|
|
170
|
+
return acc;
|
|
171
|
+
}
|
|
172
|
+
if (formatElementA.location !== formatElementB.location) {
|
|
173
|
+
acc.push(`Expected location to be ${formatElementA.location?.start?.line}:${formatElementA.location?.start?.column}`);
|
|
174
|
+
return acc;
|
|
175
|
+
}
|
|
176
|
+
if ((0, icu_messageformat_parser_1.isPoundElement)(formatElementA) && (0, icu_messageformat_parser_1.isPoundElement)(formatElementB)) {
|
|
177
|
+
return acc;
|
|
178
|
+
}
|
|
179
|
+
if (!(0, icu_messageformat_parser_1.isPoundElement)(formatElementA) &&
|
|
180
|
+
!(0, icu_messageformat_parser_1.isPoundElement)(formatElementB) &&
|
|
181
|
+
formatElementA.value !== formatElementB.value) {
|
|
182
|
+
acc.push(`Expected ${typeLookup[formatElementA.type]} to contain "${formatElementA.value}" but received "${formatElementB.value}"`);
|
|
183
|
+
return acc;
|
|
184
|
+
}
|
|
185
|
+
if ((0, icu_messageformat_parser_1.isTagElement)(formatElementA) && (0, icu_messageformat_parser_1.isTagElement)(formatElementB)) {
|
|
186
|
+
acc.push(`Error in pound element: ${getErrorMessage(formatElementA.children, formatElementB.children)}`);
|
|
187
|
+
return acc;
|
|
188
|
+
}
|
|
189
|
+
if ((0, icu_messageformat_parser_1.isSelectElement)(formatElementA) && (0, icu_messageformat_parser_1.isSelectElement)(formatElementB)) {
|
|
190
|
+
const optionsA = Object.keys(formatElementA.options).sort();
|
|
191
|
+
const elementErrors = [];
|
|
192
|
+
optionsA.forEach((key) => {
|
|
193
|
+
if (formatElementB.options[key]) {
|
|
194
|
+
elementErrors.push(getErrorMessage(formatElementA.options[key].value, formatElementB.options[key].value));
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
acc.push(`Error in select: ${elementErrors
|
|
198
|
+
.flatMap((elementError) => elementError)
|
|
199
|
+
.join(', ')}`);
|
|
200
|
+
return acc;
|
|
201
|
+
}
|
|
202
|
+
if ((0, icu_messageformat_parser_1.isPluralElement)(formatElementA) && (0, icu_messageformat_parser_1.isPluralElement)(formatElementB)) {
|
|
203
|
+
const optionsA = Object.keys(formatElementA.options).sort();
|
|
204
|
+
const elementErrors = [];
|
|
205
|
+
optionsA.forEach((key) => {
|
|
206
|
+
if (formatElementB.options[key]) {
|
|
207
|
+
elementErrors.push(getErrorMessage(formatElementA.options[key].value, formatElementB.options[key].value));
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
acc.push(`Error in plural: ${elementErrors
|
|
211
|
+
.flatMap((elementError) => elementError)
|
|
212
|
+
.join(', ')}`);
|
|
213
|
+
return acc;
|
|
214
|
+
}
|
|
215
|
+
return acc;
|
|
216
|
+
}, []);
|
|
217
|
+
if (compA.length < compB.length) {
|
|
218
|
+
const unexpectedElements = compB
|
|
219
|
+
.slice(compA.length)
|
|
220
|
+
.reduce((acc, formatElementB) => {
|
|
221
|
+
acc.push(`Unexpected ${typeLookup[formatElementB.type]} element`);
|
|
222
|
+
return acc;
|
|
223
|
+
}, [])
|
|
224
|
+
.join(', ');
|
|
225
|
+
return [...errors, unexpectedElements].join(', ');
|
|
226
|
+
}
|
|
227
|
+
return errors.join(', ');
|
|
228
|
+
};
|
|
229
|
+
const typeLookup = {
|
|
230
|
+
0: 'literal',
|
|
231
|
+
1: 'argument',
|
|
232
|
+
2: 'number',
|
|
233
|
+
3: 'date',
|
|
234
|
+
4: 'time',
|
|
235
|
+
5: 'select',
|
|
236
|
+
6: 'plural',
|
|
237
|
+
7: 'pound',
|
|
238
|
+
8: 'tag',
|
|
239
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Options, Translation } from '../types';
|
|
2
|
+
export declare const findMissingKeys: (source: Translation, targets: Record<string, Translation>, options?: Options) => Record<string, string[]>;
|
|
3
|
+
export declare const compareTranslationFiles: (src: Translation, target: Translation) => string[];
|
|
4
|
+
export declare const compareI18nextTranslationFiles: (src: Translation, target: Translation) => string[];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.compareI18nextTranslationFiles = exports.compareTranslationFiles = exports.findMissingKeys = void 0;
|
|
4
|
+
const constants_1 = require("./constants");
|
|
5
|
+
const findMissingKeys = (source, targets, options = {}) => {
|
|
6
|
+
const differences = {};
|
|
7
|
+
for (const [lang, file] of Object.entries(targets)) {
|
|
8
|
+
const result = options.format === 'i18next'
|
|
9
|
+
? (0, exports.compareI18nextTranslationFiles)(source, file)
|
|
10
|
+
: (0, exports.compareTranslationFiles)(source, file);
|
|
11
|
+
if (result.length > 0) {
|
|
12
|
+
differences[lang] = result;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return differences;
|
|
16
|
+
};
|
|
17
|
+
exports.findMissingKeys = findMissingKeys;
|
|
18
|
+
const compareTranslationFiles = (src, target) => {
|
|
19
|
+
const diffs = [];
|
|
20
|
+
for (const key in src) {
|
|
21
|
+
const counterKey = target[key];
|
|
22
|
+
if (!counterKey) {
|
|
23
|
+
diffs.push(key);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return diffs;
|
|
27
|
+
};
|
|
28
|
+
exports.compareTranslationFiles = compareTranslationFiles;
|
|
29
|
+
const compareI18nextTranslationFiles = (src, target) => {
|
|
30
|
+
const diffs = [];
|
|
31
|
+
const flattedSrc = Object.entries(src).reduce((acc, [k, v]) => {
|
|
32
|
+
const pluralSuffix = constants_1.I18NEXT_PLURAL_SUFFIX.find((suffix) => {
|
|
33
|
+
return k.endsWith(suffix);
|
|
34
|
+
});
|
|
35
|
+
const key = pluralSuffix ? k.replace(pluralSuffix, '') : k;
|
|
36
|
+
acc[key] = v;
|
|
37
|
+
return acc;
|
|
38
|
+
}, {});
|
|
39
|
+
const flattedTarget = Object.entries(target).reduce((acc, [k, v]) => {
|
|
40
|
+
const pluralSuffix = constants_1.I18NEXT_PLURAL_SUFFIX.find((suffix) => {
|
|
41
|
+
return k.endsWith(suffix);
|
|
42
|
+
});
|
|
43
|
+
const key = pluralSuffix ? k.replace(pluralSuffix, '') : k;
|
|
44
|
+
acc[key] = v;
|
|
45
|
+
return acc;
|
|
46
|
+
}, {});
|
|
47
|
+
for (const key in flattedSrc) {
|
|
48
|
+
const counterKey = flattedTarget[key];
|
|
49
|
+
if (!counterKey) {
|
|
50
|
+
diffs.push(key);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return diffs;
|
|
54
|
+
};
|
|
55
|
+
exports.compareI18nextTranslationFiles = compareI18nextTranslationFiles;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.flattenEntry = exports.flattenTranslations = void 0;
|
|
4
|
+
const flattenTranslations = (translations) => {
|
|
5
|
+
if (!hasNestedDefinitions(translations)) {
|
|
6
|
+
return translations;
|
|
7
|
+
}
|
|
8
|
+
return (0, exports.flattenEntry)(translations);
|
|
9
|
+
};
|
|
10
|
+
exports.flattenTranslations = flattenTranslations;
|
|
11
|
+
/**
|
|
12
|
+
* Top level search for any objects
|
|
13
|
+
*/
|
|
14
|
+
const hasNestedDefinitions = (translations) => {
|
|
15
|
+
return Object.values(translations).find((translation) => typeof translation === 'object');
|
|
16
|
+
};
|
|
17
|
+
const isTranslationObject = (entry) => {
|
|
18
|
+
return typeof entry === 'object';
|
|
19
|
+
};
|
|
20
|
+
const flattenEntry = (entry, keys = []) => {
|
|
21
|
+
const result = {};
|
|
22
|
+
if (!entry) {
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
const entries = Object.entries(entry);
|
|
26
|
+
for (const [k, v] of entries) {
|
|
27
|
+
Object.assign(result, isTranslationObject(v)
|
|
28
|
+
? (0, exports.flattenEntry)(v, [...keys, String(k)])
|
|
29
|
+
: { [[...keys, String(k)].join('.')]: v });
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
};
|
|
33
|
+
exports.flattenEntry = flattenEntry;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type MessageFormatElement = {
|
|
2
|
+
type: 'text';
|
|
3
|
+
content: string;
|
|
4
|
+
} | {
|
|
5
|
+
type: 'interpolation';
|
|
6
|
+
raw: string;
|
|
7
|
+
prefix: string;
|
|
8
|
+
suffix: string;
|
|
9
|
+
content: string;
|
|
10
|
+
variable: string;
|
|
11
|
+
} | {
|
|
12
|
+
type: 'interpolation_unescaped';
|
|
13
|
+
raw: string;
|
|
14
|
+
prefix: string;
|
|
15
|
+
suffix: string;
|
|
16
|
+
content: string;
|
|
17
|
+
variable: string;
|
|
18
|
+
} | {
|
|
19
|
+
type: 'nesting';
|
|
20
|
+
raw: string;
|
|
21
|
+
prefix: string;
|
|
22
|
+
suffix: string;
|
|
23
|
+
content: string;
|
|
24
|
+
variable: string;
|
|
25
|
+
} | {
|
|
26
|
+
type: 'plural';
|
|
27
|
+
raw: string;
|
|
28
|
+
prefix: string;
|
|
29
|
+
suffix: string;
|
|
30
|
+
content: string;
|
|
31
|
+
variable: string;
|
|
32
|
+
} | {
|
|
33
|
+
type: 'tag';
|
|
34
|
+
raw: string;
|
|
35
|
+
voidElement: boolean;
|
|
36
|
+
};
|
|
37
|
+
export declare const parse: (input: string) => MessageFormatElement[];
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Based on https://github.com/i18next/i18next-translation-parser/blob/v1.0.0/src/parse.js
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.parse = void 0;
|
|
5
|
+
const REGEXP = new RegExp('({{[^}]+}}|\\$t{[^}]+}|\\$t\\([^\\)]+\\)|\\([0-9\\-inf]+\\)(?=\\[)|<[^>]+>)', 'g');
|
|
6
|
+
const DOUBLE_BRACE = '{{';
|
|
7
|
+
const $_T_BRACE = '$t{';
|
|
8
|
+
const $_T_PARENTHESIS = '$t(';
|
|
9
|
+
const OPEN_PARENTHESIS = '(';
|
|
10
|
+
const OPEN_TAG = '<';
|
|
11
|
+
const CLOSE_TAG = '</';
|
|
12
|
+
const parse = (input) => {
|
|
13
|
+
let ast = [];
|
|
14
|
+
ast = parseInput([input]);
|
|
15
|
+
return ast;
|
|
16
|
+
};
|
|
17
|
+
exports.parse = parse;
|
|
18
|
+
const parseInput = (input) => {
|
|
19
|
+
if (input.length === 0) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
let ast = [];
|
|
23
|
+
input.forEach((element) => {
|
|
24
|
+
const elements = element.split(REGEXP).filter((element) => element !== '');
|
|
25
|
+
const result = elements.reduce((acc, match) => {
|
|
26
|
+
if (match.indexOf('{{-') === 0) {
|
|
27
|
+
const content = match.substring(3, match.length - 2);
|
|
28
|
+
acc.push({
|
|
29
|
+
type: 'interpolation_unescaped',
|
|
30
|
+
raw: match,
|
|
31
|
+
prefix: '{{-',
|
|
32
|
+
suffix: '}}',
|
|
33
|
+
content,
|
|
34
|
+
variable: content.trim(),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
else if (match.indexOf(DOUBLE_BRACE) === 0) {
|
|
38
|
+
const content = match.substring(2, match.length - 2);
|
|
39
|
+
acc.push({
|
|
40
|
+
type: 'interpolation',
|
|
41
|
+
raw: match,
|
|
42
|
+
prefix: '{{',
|
|
43
|
+
suffix: '}}',
|
|
44
|
+
content,
|
|
45
|
+
variable: content.trim(),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
else if (match.indexOf($_T_BRACE) === 0) {
|
|
49
|
+
const content = match.substring(3, match.length - 1);
|
|
50
|
+
acc.push({
|
|
51
|
+
type: 'nesting',
|
|
52
|
+
raw: match,
|
|
53
|
+
prefix: '$t{',
|
|
54
|
+
suffix: '}',
|
|
55
|
+
content,
|
|
56
|
+
variable: content.trim(),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else if (match.indexOf($_T_PARENTHESIS) === 0) {
|
|
60
|
+
const content = match.substring(3, match.length - 1);
|
|
61
|
+
acc.push({
|
|
62
|
+
type: 'nesting',
|
|
63
|
+
raw: match,
|
|
64
|
+
prefix: '$t(',
|
|
65
|
+
suffix: ')',
|
|
66
|
+
content,
|
|
67
|
+
variable: content.trim(),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
else if (match.indexOf(OPEN_PARENTHESIS) === 0 &&
|
|
71
|
+
/\([0-9\-inf]+\)/.test(match)) {
|
|
72
|
+
const content = match.substring(1, match.length - 1);
|
|
73
|
+
acc.push({
|
|
74
|
+
type: 'plural',
|
|
75
|
+
raw: match,
|
|
76
|
+
prefix: '(',
|
|
77
|
+
suffix: ')',
|
|
78
|
+
content,
|
|
79
|
+
variable: content.trim(),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
else if (match.indexOf(CLOSE_TAG) === 0) {
|
|
83
|
+
acc.push({
|
|
84
|
+
type: 'tag',
|
|
85
|
+
raw: match,
|
|
86
|
+
voidElement: match.substring(match.length - 2) === '/>',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
else if (match.indexOf(OPEN_TAG) === 0 && /<[^\s]+/.test(match)) {
|
|
90
|
+
acc.push({
|
|
91
|
+
type: 'tag',
|
|
92
|
+
raw: match,
|
|
93
|
+
voidElement: match.substring(match.length - 2) === '/>',
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
acc.push({ type: 'text', content: match });
|
|
98
|
+
}
|
|
99
|
+
return acc;
|
|
100
|
+
}, []);
|
|
101
|
+
ast = ast.concat(result);
|
|
102
|
+
});
|
|
103
|
+
return ast;
|
|
104
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Based on the original [i18next-parser](https://github.com/i18next/i18next-parser)
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
type Options = {
|
|
7
|
+
attr: string;
|
|
8
|
+
componentFunctions: string[];
|
|
9
|
+
functions: string[];
|
|
10
|
+
namespaceFunctions: string[];
|
|
11
|
+
parseGenerics: boolean;
|
|
12
|
+
transSupportBasicHtmlNodes: boolean;
|
|
13
|
+
transIdentityFunctionsToIgnore: string[];
|
|
14
|
+
typeMap: Record<string, unknown>;
|
|
15
|
+
translationFunctionsWithArgs: Record<string, {
|
|
16
|
+
pos: number;
|
|
17
|
+
storeGlobally: boolean;
|
|
18
|
+
keyPrefix?: string;
|
|
19
|
+
ns?: string;
|
|
20
|
+
}>;
|
|
21
|
+
keyPrefix?: string;
|
|
22
|
+
defaultNamespace?: string;
|
|
23
|
+
transKeepBasicHtmlNodesFor: string[];
|
|
24
|
+
omitAttributes: string[];
|
|
25
|
+
};
|
|
26
|
+
type FoundKey = {
|
|
27
|
+
key: string;
|
|
28
|
+
ns?: string;
|
|
29
|
+
namespace?: string;
|
|
30
|
+
functionName?: string;
|
|
31
|
+
defaultValue?: string;
|
|
32
|
+
[key: string]: unknown;
|
|
33
|
+
};
|
|
34
|
+
export declare const getKeys: (path: string, options: Partial<Options>, content: string) => FoundKey[];
|
|
35
|
+
export {};
|