gtx-cli 2.4.10 → 2.4.12
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/CHANGELOG.md +12 -0
- package/dist/console/index.d.ts +1 -0
- package/dist/console/index.js +2 -0
- package/dist/react/jsx/evaluateJsx.d.ts +4 -0
- package/dist/react/jsx/evaluateJsx.js +13 -0
- package/dist/react/jsx/trimJsxStringChildren.js +32 -5
- package/dist/react/jsx/utils/parseStringFunction.d.ts +1 -1
- package/dist/react/jsx/utils/parseStringFunction.js +29 -19
- package/dist/react/parse/createDictionaryUpdates.d.ts +1 -1
- package/dist/react/parse/createDictionaryUpdates.js +9 -1
- package/dist/react/parse/createInlineUpdates.js +1 -1
- package/dist/translation/parse.js +2 -2
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# gtx-cli
|
|
2
2
|
|
|
3
|
+
## 2.4.12
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#782](https://github.com/generaltranslation/gt/pull/782) [`155fc2c`](https://github.com/generaltranslation/gt/commit/155fc2c987078b2ffc12c55abb65bb7ff16eb09b) Thanks [@ErnestM1234](https://github.com/ErnestM1234)! - fix: only throw errors in development for invalid icu strings
|
|
8
|
+
|
|
9
|
+
## 2.4.11
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#780](https://github.com/generaltranslation/gt/pull/780) [`c048320`](https://github.com/generaltranslation/gt/commit/c048320ae0daf91bebf65145aba6fb15c2f3612d) Thanks [@ErnestM1234](https://github.com/ErnestM1234)! - Fix CLI parsing for nbsp characters
|
|
14
|
+
|
|
3
15
|
## 2.4.10
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/dist/console/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export declare const warnVariablePropSync: (file: string, attrName: string, valu
|
|
|
3
3
|
export declare const warnHasUnwrappedExpressionSync: (file: string, unwrappedExpressions: string[], id?: string, location?: string) => string;
|
|
4
4
|
export declare const warnNestedTComponent: (file: string, location?: string) => string;
|
|
5
5
|
export declare const warnNonStaticExpressionSync: (file: string, attrName: string, value: string, location?: string) => string;
|
|
6
|
+
export declare const warnInvalidIcuSync: (file: string, value: string, error: string, location?: string) => string;
|
|
6
7
|
export declare const warnTemplateLiteralSync: (file: string, value: string, location?: string) => string;
|
|
7
8
|
export declare const warnNonStringSync: (file: string, value: string, location?: string) => string;
|
|
8
9
|
export declare const warnAsyncUseGT: (file: string, location?: string) => string;
|
package/dist/console/index.js
CHANGED
|
@@ -5,12 +5,14 @@ export const warnVariablePropSync = (file, attrName, value, location) => withLoc
|
|
|
5
5
|
export const warnHasUnwrappedExpressionSync = (file, unwrappedExpressions, id, location) => withLocation(file, `${colorizeComponent('<T>')} component${id ? ` with id ${colorizeIdString(id)}` : ''} has children that could change at runtime. Use a variable component like ${colorizeComponent('<Var>')} to ensure this content is translated.\n${colorizeContent(unwrappedExpressions.join('\n'))}`, location);
|
|
6
6
|
export const warnNestedTComponent = (file, location) => withLocation(file, `Found nested <T> component. <T> components cannot be directly nested.`, location);
|
|
7
7
|
export const warnNonStaticExpressionSync = (file, attrName, value, location) => withLocation(file, `Found non-static expression for attribute ${colorizeIdString(attrName)}: ${colorizeContent(value)}. Change "${colorizeIdString(attrName)}" to ensure this content is translated.`, location);
|
|
8
|
+
export const warnInvalidIcuSync = (file, value, error, location) => withWillErrorInNextVersion(withLocation(file, `Found invalid ICU string: ${colorizeContent(value)}. Change the value to a valid ICU to ensure this content is translated. Error message: ${error}.`, location));
|
|
8
9
|
export const warnTemplateLiteralSync = (file, value, location) => withLocation(file, `Found template literal with quasis (${colorizeContent(value)}). Change the template literal to a string to ensure this content is translated.`, location);
|
|
9
10
|
export const warnNonStringSync = (file, value, location) => withLocation(file, `Found non-string literal (${colorizeContent(value)}). Change the value to a string literal to ensure this content is translated.`, location);
|
|
10
11
|
export const warnAsyncUseGT = (file, location) => withLocation(file, `Found useGT() in an async function. Use getGT() instead, or make the function synchronous.`, location);
|
|
11
12
|
export const warnSyncGetGT = (file, location) => withLocation(file, `Found getGT() in a synchronous function. Use useGT() instead, or make the function async.`, location);
|
|
12
13
|
export const warnTernarySync = (file, location) => withLocation(file, 'Found ternary expression. A Branch component may be more appropriate here.', location);
|
|
13
14
|
export const withLocation = (file, message, location) => `${colorizeFilepath(file)}${location ? ` (${colorizeLine(location)})` : ''}: ${message}`;
|
|
15
|
+
const withWillErrorInNextVersion = (message) => `${message} (This will become an error in the next major version of the CLI.)`;
|
|
14
16
|
// Re-export error messages
|
|
15
17
|
export const noLocalesError = `No locales found! Please provide a list of locales to translate to, or specify them in your gt.config.json file.`;
|
|
16
18
|
export const noDefaultLocaleError = `No default locale found! Please provide a default locale, or specify it in your gt.config.json file.`;
|
|
@@ -15,3 +15,7 @@ export declare function isStaticExpression(expr: t.Expression | t.JSXEmptyExpres
|
|
|
15
15
|
value?: string;
|
|
16
16
|
};
|
|
17
17
|
export declare function isStaticValue(expr: t.Expression | t.JSXEmptyExpression): boolean;
|
|
18
|
+
export declare function isValidIcu(string: string): {
|
|
19
|
+
isValid: boolean;
|
|
20
|
+
error?: string;
|
|
21
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as t from '@babel/types';
|
|
2
|
+
import { parse } from '@formatjs/icu-messageformat-parser';
|
|
2
3
|
import generateModule from '@babel/generator';
|
|
3
4
|
// Handle CommonJS/ESM interop
|
|
4
5
|
const generate = generateModule.default || generateModule;
|
|
@@ -83,3 +84,15 @@ export function isStaticValue(expr) {
|
|
|
83
84
|
}
|
|
84
85
|
return false;
|
|
85
86
|
}
|
|
87
|
+
export function isValidIcu(string) {
|
|
88
|
+
try {
|
|
89
|
+
parse(string);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
return {
|
|
93
|
+
isValid: false,
|
|
94
|
+
error: error instanceof Error ? error.message : String(error),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return { isValid: true };
|
|
98
|
+
}
|
|
@@ -1,14 +1,41 @@
|
|
|
1
1
|
import { isAcceptedPluralForm } from 'generaltranslation/internal';
|
|
2
|
+
// JSX whitespace characters (space, tab, newline, carriage return)
|
|
3
|
+
// Does NOT include non-breaking space (U+00A0) which should be preserved
|
|
4
|
+
const isJsxWhitespace = (char) => {
|
|
5
|
+
return char === ' ' || char === '\t' || char === '\n' || char === '\r';
|
|
6
|
+
};
|
|
7
|
+
const trimJsxWhitespace = (str, side = 'both') => {
|
|
8
|
+
let start = 0;
|
|
9
|
+
let end = str.length;
|
|
10
|
+
if (side === 'start' || side === 'both') {
|
|
11
|
+
while (start < end && isJsxWhitespace(str[start])) {
|
|
12
|
+
start++;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
if (side === 'end' || side === 'both') {
|
|
16
|
+
while (end > start && isJsxWhitespace(str[end - 1])) {
|
|
17
|
+
end--;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return str.slice(start, end);
|
|
21
|
+
};
|
|
22
|
+
const hasNonJsxWhitespace = (str) => {
|
|
23
|
+
for (const char of str) {
|
|
24
|
+
if (!isJsxWhitespace(char))
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
};
|
|
2
29
|
export function trimJsxStringChild(child, index, childrenTypes) {
|
|
3
30
|
// Normalize line endings to \n for consistency across platforms
|
|
4
31
|
let result = child.replace(/\r\n|\r/g, '\n');
|
|
5
|
-
// Collapse multiple spaces/tabs into a single space
|
|
32
|
+
// Collapse multiple spaces/tabs into a single space (but not nbsp)
|
|
6
33
|
result = result.replace(/[\t ]+/g, ' ');
|
|
7
34
|
let newResult = '';
|
|
8
35
|
let newline = false;
|
|
9
36
|
for (const char of result) {
|
|
10
37
|
if (char === '\n') {
|
|
11
|
-
if (newResult
|
|
38
|
+
if (hasNonJsxWhitespace(newResult))
|
|
12
39
|
newResult += ' ';
|
|
13
40
|
else
|
|
14
41
|
newResult = '';
|
|
@@ -19,15 +46,15 @@ export function trimJsxStringChild(child, index, childrenTypes) {
|
|
|
19
46
|
newResult += char;
|
|
20
47
|
continue;
|
|
21
48
|
}
|
|
22
|
-
if (char
|
|
49
|
+
if (isJsxWhitespace(char))
|
|
23
50
|
continue;
|
|
24
51
|
newResult += char;
|
|
25
52
|
newline = false;
|
|
26
53
|
}
|
|
27
54
|
if (newline)
|
|
28
|
-
newResult = newResult
|
|
55
|
+
newResult = trimJsxWhitespace(newResult, 'end');
|
|
29
56
|
result = newResult;
|
|
30
|
-
// Collapse multiple spaces/tabs into a single space
|
|
57
|
+
// Collapse multiple spaces/tabs into a single space (but not nbsp)
|
|
31
58
|
result = result.replace(/[\t ]+/g, ' ');
|
|
32
59
|
return result;
|
|
33
60
|
}
|
|
@@ -18,4 +18,4 @@ export declare function clearParsingCaches(): void;
|
|
|
18
18
|
* - const { home } = getInfo(t); // getInfo is imported from './constants'
|
|
19
19
|
* - This will parse constants.ts to find translation calls within getInfo function
|
|
20
20
|
*/
|
|
21
|
-
export declare function parseStrings(importName: string, originalName: string, path: NodePath, updates: Updates, errors: string[], file: string, parsingOptions: ParsingConfigOptions): void;
|
|
21
|
+
export declare function parseStrings(importName: string, originalName: string, path: NodePath, updates: Updates, errors: string[], warnings: Set<string>, file: string, parsingOptions: ParsingConfigOptions): void;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as t from '@babel/types';
|
|
2
|
-
import { isStaticExpression } from '../evaluateJsx.js';
|
|
2
|
+
import { isStaticExpression, isValidIcu } from '../evaluateJsx.js';
|
|
3
3
|
import { GT_ATTRIBUTES_WITH_SUGAR, MSG_TRANSLATION_HOOK, INLINE_TRANSLATION_HOOK, INLINE_TRANSLATION_HOOK_ASYNC, mapAttributeName, INLINE_MESSAGE_HOOK, INLINE_MESSAGE_HOOK_ASYNC, } from './constants.js';
|
|
4
|
-
import { warnNonStaticExpressionSync, warnNonStringSync, warnTemplateLiteralSync, warnAsyncUseGT, warnSyncGetGT, } from '../../../console/index.js';
|
|
4
|
+
import { warnNonStaticExpressionSync, warnNonStringSync, warnTemplateLiteralSync, warnAsyncUseGT, warnSyncGetGT, warnInvalidIcuSync, } from '../../../console/index.js';
|
|
5
5
|
import generateModule from '@babel/generator';
|
|
6
6
|
import traverseModule from '@babel/traverse';
|
|
7
7
|
// Handle CommonJS/ESM interop
|
|
@@ -43,13 +43,21 @@ export function clearParsingCaches() {
|
|
|
43
43
|
* - Metadata extraction from options object
|
|
44
44
|
* - Error reporting for non-static expressions and template literals with expressions
|
|
45
45
|
*/
|
|
46
|
-
function processTranslationCall(tPath, updates, errors, file, ignoreAdditionalData, ignoreDynamicContent) {
|
|
46
|
+
function processTranslationCall(tPath, updates, errors, warnings, file, ignoreAdditionalData, ignoreDynamicContent, ignoreInvalidIcu) {
|
|
47
47
|
if (tPath.parent.type === 'CallExpression' &&
|
|
48
48
|
tPath.parent.arguments.length > 0) {
|
|
49
49
|
const arg = tPath.parent.arguments[0];
|
|
50
50
|
if (arg.type === 'StringLiteral' ||
|
|
51
51
|
(t.isTemplateLiteral(arg) && arg.expressions.length === 0)) {
|
|
52
52
|
const source = arg.type === 'StringLiteral' ? arg.value : arg.quasis[0].value.raw;
|
|
53
|
+
// Validate is ICU
|
|
54
|
+
if (!ignoreInvalidIcu) {
|
|
55
|
+
const { isValid, error } = isValidIcu(source);
|
|
56
|
+
if (!isValid) {
|
|
57
|
+
warnings.add(warnInvalidIcuSync(file, source, error ?? 'Unknown error', `${arg.loc?.start?.line}:${arg.loc?.start?.column}`));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
53
61
|
// get metadata and id from options
|
|
54
62
|
const options = tPath.parent.arguments[1];
|
|
55
63
|
const metadata = {};
|
|
@@ -174,11 +182,11 @@ function resolveVariableAliases(scope, variableName, visited = new Set()) {
|
|
|
174
182
|
* This covers both direct translation calls (t('hello')) and prop drilling
|
|
175
183
|
* where the translation callback is passed to other functions (getData(t)).
|
|
176
184
|
*/
|
|
177
|
-
function handleFunctionCall(tPath, updates, errors, file, importMap, ignoreAdditionalData, ignoreDynamicContent, parsingOptions) {
|
|
185
|
+
function handleFunctionCall(tPath, updates, errors, warnings, file, importMap, ignoreAdditionalData, ignoreDynamicContent, ignoreInvalidIcu, parsingOptions) {
|
|
178
186
|
if (tPath.parent.type === 'CallExpression' &&
|
|
179
187
|
tPath.parent.callee === tPath.node) {
|
|
180
188
|
// Direct translation call: t('hello')
|
|
181
|
-
processTranslationCall(tPath, updates, errors, file, ignoreAdditionalData, ignoreDynamicContent);
|
|
189
|
+
processTranslationCall(tPath, updates, errors, warnings, file, ignoreAdditionalData, ignoreDynamicContent, ignoreInvalidIcu);
|
|
182
190
|
}
|
|
183
191
|
else if (tPath.parent.type === 'CallExpression' &&
|
|
184
192
|
t.isExpression(tPath.node) &&
|
|
@@ -190,7 +198,7 @@ function handleFunctionCall(tPath, updates, errors, file, importMap, ignoreAddit
|
|
|
190
198
|
const calleeBinding = tPath.scope.getBinding(callee.name);
|
|
191
199
|
if (calleeBinding && calleeBinding.path.isFunction()) {
|
|
192
200
|
const functionPath = calleeBinding.path;
|
|
193
|
-
processFunctionIfMatches(callee.name, argIndex, functionPath.node, functionPath, updates, errors, file, ignoreAdditionalData, ignoreDynamicContent, parsingOptions);
|
|
201
|
+
processFunctionIfMatches(callee.name, argIndex, functionPath.node, functionPath, updates, errors, warnings, file, ignoreAdditionalData, ignoreDynamicContent, ignoreInvalidIcu, parsingOptions);
|
|
194
202
|
}
|
|
195
203
|
// Handle arrow functions assigned to variables: const getData = (t) => {...}
|
|
196
204
|
else if (calleeBinding &&
|
|
@@ -199,14 +207,14 @@ function handleFunctionCall(tPath, updates, errors, file, importMap, ignoreAddit
|
|
|
199
207
|
(t.isArrowFunctionExpression(calleeBinding.path.node.init) ||
|
|
200
208
|
t.isFunctionExpression(calleeBinding.path.node.init))) {
|
|
201
209
|
const initPath = calleeBinding.path.get('init');
|
|
202
|
-
processFunctionIfMatches(callee.name, argIndex, calleeBinding.path.node.init, initPath, updates, errors, file, ignoreAdditionalData, ignoreDynamicContent, parsingOptions);
|
|
210
|
+
processFunctionIfMatches(callee.name, argIndex, calleeBinding.path.node.init, initPath, updates, errors, warnings, file, ignoreAdditionalData, ignoreDynamicContent, ignoreInvalidIcu, parsingOptions);
|
|
203
211
|
}
|
|
204
212
|
// If not found locally, check if it's an imported function
|
|
205
213
|
else if (importMap.has(callee.name)) {
|
|
206
214
|
const importPath = importMap.get(callee.name);
|
|
207
215
|
const resolvedPath = resolveImportPath(file, importPath, parsingOptions);
|
|
208
216
|
if (resolvedPath) {
|
|
209
|
-
processFunctionInFile(resolvedPath, callee.name, argIndex, updates, errors, ignoreAdditionalData, ignoreDynamicContent, parsingOptions);
|
|
217
|
+
processFunctionInFile(resolvedPath, callee.name, argIndex, updates, errors, warnings, ignoreAdditionalData, ignoreDynamicContent, ignoreInvalidIcu, parsingOptions);
|
|
210
218
|
}
|
|
211
219
|
}
|
|
212
220
|
}
|
|
@@ -217,12 +225,12 @@ function handleFunctionCall(tPath, updates, errors, file, importMap, ignoreAddit
|
|
|
217
225
|
* Validates the function has enough parameters and traces how the translation callback
|
|
218
226
|
* is used within that function's body.
|
|
219
227
|
*/
|
|
220
|
-
function processFunctionIfMatches(_functionName, argIndex, functionNode, functionPath, updates, errors, filePath, ignoreAdditionalData, ignoreDynamicContent, parsingOptions) {
|
|
228
|
+
function processFunctionIfMatches(_functionName, argIndex, functionNode, functionPath, updates, errors, warnings, filePath, ignoreAdditionalData, ignoreDynamicContent, ignoreInvalidIcu, parsingOptions) {
|
|
221
229
|
if (functionNode.params.length > argIndex) {
|
|
222
230
|
const param = functionNode.params[argIndex];
|
|
223
231
|
const paramName = extractParameterName(param);
|
|
224
232
|
if (paramName) {
|
|
225
|
-
findFunctionParameterUsage(functionPath, paramName, updates, errors, filePath, ignoreAdditionalData, ignoreDynamicContent, parsingOptions);
|
|
233
|
+
findFunctionParameterUsage(functionPath, paramName, updates, errors, warnings, filePath, ignoreAdditionalData, ignoreDynamicContent, ignoreInvalidIcu, parsingOptions);
|
|
226
234
|
}
|
|
227
235
|
}
|
|
228
236
|
}
|
|
@@ -234,7 +242,7 @@ function processFunctionIfMatches(_functionName, argIndex, functionNode, functio
|
|
|
234
242
|
* Example: In function getInfo(t) { return t('hello'); }, this finds the t('hello') call.
|
|
235
243
|
* Example: In function getData(t) { return getFooter(t); }, this finds and traces into getFooter.
|
|
236
244
|
*/
|
|
237
|
-
function findFunctionParameterUsage(functionPath, parameterName, updates, errors, file, ignoreAdditionalData, ignoreDynamicContent, parsingOptions) {
|
|
245
|
+
function findFunctionParameterUsage(functionPath, parameterName, updates, errors, warnings, file, ignoreAdditionalData, ignoreDynamicContent, ignoreInvalidIcu, parsingOptions) {
|
|
238
246
|
// Look for the function body and find all usages of the parameter
|
|
239
247
|
if (functionPath.isFunction()) {
|
|
240
248
|
const functionScope = functionPath.scope;
|
|
@@ -249,7 +257,7 @@ function findFunctionParameterUsage(functionPath, parameterName, updates, errors
|
|
|
249
257
|
const binding = functionScope.bindings[name];
|
|
250
258
|
if (binding) {
|
|
251
259
|
binding.referencePaths.forEach((refPath) => {
|
|
252
|
-
handleFunctionCall(refPath, updates, errors, file, importMap, ignoreAdditionalData, ignoreDynamicContent, parsingOptions);
|
|
260
|
+
handleFunctionCall(refPath, updates, errors, warnings, file, importMap, ignoreAdditionalData, ignoreDynamicContent, ignoreInvalidIcu, parsingOptions);
|
|
253
261
|
});
|
|
254
262
|
}
|
|
255
263
|
});
|
|
@@ -371,7 +379,7 @@ function resolveImportPath(currentFile, importPath, parsingOptions) {
|
|
|
371
379
|
*
|
|
372
380
|
* If the function is not found in the file, follows re-exports (export * from './other')
|
|
373
381
|
*/
|
|
374
|
-
function processFunctionInFile(filePath, functionName, argIndex, updates, errors, ignoreAdditionalData, ignoreDynamicContent, parsingOptions, visited = new Set()) {
|
|
382
|
+
function processFunctionInFile(filePath, functionName, argIndex, updates, errors, warnings, ignoreAdditionalData, ignoreDynamicContent, ignoreInvalidIcu, parsingOptions, visited = new Set()) {
|
|
375
383
|
// Check cache first to avoid redundant parsing
|
|
376
384
|
const cacheKey = `${filePath}::${functionName}::${argIndex}`;
|
|
377
385
|
if (processFunctionCache.has(cacheKey)) {
|
|
@@ -395,7 +403,7 @@ function processFunctionInFile(filePath, functionName, argIndex, updates, errors
|
|
|
395
403
|
FunctionDeclaration(path) {
|
|
396
404
|
if (path.node.id?.name === functionName) {
|
|
397
405
|
found = true;
|
|
398
|
-
processFunctionIfMatches(functionName, argIndex, path.node, path, updates, errors, filePath, ignoreAdditionalData, ignoreDynamicContent, parsingOptions);
|
|
406
|
+
processFunctionIfMatches(functionName, argIndex, path.node, path, updates, errors, warnings, filePath, ignoreAdditionalData, ignoreDynamicContent, ignoreInvalidIcu, parsingOptions);
|
|
399
407
|
}
|
|
400
408
|
},
|
|
401
409
|
// Handle variable declarations: const getInfo = (t) => { ... }
|
|
@@ -407,7 +415,7 @@ function processFunctionInFile(filePath, functionName, argIndex, updates, errors
|
|
|
407
415
|
t.isFunctionExpression(path.node.init))) {
|
|
408
416
|
found = true;
|
|
409
417
|
const initPath = path.get('init');
|
|
410
|
-
processFunctionIfMatches(functionName, argIndex, path.node.init, initPath, updates, errors, filePath, ignoreAdditionalData, ignoreDynamicContent, parsingOptions);
|
|
418
|
+
processFunctionIfMatches(functionName, argIndex, path.node.init, initPath, updates, errors, warnings, filePath, ignoreAdditionalData, ignoreDynamicContent, ignoreInvalidIcu, parsingOptions);
|
|
411
419
|
}
|
|
412
420
|
},
|
|
413
421
|
// Collect re-exports: export * from './other'
|
|
@@ -440,7 +448,7 @@ function processFunctionInFile(filePath, functionName, argIndex, updates, errors
|
|
|
440
448
|
for (const reExportPath of reExports) {
|
|
441
449
|
const resolvedPath = resolveImportPath(filePath, reExportPath, parsingOptions);
|
|
442
450
|
if (resolvedPath) {
|
|
443
|
-
processFunctionInFile(resolvedPath, functionName, argIndex, updates, errors, ignoreAdditionalData, ignoreDynamicContent, parsingOptions, visited);
|
|
451
|
+
processFunctionInFile(resolvedPath, functionName, argIndex, updates, errors, warnings, ignoreAdditionalData, ignoreDynamicContent, ignoreInvalidIcu, parsingOptions, visited);
|
|
444
452
|
}
|
|
445
453
|
}
|
|
446
454
|
}
|
|
@@ -466,7 +474,7 @@ function processFunctionInFile(filePath, functionName, argIndex, updates, errors
|
|
|
466
474
|
* - const { home } = getInfo(t); // getInfo is imported from './constants'
|
|
467
475
|
* - This will parse constants.ts to find translation calls within getInfo function
|
|
468
476
|
*/
|
|
469
|
-
export function parseStrings(importName, originalName, path, updates, errors, file, parsingOptions) {
|
|
477
|
+
export function parseStrings(importName, originalName, path, updates, errors, warnings, file, parsingOptions) {
|
|
470
478
|
// First, collect all imports in this file to track cross-file function calls
|
|
471
479
|
const importMap = buildImportMap(path.scope.getProgramParent().path);
|
|
472
480
|
const referencePaths = path.scope.bindings[importName]?.referencePaths || [];
|
|
@@ -475,10 +483,11 @@ export function parseStrings(importName, originalName, path, updates, errors, fi
|
|
|
475
483
|
if (originalName === MSG_TRANSLATION_HOOK) {
|
|
476
484
|
const ignoreAdditionalData = false;
|
|
477
485
|
const ignoreDynamicContent = false;
|
|
486
|
+
const ignoreInvalidIcu = false;
|
|
478
487
|
// Check if this is a direct call to msg('string')
|
|
479
488
|
if (refPath.parent.type === 'CallExpression' &&
|
|
480
489
|
refPath.parent.callee === refPath.node) {
|
|
481
|
-
processTranslationCall(refPath, updates, errors, file, ignoreAdditionalData, ignoreDynamicContent);
|
|
490
|
+
processTranslationCall(refPath, updates, errors, warnings, file, ignoreAdditionalData, ignoreDynamicContent, ignoreInvalidIcu);
|
|
482
491
|
}
|
|
483
492
|
continue;
|
|
484
493
|
}
|
|
@@ -505,6 +514,7 @@ export function parseStrings(importName, originalName, path, updates, errors, fi
|
|
|
505
514
|
originalName === INLINE_MESSAGE_HOOK_ASYNC;
|
|
506
515
|
const ignoreAdditionalData = isMessageHook;
|
|
507
516
|
const ignoreDynamicContent = isMessageHook;
|
|
517
|
+
const ignoreInvalidIcu = isMessageHook;
|
|
508
518
|
const effectiveParent = parentPath?.node.type === 'AwaitExpression'
|
|
509
519
|
? parentPath.parentPath
|
|
510
520
|
: parentPath;
|
|
@@ -521,7 +531,7 @@ export function parseStrings(importName, originalName, path, updates, errors, fi
|
|
|
521
531
|
allTranslationNames.forEach((name) => {
|
|
522
532
|
const tReferencePaths = variableScope.bindings[name]?.referencePaths || [];
|
|
523
533
|
for (const tPath of tReferencePaths) {
|
|
524
|
-
handleFunctionCall(tPath, updates, errors, file, importMap, ignoreAdditionalData, ignoreDynamicContent, parsingOptions);
|
|
534
|
+
handleFunctionCall(tPath, updates, errors, warnings, file, importMap, ignoreAdditionalData, ignoreDynamicContent, ignoreInvalidIcu, parsingOptions);
|
|
525
535
|
}
|
|
526
536
|
});
|
|
527
537
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { BuildOptions } from 'esbuild';
|
|
2
2
|
import { Updates } from '../../types/index.js';
|
|
3
|
-
export declare function createDictionaryUpdates(dictionaryPath: string, esbuildConfig?: BuildOptions): Promise<Updates>;
|
|
3
|
+
export declare function createDictionaryUpdates(dictionaryPath: string, warnings: string[], esbuildConfig?: BuildOptions): Promise<Updates>;
|
|
@@ -8,7 +8,9 @@ import { hashSource } from 'generaltranslation/id';
|
|
|
8
8
|
import getEntryAndMetadata from '../utils/getEntryAndMetadata.js';
|
|
9
9
|
import { logError } from '../../console/logging.js';
|
|
10
10
|
import { randomUUID } from 'node:crypto';
|
|
11
|
-
|
|
11
|
+
import { isValidIcu } from '../jsx/evaluateJsx.js';
|
|
12
|
+
import { warnInvalidIcuSync } from '../../console/index.js';
|
|
13
|
+
export async function createDictionaryUpdates(dictionaryPath, warnings, esbuildConfig) {
|
|
12
14
|
let dictionary;
|
|
13
15
|
// ---- HANDLE JSON STRING DICTIONARY ----- //
|
|
14
16
|
if (dictionaryPath.endsWith('.json')) {
|
|
@@ -45,6 +47,12 @@ export async function createDictionaryUpdates(dictionaryPath, esbuildConfig) {
|
|
|
45
47
|
for (const id of Object.keys(dictionary)) {
|
|
46
48
|
const { entry, metadata: props, // context, etc.
|
|
47
49
|
} = getEntryAndMetadata(dictionary[id]);
|
|
50
|
+
// Validate ICU
|
|
51
|
+
const { isValid, error } = isValidIcu(entry);
|
|
52
|
+
if (!isValid) {
|
|
53
|
+
warnings.push(warnInvalidIcuSync(dictionaryPath, entry, error ?? 'Unknown error'));
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
48
56
|
// Map $context to context
|
|
49
57
|
const context = props?.$context;
|
|
50
58
|
const metadata = {
|
|
@@ -91,7 +91,7 @@ export async function createInlineUpdates(pkg, validate, filePatterns, parsingOp
|
|
|
91
91
|
});
|
|
92
92
|
// Process translation functions asynchronously
|
|
93
93
|
for (const { localName: name, originalName, path } of translationPaths) {
|
|
94
|
-
parseStrings(name, originalName, path, updates, errors, file, parsingOptions);
|
|
94
|
+
parseStrings(name, originalName, path, updates, errors, warnings, file, parsingOptions);
|
|
95
95
|
}
|
|
96
96
|
// Parse <T> components
|
|
97
97
|
traverse(ast, {
|
|
@@ -25,7 +25,7 @@ export async function createUpdates(options, src, sourceDictionary, pkg, validat
|
|
|
25
25
|
if (sourceDictionary.endsWith('.json')) {
|
|
26
26
|
updates = [
|
|
27
27
|
...updates,
|
|
28
|
-
...(await createDictionaryUpdates(sourceDictionary)),
|
|
28
|
+
...(await createDictionaryUpdates(sourceDictionary, warnings)),
|
|
29
29
|
];
|
|
30
30
|
}
|
|
31
31
|
else {
|
|
@@ -43,7 +43,7 @@ export async function createUpdates(options, src, sourceDictionary, pkg, validat
|
|
|
43
43
|
}
|
|
44
44
|
updates = [
|
|
45
45
|
...updates,
|
|
46
|
-
...(await createDictionaryUpdates(sourceDictionary, esbuildConfig)),
|
|
46
|
+
...(await createDictionaryUpdates(sourceDictionary, warnings, esbuildConfig)),
|
|
47
47
|
];
|
|
48
48
|
}
|
|
49
49
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gtx-cli",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.12",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"bin": "dist/main.js",
|
|
6
6
|
"files": [
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
"@babel/plugin-transform-react-jsx": "^7.25.9",
|
|
69
69
|
"@babel/traverse": "^7.25.7",
|
|
70
70
|
"@clack/prompts": "^1.0.0-alpha.1",
|
|
71
|
+
"@formatjs/icu-messageformat-parser": "^2.11.4",
|
|
71
72
|
"chalk": "^5.4.1",
|
|
72
73
|
"commander": "^12.1.0",
|
|
73
74
|
"dotenv": "^16.4.5",
|