gtx-cli 2.5.23 → 2.5.25

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,20 @@
1
1
  # gtx-cli
2
2
 
3
+ ## 2.5.25
4
+
5
+ ### Patch Changes
6
+
7
+ - [#876](https://github.com/generaltranslation/gt/pull/876) [`28bd6d5`](https://github.com/generaltranslation/gt/commit/28bd6d5f1ed50658da2e3adc5b59a40804b00b02) Thanks [@fernando-aviles](https://github.com/fernando-aviles)! - Adding experimental alphabetical sort for JSONs with locales as keys
8
+
9
+ ## 2.5.24
10
+
11
+ ### Patch Changes
12
+
13
+ - [#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
14
+
15
+ - Updated dependencies [[`37bac4c`](https://github.com/generaltranslation/gt/commit/37bac4ce11689a2f729efbcb2e052205447a7f71)]:
16
+ - generaltranslation@8.1.1
17
+
3
18
  ## 2.5.23
4
19
 
5
20
  ### 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);
@@ -207,16 +207,10 @@ export function mergeJson(originalContent, inputPath, options, targets, defaultL
207
207
  return [JSON.stringify(mergedJson, null, 2)];
208
208
  }
209
209
  function sortByLocaleOrder(items, sourceObjectOptions, localeOrder, sourceObjectPointer, defaultLocale) {
210
- if (sourceObjectOptions.experimentalSort !== 'locales' ||
211
- !localeOrder.length ||
212
- !sourceObjectOptions.key) {
210
+ const sortMode = sourceObjectOptions.experimentalSort;
211
+ if (!sortMode || !sourceObjectOptions.key) {
213
212
  return items;
214
213
  }
215
- const orderedLocaleList = [
216
- defaultLocale,
217
- ...localeOrder.filter((locale) => locale !== defaultLocale),
218
- ];
219
- const localeOrderValues = orderedLocaleList.map((locale) => getIdentifyingLocaleProperty(locale, sourceObjectPointer, sourceObjectOptions));
220
214
  const itemsWithLocale = items.map((item) => {
221
215
  let localeValue;
222
216
  try {
@@ -237,21 +231,56 @@ function sortByLocaleOrder(items, sourceObjectOptions, localeOrder, sourceObject
237
231
  }
238
232
  return { item, localeValue };
239
233
  });
240
- const orderedItems = [];
241
- const remainingItems = [...itemsWithLocale];
242
- for (const localeValue of localeOrderValues) {
243
- for (let i = 0; i < remainingItems.length;) {
244
- const entry = remainingItems[i];
245
- if (entry.localeValue === localeValue) {
246
- orderedItems.push(entry.item);
247
- remainingItems.splice(i, 1);
234
+ if (sortMode === 'locales') {
235
+ if (!localeOrder.length) {
236
+ return items;
237
+ }
238
+ const orderedLocaleList = [
239
+ defaultLocale,
240
+ ...localeOrder.filter((locale) => locale !== defaultLocale),
241
+ ];
242
+ const localeOrderValues = orderedLocaleList.map((locale) => getIdentifyingLocaleProperty(locale, sourceObjectPointer, sourceObjectOptions));
243
+ const orderedItems = [];
244
+ const remainingItems = [...itemsWithLocale];
245
+ for (const localeValue of localeOrderValues) {
246
+ for (let i = 0; i < remainingItems.length;) {
247
+ const entry = remainingItems[i];
248
+ if (entry.localeValue === localeValue) {
249
+ orderedItems.push(entry.item);
250
+ remainingItems.splice(i, 1);
251
+ continue;
252
+ }
253
+ i += 1;
254
+ }
255
+ }
256
+ remainingItems.forEach((entry) => orderedItems.push(entry.item));
257
+ return orderedItems;
258
+ }
259
+ if (sortMode === 'localesAlphabetical') {
260
+ const defaultLocaleValue = getIdentifyingLocaleProperty(defaultLocale, sourceObjectPointer, sourceObjectOptions);
261
+ const defaultItems = [];
262
+ const sortableItems = [];
263
+ const remainingItems = [];
264
+ for (const entry of itemsWithLocale) {
265
+ if (entry.localeValue === defaultLocaleValue) {
266
+ defaultItems.push(entry);
267
+ continue;
268
+ }
269
+ if (entry.localeValue) {
270
+ sortableItems.push(entry);
248
271
  continue;
249
272
  }
250
- i += 1;
273
+ remainingItems.push(entry);
251
274
  }
275
+ sortableItems.sort((a, b) => {
276
+ if (!a.localeValue || !b.localeValue) {
277
+ return 0;
278
+ }
279
+ return a.localeValue.localeCompare(b.localeValue);
280
+ });
281
+ return [...defaultItems, ...sortableItems, ...remainingItems].map((entry) => entry.item);
252
282
  }
253
- remainingItems.forEach((entry) => orderedItems.push(entry.item));
254
- return orderedItems;
283
+ return items;
255
284
  }
256
285
  /**
257
286
  * Apply transformations to the sourceItem in-place
@@ -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
  };
@@ -208,7 +208,7 @@ export type SourceObjectOptions = {
208
208
  key?: string;
209
209
  localeProperty?: string;
210
210
  transform?: TransformOptions;
211
- experimentalSort?: 'locales';
211
+ experimentalSort?: 'locales' | 'localesAlphabetical';
212
212
  };
213
213
  export type TransformOptions = {
214
214
  [transformPath: string]: TransformOption;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtx-cli",
3
- "version": "2.5.23",
3
+ "version": "2.5.25",
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.1.0"
107
+ "generaltranslation": "8.1.1"
108
108
  },
109
109
  "devDependencies": {
110
110
  "@babel/types": "^7.28.4",