gtx-cli 2.4.5 → 2.4.7
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/config/generateSettings.js +4 -0
- package/dist/formats/files/fileMapping.js +33 -0
- package/dist/fs/config/parseFilesConfig.d.ts +1 -1
- package/dist/fs/config/parseFilesConfig.js +3 -2
- package/dist/react/jsx/utils/parseStringFunction.d.ts +6 -1
- package/dist/react/jsx/utils/parseStringFunction.js +143 -24
- package/dist/react/parse/createInlineUpdates.d.ts +2 -1
- package/dist/react/parse/createInlineUpdates.js +2 -2
- package/dist/translation/parse.d.ts +2 -1
- package/dist/translation/parse.js +2 -2
- package/dist/translation/stage.js +1 -1
- package/dist/translation/validate.js +2 -2
- package/dist/types/index.d.ts +4 -2
- package/dist/types/parsing.d.ts +3 -0
- package/dist/types/parsing.js +1 -0
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# gtx-cli
|
|
2
2
|
|
|
3
|
+
## 2.4.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#767](https://github.com/generaltranslation/gt/pull/767) [`b27a947`](https://github.com/generaltranslation/gt/commit/b27a947a46d2ad802278d79d45d25cdccd7193d5) Thanks [@brian-lou](https://github.com/brian-lou)! - Fix monorepo in-line string resolution
|
|
8
|
+
|
|
9
|
+
## 2.4.6
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#763](https://github.com/generaltranslation/gt/pull/763) [`b6a79a8`](https://github.com/generaltranslation/gt/commit/b6a79a868630725eb1106faaa2c385c305891e9c) Thanks [@fernando-aviles](https://github.com/fernando-aviles)! - Allow lists for overrides in gt.config.json
|
|
14
|
+
|
|
3
15
|
## 2.4.5
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -155,6 +155,10 @@ export async function generateSettings(flags, cwd = process.cwd()) {
|
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
|
+
// Add parsing options if not provided
|
|
159
|
+
mergedOptions.parsingOptions = mergedOptions.parsingOptions || {};
|
|
160
|
+
mergedOptions.parsingOptions.conditionNames = mergedOptions.parsingOptions
|
|
161
|
+
.conditionNames || ['browser', 'module', 'import', 'require', 'default'];
|
|
158
162
|
// if there's no existing config file, creates one
|
|
159
163
|
// does not include the API key to avoid exposing it
|
|
160
164
|
if (!fs.existsSync(mergedOptions.config)) {
|
|
@@ -44,6 +44,39 @@ export function createFileMapping(filePaths, placeholderPaths, transformPaths, t
|
|
|
44
44
|
return path.join(directory, transformedFileName);
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
|
+
else if (Array.isArray(transformPath)) {
|
|
48
|
+
// transformPath is an array of TransformOption objects
|
|
49
|
+
const targetLocaleProperties = getLocaleProperties(locale);
|
|
50
|
+
const defaultLocaleProperties = getLocaleProperties(defaultLocale);
|
|
51
|
+
translatedFiles = translatedFiles.map((filePath) => {
|
|
52
|
+
const relativePath = getRelative(filePath);
|
|
53
|
+
// Try each transform in order until one matches
|
|
54
|
+
for (const transform of transformPath) {
|
|
55
|
+
if (!transform.replace || typeof transform.replace !== 'string') {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
// Replace all locale property placeholders in the replace string
|
|
59
|
+
const replaceString = replaceLocalePlaceholders(transform.replace, targetLocaleProperties);
|
|
60
|
+
if (transform.match && typeof transform.match === 'string') {
|
|
61
|
+
// Replace locale placeholders in the match string using defaultLocale properties
|
|
62
|
+
let matchString = transform.match;
|
|
63
|
+
matchString = replaceLocalePlaceholders(matchString, defaultLocaleProperties);
|
|
64
|
+
const regex = new RegExp(matchString);
|
|
65
|
+
if (regex.test(relativePath)) {
|
|
66
|
+
// This transform matches, apply it and break
|
|
67
|
+
const transformedPath = relativePath.replace(new RegExp(matchString, 'g'), replaceString);
|
|
68
|
+
return path.resolve(transformedPath);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// No match provided: treat as a direct replacement (override)
|
|
73
|
+
return path.resolve(replaceString);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// If no transforms matched, return the original path
|
|
77
|
+
return filePath;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
47
80
|
else {
|
|
48
81
|
// transformPath is an object
|
|
49
82
|
const targetLocaleProperties = getLocaleProperties(locale);
|
|
@@ -21,7 +21,7 @@ export declare function resolveFiles(files: FilesOptions, locale: string, locale
|
|
|
21
21
|
placeholderPaths: ResolvedFiles;
|
|
22
22
|
transformPaths: TransformFiles;
|
|
23
23
|
};
|
|
24
|
-
export declare function expandGlobPatterns(cwd: string, includePatterns: string[], excludePatterns: string[], locale: string, locales: string[], transformPatterns?: TransformOption | string, compositePatterns?: string[]): {
|
|
24
|
+
export declare function expandGlobPatterns(cwd: string, includePatterns: string[], excludePatterns: string[], locale: string, locales: string[], transformPatterns?: TransformOption | string | TransformOption[], compositePatterns?: string[]): {
|
|
25
25
|
resolvedPaths: string[];
|
|
26
26
|
placeholderPaths: string[];
|
|
27
27
|
};
|
|
@@ -41,8 +41,9 @@ export function resolveFiles(files, locale, locales, cwd, compositePatterns) {
|
|
|
41
41
|
// ==== TRANSFORMS ==== //
|
|
42
42
|
const transform = files[fileType]?.transform;
|
|
43
43
|
if (transform &&
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
(typeof transform === 'string' ||
|
|
45
|
+
typeof transform === 'object' ||
|
|
46
|
+
Array.isArray(transform))) {
|
|
46
47
|
transformPaths[fileType] = transform;
|
|
47
48
|
}
|
|
48
49
|
// ==== PLACEHOLDERS ==== //
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { NodePath } from '@babel/traverse';
|
|
2
2
|
import { Updates } from '../../../types/index.js';
|
|
3
|
+
import type { ParsingConfigOptions } from '../../../types/parsing.js';
|
|
4
|
+
/**
|
|
5
|
+
* Clears all caches. Useful for testing or when file system changes.
|
|
6
|
+
*/
|
|
7
|
+
export declare function clearParsingCaches(): void;
|
|
3
8
|
/**
|
|
4
9
|
* Main entry point for parsing translation strings from useGT() and getGT() calls.
|
|
5
10
|
*
|
|
@@ -13,4 +18,4 @@ import { Updates } from '../../../types/index.js';
|
|
|
13
18
|
* - const { home } = getInfo(t); // getInfo is imported from './constants'
|
|
14
19
|
* - This will parse constants.ts to find translation calls within getInfo function
|
|
15
20
|
*/
|
|
16
|
-
export declare function parseStrings(importName: string, originalName: string, path: NodePath, updates: Updates, errors: string[], file: string): void;
|
|
21
|
+
export declare function parseStrings(importName: string, originalName: string, path: NodePath, updates: Updates, errors: string[], file: string, parsingOptions: ParsingConfigOptions): void;
|
|
@@ -12,6 +12,27 @@ import path from 'node:path';
|
|
|
12
12
|
import { parse } from '@babel/parser';
|
|
13
13
|
import { createMatchPath, loadConfig } from 'tsconfig-paths';
|
|
14
14
|
import resolve from 'resolve';
|
|
15
|
+
import enhancedResolve from 'enhanced-resolve';
|
|
16
|
+
const { ResolverFactory } = enhancedResolve;
|
|
17
|
+
/**
|
|
18
|
+
* Cache for resolved import paths to avoid redundant I/O operations.
|
|
19
|
+
* Key: `${currentFile}::${importPath}`
|
|
20
|
+
* Value: resolved absolute path or null
|
|
21
|
+
*/
|
|
22
|
+
const resolveImportPathCache = new Map();
|
|
23
|
+
/**
|
|
24
|
+
* Cache for processed functions to avoid re-parsing the same files.
|
|
25
|
+
* Key: `${filePath}::${functionName}::${argIndex}`
|
|
26
|
+
* Value: boolean indicating whether the function was found and processed
|
|
27
|
+
*/
|
|
28
|
+
const processFunctionCache = new Map();
|
|
29
|
+
/**
|
|
30
|
+
* Clears all caches. Useful for testing or when file system changes.
|
|
31
|
+
*/
|
|
32
|
+
export function clearParsingCaches() {
|
|
33
|
+
resolveImportPathCache.clear();
|
|
34
|
+
processFunctionCache.clear();
|
|
35
|
+
}
|
|
15
36
|
/**
|
|
16
37
|
* Processes a single translation function call (e.g., t('hello world', { id: 'greeting' })).
|
|
17
38
|
* Extracts the translatable string content and metadata, then adds it to the updates array.
|
|
@@ -71,12 +92,16 @@ function processTranslationCall(tPath, updates, errors, file, ignoreAdditionalDa
|
|
|
71
92
|
}
|
|
72
93
|
}
|
|
73
94
|
/**
|
|
74
|
-
* Extracts the parameter name from a function parameter node, handling TypeScript annotations.
|
|
95
|
+
* Extracts the parameter name from a function parameter node, handling TypeScript annotations and default values.
|
|
75
96
|
*/
|
|
76
97
|
function extractParameterName(param) {
|
|
77
98
|
if (t.isIdentifier(param)) {
|
|
78
99
|
return param.name;
|
|
79
100
|
}
|
|
101
|
+
// Handle parameters with default values: (gt = () => {})
|
|
102
|
+
if (t.isAssignmentPattern(param) && t.isIdentifier(param.left)) {
|
|
103
|
+
return param.left.name;
|
|
104
|
+
}
|
|
80
105
|
return null;
|
|
81
106
|
}
|
|
82
107
|
/**
|
|
@@ -149,7 +174,7 @@ function resolveVariableAliases(scope, variableName, visited = new Set()) {
|
|
|
149
174
|
* This covers both direct translation calls (t('hello')) and prop drilling
|
|
150
175
|
* where the translation callback is passed to other functions (getData(t)).
|
|
151
176
|
*/
|
|
152
|
-
function handleFunctionCall(tPath, updates, errors, file, importMap, ignoreAdditionalData, ignoreDynamicContent) {
|
|
177
|
+
function handleFunctionCall(tPath, updates, errors, file, importMap, ignoreAdditionalData, ignoreDynamicContent, parsingOptions) {
|
|
153
178
|
if (tPath.parent.type === 'CallExpression' &&
|
|
154
179
|
tPath.parent.callee === tPath.node) {
|
|
155
180
|
// Direct translation call: t('hello')
|
|
@@ -165,7 +190,7 @@ function handleFunctionCall(tPath, updates, errors, file, importMap, ignoreAddit
|
|
|
165
190
|
const calleeBinding = tPath.scope.getBinding(callee.name);
|
|
166
191
|
if (calleeBinding && calleeBinding.path.isFunction()) {
|
|
167
192
|
const functionPath = calleeBinding.path;
|
|
168
|
-
processFunctionIfMatches(callee.name, argIndex, functionPath.node, functionPath, updates, errors, file, ignoreAdditionalData, ignoreDynamicContent);
|
|
193
|
+
processFunctionIfMatches(callee.name, argIndex, functionPath.node, functionPath, updates, errors, file, ignoreAdditionalData, ignoreDynamicContent, parsingOptions);
|
|
169
194
|
}
|
|
170
195
|
// Handle arrow functions assigned to variables: const getData = (t) => {...}
|
|
171
196
|
else if (calleeBinding &&
|
|
@@ -174,14 +199,14 @@ function handleFunctionCall(tPath, updates, errors, file, importMap, ignoreAddit
|
|
|
174
199
|
(t.isArrowFunctionExpression(calleeBinding.path.node.init) ||
|
|
175
200
|
t.isFunctionExpression(calleeBinding.path.node.init))) {
|
|
176
201
|
const initPath = calleeBinding.path.get('init');
|
|
177
|
-
processFunctionIfMatches(callee.name, argIndex, calleeBinding.path.node.init, initPath, updates, errors, file, ignoreAdditionalData, ignoreDynamicContent);
|
|
202
|
+
processFunctionIfMatches(callee.name, argIndex, calleeBinding.path.node.init, initPath, updates, errors, file, ignoreAdditionalData, ignoreDynamicContent, parsingOptions);
|
|
178
203
|
}
|
|
179
204
|
// If not found locally, check if it's an imported function
|
|
180
205
|
else if (importMap.has(callee.name)) {
|
|
181
206
|
const importPath = importMap.get(callee.name);
|
|
182
|
-
const resolvedPath = resolveImportPath(file, importPath);
|
|
207
|
+
const resolvedPath = resolveImportPath(file, importPath, parsingOptions);
|
|
183
208
|
if (resolvedPath) {
|
|
184
|
-
|
|
209
|
+
processFunctionInFile(resolvedPath, callee.name, argIndex, updates, errors, ignoreAdditionalData, ignoreDynamicContent, parsingOptions);
|
|
185
210
|
}
|
|
186
211
|
}
|
|
187
212
|
}
|
|
@@ -192,12 +217,12 @@ function handleFunctionCall(tPath, updates, errors, file, importMap, ignoreAddit
|
|
|
192
217
|
* Validates the function has enough parameters and traces how the translation callback
|
|
193
218
|
* is used within that function's body.
|
|
194
219
|
*/
|
|
195
|
-
function processFunctionIfMatches(_functionName, argIndex, functionNode, functionPath, updates, errors, filePath, ignoreAdditionalData, ignoreDynamicContent) {
|
|
220
|
+
function processFunctionIfMatches(_functionName, argIndex, functionNode, functionPath, updates, errors, filePath, ignoreAdditionalData, ignoreDynamicContent, parsingOptions) {
|
|
196
221
|
if (functionNode.params.length > argIndex) {
|
|
197
222
|
const param = functionNode.params[argIndex];
|
|
198
223
|
const paramName = extractParameterName(param);
|
|
199
224
|
if (paramName) {
|
|
200
|
-
findFunctionParameterUsage(functionPath, paramName, updates, errors, filePath, ignoreAdditionalData, ignoreDynamicContent);
|
|
225
|
+
findFunctionParameterUsage(functionPath, paramName, updates, errors, filePath, ignoreAdditionalData, ignoreDynamicContent, parsingOptions);
|
|
201
226
|
}
|
|
202
227
|
}
|
|
203
228
|
}
|
|
@@ -209,7 +234,7 @@ function processFunctionIfMatches(_functionName, argIndex, functionNode, functio
|
|
|
209
234
|
* Example: In function getInfo(t) { return t('hello'); }, this finds the t('hello') call.
|
|
210
235
|
* Example: In function getData(t) { return getFooter(t); }, this finds and traces into getFooter.
|
|
211
236
|
*/
|
|
212
|
-
function findFunctionParameterUsage(functionPath, parameterName, updates, errors, file, ignoreAdditionalData, ignoreDynamicContent) {
|
|
237
|
+
function findFunctionParameterUsage(functionPath, parameterName, updates, errors, file, ignoreAdditionalData, ignoreDynamicContent, parsingOptions) {
|
|
213
238
|
// Look for the function body and find all usages of the parameter
|
|
214
239
|
if (functionPath.isFunction()) {
|
|
215
240
|
const functionScope = functionPath.scope;
|
|
@@ -224,7 +249,7 @@ function findFunctionParameterUsage(functionPath, parameterName, updates, errors
|
|
|
224
249
|
const binding = functionScope.bindings[name];
|
|
225
250
|
if (binding) {
|
|
226
251
|
binding.referencePaths.forEach((refPath) => {
|
|
227
|
-
handleFunctionCall(refPath, updates, errors, file, importMap, ignoreAdditionalData, ignoreDynamicContent);
|
|
252
|
+
handleFunctionCall(refPath, updates, errors, file, importMap, ignoreAdditionalData, ignoreDynamicContent, parsingOptions);
|
|
228
253
|
});
|
|
229
254
|
}
|
|
230
255
|
});
|
|
@@ -239,44 +264,82 @@ function findFunctionParameterUsage(functionPath, parameterName, updates, errors
|
|
|
239
264
|
* - '@/components/ui/button' -> '/full/path/to/src/components/ui/button.tsx'
|
|
240
265
|
* - '@shared/utils' -> '/full/path/to/packages/utils/index.ts'
|
|
241
266
|
*/
|
|
242
|
-
function resolveImportPath(currentFile, importPath) {
|
|
267
|
+
function resolveImportPath(currentFile, importPath, parsingOptions) {
|
|
268
|
+
// Check cache first
|
|
269
|
+
const cacheKey = `${currentFile}::${importPath}`;
|
|
270
|
+
if (resolveImportPathCache.has(cacheKey)) {
|
|
271
|
+
return resolveImportPathCache.get(cacheKey);
|
|
272
|
+
}
|
|
243
273
|
const basedir = path.dirname(currentFile);
|
|
244
274
|
const extensions = ['.tsx', '.ts', '.jsx', '.js'];
|
|
275
|
+
const mainFields = ['module', 'main'];
|
|
276
|
+
let result = null;
|
|
245
277
|
// 1. Try tsconfig-paths resolution first (handles TypeScript path mapping)
|
|
246
278
|
const tsConfigResult = loadConfig(basedir);
|
|
247
279
|
if (tsConfigResult.resultType === 'success') {
|
|
248
|
-
const matchPath = createMatchPath(tsConfigResult.absoluteBaseUrl, tsConfigResult.paths,
|
|
280
|
+
const matchPath = createMatchPath(tsConfigResult.absoluteBaseUrl, tsConfigResult.paths, mainFields);
|
|
249
281
|
// First try without any extension
|
|
250
282
|
let tsResolved = matchPath(importPath);
|
|
251
283
|
if (tsResolved && fs.existsSync(tsResolved)) {
|
|
252
|
-
|
|
284
|
+
result = tsResolved;
|
|
285
|
+
resolveImportPathCache.set(cacheKey, result);
|
|
286
|
+
return result;
|
|
253
287
|
}
|
|
254
288
|
// Then try with each extension
|
|
255
289
|
for (const ext of extensions) {
|
|
256
290
|
tsResolved = matchPath(importPath + ext);
|
|
257
291
|
if (tsResolved && fs.existsSync(tsResolved)) {
|
|
258
|
-
|
|
292
|
+
result = tsResolved;
|
|
293
|
+
resolveImportPathCache.set(cacheKey, result);
|
|
294
|
+
return result;
|
|
259
295
|
}
|
|
260
296
|
// Also try the resolved path with extension
|
|
261
297
|
tsResolved = matchPath(importPath);
|
|
262
298
|
if (tsResolved) {
|
|
263
299
|
const resolvedWithExt = tsResolved + ext;
|
|
264
300
|
if (fs.existsSync(resolvedWithExt)) {
|
|
265
|
-
|
|
301
|
+
result = resolvedWithExt;
|
|
302
|
+
resolveImportPathCache.set(cacheKey, result);
|
|
303
|
+
return result;
|
|
266
304
|
}
|
|
267
305
|
}
|
|
268
306
|
}
|
|
269
307
|
}
|
|
270
|
-
// 2.
|
|
308
|
+
// 2. Try enhanced-resolve (handles package.json exports field and modern resolution)
|
|
309
|
+
try {
|
|
310
|
+
const resolver = ResolverFactory.createResolver({
|
|
311
|
+
useSyncFileSystemCalls: true,
|
|
312
|
+
fileSystem: fs,
|
|
313
|
+
extensions,
|
|
314
|
+
// Include 'development' condition to resolve to source files in monorepos
|
|
315
|
+
conditionNames: parsingOptions.conditionNames, // defaults to ['browser', 'module', 'import', 'require', 'default']. See generateSettings.ts for more details
|
|
316
|
+
exportsFields: ['exports'],
|
|
317
|
+
mainFields,
|
|
318
|
+
});
|
|
319
|
+
const resolved = resolver.resolveSync({}, basedir, importPath);
|
|
320
|
+
if (resolved) {
|
|
321
|
+
result = resolved;
|
|
322
|
+
resolveImportPathCache.set(cacheKey, result);
|
|
323
|
+
return result;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
// Fall through to next resolution strategy
|
|
328
|
+
}
|
|
329
|
+
// 3. Fallback to Node.js resolution (handles relative paths and node_modules)
|
|
271
330
|
try {
|
|
272
|
-
|
|
331
|
+
result = resolve.sync(importPath, { basedir, extensions });
|
|
332
|
+
resolveImportPathCache.set(cacheKey, result);
|
|
333
|
+
return result;
|
|
273
334
|
}
|
|
274
335
|
catch {
|
|
275
336
|
// If resolution fails, try to manually replace .js/.jsx with .ts/.tsx for source files
|
|
276
337
|
if (importPath.endsWith('.js')) {
|
|
277
338
|
const tsPath = importPath.replace(/\.js$/, '.ts');
|
|
278
339
|
try {
|
|
279
|
-
|
|
340
|
+
result = resolve.sync(tsPath, { basedir, extensions });
|
|
341
|
+
resolveImportPathCache.set(cacheKey, result);
|
|
342
|
+
return result;
|
|
280
343
|
}
|
|
281
344
|
catch {
|
|
282
345
|
// Continue to return null
|
|
@@ -285,12 +348,15 @@ function resolveImportPath(currentFile, importPath) {
|
|
|
285
348
|
else if (importPath.endsWith('.jsx')) {
|
|
286
349
|
const tsxPath = importPath.replace(/\.jsx$/, '.tsx');
|
|
287
350
|
try {
|
|
288
|
-
|
|
351
|
+
result = resolve.sync(tsxPath, { basedir, extensions });
|
|
352
|
+
resolveImportPathCache.set(cacheKey, result);
|
|
353
|
+
return result;
|
|
289
354
|
}
|
|
290
355
|
catch {
|
|
291
356
|
// Continue to return null
|
|
292
357
|
}
|
|
293
358
|
}
|
|
359
|
+
resolveImportPathCache.set(cacheKey, null);
|
|
294
360
|
return null;
|
|
295
361
|
}
|
|
296
362
|
}
|
|
@@ -302,19 +368,34 @@ function resolveImportPath(currentFile, importPath) {
|
|
|
302
368
|
* - function getInfo(t) { ... }
|
|
303
369
|
* - export function getInfo(t) { ... }
|
|
304
370
|
* - const getInfo = (t) => { ... }
|
|
371
|
+
*
|
|
372
|
+
* If the function is not found in the file, follows re-exports (export * from './other')
|
|
305
373
|
*/
|
|
306
|
-
function
|
|
374
|
+
function processFunctionInFile(filePath, functionName, argIndex, updates, errors, ignoreAdditionalData, ignoreDynamicContent, parsingOptions, visited = new Set()) {
|
|
375
|
+
// Check cache first to avoid redundant parsing
|
|
376
|
+
const cacheKey = `${filePath}::${functionName}::${argIndex}`;
|
|
377
|
+
if (processFunctionCache.has(cacheKey)) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
// Prevent infinite loops from circular re-exports
|
|
381
|
+
if (visited.has(filePath)) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
visited.add(filePath);
|
|
307
385
|
try {
|
|
308
386
|
const code = fs.readFileSync(filePath, 'utf8');
|
|
309
387
|
const ast = parse(code, {
|
|
310
388
|
sourceType: 'module',
|
|
311
389
|
plugins: ['jsx', 'typescript'],
|
|
312
390
|
});
|
|
391
|
+
let found = false;
|
|
392
|
+
const reExports = [];
|
|
313
393
|
traverse(ast, {
|
|
314
394
|
// Handle function declarations: function getInfo(t) { ... }
|
|
315
395
|
FunctionDeclaration(path) {
|
|
316
396
|
if (path.node.id?.name === functionName) {
|
|
317
|
-
|
|
397
|
+
found = true;
|
|
398
|
+
processFunctionIfMatches(functionName, argIndex, path.node, path, updates, errors, filePath, ignoreAdditionalData, ignoreDynamicContent, parsingOptions);
|
|
318
399
|
}
|
|
319
400
|
},
|
|
320
401
|
// Handle variable declarations: const getInfo = (t) => { ... }
|
|
@@ -324,14 +405,52 @@ function findFunctionInFile(filePath, functionName, argIndex, updates, errors, i
|
|
|
324
405
|
path.node.init &&
|
|
325
406
|
(t.isArrowFunctionExpression(path.node.init) ||
|
|
326
407
|
t.isFunctionExpression(path.node.init))) {
|
|
408
|
+
found = true;
|
|
327
409
|
const initPath = path.get('init');
|
|
328
|
-
processFunctionIfMatches(functionName, argIndex, path.node.init, initPath, updates, errors, filePath, ignoreAdditionalData, ignoreDynamicContent);
|
|
410
|
+
processFunctionIfMatches(functionName, argIndex, path.node.init, initPath, updates, errors, filePath, ignoreAdditionalData, ignoreDynamicContent, parsingOptions);
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
// Collect re-exports: export * from './other'
|
|
414
|
+
ExportAllDeclaration(path) {
|
|
415
|
+
if (t.isStringLiteral(path.node.source)) {
|
|
416
|
+
reExports.push(path.node.source.value);
|
|
417
|
+
}
|
|
418
|
+
},
|
|
419
|
+
// Collect named re-exports: export { foo } from './other'
|
|
420
|
+
ExportNamedDeclaration(path) {
|
|
421
|
+
if (path.node.source && t.isStringLiteral(path.node.source)) {
|
|
422
|
+
// Check if this export includes our function
|
|
423
|
+
const exportsFunction = path.node.specifiers.some((spec) => {
|
|
424
|
+
if (t.isExportSpecifier(spec)) {
|
|
425
|
+
const exportedName = t.isIdentifier(spec.exported)
|
|
426
|
+
? spec.exported.name
|
|
427
|
+
: spec.exported.value;
|
|
428
|
+
return exportedName === functionName;
|
|
429
|
+
}
|
|
430
|
+
return false;
|
|
431
|
+
});
|
|
432
|
+
if (exportsFunction) {
|
|
433
|
+
reExports.push(path.node.source.value);
|
|
434
|
+
}
|
|
329
435
|
}
|
|
330
436
|
},
|
|
331
437
|
});
|
|
438
|
+
// If function not found, follow re-exports
|
|
439
|
+
if (!found && reExports.length > 0) {
|
|
440
|
+
for (const reExportPath of reExports) {
|
|
441
|
+
const resolvedPath = resolveImportPath(filePath, reExportPath, parsingOptions);
|
|
442
|
+
if (resolvedPath) {
|
|
443
|
+
processFunctionInFile(resolvedPath, functionName, argIndex, updates, errors, ignoreAdditionalData, ignoreDynamicContent, parsingOptions, visited);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
// Mark this function search as processed in the cache
|
|
448
|
+
processFunctionCache.set(cacheKey, found);
|
|
332
449
|
}
|
|
333
450
|
catch {
|
|
334
451
|
// Silently skip files that can't be parsed or accessed
|
|
452
|
+
// Still mark as processed to avoid retrying failed parses
|
|
453
|
+
processFunctionCache.set(cacheKey, false);
|
|
335
454
|
}
|
|
336
455
|
}
|
|
337
456
|
/**
|
|
@@ -347,7 +466,7 @@ function findFunctionInFile(filePath, functionName, argIndex, updates, errors, i
|
|
|
347
466
|
* - const { home } = getInfo(t); // getInfo is imported from './constants'
|
|
348
467
|
* - This will parse constants.ts to find translation calls within getInfo function
|
|
349
468
|
*/
|
|
350
|
-
export function parseStrings(importName, originalName, path, updates, errors, file) {
|
|
469
|
+
export function parseStrings(importName, originalName, path, updates, errors, file, parsingOptions) {
|
|
351
470
|
// First, collect all imports in this file to track cross-file function calls
|
|
352
471
|
const importMap = buildImportMap(path.scope.getProgramParent().path);
|
|
353
472
|
const referencePaths = path.scope.bindings[importName]?.referencePaths || [];
|
|
@@ -402,7 +521,7 @@ export function parseStrings(importName, originalName, path, updates, errors, fi
|
|
|
402
521
|
allTranslationNames.forEach((name) => {
|
|
403
522
|
const tReferencePaths = variableScope.bindings[name]?.referencePaths || [];
|
|
404
523
|
for (const tPath of tReferencePaths) {
|
|
405
|
-
handleFunctionCall(tPath, updates, errors, file, importMap, ignoreAdditionalData, ignoreDynamicContent);
|
|
524
|
+
handleFunctionCall(tPath, updates, errors, file, importMap, ignoreAdditionalData, ignoreDynamicContent, parsingOptions);
|
|
406
525
|
}
|
|
407
526
|
});
|
|
408
527
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Updates } from '../../types/index.js';
|
|
2
|
-
|
|
2
|
+
import type { ParsingConfigOptions } from '../../types/parsing.js';
|
|
3
|
+
export declare function createInlineUpdates(pkg: 'gt-react' | 'gt-next', validate: boolean, filePatterns: string[] | undefined, parsingOptions: ParsingConfigOptions): Promise<{
|
|
3
4
|
updates: Updates;
|
|
4
5
|
errors: string[];
|
|
5
6
|
warnings: string[];
|
|
@@ -11,7 +11,7 @@ import { logError } from '../../console/logging.js';
|
|
|
11
11
|
import { GT_TRANSLATION_FUNCS, INLINE_TRANSLATION_HOOK, INLINE_TRANSLATION_HOOK_ASYNC, INLINE_MESSAGE_HOOK, INLINE_MESSAGE_HOOK_ASYNC, MSG_TRANSLATION_HOOK, } from '../jsx/utils/constants.js';
|
|
12
12
|
import { matchFiles } from '../../fs/matchFiles.js';
|
|
13
13
|
import { DEFAULT_SRC_PATTERNS } from '../../config/generateSettings.js';
|
|
14
|
-
export async function createInlineUpdates(pkg, validate, filePatterns) {
|
|
14
|
+
export async function createInlineUpdates(pkg, validate, filePatterns, parsingOptions) {
|
|
15
15
|
const updates = [];
|
|
16
16
|
const errors = [];
|
|
17
17
|
const warnings = new Set();
|
|
@@ -91,7 +91,7 @@ export async function createInlineUpdates(pkg, validate, filePatterns) {
|
|
|
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);
|
|
94
|
+
parseStrings(name, originalName, path, updates, errors, file, parsingOptions);
|
|
95
95
|
}
|
|
96
96
|
// Parse <T> components
|
|
97
97
|
traverse(ast, {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Updates, TranslateFlags } from '../types/index.js';
|
|
2
|
+
import type { ParsingConfigOptions } from '../types/parsing.js';
|
|
2
3
|
/**
|
|
3
4
|
* Searches for gt-react or gt-next dictionary files and creates updates for them,
|
|
4
5
|
* as well as inline updates for <T> tags and useGT()/getGT() calls
|
|
@@ -8,7 +9,7 @@ import { Updates, TranslateFlags } from '../types/index.js';
|
|
|
8
9
|
* @param pkg - The package name
|
|
9
10
|
* @returns An object containing the updates and errors
|
|
10
11
|
*/
|
|
11
|
-
export declare function createUpdates(options: TranslateFlags, src: string[] | undefined, sourceDictionary: string | undefined, pkg: 'gt-react' | 'gt-next', validate: boolean): Promise<{
|
|
12
|
+
export declare function createUpdates(options: TranslateFlags, src: string[] | undefined, sourceDictionary: string | undefined, pkg: 'gt-react' | 'gt-next', validate: boolean, parsingOptions: ParsingConfigOptions): Promise<{
|
|
12
13
|
updates: Updates;
|
|
13
14
|
errors: string[];
|
|
14
15
|
warnings: string[];
|
|
@@ -14,7 +14,7 @@ import chalk from 'chalk';
|
|
|
14
14
|
* @param pkg - The package name
|
|
15
15
|
* @returns An object containing the updates and errors
|
|
16
16
|
*/
|
|
17
|
-
export async function createUpdates(options, src, sourceDictionary, pkg, validate) {
|
|
17
|
+
export async function createUpdates(options, src, sourceDictionary, pkg, validate, parsingOptions) {
|
|
18
18
|
let updates = [];
|
|
19
19
|
let errors = [];
|
|
20
20
|
let warnings = [];
|
|
@@ -48,7 +48,7 @@ export async function createUpdates(options, src, sourceDictionary, pkg, validat
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
// Scan through project for <T> tags
|
|
51
|
-
const { updates: newUpdates, errors: newErrors, warnings: newWarnings, } = await createInlineUpdates(pkg, validate, src);
|
|
51
|
+
const { updates: newUpdates, errors: newErrors, warnings: newWarnings, } = await createInlineUpdates(pkg, validate, src, parsingOptions);
|
|
52
52
|
errors = [...errors, ...newErrors];
|
|
53
53
|
warnings = [...warnings, ...newWarnings];
|
|
54
54
|
updates = [...updates, ...newUpdates];
|
|
@@ -15,7 +15,7 @@ export async function aggregateReactTranslations(options, settings, library) {
|
|
|
15
15
|
]);
|
|
16
16
|
}
|
|
17
17
|
// ---- CREATING UPDATES ---- //
|
|
18
|
-
const { updates, errors, warnings } = await createUpdates(options, settings.src, options.dictionary, library, false);
|
|
18
|
+
const { updates, errors, warnings } = await createUpdates(options, settings.src, options.dictionary, library, false, settings.parsingOptions);
|
|
19
19
|
if (warnings.length > 0) {
|
|
20
20
|
logWarning(chalk.yellow(`CLI tool encountered ${warnings.length} warnings while scanning for translatable content.\n` +
|
|
21
21
|
warnings
|
|
@@ -7,7 +7,7 @@ import { createInlineUpdates } from '../react/parse/createInlineUpdates.js';
|
|
|
7
7
|
export async function validateProject(settings, pkg, files) {
|
|
8
8
|
if (files && files.length > 0) {
|
|
9
9
|
// Validate specific files using createInlineUpdates
|
|
10
|
-
const { errors, updates } = await createInlineUpdates(pkg, true, files);
|
|
10
|
+
const { errors, updates } = await createInlineUpdates(pkg, true, files, settings.parsingOptions);
|
|
11
11
|
if (errors.length > 0) {
|
|
12
12
|
logErrorAndExit(chalk.red(`Error: CLI tool encountered ${errors.length} syntax errors:\n` +
|
|
13
13
|
errors
|
|
@@ -27,7 +27,7 @@ export async function validateProject(settings, pkg, files) {
|
|
|
27
27
|
'./src/dictionary.ts',
|
|
28
28
|
]);
|
|
29
29
|
}
|
|
30
|
-
const { updates, errors, warnings } = await createUpdates(settings, settings.src, settings.dictionary, pkg, true);
|
|
30
|
+
const { updates, errors, warnings } = await createUpdates(settings, settings.src, settings.dictionary, pkg, true, settings.parsingOptions);
|
|
31
31
|
if (warnings.length > 0) {
|
|
32
32
|
logWarning(chalk.yellow(`CLI tool encountered ${warnings.length} warnings while scanning for translatable content.`) +
|
|
33
33
|
'\n' +
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CustomMapping } from 'generaltranslation/types';
|
|
2
2
|
import { SUPPORTED_FILE_EXTENSIONS } from '../formats/files/supportedFiles.js';
|
|
3
|
+
import { ParsingConfigOptions } from './parsing.js';
|
|
3
4
|
export type { Updates } from 'generaltranslation/types';
|
|
4
5
|
export type Options = {
|
|
5
6
|
config: string;
|
|
@@ -93,13 +94,13 @@ export type TransformOption = {
|
|
|
93
94
|
replace: string;
|
|
94
95
|
};
|
|
95
96
|
export type TransformFiles = {
|
|
96
|
-
[K in SupportedFileExtension]?: TransformOption | string;
|
|
97
|
+
[K in SupportedFileExtension]?: TransformOption | string | TransformOption[];
|
|
97
98
|
};
|
|
98
99
|
export type FilesOptions = {
|
|
99
100
|
[K in SupportedFileExtension]?: {
|
|
100
101
|
include: string[];
|
|
101
102
|
exclude?: string[];
|
|
102
|
-
transform?: string | TransformOption;
|
|
103
|
+
transform?: string | TransformOption | TransformOption[];
|
|
103
104
|
};
|
|
104
105
|
} & {
|
|
105
106
|
gt?: {
|
|
@@ -130,6 +131,7 @@ export type Settings = {
|
|
|
130
131
|
framework?: SupportedFrameworks;
|
|
131
132
|
options?: AdditionalOptions;
|
|
132
133
|
modelProvider?: string;
|
|
134
|
+
parsingOptions: ParsingConfigOptions;
|
|
133
135
|
};
|
|
134
136
|
export type AdditionalOptions = {
|
|
135
137
|
jsonSchema?: {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gtx-cli",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.7",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"bin": "dist/main.js",
|
|
6
6
|
"files": [
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"chalk": "^5.4.1",
|
|
72
72
|
"commander": "^12.1.0",
|
|
73
73
|
"dotenv": "^16.4.5",
|
|
74
|
+
"enhanced-resolve": "^5.18.3",
|
|
74
75
|
"esbuild": "^0.25.4",
|
|
75
76
|
"fast-glob": "^3.3.3",
|
|
76
77
|
"fast-json-stable-stringify": "^2.1.0",
|