gtx-cli 2.4.11 → 2.4.13

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 CHANGED
@@ -1,5 +1,18 @@
1
1
  # gtx-cli
2
2
 
3
+ ## 2.4.13
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [[`93881f1`](https://github.com/generaltranslation/gt/commit/93881f159455a9bbc13d14e7fec9befa60998ba3)]:
8
+ - generaltranslation@7.9.0
9
+
10
+ ## 2.4.12
11
+
12
+ ### Patch Changes
13
+
14
+ - [#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
15
+
3
16
  ## 2.4.11
4
17
 
5
18
  ### Patch Changes
@@ -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;
@@ -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
+ }
@@ -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
- export async function createDictionaryUpdates(dictionaryPath, esbuildConfig) {
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.11",
3
+ "version": "2.4.13",
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",
@@ -93,7 +94,7 @@
93
94
  "unified": "^11.0.5",
94
95
  "unist-util-visit": "^5.0.0",
95
96
  "yaml": "^2.8.0",
96
- "generaltranslation": "7.8.0"
97
+ "generaltranslation": "7.9.0"
97
98
  },
98
99
  "devDependencies": {
99
100
  "@babel/types": "^7.28.4",