@lingual/i18n-check 0.9.0 → 0.9.2
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,432 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.extract = void 0;
|
|
40
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
41
|
+
const ts = __importStar(require("typescript"));
|
|
42
|
+
const USE_TRANSLATIONS = 'useTranslations';
|
|
43
|
+
const GET_TRANSLATIONS = 'getTranslations';
|
|
44
|
+
const COMMENT_CONTAINS_STATIC_KEY_REGEX = /i18n-check t\((["'])(.*?[^\\])(["'])\)/;
|
|
45
|
+
const extract = (filesPaths) => {
|
|
46
|
+
return filesPaths.flatMap(getKeys).sort((a, b) => {
|
|
47
|
+
return a.key > b.key ? 1 : -1;
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
exports.extract = extract;
|
|
51
|
+
const getKeys = (path) => {
|
|
52
|
+
const content = node_fs_1.default.readFileSync(path, 'utf-8');
|
|
53
|
+
const sourceFile = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true);
|
|
54
|
+
const foundKeys = [];
|
|
55
|
+
let namespaces = [];
|
|
56
|
+
const getCurrentNamespaces = (range = 1) => {
|
|
57
|
+
if (namespaces.length > 0) {
|
|
58
|
+
return namespaces.slice(namespaces.length - range);
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
};
|
|
62
|
+
const getCurrentNamespaceForIdentifier = (name) => {
|
|
63
|
+
return [...namespaces].reverse().find((namespace) => {
|
|
64
|
+
return namespace.variable === name;
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
const pushNamespace = (namespace) => {
|
|
68
|
+
namespaces.push(namespace);
|
|
69
|
+
};
|
|
70
|
+
const setNamespaceAsDynamic = (name) => {
|
|
71
|
+
namespaces = namespaces.map((namespace) => {
|
|
72
|
+
if (namespace.name === name) {
|
|
73
|
+
return { ...namespace, dynamic: true };
|
|
74
|
+
}
|
|
75
|
+
return namespace;
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
const removeNamespaces = (range = 1) => {
|
|
79
|
+
if (namespaces.length > 0) {
|
|
80
|
+
namespaces = namespaces.slice(0, namespaces.length - range);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
const visit = (node) => {
|
|
84
|
+
let key = null;
|
|
85
|
+
const initialNamespacesLength = namespaces.length;
|
|
86
|
+
if (node === undefined) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (ts.isVariableDeclaration(node)) {
|
|
90
|
+
if (node.initializer && ts.isCallExpression(node.initializer)) {
|
|
91
|
+
if (ts.isIdentifier(node.initializer.expression)) {
|
|
92
|
+
// Search for `useTranslations` calls and extract the namespace
|
|
93
|
+
// Additionally check for assigned variable name, as it might differ
|
|
94
|
+
// from the default `t`, i.e.: const other = useTranslations("namespace1");
|
|
95
|
+
if (node.initializer.expression.text === USE_TRANSLATIONS) {
|
|
96
|
+
const [argument] = node.initializer.arguments;
|
|
97
|
+
const variable = ts.isIdentifier(node.name) ? node.name.text : 't';
|
|
98
|
+
if (argument && ts.isStringLiteral(argument)) {
|
|
99
|
+
pushNamespace({ name: argument.text, variable });
|
|
100
|
+
}
|
|
101
|
+
else if (argument === undefined) {
|
|
102
|
+
pushNamespace({ name: '', variable });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Search for `getTranslations` calls and extract the namespace
|
|
108
|
+
// There are two different ways `getTranslations` can be used:
|
|
109
|
+
//
|
|
110
|
+
// import {getTranslations} from 'next-intl/server';
|
|
111
|
+
// const t = await getTranslations(namespace?);
|
|
112
|
+
// const t = await getTranslations({locale, namespace});
|
|
113
|
+
//
|
|
114
|
+
// Additionally check for assigned variable name, as it might differ
|
|
115
|
+
// from the default `t`, i.e.: const other = getTranslations("namespace1");
|
|
116
|
+
// Simplified usage in async components
|
|
117
|
+
if (node.initializer && ts.isAwaitExpression(node.initializer)) {
|
|
118
|
+
if (ts.isCallExpression(node.initializer.expression) &&
|
|
119
|
+
ts.isIdentifier(node.initializer.expression.expression)) {
|
|
120
|
+
if (node.initializer.expression.expression.text === GET_TRANSLATIONS) {
|
|
121
|
+
const [argument] = node.initializer.expression.arguments;
|
|
122
|
+
const variable = ts.isIdentifier(node.name) ? node.name.text : 't';
|
|
123
|
+
if (argument && ts.isObjectLiteralExpression(argument)) {
|
|
124
|
+
argument.properties.forEach((property) => {
|
|
125
|
+
if (property &&
|
|
126
|
+
ts.isPropertyAssignment(property) &&
|
|
127
|
+
property.name &&
|
|
128
|
+
ts.isIdentifier(property.name) &&
|
|
129
|
+
property.name.text === 'namespace' &&
|
|
130
|
+
ts.isStringLiteral(property.initializer)) {
|
|
131
|
+
pushNamespace({ name: property.initializer.text, variable });
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
pushNamespace({ name: '', variable });
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
else if (argument && ts.isStringLiteral(argument)) {
|
|
139
|
+
pushNamespace({ name: argument.text, variable });
|
|
140
|
+
}
|
|
141
|
+
else if (argument === undefined) {
|
|
142
|
+
pushNamespace({ name: '', variable });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Check if getTranslations is called inside a promise.all
|
|
147
|
+
// Example:
|
|
148
|
+
// const [data, t] = await Promise.all([
|
|
149
|
+
// loadData(id),
|
|
150
|
+
// getTranslations('asyncPromiseAll'),
|
|
151
|
+
// ]);
|
|
152
|
+
if (ts.isCallExpression(node.initializer.expression) &&
|
|
153
|
+
node.initializer.expression.arguments.length > 0 &&
|
|
154
|
+
ts.isArrayLiteralExpression(node.initializer.expression.arguments[0])) {
|
|
155
|
+
const functionNameIndex = node.initializer.expression.arguments[0].elements.findIndex((argument) => {
|
|
156
|
+
return (ts.isCallExpression(argument) &&
|
|
157
|
+
ts.isIdentifier(argument.expression) &&
|
|
158
|
+
argument.expression.text === GET_TRANSLATIONS);
|
|
159
|
+
});
|
|
160
|
+
// Try to find the correct function name via the position in the variable declaration
|
|
161
|
+
if (functionNameIndex !== -1 &&
|
|
162
|
+
ts.isArrayBindingPattern(node.name) &&
|
|
163
|
+
ts.isBindingElement(node.name.elements[functionNameIndex]) &&
|
|
164
|
+
ts.isIdentifier(node.name.elements[functionNameIndex].name)) {
|
|
165
|
+
const variable = node.name.elements[functionNameIndex].name.text;
|
|
166
|
+
const [argument] = ts.isCallExpression(node.initializer.expression.arguments[0].elements[functionNameIndex])
|
|
167
|
+
? node.initializer.expression.arguments[0].elements[functionNameIndex].arguments
|
|
168
|
+
: [];
|
|
169
|
+
if (argument && ts.isObjectLiteralExpression(argument)) {
|
|
170
|
+
argument.properties.forEach((property) => {
|
|
171
|
+
if (property &&
|
|
172
|
+
ts.isPropertyAssignment(property) &&
|
|
173
|
+
property.name &&
|
|
174
|
+
ts.isIdentifier(property.name) &&
|
|
175
|
+
property.name.text === 'namespace' &&
|
|
176
|
+
ts.isStringLiteral(property.initializer)) {
|
|
177
|
+
pushNamespace({ name: property.initializer.text, variable });
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
else if (argument && ts.isStringLiteral(argument)) {
|
|
182
|
+
pushNamespace({ name: argument.text, variable });
|
|
183
|
+
}
|
|
184
|
+
else if (argument === undefined) {
|
|
185
|
+
pushNamespace({ name: '', variable });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// Search for direct inline calls and extract namespace and key
|
|
192
|
+
//
|
|
193
|
+
// useTranslations("ns1")("one")
|
|
194
|
+
// useTranslations("ns1").rich("one");
|
|
195
|
+
// useTranslations("ns1").raw("one");
|
|
196
|
+
if (ts.isExpressionStatement(node)) {
|
|
197
|
+
let inlineNamespace = null;
|
|
198
|
+
if (node.expression && ts.isCallExpression(node.expression)) {
|
|
199
|
+
// Search: useTranslations("ns1")("one")
|
|
200
|
+
if (ts.isCallExpression(node.expression.expression) &&
|
|
201
|
+
ts.isIdentifier(node.expression.expression.expression)) {
|
|
202
|
+
if (node.expression.expression.expression.text === USE_TRANSLATIONS) {
|
|
203
|
+
const [argument] = node.expression.expression.arguments;
|
|
204
|
+
if (argument && ts.isStringLiteral(argument)) {
|
|
205
|
+
inlineNamespace = argument.text;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Search: useTranslations("ns1").*("one")
|
|
210
|
+
if (ts.isPropertyAccessExpression(node.expression.expression) &&
|
|
211
|
+
ts.isCallExpression(node.expression.expression.expression) &&
|
|
212
|
+
ts.isIdentifier(node.expression.expression.expression.expression)) {
|
|
213
|
+
if (node.expression.expression.expression.expression.text ===
|
|
214
|
+
USE_TRANSLATIONS) {
|
|
215
|
+
const [argument] = node.expression.expression.expression.arguments;
|
|
216
|
+
if (argument && ts.isStringLiteral(argument)) {
|
|
217
|
+
inlineNamespace = argument.text;
|
|
218
|
+
}
|
|
219
|
+
const [callArgument] = node.expression.arguments;
|
|
220
|
+
if (callArgument && ts.isStringLiteral(callArgument)) {
|
|
221
|
+
const key = callArgument.text;
|
|
222
|
+
if (key) {
|
|
223
|
+
foundKeys.push({
|
|
224
|
+
key: inlineNamespace ? `${inlineNamespace}.${key}` : key,
|
|
225
|
+
meta: { file: path, namespace: inlineNamespace ?? undefined },
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Check if a function has the t function as a parameter
|
|
234
|
+
// and if a namespace can be extracted from it
|
|
235
|
+
if (ts.isFunctionLike(node)) {
|
|
236
|
+
// The first scenario is the t function is defined as:
|
|
237
|
+
// someFn(t: ReturnType<typeof useTranslations<'someNamespace'>>)
|
|
238
|
+
const tFunctionParam = node.parameters &&
|
|
239
|
+
node.parameters.find((param) => param.type &&
|
|
240
|
+
ts.isTypeReferenceNode(param.type) &&
|
|
241
|
+
param.type.typeName &&
|
|
242
|
+
ts.isIdentifier(param.type.typeName) &&
|
|
243
|
+
param.type.typeName.text === 'ReturnType' &&
|
|
244
|
+
param.type.typeArguments &&
|
|
245
|
+
param.type.typeArguments.length > 0 &&
|
|
246
|
+
ts.isTypeQueryNode(param.type.typeArguments[0]) &&
|
|
247
|
+
ts.isIdentifier(param.type.typeArguments[0].exprName) &&
|
|
248
|
+
param.type.typeArguments[0].exprName.text === USE_TRANSLATIONS);
|
|
249
|
+
if (tFunctionParam !== undefined &&
|
|
250
|
+
tFunctionParam.type &&
|
|
251
|
+
ts.isTypeReferenceNode(tFunctionParam.type) &&
|
|
252
|
+
tFunctionParam.type.typeArguments &&
|
|
253
|
+
tFunctionParam.type.typeArguments.length > 0 &&
|
|
254
|
+
ts.isTypeQueryNode(tFunctionParam.type.typeArguments[0]) &&
|
|
255
|
+
ts.isIdentifier(tFunctionParam.type.typeArguments[0].exprName) &&
|
|
256
|
+
tFunctionParam.type.typeArguments[0].exprName.text === USE_TRANSLATIONS) {
|
|
257
|
+
const [namespaceArgument] = tFunctionParam.type.typeArguments[0].typeArguments &&
|
|
258
|
+
tFunctionParam.type.typeArguments[0].typeArguments.length > 0
|
|
259
|
+
? tFunctionParam.type.typeArguments[0].typeArguments
|
|
260
|
+
: [];
|
|
261
|
+
if (ts.isIdentifier(tFunctionParam.name)) {
|
|
262
|
+
const name = namespaceArgument &&
|
|
263
|
+
ts.isLiteralTypeNode(namespaceArgument) &&
|
|
264
|
+
ts.isStringLiteral(namespaceArgument.literal)
|
|
265
|
+
? namespaceArgument.literal.text
|
|
266
|
+
: '';
|
|
267
|
+
pushNamespace({
|
|
268
|
+
name,
|
|
269
|
+
variable: tFunctionParam.name.text,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// The second scenario is the t function is defined as an object property:
|
|
274
|
+
// someFn({t}: {t: ReturnType<typeof useTranslations<'someNamespace'>>}>
|
|
275
|
+
const tFunctionParamAsProperty = node.parameters &&
|
|
276
|
+
node.parameters.find((param) => param.type &&
|
|
277
|
+
ts.isTypeLiteralNode(param.type) &&
|
|
278
|
+
param.type.members.find((member) => {
|
|
279
|
+
return (ts.isPropertySignature(member) &&
|
|
280
|
+
member.type &&
|
|
281
|
+
ts.isTypeReferenceNode(member.type) &&
|
|
282
|
+
member.type.typeName &&
|
|
283
|
+
ts.isIdentifier(member.type.typeName) &&
|
|
284
|
+
member.type.typeName.text === 'ReturnType' &&
|
|
285
|
+
member.type.typeArguments &&
|
|
286
|
+
member.type.typeArguments.length > 0 &&
|
|
287
|
+
ts.isTypeQueryNode(member.type.typeArguments[0]) &&
|
|
288
|
+
ts.isIdentifier(member.type.typeArguments[0].exprName) &&
|
|
289
|
+
member.type.typeArguments[0].exprName.text === USE_TRANSLATIONS);
|
|
290
|
+
}));
|
|
291
|
+
if (tFunctionParamAsProperty !== undefined &&
|
|
292
|
+
tFunctionParamAsProperty.type &&
|
|
293
|
+
ts.isTypeLiteralNode(tFunctionParamAsProperty.type)) {
|
|
294
|
+
const fnType = tFunctionParamAsProperty.type.members.find((member) => {
|
|
295
|
+
return (ts.isPropertySignature(member) &&
|
|
296
|
+
member.type &&
|
|
297
|
+
ts.isTypeReferenceNode(member.type) &&
|
|
298
|
+
member.type.typeName &&
|
|
299
|
+
ts.isIdentifier(member.type.typeName) &&
|
|
300
|
+
member.type.typeName.text === 'ReturnType' &&
|
|
301
|
+
member.type.typeArguments &&
|
|
302
|
+
member.type.typeArguments.length > 0 &&
|
|
303
|
+
ts.isTypeQueryNode(member.type.typeArguments[0]) &&
|
|
304
|
+
ts.isIdentifier(member.type.typeArguments[0].exprName) &&
|
|
305
|
+
member.type.typeArguments[0].exprName.text === USE_TRANSLATIONS);
|
|
306
|
+
});
|
|
307
|
+
if (fnType &&
|
|
308
|
+
ts.isPropertySignature(fnType) &&
|
|
309
|
+
fnType.type &&
|
|
310
|
+
ts.isTypeReferenceNode(fnType.type) &&
|
|
311
|
+
fnType.type.typeArguments &&
|
|
312
|
+
fnType.type.typeArguments.length > 0 &&
|
|
313
|
+
ts.isTypeQueryNode(fnType.type.typeArguments[0])) {
|
|
314
|
+
const [namespaceArgument] = fnType.type.typeArguments[0].typeArguments &&
|
|
315
|
+
fnType.type.typeArguments[0].typeArguments.length > 0
|
|
316
|
+
? fnType.type.typeArguments[0].typeArguments
|
|
317
|
+
: [];
|
|
318
|
+
if (fnType.name && ts.isIdentifier(fnType.name)) {
|
|
319
|
+
const name = namespaceArgument &&
|
|
320
|
+
ts.isLiteralTypeNode(namespaceArgument) &&
|
|
321
|
+
ts.isStringLiteral(namespaceArgument.literal)
|
|
322
|
+
? namespaceArgument.literal.text
|
|
323
|
+
: '';
|
|
324
|
+
pushNamespace({
|
|
325
|
+
name,
|
|
326
|
+
variable: fnType.name.text,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
// Search for `t()` calls
|
|
333
|
+
if (getCurrentNamespaces() !== null &&
|
|
334
|
+
ts.isCallExpression(node) &&
|
|
335
|
+
ts.isIdentifier(node.expression)) {
|
|
336
|
+
const expressionName = node.expression.text;
|
|
337
|
+
const namespace = getCurrentNamespaceForIdentifier(expressionName);
|
|
338
|
+
if (namespace) {
|
|
339
|
+
const [argument] = node.arguments;
|
|
340
|
+
if (argument && ts.isStringLiteral(argument)) {
|
|
341
|
+
key = { name: argument.text, identifier: expressionName };
|
|
342
|
+
}
|
|
343
|
+
else if (argument && ts.isIdentifier(argument)) {
|
|
344
|
+
setNamespaceAsDynamic(namespace.name);
|
|
345
|
+
}
|
|
346
|
+
else if (argument && ts.isTemplateExpression(argument)) {
|
|
347
|
+
setNamespaceAsDynamic(namespace.name);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
// Search for `t.*()` calls, i.e. t.html() or t.rich()
|
|
352
|
+
if (getCurrentNamespaces() !== null &&
|
|
353
|
+
ts.isCallExpression(node) &&
|
|
354
|
+
ts.isPropertyAccessExpression(node.expression) &&
|
|
355
|
+
ts.isIdentifier(node.expression.expression)) {
|
|
356
|
+
const expressionName = node.expression.expression.text;
|
|
357
|
+
const namespace = getCurrentNamespaceForIdentifier(expressionName);
|
|
358
|
+
if (namespace) {
|
|
359
|
+
const [argument] = node.arguments;
|
|
360
|
+
if (argument && ts.isStringLiteral(argument)) {
|
|
361
|
+
key = { name: argument.text, identifier: expressionName };
|
|
362
|
+
}
|
|
363
|
+
else if (argument && ts.isIdentifier(argument)) {
|
|
364
|
+
setNamespaceAsDynamic(namespace.name);
|
|
365
|
+
}
|
|
366
|
+
else if (argument && ts.isTemplateExpression(argument)) {
|
|
367
|
+
setNamespaceAsDynamic(namespace.name);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (key) {
|
|
372
|
+
const namespace = getCurrentNamespaceForIdentifier(key.identifier);
|
|
373
|
+
const namespaceName = namespace ? namespace.name : '';
|
|
374
|
+
foundKeys.push({
|
|
375
|
+
key: namespaceName ? `${namespaceName}.${key.name}` : key.name,
|
|
376
|
+
meta: { file: path, namespace: namespaceName },
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
// Search for single-line comments that contain the static values of a dynamic key
|
|
380
|
+
// Example:
|
|
381
|
+
// const someKeys = messages[selectedOption];
|
|
382
|
+
// Define as a single-line comment all the possible static keys for that dynamic key
|
|
383
|
+
// i18n-check t('some.static.key.we.want.to.extract');
|
|
384
|
+
// i18n-check t('some.other.key.we.want.to.extract.without.semicolons')
|
|
385
|
+
const commentRanges = ts.getLeadingCommentRanges(sourceFile.getFullText(), node.getFullStart());
|
|
386
|
+
if (commentRanges?.length && commentRanges.length > 0) {
|
|
387
|
+
commentRanges.forEach((range) => {
|
|
388
|
+
const comment = sourceFile.getFullText().slice(range.pos, range.end);
|
|
389
|
+
// parse the string and check if it includes the following format:
|
|
390
|
+
// i18n-check t('someString')
|
|
391
|
+
const hasStaticKeyComment = COMMENT_CONTAINS_STATIC_KEY_REGEX.test(comment);
|
|
392
|
+
if (hasStaticKeyComment) {
|
|
393
|
+
// capture the string comment
|
|
394
|
+
const commentKey = COMMENT_CONTAINS_STATIC_KEY_REGEX.exec(comment)?.[2];
|
|
395
|
+
if (commentKey) {
|
|
396
|
+
const namespace = getCurrentNamespaces();
|
|
397
|
+
const namespaceName = namespace ? namespace[0]?.name : '';
|
|
398
|
+
const key = namespaceName
|
|
399
|
+
? `${namespaceName}.${commentKey}`
|
|
400
|
+
: commentKey;
|
|
401
|
+
const keyExists = foundKeys.find((foundKey) => {
|
|
402
|
+
return foundKey.key === key;
|
|
403
|
+
});
|
|
404
|
+
if (!keyExists) {
|
|
405
|
+
foundKeys.push({
|
|
406
|
+
key,
|
|
407
|
+
meta: { file: path, namespace: namespaceName },
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
ts.forEachChild(node, visit);
|
|
415
|
+
if (ts.isFunctionLike(node) &&
|
|
416
|
+
namespaces.length > initialNamespacesLength) {
|
|
417
|
+
// check if the namespaces are dynamic and add a placeholder key
|
|
418
|
+
const currentNamespaces = getCurrentNamespaces(namespaces?.length - initialNamespacesLength);
|
|
419
|
+
currentNamespaces?.forEach((namespace) => {
|
|
420
|
+
if (namespace.dynamic) {
|
|
421
|
+
foundKeys.push({
|
|
422
|
+
key: namespace.name,
|
|
423
|
+
meta: { file: path, namespace: namespace.name, dynamic: true },
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
removeNamespaces(namespaces.length - initialNamespacesLength);
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
ts.forEachChild(sourceFile, visit);
|
|
431
|
+
return foundKeys;
|
|
432
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lingual/i18n-check",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "i18n translation messages check",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"lint:fix": "eslint src --fix ",
|
|
17
17
|
"check-format": "prettier --check './{src,translations}/**/*.{js,jsx,ts,tsx,json,html,css}'",
|
|
18
18
|
"test": "vitest run",
|
|
19
|
-
"test:cli": "pnpm build && vitest --config vitest.bin.config.ts run src/bin/index.test.ts"
|
|
19
|
+
"test:cli": "pnpm build && vitest --config vitest.bin.config.ts run src/bin/index.test.ts",
|
|
20
|
+
"prepublishOnly": "pnpm install --frozen-lockfile && pnpm run build"
|
|
20
21
|
},
|
|
21
22
|
"files": [
|
|
22
23
|
"dist/",
|