gt 2.14.3 → 2.14.4
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 +10 -0
- package/dist/config/defaults.js +2 -1
- package/dist/console/index.d.ts +1 -0
- package/dist/console/index.js +1 -0
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/react/jsx/utils/constants.d.ts +7 -0
- package/dist/react/jsx/utils/constants.js +10 -0
- package/dist/react/jsx/utils/getPathsAndAliases.js +5 -3
- package/dist/react/jsx/utils/jsxParsing/addGTIdentifierToSyntaxTree.d.ts +1 -1
- package/dist/react/jsx/utils/jsxParsing/addGTIdentifierToSyntaxTree.js +23 -8
- package/dist/react/jsx/utils/jsxParsing/autoInsertion.d.ts +27 -0
- package/dist/react/jsx/utils/jsxParsing/autoInsertion.js +427 -0
- package/dist/react/jsx/utils/jsxParsing/parseJsx.d.ts +1 -0
- package/dist/react/jsx/utils/jsxParsing/parseJsx.js +149 -40
- package/dist/react/parse/createInlineUpdates.js +54 -1
- package/dist/react/utils/getVariableName.d.ts +2 -0
- package/dist/react/utils/getVariableName.js +4 -0
- package/dist/types/parsing.d.ts +2 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# gtx-cli
|
|
2
2
|
|
|
3
|
+
## 2.14.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#1158](https://github.com/generaltranslation/gt/pull/1158) [`5b85ccd`](https://github.com/generaltranslation/gt/commit/5b85ccd80b93b91eae9c873b258a13b6a57443c8) Thanks [@ErnestM1234](https://github.com/ErnestM1234)! - add auto injection for jsx translation
|
|
8
|
+
|
|
9
|
+
- Updated dependencies [[`5b85ccd`](https://github.com/generaltranslation/gt/commit/5b85ccd80b93b91eae9c873b258a13b6a57443c8)]:
|
|
10
|
+
- generaltranslation@8.2.2
|
|
11
|
+
- @generaltranslation/python-extractor@0.2.6
|
|
12
|
+
|
|
3
13
|
## 2.14.3
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
package/dist/config/defaults.js
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* @property {boolean} includeSourceCodeContext - Include surrounding source code lines as context for translations.
|
|
5
5
|
*/
|
|
6
6
|
export const GT_PARSING_FLAGS_DEFAULT = {
|
|
7
|
-
autoDerive:
|
|
7
|
+
autoDerive: false,
|
|
8
8
|
includeSourceCodeContext: false,
|
|
9
|
+
enableAutoJsxInjection: false,
|
|
9
10
|
};
|
|
10
11
|
/**
|
|
11
12
|
* Default parsing flags for all files
|
package/dist/console/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export declare const warnMissingReturnSync: (file: string, functionName: string,
|
|
|
5
5
|
export declare const warnHasUnwrappedExpressionSync: (file: string, unwrappedExpressions: string[], id?: string, location?: string) => string;
|
|
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
|
+
export declare const warnNestedInternalTComponent: (file: string, location?: string) => string;
|
|
8
9
|
export declare const warnNonStaticExpressionSync: (file: string, attrName: string, value: string, location?: string) => string;
|
|
9
10
|
export declare const warnInvalidMaxCharsSync: (file: string, value: string, location?: string) => string;
|
|
10
11
|
export declare const warnInvalidFormatSync: (file: string, value: string, location?: string) => string;
|
package/dist/console/index.js
CHANGED
|
@@ -14,6 +14,7 @@ export const warnMissingReturnSync = (file, functionName, location) => withLocat
|
|
|
14
14
|
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);
|
|
15
15
|
export const warnFailedToConstructJsxTreeSync = (file, code, location) => withLocation(file, `Failed to construct JsxTree! Call expression is not a valid createElement call: ${colorizeContent(code)}`, location);
|
|
16
16
|
export const warnNestedTComponent = (file, location) => withLocation(file, `Found nested <T> component. <T> components cannot be directly nested.`, location);
|
|
17
|
+
export const warnNestedInternalTComponent = (file, location) => withLocation(file, `DEBUG: Found nested <GtInternalTranslateJsx> component. <GtInternalTranslateJsx> components cannot be directly nested.`, location);
|
|
17
18
|
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);
|
|
18
19
|
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);
|
|
19
20
|
export const warnInvalidFormatSync = (file, value, location) => withLocation(file, `Found invalid $format value: ${colorizeContent(value)}. Must be one of: 'ICU', 'STRING', 'I18NEXT'.`, location);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const PACKAGE_VERSION = "2.14.
|
|
1
|
+
export declare const PACKAGE_VERSION = "2.14.4";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// This file is auto-generated. Do not edit manually.
|
|
2
|
-
export const PACKAGE_VERSION = '2.14.
|
|
2
|
+
export const PACKAGE_VERSION = '2.14.4';
|
|
@@ -18,6 +18,9 @@ export declare const TRANSLATION_COMPONENT = "T";
|
|
|
18
18
|
export declare const STATIC_COMPONENT = "Static";
|
|
19
19
|
export declare const DERIVE_COMPONENT = "Derive";
|
|
20
20
|
export declare const BRANCH_COMPONENT = "Branch";
|
|
21
|
+
export declare const DEFAULT_GT_IMPORT_SOURCE = "gt-react/browser";
|
|
22
|
+
export declare const INTERNAL_TRANSLATION_COMPONENT = "GtInternalTranslateJsx";
|
|
23
|
+
export declare const INTERNAL_VAR_COMPONENT = "GtInternalVar";
|
|
21
24
|
export declare const VAR_COMPONENT = "Var";
|
|
22
25
|
export declare const DATETIME_COMPONENT = "DateTime";
|
|
23
26
|
export declare const RELATIVE_TIME_COMPONENT = "RelativeTime";
|
|
@@ -31,4 +34,8 @@ export declare const VARIABLE_COMPONENTS: string[];
|
|
|
31
34
|
export declare const GT_ATTRIBUTES_WITH_SUGAR: readonly ["$id", "$context", "$maxChars", "$format"];
|
|
32
35
|
export declare const GT_ATTRIBUTES: readonly ["id", "context", "maxChars", "$id", "$context", "$maxChars", "$format"];
|
|
33
36
|
export declare const DATA_ATTR_PREFIX: "data-";
|
|
37
|
+
/** Branch control props — not translatable content. */
|
|
38
|
+
export declare const BRANCH_CONTROL_PROPS: Set<string>;
|
|
39
|
+
/** Plural control props — not translatable content. */
|
|
40
|
+
export declare const PLURAL_CONTROL_PROPS: Set<string>;
|
|
34
41
|
export declare const T_GLOBAL_REGISTRATION_FUNCTION_MARKER = "_gt_internal_t_global_registration_marker";
|
|
@@ -18,6 +18,9 @@ export const TRANSLATION_COMPONENT = 'T';
|
|
|
18
18
|
export const STATIC_COMPONENT = 'Static';
|
|
19
19
|
export const DERIVE_COMPONENT = 'Derive';
|
|
20
20
|
export const BRANCH_COMPONENT = 'Branch';
|
|
21
|
+
export const DEFAULT_GT_IMPORT_SOURCE = 'gt-react/browser';
|
|
22
|
+
export const INTERNAL_TRANSLATION_COMPONENT = 'GtInternalTranslateJsx';
|
|
23
|
+
export const INTERNAL_VAR_COMPONENT = 'GtInternalVar';
|
|
21
24
|
// Variable components
|
|
22
25
|
export const VAR_COMPONENT = 'Var';
|
|
23
26
|
export const DATETIME_COMPONENT = 'DateTime';
|
|
@@ -45,6 +48,8 @@ export const GT_TRANSLATION_FUNCS = [
|
|
|
45
48
|
CURRENCY_COMPONENT,
|
|
46
49
|
NUM_COMPONENT,
|
|
47
50
|
BRANCH_COMPONENT,
|
|
51
|
+
INTERNAL_TRANSLATION_COMPONENT,
|
|
52
|
+
INTERNAL_VAR_COMPONENT,
|
|
48
53
|
PLURAL_COMPONENT,
|
|
49
54
|
];
|
|
50
55
|
// GT String translation functions
|
|
@@ -66,6 +71,7 @@ export const VARIABLE_COMPONENTS = [
|
|
|
66
71
|
NUM_COMPONENT,
|
|
67
72
|
STATIC_COMPONENT,
|
|
68
73
|
DERIVE_COMPONENT,
|
|
74
|
+
INTERNAL_VAR_COMPONENT,
|
|
69
75
|
];
|
|
70
76
|
export const GT_ATTRIBUTES_WITH_SUGAR = [
|
|
71
77
|
'$id',
|
|
@@ -81,5 +87,9 @@ export const GT_ATTRIBUTES = [
|
|
|
81
87
|
];
|
|
82
88
|
// Data attribute prefix injected by build tools
|
|
83
89
|
export const DATA_ATTR_PREFIX = 'data-';
|
|
90
|
+
/** Branch control props — not translatable content. */
|
|
91
|
+
export const BRANCH_CONTROL_PROPS = new Set(['branch']);
|
|
92
|
+
/** Plural control props — not translatable content. */
|
|
93
|
+
export const PLURAL_CONTROL_PROPS = new Set(['n', 'locales']);
|
|
84
94
|
// demarcation for global t macro
|
|
85
95
|
export const T_GLOBAL_REGISTRATION_FUNCTION_MARKER = '_gt_internal_t_global_registration_marker';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import traverseModule from '@babel/traverse';
|
|
2
|
-
import { GT_TRANSLATION_FUNCS, INLINE_TRANSLATION_HOOK, INLINE_TRANSLATION_HOOK_ASYNC, INLINE_MESSAGE_HOOK, INLINE_MESSAGE_HOOK_ASYNC, MSG_REGISTRATION_FUNCTION, T_REGISTRATION_FUNCTION, TRANSLATION_COMPONENT, T_GLOBAL_REGISTRATION_FUNCTION, T_GLOBAL_REGISTRATION_FUNCTION_MARKER, } from '../../jsx/utils/constants.js';
|
|
2
|
+
import { GT_TRANSLATION_FUNCS, INLINE_TRANSLATION_HOOK, INLINE_TRANSLATION_HOOK_ASYNC, INLINE_MESSAGE_HOOK, INLINE_MESSAGE_HOOK_ASYNC, MSG_REGISTRATION_FUNCTION, T_REGISTRATION_FUNCTION, TRANSLATION_COMPONENT, INTERNAL_TRANSLATION_COMPONENT, T_GLOBAL_REGISTRATION_FUNCTION, T_GLOBAL_REGISTRATION_FUNCTION_MARKER, } from '../../jsx/utils/constants.js';
|
|
3
3
|
import { extractImportName } from './parseAst.js';
|
|
4
4
|
import * as t from '@babel/types';
|
|
5
5
|
// Handle CommonJS/ESM interop
|
|
@@ -45,7 +45,8 @@ export function getPathsAndAliases(ast, pkgs) {
|
|
|
45
45
|
originalName: name.original,
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
|
-
else if (name.original === TRANSLATION_COMPONENT
|
|
48
|
+
else if (name.original === TRANSLATION_COMPONENT ||
|
|
49
|
+
name.original === INTERNAL_TRANSLATION_COMPONENT) {
|
|
49
50
|
translationComponentPaths.push({
|
|
50
51
|
localName: name.local,
|
|
51
52
|
path,
|
|
@@ -82,7 +83,8 @@ export function getPathsAndAliases(ast, pkgs) {
|
|
|
82
83
|
originalName: name.original,
|
|
83
84
|
});
|
|
84
85
|
}
|
|
85
|
-
else if (name.original === TRANSLATION_COMPONENT
|
|
86
|
+
else if (name.original === TRANSLATION_COMPONENT ||
|
|
87
|
+
name.original === INTERNAL_TRANSLATION_COMPONENT) {
|
|
86
88
|
translationComponentPaths.push({
|
|
87
89
|
localName: name.local,
|
|
88
90
|
path: parentPath,
|
|
@@ -6,4 +6,4 @@ import { MultipliedTreeNode } from './types.js';
|
|
|
6
6
|
* @param startingIndex - The starting index for GT IDs
|
|
7
7
|
* @returns The tree with GT identifiers added
|
|
8
8
|
*/
|
|
9
|
-
export default function addGTIdentifierToSyntaxTree(tree: MultipliedTreeNode, startingIndex?: number): JsxChildren;
|
|
9
|
+
export default function addGTIdentifierToSyntaxTree(tree: MultipliedTreeNode, startingIndex?: number, gtVariableNames?: Set<string>): JsxChildren;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { HTML_CONTENT_PROPS, } from 'generaltranslation/types';
|
|
2
2
|
import { defaultVariableNames, getVariableName, minifyVariableType, } from '../../../utils/getVariableName.js';
|
|
3
3
|
import { isAcceptedPluralForm } from 'generaltranslation/internal';
|
|
4
|
-
import { DATA_ATTR_PREFIX } from '../constants.js';
|
|
4
|
+
import { DATA_ATTR_PREFIX, BRANCH_COMPONENT, PLURAL_COMPONENT, } from '../constants.js';
|
|
5
5
|
/**
|
|
6
6
|
* Construct the data-_gt prop
|
|
7
7
|
* @param type - The type of the element
|
|
@@ -18,7 +18,7 @@ function constructGTProp(type, props, id) {
|
|
|
18
18
|
return acc;
|
|
19
19
|
}, {});
|
|
20
20
|
// Plural
|
|
21
|
-
if (type ===
|
|
21
|
+
if (type === PLURAL_COMPONENT) {
|
|
22
22
|
const pluralBranches = Object.entries(props).reduce((acc, [branchName, branch]) => {
|
|
23
23
|
if (isAcceptedPluralForm(branchName)) {
|
|
24
24
|
acc[branchName] = addGTIdentifierToSyntaxTree(branch, id);
|
|
@@ -32,7 +32,7 @@ function constructGTProp(type, props, id) {
|
|
|
32
32
|
}
|
|
33
33
|
// Branch
|
|
34
34
|
}
|
|
35
|
-
else if (type ===
|
|
35
|
+
else if (type === BRANCH_COMPONENT) {
|
|
36
36
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
|
|
37
37
|
const { children, branch, ...allBranches } = props;
|
|
38
38
|
// Filter out data-* attributes injected by build tools
|
|
@@ -57,7 +57,7 @@ function constructGTProp(type, props, id) {
|
|
|
57
57
|
* @param startingIndex - The starting index for GT IDs
|
|
58
58
|
* @returns The tree with GT identifiers added
|
|
59
59
|
*/
|
|
60
|
-
export default function addGTIdentifierToSyntaxTree(tree, startingIndex = 0) {
|
|
60
|
+
export default function addGTIdentifierToSyntaxTree(tree, startingIndex = 0, gtVariableNames) {
|
|
61
61
|
// Edge case: boolean or null, just return the tree
|
|
62
62
|
if (typeof tree === 'boolean' || tree === null) {
|
|
63
63
|
return tree;
|
|
@@ -79,8 +79,11 @@ export default function addGTIdentifierToSyntaxTree(tree, startingIndex = 0) {
|
|
|
79
79
|
if (type === 'React.Fragment') {
|
|
80
80
|
type = '';
|
|
81
81
|
}
|
|
82
|
-
// Variables
|
|
83
|
-
|
|
82
|
+
// Variables — only treat as GT variable if confirmed as GT import
|
|
83
|
+
const isGTVariable = gtVariableNames
|
|
84
|
+
? gtVariableNames.has(type)
|
|
85
|
+
: Object.keys(defaultVariableNames).includes(type);
|
|
86
|
+
if (isGTVariable) {
|
|
84
87
|
const variableType = minifyVariableType(type);
|
|
85
88
|
const variableName = getVariableName(props, type, indexObject.index);
|
|
86
89
|
return {
|
|
@@ -91,9 +94,21 @@ export default function addGTIdentifierToSyntaxTree(tree, startingIndex = 0) {
|
|
|
91
94
|
}
|
|
92
95
|
// Construct the data-_gt prop
|
|
93
96
|
const generaltranslation = constructGTProp(type, (props || {}), indexObject.index);
|
|
94
|
-
// Save current index and recurse
|
|
97
|
+
// Save current index and recurse.
|
|
98
|
+
// For Branch/Plural, children use an independent index counter
|
|
99
|
+
// (same as branch props) so they don't inflate sibling indices.
|
|
100
|
+
// This matches the compiler's id.copy() behavior.
|
|
95
101
|
const currentIndex = indexObject.index;
|
|
96
|
-
const
|
|
102
|
+
const isBranching = type === BRANCH_COMPONENT || type === PLURAL_COMPONENT;
|
|
103
|
+
let children;
|
|
104
|
+
if (isBranching) {
|
|
105
|
+
const savedIndex = indexObject.index;
|
|
106
|
+
children = handleChildren(props?.children === undefined ? null : props?.children);
|
|
107
|
+
indexObject.index = savedIndex;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
children = handleChildren(props?.children === undefined ? null : props?.children);
|
|
111
|
+
}
|
|
97
112
|
// Enforce boolean rendering behavior
|
|
98
113
|
// <T>{true}</T> -> true <- this is a boolean value, not a string
|
|
99
114
|
// <T>{false}</T> -> nothing
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto JSX Insertion for CLI extraction.
|
|
3
|
+
*
|
|
4
|
+
* Inserts <T> and <Var> JSX elements into the AST so the extraction pipeline
|
|
5
|
+
* can process them as if the user wrote them. This operates on raw JSX syntax
|
|
6
|
+
* (JSXElement, JSXText, JSXExpressionContainer) — NOT compiled jsx() calls.
|
|
7
|
+
*
|
|
8
|
+
* Rules follow JSX_INSERTION_RULES.md from the compiler package.
|
|
9
|
+
*/
|
|
10
|
+
import * as t from '@babel/types';
|
|
11
|
+
/** Check if a node was auto-inserted */
|
|
12
|
+
export declare function isAutoInserted(node: t.Node): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Ensure GtInternalTranslateJsx and GtInternalVar are imported in the AST.
|
|
15
|
+
* Always adds: import { GtInternalTranslateJsx, GtInternalVar } from 'gt-react/browser';
|
|
16
|
+
* These are distinct from user T/Var so there's no ambiguity.
|
|
17
|
+
*
|
|
18
|
+
* Updates importAliases in-place.
|
|
19
|
+
*/
|
|
20
|
+
export declare function ensureTAndVarImported(ast: t.File, importAliases: Record<string, string>): void;
|
|
21
|
+
/**
|
|
22
|
+
* Traverse the AST and insert GtInternalTranslateJsx and GtInternalVar JSX elements following
|
|
23
|
+
* the insertion rules. Uses deliberate children traversal.
|
|
24
|
+
*
|
|
25
|
+
* Every inserted node gets node._autoInserted = true.
|
|
26
|
+
*/
|
|
27
|
+
export declare function autoInsertJsxComponents(ast: t.File, importAliases: Record<string, string>): void;
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto JSX Insertion for CLI extraction.
|
|
3
|
+
*
|
|
4
|
+
* Inserts <T> and <Var> JSX elements into the AST so the extraction pipeline
|
|
5
|
+
* can process them as if the user wrote them. This operates on raw JSX syntax
|
|
6
|
+
* (JSXElement, JSXText, JSXExpressionContainer) — NOT compiled jsx() calls.
|
|
7
|
+
*
|
|
8
|
+
* Rules follow JSX_INSERTION_RULES.md from the compiler package.
|
|
9
|
+
*/
|
|
10
|
+
import * as t from '@babel/types';
|
|
11
|
+
import traverseModule from '@babel/traverse';
|
|
12
|
+
const traverse = traverseModule.default || traverseModule;
|
|
13
|
+
import { isStaticExpression } from '../../evaluateJsx.js';
|
|
14
|
+
import { TRANSLATION_COMPONENT, INTERNAL_TRANSLATION_COMPONENT, INTERNAL_VAR_COMPONENT, VARIABLE_COMPONENTS, BRANCH_COMPONENT, PLURAL_COMPONENT, DEFAULT_GT_IMPORT_SOURCE, DERIVE_COMPONENT, STATIC_COMPONENT, BRANCH_CONTROL_PROPS, PLURAL_CONTROL_PROPS, } from '../constants.js';
|
|
15
|
+
/** Tracks which AST nodes were auto-inserted by this module */
|
|
16
|
+
const autoInsertedNodes = new WeakSet();
|
|
17
|
+
/** Check if a node was auto-inserted */
|
|
18
|
+
export function isAutoInserted(node) {
|
|
19
|
+
return autoInsertedNodes.has(node);
|
|
20
|
+
}
|
|
21
|
+
// ===== Public API ===== //
|
|
22
|
+
/**
|
|
23
|
+
* Ensure GtInternalTranslateJsx and GtInternalVar are imported in the AST.
|
|
24
|
+
* Always adds: import { GtInternalTranslateJsx, GtInternalVar } from 'gt-react/browser';
|
|
25
|
+
* These are distinct from user T/Var so there's no ambiguity.
|
|
26
|
+
*
|
|
27
|
+
* Updates importAliases in-place.
|
|
28
|
+
*/
|
|
29
|
+
export function ensureTAndVarImported(ast, importAliases) {
|
|
30
|
+
// Check if internal components are already imported
|
|
31
|
+
const hasInternalT = Object.values(importAliases).includes(INTERNAL_TRANSLATION_COMPONENT);
|
|
32
|
+
const hasInternalVar = Object.values(importAliases).includes(INTERNAL_VAR_COMPONENT);
|
|
33
|
+
if (hasInternalT && hasInternalVar)
|
|
34
|
+
return;
|
|
35
|
+
const specifiers = [];
|
|
36
|
+
if (!hasInternalT) {
|
|
37
|
+
specifiers.push(t.importSpecifier(t.identifier(INTERNAL_TRANSLATION_COMPONENT), t.identifier(INTERNAL_TRANSLATION_COMPONENT)));
|
|
38
|
+
importAliases[INTERNAL_TRANSLATION_COMPONENT] =
|
|
39
|
+
INTERNAL_TRANSLATION_COMPONENT;
|
|
40
|
+
}
|
|
41
|
+
if (!hasInternalVar) {
|
|
42
|
+
specifiers.push(t.importSpecifier(t.identifier(INTERNAL_VAR_COMPONENT), t.identifier(INTERNAL_VAR_COMPONENT)));
|
|
43
|
+
importAliases[INTERNAL_VAR_COMPONENT] = INTERNAL_VAR_COMPONENT;
|
|
44
|
+
}
|
|
45
|
+
const importDecl = t.importDeclaration(specifiers, t.stringLiteral(DEFAULT_GT_IMPORT_SOURCE));
|
|
46
|
+
traverse(ast, {
|
|
47
|
+
Program(path) {
|
|
48
|
+
path.unshiftContainer('body', importDecl);
|
|
49
|
+
path.stop();
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Traverse the AST and insert GtInternalTranslateJsx and GtInternalVar JSX elements following
|
|
55
|
+
* the insertion rules. Uses deliberate children traversal.
|
|
56
|
+
*
|
|
57
|
+
* Every inserted node gets node._autoInserted = true.
|
|
58
|
+
*/
|
|
59
|
+
export function autoInsertJsxComponents(ast, importAliases) {
|
|
60
|
+
const processedNodes = new WeakSet();
|
|
61
|
+
const tLocalName = getLocalName(importAliases, INTERNAL_TRANSLATION_COMPONENT);
|
|
62
|
+
const varLocalName = getLocalName(importAliases, INTERNAL_VAR_COMPONENT);
|
|
63
|
+
traverse(ast, {
|
|
64
|
+
JSXElement(path) {
|
|
65
|
+
if (processedNodes.has(path.node))
|
|
66
|
+
return;
|
|
67
|
+
processJsxElement({
|
|
68
|
+
path,
|
|
69
|
+
insideAutoT: false,
|
|
70
|
+
importAliases,
|
|
71
|
+
processedNodes,
|
|
72
|
+
tLocalName,
|
|
73
|
+
varLocalName,
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
JSXFragment(path) {
|
|
77
|
+
if (processedNodes.has(path.node))
|
|
78
|
+
return;
|
|
79
|
+
processJsxFragment({
|
|
80
|
+
path,
|
|
81
|
+
insideAutoT: false,
|
|
82
|
+
importAliases,
|
|
83
|
+
processedNodes,
|
|
84
|
+
tLocalName,
|
|
85
|
+
varLocalName,
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function processJsxElement({ path, insideAutoT, importAliases, processedNodes, tLocalName, varLocalName, }) {
|
|
91
|
+
processedNodes.add(path.node);
|
|
92
|
+
// Get component type
|
|
93
|
+
const typeName = getElementTypeName(path.node);
|
|
94
|
+
const canonicalName = typeName ? importAliases[typeName] : undefined;
|
|
95
|
+
// User T → mark all descendants, hands off
|
|
96
|
+
if (canonicalName === TRANSLATION_COMPONENT) {
|
|
97
|
+
markDescendantJsx(path, processedNodes);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// Branch/Plural/Derive/Static → opaque, process props for dynamic Var
|
|
101
|
+
// Must be checked BEFORE user variable check because Derive/Static are in VARIABLE_COMPONENTS
|
|
102
|
+
if (canonicalName === BRANCH_COMPONENT ||
|
|
103
|
+
canonicalName === PLURAL_COMPONENT ||
|
|
104
|
+
canonicalName === DERIVE_COMPONENT ||
|
|
105
|
+
canonicalName === STATIC_COMPONENT) {
|
|
106
|
+
if (!insideAutoT) {
|
|
107
|
+
// Root-level opaque component — wrap in _T
|
|
108
|
+
const tWrapper = createTWrapper([path.node], tLocalName);
|
|
109
|
+
processedNodes.add(tWrapper);
|
|
110
|
+
path.replaceWith(tWrapper);
|
|
111
|
+
}
|
|
112
|
+
processOpaqueComponentProps({
|
|
113
|
+
path,
|
|
114
|
+
insideAutoT: true,
|
|
115
|
+
importAliases,
|
|
116
|
+
processedNodes,
|
|
117
|
+
tLocalName,
|
|
118
|
+
varLocalName,
|
|
119
|
+
canonicalName,
|
|
120
|
+
});
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
// User Var/Num/Currency/DateTime → mark descendants, hands off
|
|
124
|
+
if (canonicalName && VARIABLE_COMPONENTS.includes(canonicalName)) {
|
|
125
|
+
markDescendantJsx(path, processedNodes);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
// Process children
|
|
129
|
+
processElementChildren({
|
|
130
|
+
path,
|
|
131
|
+
insideAutoT,
|
|
132
|
+
importAliases,
|
|
133
|
+
processedNodes,
|
|
134
|
+
tLocalName,
|
|
135
|
+
varLocalName,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
function processJsxFragment({ path, insideAutoT, importAliases, processedNodes, tLocalName, varLocalName, }) {
|
|
139
|
+
processedNodes.add(path.node);
|
|
140
|
+
// Fragments are treated like regular elements
|
|
141
|
+
processFragmentChildren({
|
|
142
|
+
path,
|
|
143
|
+
insideAutoT,
|
|
144
|
+
importAliases,
|
|
145
|
+
processedNodes,
|
|
146
|
+
tLocalName,
|
|
147
|
+
varLocalName,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
// ===== Children processing ===== //
|
|
151
|
+
function processElementChildren({ path, insideAutoT, importAliases, processedNodes, tLocalName, varLocalName, }) {
|
|
152
|
+
const children = path.node.children;
|
|
153
|
+
const ctx = {
|
|
154
|
+
insideAutoT,
|
|
155
|
+
importAliases,
|
|
156
|
+
processedNodes,
|
|
157
|
+
tLocalName,
|
|
158
|
+
varLocalName,
|
|
159
|
+
};
|
|
160
|
+
const hasText = hasTranslatableText(children);
|
|
161
|
+
const hasOpaque = hasOpaqueGTChild(children, importAliases);
|
|
162
|
+
const shouldClaimT = !insideAutoT && (hasText || hasOpaque);
|
|
163
|
+
if (shouldClaimT) {
|
|
164
|
+
// Process children: wrap dynamic expressions in Var, recurse into child elements
|
|
165
|
+
const childPaths = path.get('children');
|
|
166
|
+
for (const childPath of childPaths) {
|
|
167
|
+
processChild(childPath, { ...ctx, insideAutoT: true });
|
|
168
|
+
}
|
|
169
|
+
// Wrap all children in <T>
|
|
170
|
+
wrapChildrenInT(path, tLocalName, processedNodes);
|
|
171
|
+
}
|
|
172
|
+
else if (insideAutoT) {
|
|
173
|
+
// Inside a T region: wrap dynamic expressions, recurse
|
|
174
|
+
const childPaths = path.get('children');
|
|
175
|
+
for (const childPath of childPaths) {
|
|
176
|
+
processChild(childPath, ctx);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
// No text, no opaque, not inside T: just recurse into child elements
|
|
181
|
+
const childPaths = path.get('children');
|
|
182
|
+
for (const childPath of childPaths) {
|
|
183
|
+
if (childPath.isJSXElement() && !processedNodes.has(childPath.node)) {
|
|
184
|
+
processJsxElement({ path: childPath, ...ctx });
|
|
185
|
+
}
|
|
186
|
+
else if (childPath.isJSXFragment() &&
|
|
187
|
+
!processedNodes.has(childPath.node)) {
|
|
188
|
+
processJsxFragment({ path: childPath, ...ctx });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function processFragmentChildren({ path, insideAutoT, importAliases, processedNodes, tLocalName, varLocalName, }) {
|
|
194
|
+
const children = path.node.children;
|
|
195
|
+
const ctx = {
|
|
196
|
+
insideAutoT,
|
|
197
|
+
importAliases,
|
|
198
|
+
processedNodes,
|
|
199
|
+
tLocalName,
|
|
200
|
+
varLocalName,
|
|
201
|
+
};
|
|
202
|
+
const hasText = hasTranslatableText(children);
|
|
203
|
+
const hasOpaque = hasOpaqueGTChild(children, importAliases);
|
|
204
|
+
const shouldClaimT = !insideAutoT && (hasText || hasOpaque);
|
|
205
|
+
if (shouldClaimT) {
|
|
206
|
+
const childPaths = path.get('children');
|
|
207
|
+
for (const childPath of childPaths) {
|
|
208
|
+
processChild(childPath, { ...ctx, insideAutoT: true });
|
|
209
|
+
}
|
|
210
|
+
wrapFragmentChildrenInT(path, tLocalName, processedNodes);
|
|
211
|
+
}
|
|
212
|
+
else if (insideAutoT) {
|
|
213
|
+
const childPaths = path.get('children');
|
|
214
|
+
for (const childPath of childPaths) {
|
|
215
|
+
processChild(childPath, ctx);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
const childPaths = path.get('children');
|
|
220
|
+
for (const childPath of childPaths) {
|
|
221
|
+
if (childPath.isJSXElement() && !processedNodes.has(childPath.node)) {
|
|
222
|
+
processJsxElement({ path: childPath, ...ctx });
|
|
223
|
+
}
|
|
224
|
+
else if (childPath.isJSXFragment() &&
|
|
225
|
+
!processedNodes.has(childPath.node)) {
|
|
226
|
+
processJsxFragment({ path: childPath, ...ctx });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function processChild(childPath, ctx) {
|
|
232
|
+
if (childPath.isJSXElement() && !ctx.processedNodes.has(childPath.node)) {
|
|
233
|
+
processJsxElement({ path: childPath, ...ctx });
|
|
234
|
+
}
|
|
235
|
+
else if (childPath.isJSXFragment() &&
|
|
236
|
+
!ctx.processedNodes.has(childPath.node)) {
|
|
237
|
+
processJsxFragment({ path: childPath, ...ctx });
|
|
238
|
+
}
|
|
239
|
+
else if (childPath.isJSXExpressionContainer() &&
|
|
240
|
+
ctx.insideAutoT &&
|
|
241
|
+
needsVarWrapping(childPath.node)) {
|
|
242
|
+
// Wrap dynamic expression in <Var>
|
|
243
|
+
const varWrapper = createVarWrapper(childPath.node, ctx.varLocalName, ctx.processedNodes);
|
|
244
|
+
childPath.replaceWith(varWrapper);
|
|
245
|
+
ctx.processedNodes.add(varWrapper);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// ===== Opaque component props ===== //
|
|
249
|
+
function processOpaqueComponentProps({ path, insideAutoT, importAliases, processedNodes, tLocalName, varLocalName, canonicalName, }) {
|
|
250
|
+
// Branch/Plural children (fallback content) — process element-by-element before marking
|
|
251
|
+
if (insideAutoT &&
|
|
252
|
+
(canonicalName === BRANCH_COMPONENT || canonicalName === PLURAL_COMPONENT)) {
|
|
253
|
+
const childPaths = path.get('children');
|
|
254
|
+
for (const childPath of childPaths) {
|
|
255
|
+
processChild(childPath, {
|
|
256
|
+
insideAutoT: true,
|
|
257
|
+
importAliases,
|
|
258
|
+
processedNodes,
|
|
259
|
+
tLocalName,
|
|
260
|
+
varLocalName,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (!insideAutoT) {
|
|
265
|
+
markDescendantJsx(path, processedNodes);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const ctx = {
|
|
269
|
+
insideAutoT: true,
|
|
270
|
+
importAliases,
|
|
271
|
+
processedNodes,
|
|
272
|
+
tLocalName,
|
|
273
|
+
varLocalName,
|
|
274
|
+
};
|
|
275
|
+
// Wrap dynamic prop values in <Var>, skipping control props.
|
|
276
|
+
// This must happen BEFORE markDescendantJsx so that JSX inside auto-inserted
|
|
277
|
+
// _Var wrappers stays unmarked for the top-level visitor to process independently.
|
|
278
|
+
const attrs = path.get('openingElement').get('attributes');
|
|
279
|
+
for (const attrPath of attrs) {
|
|
280
|
+
if (!attrPath.isJSXAttribute())
|
|
281
|
+
continue;
|
|
282
|
+
// Determine prop name and skip control props
|
|
283
|
+
const nameNode = attrPath.node.name;
|
|
284
|
+
const propName = t.isJSXIdentifier(nameNode) ? nameNode.name : null;
|
|
285
|
+
if (isControlProp(canonicalName, propName))
|
|
286
|
+
continue;
|
|
287
|
+
const valuePath = attrPath.get('value');
|
|
288
|
+
if (!valuePath.isJSXExpressionContainer())
|
|
289
|
+
continue;
|
|
290
|
+
const expr = valuePath.node.expression;
|
|
291
|
+
// Content prop with JSX value — recurse into children for Var-wrapping
|
|
292
|
+
if (t.isJSXElement(expr) || t.isJSXFragment(expr)) {
|
|
293
|
+
const exprPath = valuePath.get('expression');
|
|
294
|
+
if (exprPath.isJSXElement() || exprPath.isJSXFragment()) {
|
|
295
|
+
const childPaths = exprPath.get('children');
|
|
296
|
+
for (const childPath of childPaths) {
|
|
297
|
+
processChild(childPath, ctx);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
// Non-JSX dynamic value — wrap in Var
|
|
303
|
+
if (needsVarWrapping(valuePath.node)) {
|
|
304
|
+
const varWrapper = createVarWrapper(valuePath.node, varLocalName, processedNodes);
|
|
305
|
+
valuePath.replaceWith(varWrapper);
|
|
306
|
+
processedNodes.add(varWrapper);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// Mark remaining descendant JSX as processed, but skip auto-inserted nodes
|
|
310
|
+
// so the top-level visitor can still discover JSX inside _Var wrappers.
|
|
311
|
+
// For Derive/Static, only mark props — leave children unmarked so the
|
|
312
|
+
// top-level visitor can independently process them (e.g. Branch/Plural
|
|
313
|
+
// inside Derive should get their own _T, matching compiler behavior).
|
|
314
|
+
if (canonicalName === DERIVE_COMPONENT ||
|
|
315
|
+
canonicalName === STATIC_COMPONENT) {
|
|
316
|
+
for (const attrPath of path.get('openingElement').get('attributes')) {
|
|
317
|
+
attrPath.traverse({
|
|
318
|
+
JSXElement(childPath) {
|
|
319
|
+
processedNodes.add(childPath.node);
|
|
320
|
+
},
|
|
321
|
+
JSXFragment(childPath) {
|
|
322
|
+
processedNodes.add(childPath.node);
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
markDescendantJsx(path, processedNodes);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
function isControlProp(canonicalName, propName) {
|
|
332
|
+
if (!propName)
|
|
333
|
+
return false;
|
|
334
|
+
if (canonicalName === BRANCH_COMPONENT) {
|
|
335
|
+
return BRANCH_CONTROL_PROPS.has(propName) || propName.startsWith('data-');
|
|
336
|
+
}
|
|
337
|
+
if (canonicalName === PLURAL_COMPONENT) {
|
|
338
|
+
return PLURAL_CONTROL_PROPS.has(propName);
|
|
339
|
+
}
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
// ===== Helper functions ===== //
|
|
343
|
+
function getElementTypeName(element) {
|
|
344
|
+
const name = element.openingElement.name;
|
|
345
|
+
if (t.isJSXIdentifier(name))
|
|
346
|
+
return name.name;
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
function getLocalName(importAliases, canonicalName) {
|
|
350
|
+
const entry = Object.entries(importAliases).find(([, v]) => v === canonicalName);
|
|
351
|
+
return entry ? entry[0] : canonicalName;
|
|
352
|
+
}
|
|
353
|
+
function hasTranslatableText(children) {
|
|
354
|
+
return children.some((child) => t.isJSXText(child) && child.value.trim().length > 0);
|
|
355
|
+
}
|
|
356
|
+
function hasOpaqueGTChild(children, importAliases) {
|
|
357
|
+
return children.some((child) => {
|
|
358
|
+
if (!t.isJSXElement(child))
|
|
359
|
+
return false;
|
|
360
|
+
const typeName = getElementTypeName(child);
|
|
361
|
+
if (!typeName)
|
|
362
|
+
return false;
|
|
363
|
+
const canonical = importAliases[typeName];
|
|
364
|
+
return (canonical === BRANCH_COMPONENT ||
|
|
365
|
+
canonical === PLURAL_COMPONENT ||
|
|
366
|
+
canonical === DERIVE_COMPONENT ||
|
|
367
|
+
canonical === STATIC_COMPONENT);
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
function needsVarWrapping(container) {
|
|
371
|
+
const expr = container.expression;
|
|
372
|
+
if (t.isJSXEmptyExpression(expr))
|
|
373
|
+
return false;
|
|
374
|
+
// Use isStaticExpression to check — if static, no wrapping needed
|
|
375
|
+
const analysis = isStaticExpression(expr, true);
|
|
376
|
+
if (analysis.isStatic)
|
|
377
|
+
return false;
|
|
378
|
+
// JSX elements/fragments inside expressions are not dynamic — they're valid children
|
|
379
|
+
if (t.isJSXElement(expr) || t.isJSXFragment(expr))
|
|
380
|
+
return false;
|
|
381
|
+
return true;
|
|
382
|
+
}
|
|
383
|
+
// ===== AST construction ===== //
|
|
384
|
+
function createTWrapper(children, tName) {
|
|
385
|
+
const element = t.jsxElement(t.jsxOpeningElement(t.jsxIdentifier(tName), []), t.jsxClosingElement(t.jsxIdentifier(tName)), children);
|
|
386
|
+
autoInsertedNodes.add(element);
|
|
387
|
+
return element;
|
|
388
|
+
}
|
|
389
|
+
function createVarWrapper(child, varName, processedNodes) {
|
|
390
|
+
const varElement = t.jsxElement(t.jsxOpeningElement(t.jsxIdentifier(varName), []), t.jsxClosingElement(t.jsxIdentifier(varName)), [child]);
|
|
391
|
+
autoInsertedNodes.add(varElement);
|
|
392
|
+
processedNodes.add(varElement);
|
|
393
|
+
return t.jsxExpressionContainer(varElement);
|
|
394
|
+
}
|
|
395
|
+
function wrapChildrenInT(elementPath, tName, processedNodes) {
|
|
396
|
+
const children = [...elementPath.node.children];
|
|
397
|
+
const tWrapper = createTWrapper(children, tName);
|
|
398
|
+
processedNodes.add(tWrapper);
|
|
399
|
+
elementPath.node.children = [tWrapper];
|
|
400
|
+
}
|
|
401
|
+
function wrapFragmentChildrenInT(fragmentPath, tName, processedNodes) {
|
|
402
|
+
const children = [...fragmentPath.node.children];
|
|
403
|
+
const tWrapper = createTWrapper(children, tName);
|
|
404
|
+
processedNodes.add(tWrapper);
|
|
405
|
+
fragmentPath.node.children = [tWrapper];
|
|
406
|
+
}
|
|
407
|
+
// ===== Marking descendants as processed ===== //
|
|
408
|
+
function markDescendantJsx(path, processedNodes) {
|
|
409
|
+
path.traverse({
|
|
410
|
+
JSXElement(childPath) {
|
|
411
|
+
// Don't mark JSX inside auto-inserted _Var — the top-level visitor
|
|
412
|
+
// needs to find and process those independently
|
|
413
|
+
if (autoInsertedNodes.has(childPath.node)) {
|
|
414
|
+
childPath.skip();
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
processedNodes.add(childPath.node);
|
|
418
|
+
},
|
|
419
|
+
JSXFragment(childPath) {
|
|
420
|
+
if (autoInsertedNodes.has(childPath.node)) {
|
|
421
|
+
childPath.skip();
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
processedNodes.add(childPath.node);
|
|
425
|
+
},
|
|
426
|
+
});
|
|
427
|
+
}
|
|
@@ -6,10 +6,10 @@ import * as t from '@babel/types';
|
|
|
6
6
|
import fs from 'node:fs';
|
|
7
7
|
import { parse } from '@babel/parser';
|
|
8
8
|
import addGTIdentifierToSyntaxTree from './addGTIdentifierToSyntaxTree.js';
|
|
9
|
-
import { warnHasUnwrappedExpressionSync, warnNestedTComponent, warnFunctionNotFoundSync, warnMissingReturnSync, warnDuplicateFunctionDefinitionSync, warnInvalidDeriveInitSync, warnRecursiveFunctionCallSync, warnDataAttrOnBranch, } from '../../../../console/index.js';
|
|
9
|
+
import { warnHasUnwrappedExpressionSync, warnNestedTComponent, warnFunctionNotFoundSync, warnMissingReturnSync, warnDuplicateFunctionDefinitionSync, warnInvalidDeriveInitSync, warnRecursiveFunctionCallSync, warnDataAttrOnBranch, warnNestedInternalTComponent, } from '../../../../console/index.js';
|
|
10
10
|
import { isAcceptedPluralForm } from 'generaltranslation/internal';
|
|
11
11
|
import { isStaticExpression } from '../../evaluateJsx.js';
|
|
12
|
-
import { DATA_ATTR_PREFIX, STATIC_COMPONENT, DERIVE_COMPONENT, TRANSLATION_COMPONENT, VARIABLE_COMPONENTS, } from '../constants.js';
|
|
12
|
+
import { DATA_ATTR_PREFIX, STATIC_COMPONENT, DERIVE_COMPONENT, TRANSLATION_COMPONENT, INTERNAL_TRANSLATION_COMPONENT, VARIABLE_COMPONENTS, } from '../constants.js';
|
|
13
13
|
import { HTML_CONTENT_PROPS } from 'generaltranslation/types';
|
|
14
14
|
import { resolveImportPath } from '../resolveImportPath.js';
|
|
15
15
|
import traverseModule from '@babel/traverse';
|
|
@@ -20,6 +20,7 @@ import { handleChildrenWhitespace } from './handleChildrenWhitespace.js';
|
|
|
20
20
|
import { isElementNode } from './types.js';
|
|
21
21
|
import { multiplyJsxTree } from './multiplication/multiplyJsxTree.js';
|
|
22
22
|
import { removeNullChildrenFields } from './removeNullChildrenFields.js';
|
|
23
|
+
import { ensureTAndVarImported, autoInsertJsxComponents, } from './autoInsertion.js';
|
|
23
24
|
import path from 'node:path';
|
|
24
25
|
import { extractSourceCode } from '../extractSourceCode.js';
|
|
25
26
|
import { SURROUNDING_LINE_COUNT } from '../../../../utils/constants.js';
|
|
@@ -145,9 +146,53 @@ function buildJSXTree({ node, helperPath, scopeNode, insideT, inDerive, config,
|
|
|
145
146
|
}
|
|
146
147
|
// Convert from alias to original name
|
|
147
148
|
const componentType = config.importAliases[typeName ?? ''];
|
|
148
|
-
|
|
149
|
+
// When enableAutoJsxInjection is on and we're inside a Derive context,
|
|
150
|
+
// any auto-inserted T component will be stripped at runtime by
|
|
151
|
+
// removeInjectedT. Unwrap it transparently — process the T's children
|
|
152
|
+
// as if the T wasn't there. Check this BEFORE the nested-T warning
|
|
153
|
+
// so we don't emit spurious errors for expected auto-inserted nesting.
|
|
154
|
+
if (componentType === INTERNAL_TRANSLATION_COMPONENT &&
|
|
155
|
+
inDerive &&
|
|
156
|
+
config.enableAutoJsxInjection) {
|
|
157
|
+
const childResults = [];
|
|
158
|
+
const helperChildren = helperPath.get('children');
|
|
159
|
+
for (let i = 0; i < element.children.length; i++) {
|
|
160
|
+
const child = element.children[i];
|
|
161
|
+
const helperChild = helperChildren[i];
|
|
162
|
+
const result = buildJSXTree({
|
|
163
|
+
node: child,
|
|
164
|
+
helperPath: helperChild,
|
|
165
|
+
scopeNode,
|
|
166
|
+
insideT: true,
|
|
167
|
+
inDerive: true,
|
|
168
|
+
config,
|
|
169
|
+
state,
|
|
170
|
+
output,
|
|
171
|
+
});
|
|
172
|
+
if (result !== null) {
|
|
173
|
+
if (Array.isArray(result)) {
|
|
174
|
+
childResults.push(...result);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
childResults.push(result);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (childResults.length === 0)
|
|
182
|
+
return null;
|
|
183
|
+
if (childResults.length === 1)
|
|
184
|
+
return childResults[0];
|
|
185
|
+
// Return array — callers flatten this into parent's children
|
|
186
|
+
return childResults;
|
|
187
|
+
}
|
|
188
|
+
if ((componentType === TRANSLATION_COMPONENT ||
|
|
189
|
+
componentType === INTERNAL_TRANSLATION_COMPONENT) &&
|
|
190
|
+
insideT) {
|
|
149
191
|
// Add warning: Nested <T> components are allowed, but they are advised against
|
|
150
192
|
output.warnings.add(warnNestedTComponent(config.file, `${element.loc?.start?.line}:${element.loc?.start?.column}`));
|
|
193
|
+
if (componentType === INTERNAL_TRANSLATION_COMPONENT) {
|
|
194
|
+
output.errors.push(warnNestedInternalTComponent(config.file, `${element.loc?.start?.line}:${element.loc?.start?.column}`));
|
|
195
|
+
}
|
|
151
196
|
}
|
|
152
197
|
// If this JSXElement is one of the recognized variable components,
|
|
153
198
|
const elementIsVariable = VARIABLE_COMPONENTS.includes(componentType);
|
|
@@ -240,7 +285,13 @@ function buildJSXTree({ node, helperPath, scopeNode, insideT, inDerive, config,
|
|
|
240
285
|
state,
|
|
241
286
|
output,
|
|
242
287
|
});
|
|
243
|
-
|
|
288
|
+
// Flatten array results from _T transparency unwrap inside Derive
|
|
289
|
+
if (Array.isArray(result)) {
|
|
290
|
+
childrenArray.push(...result);
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
childrenArray.push(result);
|
|
294
|
+
}
|
|
244
295
|
}
|
|
245
296
|
if (childrenArray.length) {
|
|
246
297
|
results.props.children = childrenArray;
|
|
@@ -256,16 +307,22 @@ function buildJSXTree({ node, helperPath, scopeNode, insideT, inDerive, config,
|
|
|
256
307
|
};
|
|
257
308
|
}
|
|
258
309
|
const children = element.children
|
|
259
|
-
.
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
310
|
+
.flatMap((child, index) => {
|
|
311
|
+
const result = buildJSXTree({
|
|
312
|
+
node: child,
|
|
313
|
+
insideT: true,
|
|
314
|
+
inDerive: inDerive,
|
|
315
|
+
scopeNode,
|
|
316
|
+
helperPath: helperPath.get('children')[index],
|
|
317
|
+
config,
|
|
318
|
+
state,
|
|
319
|
+
output,
|
|
320
|
+
});
|
|
321
|
+
// Flatten array results from _T transparency unwrap inside Derive
|
|
322
|
+
if (Array.isArray(result))
|
|
323
|
+
return result;
|
|
324
|
+
return [result];
|
|
325
|
+
})
|
|
269
326
|
.filter((child) => child !== null && child !== '');
|
|
270
327
|
if (children.length === 1) {
|
|
271
328
|
props.children = children[0];
|
|
@@ -284,16 +341,22 @@ function buildJSXTree({ node, helperPath, scopeNode, insideT, inDerive, config,
|
|
|
284
341
|
// If it's a JSX fragment
|
|
285
342
|
else if (t.isJSXFragment(node)) {
|
|
286
343
|
const children = node.children
|
|
287
|
-
.
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
344
|
+
.flatMap((child, index) => {
|
|
345
|
+
const result = buildJSXTree({
|
|
346
|
+
node: child,
|
|
347
|
+
insideT: true,
|
|
348
|
+
inDerive: inDerive,
|
|
349
|
+
scopeNode,
|
|
350
|
+
helperPath: helperPath.get('children')[index],
|
|
351
|
+
config,
|
|
352
|
+
state,
|
|
353
|
+
output,
|
|
354
|
+
});
|
|
355
|
+
// Flatten array results from _T transparency unwrap inside Derive
|
|
356
|
+
if (Array.isArray(result))
|
|
357
|
+
return result;
|
|
358
|
+
return [result];
|
|
359
|
+
})
|
|
297
360
|
.filter((child) => child !== null && child !== '');
|
|
298
361
|
const props = {};
|
|
299
362
|
if (children.length === 1) {
|
|
@@ -405,7 +468,9 @@ function parseJSXElement({ node, originalName, scopeNode, updates, config, state
|
|
|
405
468
|
const name = openingElement.name;
|
|
406
469
|
// Only proceed if it's <T> ...
|
|
407
470
|
// TODO: i don't think this condition is needed anymore
|
|
408
|
-
if (!(name.type === 'JSXIdentifier' &&
|
|
471
|
+
if (!(name.type === 'JSXIdentifier' &&
|
|
472
|
+
(originalName === TRANSLATION_COMPONENT ||
|
|
473
|
+
originalName === INTERNAL_TRANSLATION_COMPONENT))) {
|
|
409
474
|
return;
|
|
410
475
|
}
|
|
411
476
|
const componentErrors = [];
|
|
@@ -477,7 +542,15 @@ function parseJSXElement({ node, originalName, scopeNode, updates, config, state
|
|
|
477
542
|
// TODO: do this in parallel
|
|
478
543
|
const minifiedTress = [];
|
|
479
544
|
for (const multipliedTree of multipliedTrees) {
|
|
480
|
-
|
|
545
|
+
// Build set of confirmed GT variable names from importAliases.
|
|
546
|
+
// Only pass when enableAutoJsxInjection is on, to avoid breaking
|
|
547
|
+
// existing behavior where importAliases may be incomplete.
|
|
548
|
+
const gtVariableNames = config.enableAutoJsxInjection
|
|
549
|
+
? new Set(Object.values(config.importAliases).filter((name) => VARIABLE_COMPONENTS.includes(name) &&
|
|
550
|
+
name !== DERIVE_COMPONENT &&
|
|
551
|
+
name !== STATIC_COMPONENT))
|
|
552
|
+
: undefined;
|
|
553
|
+
const minifiedTree = addGTIdentifierToSyntaxTree(multipliedTree, 0, gtVariableNames);
|
|
481
554
|
minifiedTress.push(Array.isArray(minifiedTree) && minifiedTree.length === 1
|
|
482
555
|
? minifiedTree[0]
|
|
483
556
|
: minifiedTree);
|
|
@@ -620,7 +693,19 @@ function processFunctionInFile({ config, state, output, filePath, functionName,
|
|
|
620
693
|
sourceType: 'module',
|
|
621
694
|
plugins: ['jsx', 'typescript'],
|
|
622
695
|
});
|
|
623
|
-
const
|
|
696
|
+
const pathsResult = getPathsAndAliases(ast, config.pkgs);
|
|
697
|
+
const importAliases = { ...pathsResult.importAliases };
|
|
698
|
+
// Merge translation component names into importAliases so
|
|
699
|
+
// autoInsertJsxComponents can recognize user T/Var and skip them
|
|
700
|
+
for (const { localName, originalName, } of pathsResult.translationComponentPaths) {
|
|
701
|
+
importAliases[localName] = originalName;
|
|
702
|
+
}
|
|
703
|
+
// Auto-inject T/Var into the cross-file AST when enabled,
|
|
704
|
+
// so that Derive extraction sees the same structure as same-file
|
|
705
|
+
if (config.enableAutoJsxInjection) {
|
|
706
|
+
ensureTAndVarImported(ast, importAliases);
|
|
707
|
+
autoInsertJsxComponents(ast, importAliases);
|
|
708
|
+
}
|
|
624
709
|
// Collect all imports in this file to track cross-file function calls
|
|
625
710
|
let importedFunctionsMap = new Map();
|
|
626
711
|
traverse(ast, {
|
|
@@ -644,6 +729,7 @@ function processFunctionInFile({ config, state, output, filePath, functionName,
|
|
|
644
729
|
parsingOptions: config.parsingOptions,
|
|
645
730
|
pkgs: config.pkgs,
|
|
646
731
|
file: filePath,
|
|
732
|
+
enableAutoJsxInjection: config.enableAutoJsxInjection,
|
|
647
733
|
},
|
|
648
734
|
state: {
|
|
649
735
|
...state,
|
|
@@ -669,6 +755,7 @@ function processFunctionInFile({ config, state, output, filePath, functionName,
|
|
|
669
755
|
parsingOptions: config.parsingOptions,
|
|
670
756
|
pkgs: config.pkgs,
|
|
671
757
|
file: filePath,
|
|
758
|
+
enableAutoJsxInjection: config.enableAutoJsxInjection,
|
|
672
759
|
},
|
|
673
760
|
state: {
|
|
674
761
|
...state,
|
|
@@ -716,6 +803,7 @@ function processFunctionInFile({ config, state, output, filePath, functionName,
|
|
|
716
803
|
parsingOptions: config.parsingOptions,
|
|
717
804
|
pkgs: config.pkgs,
|
|
718
805
|
file: filePath,
|
|
806
|
+
enableAutoJsxInjection: config.enableAutoJsxInjection,
|
|
719
807
|
},
|
|
720
808
|
state: {
|
|
721
809
|
...state,
|
|
@@ -760,13 +848,19 @@ function processFunctionDeclarationNodePath({ config, state, output, path, }) {
|
|
|
760
848
|
if (!returnNodePath.isExpression()) {
|
|
761
849
|
return;
|
|
762
850
|
}
|
|
763
|
-
|
|
851
|
+
const deriveResult = processDeriveExpression({
|
|
764
852
|
config,
|
|
765
853
|
state,
|
|
766
854
|
output,
|
|
767
855
|
expressionNodePath: returnNodePath,
|
|
768
856
|
scopeNode: returnPath,
|
|
769
|
-
})
|
|
857
|
+
});
|
|
858
|
+
if (Array.isArray(deriveResult)) {
|
|
859
|
+
result.branches.push(...deriveResult);
|
|
860
|
+
}
|
|
861
|
+
else {
|
|
862
|
+
result.branches.push(deriveResult);
|
|
863
|
+
}
|
|
770
864
|
},
|
|
771
865
|
});
|
|
772
866
|
if (result.branches.length === 0) {
|
|
@@ -794,13 +888,19 @@ function processVariableDeclarationNodePath({ config, state, output, functionNam
|
|
|
794
888
|
const bodyNodePath = arrowFunctionPath.get('body');
|
|
795
889
|
if (bodyNodePath.isExpression()) {
|
|
796
890
|
// process expression return
|
|
797
|
-
|
|
891
|
+
const deriveResult = processDeriveExpression({
|
|
798
892
|
config,
|
|
799
893
|
state,
|
|
800
894
|
output,
|
|
801
895
|
expressionNodePath: bodyNodePath,
|
|
802
896
|
scopeNode: arrowFunctionPath,
|
|
803
|
-
})
|
|
897
|
+
});
|
|
898
|
+
if (Array.isArray(deriveResult)) {
|
|
899
|
+
result.branches.push(...deriveResult);
|
|
900
|
+
}
|
|
901
|
+
else {
|
|
902
|
+
result.branches.push(deriveResult);
|
|
903
|
+
}
|
|
804
904
|
}
|
|
805
905
|
else {
|
|
806
906
|
// search for a return statement
|
|
@@ -813,13 +913,19 @@ function processVariableDeclarationNodePath({ config, state, output, functionNam
|
|
|
813
913
|
if (!returnNodePath.isExpression()) {
|
|
814
914
|
return;
|
|
815
915
|
}
|
|
816
|
-
|
|
916
|
+
const deriveResult = processDeriveExpression({
|
|
817
917
|
config,
|
|
818
918
|
state,
|
|
819
919
|
output,
|
|
820
920
|
expressionNodePath: returnNodePath,
|
|
821
921
|
scopeNode: returnPath,
|
|
822
|
-
})
|
|
922
|
+
});
|
|
923
|
+
if (Array.isArray(deriveResult)) {
|
|
924
|
+
result.branches.push(...deriveResult);
|
|
925
|
+
}
|
|
926
|
+
else {
|
|
927
|
+
result.branches.push(deriveResult);
|
|
928
|
+
}
|
|
823
929
|
},
|
|
824
930
|
});
|
|
825
931
|
}
|
|
@@ -904,13 +1010,16 @@ function processDeriveExpression({ config, state, output, expressionNodePath, sc
|
|
|
904
1010
|
const alternateNodePath = expressionNodePath.get('alternate');
|
|
905
1011
|
const result = {
|
|
906
1012
|
nodeType: 'multiplication',
|
|
907
|
-
branches: [consequentNodePath, alternateNodePath].
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
1013
|
+
branches: [consequentNodePath, alternateNodePath].flatMap((expressionNodePath) => {
|
|
1014
|
+
const r = processDeriveExpression({
|
|
1015
|
+
config,
|
|
1016
|
+
state,
|
|
1017
|
+
output,
|
|
1018
|
+
scopeNode,
|
|
1019
|
+
expressionNodePath,
|
|
1020
|
+
});
|
|
1021
|
+
return Array.isArray(r) ? r : [r];
|
|
1022
|
+
}),
|
|
914
1023
|
};
|
|
915
1024
|
return result;
|
|
916
1025
|
}
|
|
@@ -8,6 +8,10 @@ import { DEFAULT_SRC_PATTERNS } from '../../config/generateSettings.js';
|
|
|
8
8
|
import { getPathsAndAliases } from '../jsx/utils/getPathsAndAliases.js';
|
|
9
9
|
import { GT_LIBRARIES_UPSTREAM, REACT_LIBRARIES, } from '../../types/libraries.js';
|
|
10
10
|
import { calculateHashes, dedupeUpdates, linkDeriveUpdates, } from '../../extraction/postProcess.js';
|
|
11
|
+
import { ensureTAndVarImported, autoInsertJsxComponents, } from '../jsx/utils/jsxParsing/autoInsertion.js';
|
|
12
|
+
import { INTERNAL_TRANSLATION_COMPONENT } from '../jsx/utils/constants.js';
|
|
13
|
+
import traverseModule from '@babel/traverse';
|
|
14
|
+
const traverse = traverseModule.default || traverseModule;
|
|
11
15
|
export async function createInlineUpdates(pkg, validate, filePatterns, parsingFlags, parsingOptions) {
|
|
12
16
|
const updates = [];
|
|
13
17
|
const errors = [];
|
|
@@ -46,7 +50,7 @@ export async function createInlineUpdates(pkg, validate, filePatterns, parsingFl
|
|
|
46
50
|
autoDeriveMethod: parsingFlags.autoDerive ? 'AUTO' : 'DISABLED',
|
|
47
51
|
}, { updates, errors, warnings });
|
|
48
52
|
}
|
|
49
|
-
// Parse <T> components
|
|
53
|
+
// Parse <T> components — PASS 1: user-written T
|
|
50
54
|
if (REACT_LIBRARIES.includes(pkg)) {
|
|
51
55
|
for (const { localName, path } of translationComponentPaths) {
|
|
52
56
|
parseTranslationComponent({
|
|
@@ -68,6 +72,55 @@ export async function createInlineUpdates(pkg, validate, filePatterns, parsingFl
|
|
|
68
72
|
},
|
|
69
73
|
});
|
|
70
74
|
}
|
|
75
|
+
// PASS 2: Auto-inject GtInternalTranslateJsx and GtInternalVar and extract (flag-gated)
|
|
76
|
+
if (parsingFlags.enableAutoJsxInjection) {
|
|
77
|
+
// Add translation component names to importAliases so autoInsertJsxComponents
|
|
78
|
+
// recognizes user T as hands-off (getPathsAndAliases separates them out)
|
|
79
|
+
for (const { localName, originalName } of translationComponentPaths) {
|
|
80
|
+
importAliases[localName] = originalName;
|
|
81
|
+
}
|
|
82
|
+
// Ensure GtInternalTranslateJsx and GtInternalVar are imported in the AST
|
|
83
|
+
ensureTAndVarImported(ast, importAliases);
|
|
84
|
+
// Insert T/Var into the AST
|
|
85
|
+
autoInsertJsxComponents(ast, importAliases);
|
|
86
|
+
// Refresh scope to pick up new T references
|
|
87
|
+
traverse(ast, {
|
|
88
|
+
Program(programPath) {
|
|
89
|
+
programPath.scope.crawl();
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
// Re-collect with updated AST
|
|
93
|
+
const refreshed = getPathsAndAliases(ast, pkgs);
|
|
94
|
+
// Add translation component names to refreshed aliases so parseJsx
|
|
95
|
+
// can recognize GtInternalTranslateJsx inside Derive for transparent unwrap
|
|
96
|
+
for (const { localName: tLocalName, originalName: tOrigName, } of refreshed.translationComponentPaths) {
|
|
97
|
+
refreshed.importAliases[tLocalName] = tOrigName;
|
|
98
|
+
}
|
|
99
|
+
// Extract only from auto-injected GtInternalTranslateJsx — never re-extract user T
|
|
100
|
+
for (const { localName, path, originalName, } of refreshed.translationComponentPaths) {
|
|
101
|
+
if (originalName !== INTERNAL_TRANSLATION_COMPONENT)
|
|
102
|
+
continue;
|
|
103
|
+
parseTranslationComponent({
|
|
104
|
+
originalName: localName,
|
|
105
|
+
localName,
|
|
106
|
+
path,
|
|
107
|
+
updates,
|
|
108
|
+
config: {
|
|
109
|
+
importAliases: refreshed.importAliases,
|
|
110
|
+
parsingOptions,
|
|
111
|
+
pkgs,
|
|
112
|
+
file,
|
|
113
|
+
includeSourceCodeContext: parsingFlags.includeSourceCodeContext,
|
|
114
|
+
enableAutoJsxInjection: true,
|
|
115
|
+
},
|
|
116
|
+
output: {
|
|
117
|
+
errors,
|
|
118
|
+
warnings,
|
|
119
|
+
unwrappedExpressions: [],
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
71
124
|
}
|
|
72
125
|
}
|
|
73
126
|
// Post processing steps:
|
|
@@ -4,8 +4,10 @@ import { VariableType } from 'generaltranslation/types';
|
|
|
4
4
|
*/
|
|
5
5
|
export declare const defaultVariableNames: {
|
|
6
6
|
readonly Var: "value";
|
|
7
|
+
readonly GtInternalVar: "value";
|
|
7
8
|
readonly Num: "n";
|
|
8
9
|
readonly DateTime: "date";
|
|
10
|
+
readonly RelativeTime: "time";
|
|
9
11
|
readonly Currency: "cost";
|
|
10
12
|
};
|
|
11
13
|
/**
|
|
@@ -3,14 +3,18 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const defaultVariableNames = {
|
|
5
5
|
Var: 'value',
|
|
6
|
+
GtInternalVar: 'value',
|
|
6
7
|
Num: 'n',
|
|
7
8
|
DateTime: 'date',
|
|
9
|
+
RelativeTime: 'time',
|
|
8
10
|
Currency: 'cost',
|
|
9
11
|
};
|
|
10
12
|
const minifyVariableTypeMap = {
|
|
11
13
|
Var: 'v',
|
|
14
|
+
GtInternalVar: 'v',
|
|
12
15
|
Num: 'n',
|
|
13
16
|
DateTime: 'd',
|
|
17
|
+
RelativeTime: 'rt',
|
|
14
18
|
Currency: 'c',
|
|
15
19
|
};
|
|
16
20
|
/**
|
package/dist/types/parsing.d.ts
CHANGED
|
@@ -32,10 +32,12 @@ export type BaseParsingFlags = Record<string, unknown>;
|
|
|
32
32
|
*
|
|
33
33
|
* @property {boolean} autoDerive - Whether to enable auto-derive for the t() function. (true -> 'AUTO', false -> 'DISABLED' {@link ParsingConfig['autoDeriveMethod']})
|
|
34
34
|
* @property {boolean} includeSourceCodeContext - Include surrounding source code lines as context for translations.
|
|
35
|
+
* @property {boolean} enableAutoJsxInjection - Whether to enable auto-jsx injection for the internal <_T> and <_Var> components.
|
|
35
36
|
*/
|
|
36
37
|
export type GTParsingFlags = BaseParsingFlags & {
|
|
37
38
|
autoDerive: boolean;
|
|
38
39
|
includeSourceCodeContext: boolean;
|
|
40
|
+
enableAutoJsxInjection: boolean;
|
|
39
41
|
};
|
|
40
42
|
/**
|
|
41
43
|
* Flags for parsing content with each filetype having its own flags
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gt",
|
|
3
|
-
"version": "2.14.
|
|
3
|
+
"version": "2.14.4",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"bin": "dist/main.js",
|
|
6
6
|
"files": [
|
|
@@ -110,8 +110,8 @@
|
|
|
110
110
|
"unified": "^11.0.5",
|
|
111
111
|
"unist-util-visit": "^5.0.0",
|
|
112
112
|
"yaml": "^2.8.0",
|
|
113
|
-
"@generaltranslation/python-extractor": "0.2.
|
|
114
|
-
"generaltranslation": "8.2.
|
|
113
|
+
"@generaltranslation/python-extractor": "0.2.6",
|
|
114
|
+
"generaltranslation": "8.2.2",
|
|
115
115
|
"gt-remark": "1.0.7"
|
|
116
116
|
},
|
|
117
117
|
"devDependencies": {
|