gtx-cli 2.5.22 → 2.5.24

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,21 @@
1
1
  # gtx-cli
2
2
 
3
+ ## 2.5.24
4
+
5
+ ### Patch Changes
6
+
7
+ - [#860](https://github.com/generaltranslation/gt/pull/860) [`37bac4c`](https://github.com/generaltranslation/gt/commit/37bac4ce11689a2f729efbcb2e052205447a7f71) Thanks [@ErnestM1234](https://github.com/ErnestM1234)! - chore: support for max char
8
+
9
+ - Updated dependencies [[`37bac4c`](https://github.com/generaltranslation/gt/commit/37bac4ce11689a2f729efbcb2e052205447a7f71)]:
10
+ - generaltranslation@8.1.1
11
+
12
+ ## 2.5.23
13
+
14
+ ### Patch Changes
15
+
16
+ - Updated dependencies [[`3e8ceb4`](https://github.com/generaltranslation/gt/commit/3e8ceb4526530d38eae469b05e8bf273d5ca05ac)]:
17
+ - generaltranslation@8.1.0
18
+
3
19
  ## 2.5.22
4
20
 
5
21
  ### Patch Changes
@@ -6,6 +6,7 @@ export declare const warnHasUnwrappedExpressionSync: (file: string, unwrappedExp
6
6
  export declare const warnFailedToConstructJsxTreeSync: (file: string, code: string, location?: string) => string;
7
7
  export declare const warnNestedTComponent: (file: string, location?: string) => string;
8
8
  export declare const warnNonStaticExpressionSync: (file: string, attrName: string, value: string, location?: string) => string;
9
+ export declare const warnInvalidMaxCharsSync: (file: string, value: string, location?: string) => string;
9
10
  export declare const warnInvalidIcuSync: (file: string, value: string, error: string, location?: string) => string;
10
11
  export declare const warnTemplateLiteralSync: (file: string, value: string, location?: string) => string;
11
12
  export declare const warnNonStringSync: (file: string, value: string, location?: string) => string;
@@ -12,6 +12,7 @@ export const warnHasUnwrappedExpressionSync = (file, unwrappedExpressions, id, l
12
12
  export const warnFailedToConstructJsxTreeSync = (file, code, location) => withLocation(file, `Failed to construct JsxTree! Call expression is not a valid createElement call: ${colorizeContent(code)}`, location);
13
13
  export const warnNestedTComponent = (file, location) => withLocation(file, `Found nested <T> component. <T> components cannot be directly nested.`, location);
14
14
  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);
15
+ export const warnInvalidMaxCharsSync = (file, value, location) => withLocation(file, `Found invalid maxChars value: ${colorizeContent(value)}. Change the value to a valid number to ensure this content is translated.`, location);
15
16
  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));
16
17
  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);
17
18
  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);
@@ -7,8 +7,8 @@ export declare const TRANSLATION_COMPONENT = "T";
7
7
  export declare const STATIC_COMPONENT = "Static";
8
8
  export declare const GT_TRANSLATION_FUNCS: string[];
9
9
  export declare const VARIABLE_COMPONENTS: string[];
10
- export declare const GT_ATTRIBUTES_WITH_SUGAR: string[];
11
- export declare const GT_ATTRIBUTES: string[];
10
+ export declare const GT_ATTRIBUTES_WITH_SUGAR: readonly ["$id", "$context", "$maxChars"];
11
+ export declare const GT_ATTRIBUTES: readonly ["id", "context", "maxChars", "$id", "$context", "$maxChars"];
12
12
  export declare function mapAttributeName(attrName: string): string;
13
13
  export declare const GT_LIBRARIES: readonly ["gt-react", "gt-next", "gt-react-native", "gt-i18n", "@generaltranslation/react-core"];
14
14
  export type GTLibrary = (typeof GT_LIBRARIES)[number];
@@ -29,13 +29,24 @@ export const VARIABLE_COMPONENTS = [
29
29
  'Num',
30
30
  STATIC_COMPONENT,
31
31
  ];
32
- export const GT_ATTRIBUTES_WITH_SUGAR = ['$id', '$context'];
33
- export const GT_ATTRIBUTES = ['id', 'context', ...GT_ATTRIBUTES_WITH_SUGAR];
32
+ export const GT_ATTRIBUTES_WITH_SUGAR = [
33
+ '$id',
34
+ '$context',
35
+ '$maxChars',
36
+ ];
37
+ export const GT_ATTRIBUTES = [
38
+ 'id',
39
+ 'context',
40
+ 'maxChars',
41
+ ...GT_ATTRIBUTES_WITH_SUGAR,
42
+ ];
34
43
  export function mapAttributeName(attrName) {
35
44
  if (attrName === '$id')
36
45
  return 'id';
37
46
  if (attrName === '$context')
38
47
  return 'context';
48
+ if (attrName === '$maxChars')
49
+ return 'maxChars';
39
50
  return attrName;
40
51
  }
41
52
  export const GT_LIBRARIES = [
@@ -0,0 +1,7 @@
1
+ import * as t from '@babel/types';
2
+ /**
3
+ * Given an expression, return true if it is a number literal
4
+ * @param expr - The expression to check
5
+ * @returns True if the expression is a number literal, false otherwise
6
+ */
7
+ export declare function isNumberLiteral(expr: t.Expression): boolean;
@@ -0,0 +1,13 @@
1
+ import * as t from '@babel/types';
2
+ /**
3
+ * Given an expression, return true if it is a number literal
4
+ * @param expr - The expression to check
5
+ * @returns True if the expression is a number literal, false otherwise
6
+ */
7
+ export function isNumberLiteral(expr) {
8
+ if (t.isUnaryExpression(expr)) {
9
+ return (isNumberLiteral(expr.argument) &&
10
+ (expr.operator === '-' || expr.operator === '+'));
11
+ }
12
+ return t.isNumericLiteral(expr);
13
+ }
@@ -4,7 +4,8 @@ import generateModule from '@babel/generator';
4
4
  const generate = generateModule.default || generateModule;
5
5
  import { GT_ATTRIBUTES, mapAttributeName } from '../constants.js';
6
6
  import { isStaticExpression } from '../../evaluateJsx.js';
7
- import { warnVariablePropSync } from '../../../../console/index.js';
7
+ import { warnInvalidMaxCharsSync, warnVariablePropSync, } from '../../../../console/index.js';
8
+ import { isNumberLiteral } from '../isNumberLiteral.js';
8
9
  // Parse the props of a <T> component
9
10
  export function parseTProps({ openingElement, metadata, componentErrors, file, }) {
10
11
  openingElement.attributes.forEach((attr) => {
@@ -22,7 +23,7 @@ export function parseTProps({ openingElement, metadata, componentErrors, file, }
22
23
  else if (t.isJSXExpressionContainer(attr.value)) {
23
24
  const expr = attr.value.expression;
24
25
  const code = generate(expr).code;
25
- // Only check for static expressions on id and context props
26
+ // Only check for static expressions on id and context and maxChars props
26
27
  if (GT_ATTRIBUTES.includes(attrName)) {
27
28
  const staticAnalysis = isStaticExpression(expr);
28
29
  if (!staticAnalysis.isStatic) {
@@ -30,7 +31,23 @@ export function parseTProps({ openingElement, metadata, componentErrors, file, }
30
31
  }
31
32
  // Use the static value if available
32
33
  if (staticAnalysis.isStatic && staticAnalysis.value !== undefined) {
33
- metadata[mapAttributeName(attrName)] = staticAnalysis.value;
34
+ // Check for invalid maxChars values
35
+ if (attrName === '$maxChars' || attrName === 'maxChars') {
36
+ if (typeof staticAnalysis.value === 'string' &&
37
+ (isNaN(Number(staticAnalysis.value)) ||
38
+ (t.isExpression(expr) && !isNumberLiteral(expr)) ||
39
+ !Number.isInteger(Number(staticAnalysis.value)))) {
40
+ componentErrors.push(warnInvalidMaxCharsSync(file, code, `${expr.loc?.start?.line}:${expr.loc?.start?.column}`));
41
+ }
42
+ else {
43
+ // Add the maxChars value to the metadata
44
+ metadata[mapAttributeName(attrName)] = Math.abs(Number(staticAnalysis.value));
45
+ }
46
+ }
47
+ else {
48
+ // Add the $context or $id or other attributes value to the metadata
49
+ metadata[mapAttributeName(attrName)] = staticAnalysis.value;
50
+ }
34
51
  }
35
52
  else {
36
53
  // Only store the code if we couldn't extract a static value
@@ -1,7 +1,7 @@
1
1
  import * as t from '@babel/types';
2
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, warnInvalidIcuSync, } from '../../../console/index.js';
4
+ import { warnNonStaticExpressionSync, warnNonStringSync, warnTemplateLiteralSync, warnAsyncUseGT, warnSyncGetGT, warnInvalidIcuSync, warnInvalidMaxCharsSync, } from '../../../console/index.js';
5
5
  import generateModule from '@babel/generator';
6
6
  import traverseModule from '@babel/traverse';
7
7
  // Handle CommonJS/ESM interop
@@ -11,6 +11,7 @@ import fs from 'node:fs';
11
11
  import { parse } from '@babel/parser';
12
12
  import { resolveImportPath } from './resolveImportPath.js';
13
13
  import { buildImportMap } from './buildImportMap.js';
14
+ import { isNumberLiteral } from './isNumberLiteral.js';
14
15
  /**
15
16
  * Cache for resolved import paths to avoid redundant I/O operations.
16
17
  * Key: `${currentFile}::${importPath}`
@@ -69,9 +70,26 @@ function processTranslationCall(tPath, updates, errors, warnings, file, ignoreAd
69
70
  if (!result.isStatic) {
70
71
  errors.push(warnNonStaticExpressionSync(file, attribute, generate(prop.value).code, `${prop.loc?.start?.line}:${prop.loc?.start?.column}`));
71
72
  }
72
- if (result.isStatic && result.value && !ignoreAdditionalData) {
73
- // Map $id and $context to id and context
74
- metadata[mapAttributeName(attribute)] = result.value;
73
+ if (result.isStatic &&
74
+ result.value != null &&
75
+ !ignoreAdditionalData) {
76
+ const mappedKey = mapAttributeName(attribute);
77
+ if (attribute === '$maxChars') {
78
+ if ((typeof result.value === 'string' &&
79
+ (isNaN(Number(result.value)) ||
80
+ !isNumberLiteral(prop.value))) ||
81
+ !Number.isInteger(Number(result.value))) {
82
+ errors.push(warnInvalidMaxCharsSync(file, generate(prop).code, `${prop.loc?.start?.line}:${prop.loc?.start?.column}`));
83
+ }
84
+ else if (typeof result.value === 'string') {
85
+ // Add the maxChars value to the metadata
86
+ metadata[mappedKey] = Math.abs(Number(result.value));
87
+ }
88
+ }
89
+ else {
90
+ // Add the $context or $id or other attributes value to the metadata
91
+ metadata[mappedKey] = result.value;
92
+ }
75
93
  }
76
94
  }
77
95
  }
@@ -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, warnings: string[], esbuildConfig?: BuildOptions): Promise<Updates>;
3
+ export declare function createDictionaryUpdates(dictionaryPath: string, errors: string[], warnings: string[], esbuildConfig?: BuildOptions): Promise<Updates>;
@@ -9,9 +9,9 @@ import getEntryAndMetadata from '../utils/getEntryAndMetadata.js';
9
9
  import { logger } from '../../console/logger.js';
10
10
  import { randomUUID } from 'node:crypto';
11
11
  import { isValidIcu } from '../jsx/evaluateJsx.js';
12
- import { warnInvalidIcuSync } from '../../console/index.js';
12
+ import { warnInvalidIcuSync, warnInvalidMaxCharsSync, } from '../../console/index.js';
13
13
  import { exitSync } from '../../console/logging.js';
14
- export async function createDictionaryUpdates(dictionaryPath, warnings, esbuildConfig) {
14
+ export async function createDictionaryUpdates(dictionaryPath, errors, warnings, esbuildConfig) {
15
15
  let dictionary;
16
16
  // ---- HANDLE JSON STRING DICTIONARY ----- //
17
17
  if (dictionaryPath.endsWith('.json')) {
@@ -54,16 +54,25 @@ export async function createDictionaryUpdates(dictionaryPath, warnings, esbuildC
54
54
  warnings.push(warnInvalidIcuSync(dictionaryPath, entry, error ?? 'Unknown error'));
55
55
  continue;
56
56
  }
57
+ // Validate maxChars
58
+ if (props?.$maxChars &&
59
+ (isNaN(props.$maxChars) || !Number.isInteger(props.$maxChars))) {
60
+ errors.push(warnInvalidMaxCharsSync(dictionaryPath, String(props.$maxChars), id));
61
+ continue;
62
+ }
57
63
  // Map $context to context
58
64
  const context = props?.$context;
65
+ const maxChars = props?.$maxChars;
59
66
  const metadata = {
60
67
  id,
61
68
  ...(context && { context }),
69
+ ...(maxChars != null && { maxChars: Math.abs(maxChars) }),
62
70
  // This hash isn't actually used by the GT API, just for consistency sake
63
71
  hash: hashSource({
64
72
  source: entry,
65
73
  ...(context && { context }),
66
74
  ...(id && { id }),
75
+ ...(maxChars != null && { maxChars: Math.abs(maxChars) }),
67
76
  dataFormat: 'ICU',
68
77
  }),
69
78
  };
@@ -60,11 +60,13 @@ export async function createInlineUpdates(pkg, validate, filePatterns, parsingOp
60
60
  }
61
61
  // Post-process to add a hash to each update
62
62
  await Promise.all(updates.map(async (update) => {
63
- const context = update.metadata.context;
64
63
  const hash = hashSource({
65
64
  source: update.source,
66
- ...(context && { context }),
65
+ ...(update.metadata.context && { context: update.metadata.context }),
67
66
  ...(update.metadata.id && { id: update.metadata.id }),
67
+ ...(update.metadata.maxChars != null && {
68
+ maxChars: update.metadata.maxChars,
69
+ }),
68
70
  dataFormat: update.dataFormat,
69
71
  });
70
72
  update.metadata.hash = hash;
@@ -26,7 +26,7 @@ export async function createUpdates(options, src, sourceDictionary, pkg, validat
26
26
  if (sourceDictionary.endsWith('.json')) {
27
27
  updates = [
28
28
  ...updates,
29
- ...(await createDictionaryUpdates(sourceDictionary, warnings)),
29
+ ...(await createDictionaryUpdates(sourceDictionary, errors, warnings)),
30
30
  ];
31
31
  }
32
32
  else {
@@ -44,7 +44,7 @@ export async function createUpdates(options, src, sourceDictionary, pkg, validat
44
44
  }
45
45
  updates = [
46
46
  ...updates,
47
- ...(await createDictionaryUpdates(sourceDictionary, warnings, esbuildConfig)),
47
+ ...(await createDictionaryUpdates(sourceDictionary, errors, warnings, esbuildConfig)),
48
48
  ];
49
49
  }
50
50
  }
@@ -1,6 +1,13 @@
1
1
  export type Entry = string;
2
2
  export type DictionaryMetadata = {
3
+ $context?: string;
4
+ $maxChars?: number;
5
+ $_hash?: string;
6
+ /** @deprecated use $context instead */
3
7
  context?: string;
8
+ /** @deprecated use $maxChars instead */
9
+ maxChars?: number;
10
+ /** @deprecated */
4
11
  variablesOptions?: Record<string, any>;
5
12
  [key: string]: any;
6
13
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtx-cli",
3
- "version": "2.5.22",
3
+ "version": "2.5.24",
4
4
  "main": "dist/index.js",
5
5
  "bin": "dist/main.js",
6
6
  "files": [
@@ -104,7 +104,7 @@
104
104
  "unified": "^11.0.5",
105
105
  "unist-util-visit": "^5.0.0",
106
106
  "yaml": "^2.8.0",
107
- "generaltranslation": "8.0.6"
107
+ "generaltranslation": "8.1.1"
108
108
  },
109
109
  "devDependencies": {
110
110
  "@babel/types": "^7.28.4",