gtx-cli 2.1.8 → 2.1.9

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,17 @@
1
1
  # gtx-cli
2
2
 
3
+ ## 2.1.9
4
+
5
+ ### Patch Changes
6
+
7
+ - [#604](https://github.com/generaltranslation/gt/pull/604) [`43c6a76`](https://github.com/generaltranslation/gt/commit/43c6a76be3d3be420e892b86188ef41c45ae8ffe) Thanks [@archie-mckenzie](https://github.com/archie-mckenzie)! - Refactored useGT and useMessages in order to make useMessages function like an unlintable useGT
8
+
9
+ ## 2.1.8
10
+
11
+ ### Patch Changes
12
+
13
+ - [#599](https://github.com/generaltranslation/gt/pull/599) [`5950592`](https://github.com/generaltranslation/gt/commit/5950592ca44197915216ec5c8e26f9714cb4f55c) Thanks [@ErnestM1234](https://github.com/ErnestM1234)! - feat: msg() function
14
+
3
15
  ## 2.1.8
4
16
 
5
17
  ### Patch Changes
@@ -1,3 +1,8 @@
1
+ export declare const MSG_TRANSLATION_HOOK = "msg";
2
+ export declare const INLINE_TRANSLATION_HOOK = "useGT";
3
+ export declare const INLINE_TRANSLATION_HOOK_ASYNC = "getGT";
4
+ export declare const INLINE_MESSAGE_HOOK = "useMessages";
5
+ export declare const INLINE_MESSAGE_HOOK_ASYNC = "getMessages";
1
6
  export declare const GT_TRANSLATION_FUNCS: string[];
2
7
  export declare const VARIABLE_COMPONENTS: string[];
3
8
  export declare const GT_ATTRIBUTES_WITH_SUGAR: string[];
@@ -1,7 +1,15 @@
1
+ export const MSG_TRANSLATION_HOOK = 'msg';
2
+ export const INLINE_TRANSLATION_HOOK = 'useGT';
3
+ export const INLINE_TRANSLATION_HOOK_ASYNC = 'getGT';
4
+ export const INLINE_MESSAGE_HOOK = 'useMessages';
5
+ export const INLINE_MESSAGE_HOOK_ASYNC = 'getMessages';
1
6
  // GT translation functions
2
7
  export const GT_TRANSLATION_FUNCS = [
3
- 'useGT',
4
- 'getGT',
8
+ INLINE_TRANSLATION_HOOK,
9
+ INLINE_TRANSLATION_HOOK_ASYNC,
10
+ INLINE_MESSAGE_HOOK,
11
+ INLINE_MESSAGE_HOOK_ASYNC,
12
+ MSG_TRANSLATION_HOOK,
5
13
  'T',
6
14
  'Var',
7
15
  'DateTime',
@@ -1,6 +1,6 @@
1
1
  import * as t from '@babel/types';
2
2
  import { isStaticExpression } from '../evaluateJsx.js';
3
- import { GT_ATTRIBUTES_WITH_SUGAR, mapAttributeName } from './constants.js';
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
4
  import { warnNonStaticExpressionSync, warnNonStringSync, warnTemplateLiteralSync, warnAsyncUseGT, warnSyncGetGT, } from '../../../console/index.js';
5
5
  import generateModule from '@babel/generator';
6
6
  import traverseModule from '@babel/traverse';
@@ -22,7 +22,7 @@ import resolve from 'resolve';
22
22
  * - Metadata extraction from options object
23
23
  * - Error reporting for non-static expressions and template literals with expressions
24
24
  */
25
- function processTranslationCall(tPath, updates, errors, file) {
25
+ function processTranslationCall(tPath, updates, errors, file, ignoreAdditionalData, ignoreDynamicContent) {
26
26
  if (tPath.parent.type === 'CallExpression' &&
27
27
  tPath.parent.arguments.length > 0) {
28
28
  const arg = tPath.parent.arguments[0];
@@ -43,7 +43,7 @@ function processTranslationCall(tPath, updates, errors, file) {
43
43
  if (!result.isStatic) {
44
44
  errors.push(warnNonStaticExpressionSync(file, attribute, generate(prop.value).code, `${prop.loc?.start?.line}:${prop.loc?.start?.column}`));
45
45
  }
46
- if (result.isStatic && result.value) {
46
+ if (result.isStatic && result.value && !ignoreAdditionalData) {
47
47
  // Map $id and $context to id and context
48
48
  metadata[mapAttributeName(attribute)] = result.value;
49
49
  }
@@ -59,10 +59,14 @@ function processTranslationCall(tPath, updates, errors, file) {
59
59
  }
60
60
  else if (t.isTemplateLiteral(arg)) {
61
61
  // warn if template literal
62
- errors.push(warnTemplateLiteralSync(file, generate(arg).code, `${arg.loc?.start?.line}:${arg.loc?.start?.column}`));
62
+ if (!ignoreDynamicContent) {
63
+ errors.push(warnTemplateLiteralSync(file, generate(arg).code, `${arg.loc?.start?.line}:${arg.loc?.start?.column}`));
64
+ }
63
65
  }
64
66
  else {
65
- errors.push(warnNonStringSync(file, generate(arg).code, `${arg.loc?.start?.line}:${arg.loc?.start?.column}`));
67
+ if (!ignoreDynamicContent) {
68
+ errors.push(warnNonStringSync(file, generate(arg).code, `${arg.loc?.start?.line}:${arg.loc?.start?.column}`));
69
+ }
66
70
  }
67
71
  }
68
72
  }
@@ -145,11 +149,11 @@ function resolveVariableAliases(scope, variableName, visited = new Set()) {
145
149
  * This covers both direct translation calls (t('hello')) and prop drilling
146
150
  * where the translation callback is passed to other functions (getData(t)).
147
151
  */
148
- function handleFunctionCall(tPath, updates, errors, file, importMap) {
152
+ function handleFunctionCall(tPath, updates, errors, file, importMap, ignoreAdditionalData, ignoreDynamicContent) {
149
153
  if (tPath.parent.type === 'CallExpression' &&
150
154
  tPath.parent.callee === tPath.node) {
151
155
  // Direct translation call: t('hello')
152
- processTranslationCall(tPath, updates, errors, file);
156
+ processTranslationCall(tPath, updates, errors, file, ignoreAdditionalData, ignoreDynamicContent);
153
157
  }
154
158
  else if (tPath.parent.type === 'CallExpression' &&
155
159
  t.isExpression(tPath.node) &&
@@ -161,7 +165,7 @@ function handleFunctionCall(tPath, updates, errors, file, importMap) {
161
165
  const calleeBinding = tPath.scope.getBinding(callee.name);
162
166
  if (calleeBinding && calleeBinding.path.isFunction()) {
163
167
  const functionPath = calleeBinding.path;
164
- processFunctionIfMatches(callee.name, argIndex, functionPath.node, functionPath, updates, errors, file);
168
+ processFunctionIfMatches(callee.name, argIndex, functionPath.node, functionPath, updates, errors, file, ignoreAdditionalData, ignoreDynamicContent);
165
169
  }
166
170
  // Handle arrow functions assigned to variables: const getData = (t) => {...}
167
171
  else if (calleeBinding &&
@@ -170,14 +174,14 @@ function handleFunctionCall(tPath, updates, errors, file, importMap) {
170
174
  (t.isArrowFunctionExpression(calleeBinding.path.node.init) ||
171
175
  t.isFunctionExpression(calleeBinding.path.node.init))) {
172
176
  const initPath = calleeBinding.path.get('init');
173
- processFunctionIfMatches(callee.name, argIndex, calleeBinding.path.node.init, initPath, updates, errors, file);
177
+ processFunctionIfMatches(callee.name, argIndex, calleeBinding.path.node.init, initPath, updates, errors, file, ignoreAdditionalData, ignoreDynamicContent);
174
178
  }
175
179
  // If not found locally, check if it's an imported function
176
180
  else if (importMap.has(callee.name)) {
177
181
  const importPath = importMap.get(callee.name);
178
182
  const resolvedPath = resolveImportPath(file, importPath);
179
183
  if (resolvedPath) {
180
- findFunctionInFile(resolvedPath, callee.name, argIndex, updates, errors);
184
+ findFunctionInFile(resolvedPath, callee.name, argIndex, updates, errors, ignoreAdditionalData, ignoreDynamicContent);
181
185
  }
182
186
  }
183
187
  }
@@ -188,12 +192,12 @@ function handleFunctionCall(tPath, updates, errors, file, importMap) {
188
192
  * Validates the function has enough parameters and traces how the translation callback
189
193
  * is used within that function's body.
190
194
  */
191
- function processFunctionIfMatches(_functionName, argIndex, functionNode, functionPath, updates, errors, filePath) {
195
+ function processFunctionIfMatches(_functionName, argIndex, functionNode, functionPath, updates, errors, filePath, ignoreAdditionalData, ignoreDynamicContent) {
192
196
  if (functionNode.params.length > argIndex) {
193
197
  const param = functionNode.params[argIndex];
194
198
  const paramName = extractParameterName(param);
195
199
  if (paramName) {
196
- findFunctionParameterUsage(functionPath, paramName, updates, errors, filePath);
200
+ findFunctionParameterUsage(functionPath, paramName, updates, errors, filePath, ignoreAdditionalData, ignoreDynamicContent);
197
201
  }
198
202
  }
199
203
  }
@@ -205,7 +209,7 @@ function processFunctionIfMatches(_functionName, argIndex, functionNode, functio
205
209
  * Example: In function getInfo(t) { return t('hello'); }, this finds the t('hello') call.
206
210
  * Example: In function getData(t) { return getFooter(t); }, this finds and traces into getFooter.
207
211
  */
208
- function findFunctionParameterUsage(functionPath, parameterName, updates, errors, file) {
212
+ function findFunctionParameterUsage(functionPath, parameterName, updates, errors, file, ignoreAdditionalData, ignoreDynamicContent) {
209
213
  // Look for the function body and find all usages of the parameter
210
214
  if (functionPath.isFunction()) {
211
215
  const functionScope = functionPath.scope;
@@ -220,7 +224,7 @@ function findFunctionParameterUsage(functionPath, parameterName, updates, errors
220
224
  const binding = functionScope.bindings[name];
221
225
  if (binding) {
222
226
  binding.referencePaths.forEach((refPath) => {
223
- handleFunctionCall(refPath, updates, errors, file, importMap);
227
+ handleFunctionCall(refPath, updates, errors, file, importMap, ignoreAdditionalData, ignoreDynamicContent);
224
228
  });
225
229
  }
226
230
  });
@@ -299,7 +303,7 @@ function resolveImportPath(currentFile, importPath) {
299
303
  * - export function getInfo(t) { ... }
300
304
  * - const getInfo = (t) => { ... }
301
305
  */
302
- function findFunctionInFile(filePath, functionName, argIndex, updates, errors) {
306
+ function findFunctionInFile(filePath, functionName, argIndex, updates, errors, ignoreAdditionalData, ignoreDynamicContent) {
303
307
  try {
304
308
  const code = fs.readFileSync(filePath, 'utf8');
305
309
  const ast = parse(code, {
@@ -310,7 +314,7 @@ function findFunctionInFile(filePath, functionName, argIndex, updates, errors) {
310
314
  // Handle function declarations: function getInfo(t) { ... }
311
315
  FunctionDeclaration(path) {
312
316
  if (path.node.id?.name === functionName) {
313
- processFunctionIfMatches(functionName, argIndex, path.node, path, updates, errors, filePath);
317
+ processFunctionIfMatches(functionName, argIndex, path.node, path, updates, errors, filePath, ignoreAdditionalData, ignoreDynamicContent);
314
318
  }
315
319
  },
316
320
  // Handle variable declarations: const getInfo = (t) => { ... }
@@ -321,7 +325,7 @@ function findFunctionInFile(filePath, functionName, argIndex, updates, errors) {
321
325
  (t.isArrowFunctionExpression(path.node.init) ||
322
326
  t.isFunctionExpression(path.node.init))) {
323
327
  const initPath = path.get('init');
324
- processFunctionIfMatches(functionName, argIndex, path.node.init, initPath, updates, errors, filePath);
328
+ processFunctionIfMatches(functionName, argIndex, path.node.init, initPath, updates, errors, filePath, ignoreAdditionalData, ignoreDynamicContent);
325
329
  }
326
330
  },
327
331
  });
@@ -348,21 +352,40 @@ export function parseStrings(importName, originalName, path, updates, errors, fi
348
352
  const importMap = buildImportMap(path.scope.getProgramParent().path);
349
353
  const referencePaths = path.scope.bindings[importName]?.referencePaths || [];
350
354
  for (const refPath of referencePaths) {
351
- // Find call expressions of useGT() / await getGT()
355
+ // Handle msg() calls directly without variable assignment
356
+ if (originalName === MSG_TRANSLATION_HOOK) {
357
+ const ignoreAdditionalData = false;
358
+ const ignoreDynamicContent = false;
359
+ // Check if this is a direct call to msg('string')
360
+ if (refPath.parent.type === 'CallExpression' &&
361
+ refPath.parent.callee === refPath.node) {
362
+ processTranslationCall(refPath, updates, errors, file, ignoreAdditionalData, ignoreDynamicContent);
363
+ }
364
+ continue;
365
+ }
366
+ // Handle useGT(), getGT(), useMessages(), and getMessages() calls that need variable assignment
352
367
  const callExpr = refPath.findParent((p) => p.isCallExpression());
353
368
  if (callExpr) {
354
369
  // Get the parent, handling both await and non-await cases
355
370
  const parentPath = callExpr.parentPath;
356
371
  const parentFunction = refPath.getFunctionParent();
357
372
  const asyncScope = parentFunction?.node.async;
358
- if (asyncScope && originalName === 'useGT') {
373
+ if (asyncScope &&
374
+ (originalName === INLINE_TRANSLATION_HOOK ||
375
+ originalName === INLINE_MESSAGE_HOOK)) {
359
376
  errors.push(warnAsyncUseGT(file, `${refPath.node.loc?.start?.line}:${refPath.node.loc?.start?.column}`));
360
377
  return;
361
378
  }
362
- else if (!asyncScope && originalName === 'getGT') {
379
+ else if (!asyncScope &&
380
+ (originalName === INLINE_TRANSLATION_HOOK_ASYNC ||
381
+ originalName === INLINE_MESSAGE_HOOK_ASYNC)) {
363
382
  errors.push(warnSyncGetGT(file, `${refPath.node.loc?.start?.line}:${refPath.node.loc?.start?.column}`));
364
383
  return;
365
384
  }
385
+ const isMessageHook = originalName === INLINE_MESSAGE_HOOK ||
386
+ originalName === INLINE_MESSAGE_HOOK_ASYNC;
387
+ const ignoreAdditionalData = isMessageHook;
388
+ const ignoreDynamicContent = isMessageHook;
366
389
  const effectiveParent = parentPath?.node.type === 'AwaitExpression'
367
390
  ? parentPath.parentPath
368
391
  : parentPath;
@@ -372,10 +395,16 @@ export function parseStrings(importName, originalName, path, updates, errors, fi
372
395
  const tFuncName = effectiveParent.node.id.name;
373
396
  // Get the scope from the variable declaration
374
397
  const variableScope = effectiveParent.scope;
375
- const tReferencePaths = variableScope.bindings[tFuncName]?.referencePaths || [];
376
- for (const tPath of tReferencePaths) {
377
- handleFunctionCall(tPath, updates, errors, file, importMap);
378
- }
398
+ // Resolve all aliases of the translation function
399
+ // Example: translate -> [translate, t, a, b] for const t = translate; const a = t; const b = a;
400
+ const allTranslationNames = resolveVariableAliases(variableScope, tFuncName);
401
+ // Process references for all translation function names and their aliases
402
+ allTranslationNames.forEach((name) => {
403
+ const tReferencePaths = variableScope.bindings[name]?.referencePaths || [];
404
+ for (const tPath of tReferencePaths) {
405
+ handleFunctionCall(tPath, updates, errors, file, importMap, ignoreAdditionalData, ignoreDynamicContent);
406
+ }
407
+ });
379
408
  }
380
409
  }
381
410
  }
@@ -1,4 +1,5 @@
1
1
  import { warnAsyncUseGT, warnSyncGetGT } from '../../../console/index.js';
2
+ import { INLINE_TRANSLATION_HOOK, INLINE_TRANSLATION_HOOK_ASYNC, } from './constants.js';
2
3
  /**
3
4
  * Validate useGT() / await getGT() calls
4
5
  * 1. Validates that the call does not violate the rules of React (no hooks in async functions)
@@ -12,13 +13,13 @@ export function validateStringFunction(localImportName, path, updates, errors, f
12
13
  callPath.node.callee.name === localImportName) {
13
14
  // Check the function scope
14
15
  const functionScope = callPath.getFunctionParent();
15
- if (originalImportName === 'useGT') {
16
+ if (originalImportName === INLINE_TRANSLATION_HOOK) {
16
17
  // useGT should NOT be in an async function
17
18
  if (functionScope && functionScope.node.async) {
18
19
  errors.push(warnAsyncUseGT(file, `${callPath.node.loc?.start?.line}:${callPath.node.loc?.start?.column}`));
19
20
  }
20
21
  }
21
- else if (originalImportName === 'getGT') {
22
+ else if (originalImportName === INLINE_TRANSLATION_HOOK_ASYNC) {
22
23
  // getGT should be in an async function
23
24
  if (!functionScope || !functionScope.node.async) {
24
25
  errors.push(warnSyncGetGT(file, `${callPath.node.loc?.start?.line}:${callPath.node.loc?.start?.column}`));
@@ -8,7 +8,7 @@ import { parseJSXElement } from '../jsx/utils/parseJsx.js';
8
8
  import { parseStrings } from '../jsx/utils/parseStringFunction.js';
9
9
  import { extractImportName } from '../jsx/utils/parseAst.js';
10
10
  import { logError } from '../../console/logging.js';
11
- import { GT_TRANSLATION_FUNCS } from '../jsx/utils/constants.js';
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
14
  export async function createInlineUpdates(pkg, validate, filePatterns) {
@@ -38,7 +38,11 @@ export async function createInlineUpdates(pkg, validate, filePatterns) {
38
38
  if (path.node.source.value.startsWith(pkg)) {
39
39
  const importName = extractImportName(path.node, pkg, GT_TRANSLATION_FUNCS);
40
40
  for (const name of importName) {
41
- if (name.original === 'useGT' || name.original === 'getGT') {
41
+ if (name.original === INLINE_TRANSLATION_HOOK ||
42
+ name.original === INLINE_TRANSLATION_HOOK_ASYNC ||
43
+ name.original === INLINE_MESSAGE_HOOK ||
44
+ name.original === INLINE_MESSAGE_HOOK_ASYNC ||
45
+ name.original === MSG_TRANSLATION_HOOK) {
42
46
  translationPaths.push({
43
47
  localName: name.local,
44
48
  path,
@@ -65,7 +69,11 @@ export async function createInlineUpdates(pkg, validate, filePatterns) {
65
69
  if (parentPath.isVariableDeclaration()) {
66
70
  const importName = extractImportName(parentPath.node, pkg, GT_TRANSLATION_FUNCS);
67
71
  for (const name of importName) {
68
- if (name.original === 'useGT' || name.original === 'getGT') {
72
+ if (name.original === INLINE_TRANSLATION_HOOK ||
73
+ name.original === INLINE_TRANSLATION_HOOK_ASYNC ||
74
+ name.original === INLINE_MESSAGE_HOOK ||
75
+ name.original === INLINE_MESSAGE_HOOK_ASYNC ||
76
+ name.original === MSG_TRANSLATION_HOOK) {
69
77
  translationPaths.push({
70
78
  localName: name.local,
71
79
  path: parentPath,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtx-cli",
3
- "version": "2.1.8",
3
+ "version": "2.1.9",
4
4
  "main": "dist/index.js",
5
5
  "bin": "dist/main.js",
6
6
  "files": [