@babelize/engine 0.1.3 → 0.2.1
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/dist/analysis/analyze.js +1 -1
- package/dist/config.d.ts +3 -3
- package/dist/detection/stack.d.ts +3 -1
- package/dist/detection/stack.js +13 -6
- package/dist/injection/StrategyFactory.d.ts +5 -1
- package/dist/injection/StrategyFactory.js +7 -2
- package/dist/injection/html/html-injector.d.ts +2 -0
- package/dist/injection/html/html-injector.js +55 -6
- package/dist/injection/strategies/VanillaStrategy.d.ts +8 -0
- package/dist/injection/strategies/VanillaStrategy.js +10 -3
- package/dist/local/translate-core.d.ts +2 -2
- package/dist/local/translate-core.js +2 -2
- package/dist/modules/extractor/extract.js +67 -10
- package/dist/modules/finalization/dependencies.d.ts +10 -0
- package/dist/modules/finalization/dependencies.js +23 -0
- package/dist/modules/finalization/finalize.js +47 -43
- package/dist/modules/finalization/nextjs-finalize.js +2 -1
- package/dist/modules/finalization/react-native-finalize.d.ts +45 -0
- package/dist/modules/finalization/react-native-finalize.js +324 -0
- package/dist/modules/finalization/static-runtime.d.ts +2 -2
- package/dist/modules/finalization/static-runtime.js +10 -5
- package/dist/modules/format-injection/index.js +38 -9
- package/dist/modules/format-injection/inject-formats.d.ts +3 -2
- package/dist/modules/format-injection/inject-formats.js +4 -3
- package/dist/modules/injection/inject.d.ts +14 -1
- package/dist/modules/injection/inject.js +307 -154
- package/dist/modules/sync-detector/sync-detector.js +11 -4
- package/dist/modules/translator/line-breaks.d.ts +8 -0
- package/dist/modules/translator/line-breaks.js +63 -0
- package/dist/modules/translator/translate.js +5 -2
- package/dist/modules/workspace/validate.d.ts +13 -1
- package/dist/modules/workspace/validate.js +95 -58
- package/dist/runtime/types.d.ts +22 -0
- package/dist/runtime/types.js +33 -0
- package/dist/utils/ast-helpers.d.ts +30 -2
- package/dist/utils/ast-helpers.js +355 -42
- package/dist/utils/extraction-options.js +1 -1
- package/dist/utils/file-utils.js +4 -1
- package/dist/utils/i18n-config-path.d.ts +18 -0
- package/dist/utils/i18n-config-path.js +64 -0
- package/dist/utils/i18next-icu.js +1 -1
- package/dist/utils/jsx-message.d.ts +16 -1
- package/dist/utils/jsx-message.js +89 -9
- package/dist/utils/key-usage.js +1 -1
- package/dist/utils/metadata.d.ts +5 -1
- package/dist/utils/metadata.js +19 -1
- package/dist/utils/module-constants.d.ts +21 -3
- package/dist/utils/module-constants.js +123 -25
- package/dist/utils/react-native.d.ts +17 -0
- package/dist/utils/react-native.js +108 -0
- package/dist/utils/string-utils.d.ts +8 -0
- package/dist/utils/string-utils.js +29 -1
- package/dist/utils/ui-copy.d.ts +5 -4
- package/dist/utils/ui-copy.js +10 -5
- package/package.json +3 -2
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.isNavigationTitle = isNavigationTitle;
|
|
37
|
+
exports.isAlertCall = isAlertCall;
|
|
38
|
+
exports.isAlertTextArgument = isAlertTextArgument;
|
|
39
|
+
exports.isShortUiWord = isShortUiWord;
|
|
40
|
+
const t = __importStar(require("@babel/types"));
|
|
41
|
+
const ast_helpers_1 = require("./ast-helpers");
|
|
42
|
+
/**
|
|
43
|
+
* React Native UI copy that lives outside JSX text and the shared attribute
|
|
44
|
+
* allowlist: screen titles in navigation options and Alert dialogs.
|
|
45
|
+
*/
|
|
46
|
+
/** JSX attributes holding React Navigation / Expo Router screen options. */
|
|
47
|
+
const NAV_OPTIONS_ATTRS = new Set(['options', 'screenOptions']);
|
|
48
|
+
/** Option keys whose string value is shown to the user. */
|
|
49
|
+
const NAV_TITLE_KEYS = new Set(['title', 'headerTitle', 'headerBackTitle', 'tabBarLabel', 'drawerLabel']);
|
|
50
|
+
function propertyName(prop) {
|
|
51
|
+
if (prop.computed)
|
|
52
|
+
return null;
|
|
53
|
+
if (t.isIdentifier(prop.key))
|
|
54
|
+
return prop.key.name;
|
|
55
|
+
if (t.isStringLiteral(prop.key))
|
|
56
|
+
return prop.key.value;
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* `options={{ title: 'Home' }}`, `screenOptions={{ headerTitle: 'Settings' }}`,
|
|
61
|
+
* `options={({ route }) => ({ tabBarLabel: 'Feed' })}`: the literal is the
|
|
62
|
+
* value of a title key inside an options / screenOptions attribute.
|
|
63
|
+
*/
|
|
64
|
+
function isNavigationTitle(path) {
|
|
65
|
+
let child = path.node;
|
|
66
|
+
let current = path.parentPath;
|
|
67
|
+
while (current && (0, ast_helpers_1.isValuePassthrough)(current.node, child)) {
|
|
68
|
+
child = current.node;
|
|
69
|
+
current = current.parentPath;
|
|
70
|
+
}
|
|
71
|
+
if (!current?.isObjectProperty() || current.node.value !== child)
|
|
72
|
+
return false;
|
|
73
|
+
const key = propertyName(current.node);
|
|
74
|
+
if (!key || !NAV_TITLE_KEYS.has(key))
|
|
75
|
+
return false;
|
|
76
|
+
for (let p = current.parentPath; p; p = p.parentPath) {
|
|
77
|
+
if (p.isJSXAttribute()) {
|
|
78
|
+
const name = p.node.name;
|
|
79
|
+
return t.isJSXIdentifier(name) && NAV_OPTIONS_ATTRS.has(name.name);
|
|
80
|
+
}
|
|
81
|
+
if (p.isJSXElement() || p.isJSXFragment() || p.isProgram())
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
/** `Alert.alert(…)` / `Alert.prompt(…)` from react-native. */
|
|
87
|
+
function isAlertCall(callee) {
|
|
88
|
+
return (t.isMemberExpression(callee) &&
|
|
89
|
+
!callee.computed &&
|
|
90
|
+
t.isIdentifier(callee.object, { name: 'Alert' }) &&
|
|
91
|
+
t.isIdentifier(callee.property) &&
|
|
92
|
+
(callee.property.name === 'alert' || callee.property.name === 'prompt'));
|
|
93
|
+
}
|
|
94
|
+
/** The title or message argument of an Alert call (button labels are `text:` properties). */
|
|
95
|
+
function isAlertTextArgument(path) {
|
|
96
|
+
const call = path.parentPath;
|
|
97
|
+
if (!call?.isCallExpression() || !isAlertCall(call.node.callee))
|
|
98
|
+
return false;
|
|
99
|
+
const index = call.node.arguments.indexOf(path.node);
|
|
100
|
+
return index === 0 || index === 1;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* A two-letter word that is still UI copy in a string literal: "OK", "No",
|
|
104
|
+
* "Go". Literals shorter than three characters are otherwise dropped as noise.
|
|
105
|
+
*/
|
|
106
|
+
function isShortUiWord(text) {
|
|
107
|
+
return /^\p{Lu}\p{L}$/u.test(text.trim());
|
|
108
|
+
}
|
|
@@ -8,6 +8,14 @@ export declare function toCamelCase(text: string): string;
|
|
|
8
8
|
export declare function inferNamespace(filePath: string): string;
|
|
9
9
|
export declare function generateUniqueKey(baseKey: string, existingKeys: Set<string>): string;
|
|
10
10
|
export declare function truncateForKey(text: string, maxWords?: number): string;
|
|
11
|
+
/**
|
|
12
|
+
* A file name or path, not copy: `src/app/index.tsx`, `app/index.tsx`,
|
|
13
|
+
* `index.tsx`, `@scope/pkg/file.js`. Product names that end like one
|
|
14
|
+
* ("Node.js", "Next.js") are capitalized and stay translatable.
|
|
15
|
+
*/
|
|
16
|
+
export declare function looksLikeFilePath(text: string): boolean;
|
|
17
|
+
/** A style color, not copy: `#13111E`, `rgba(255,255,255,0.72)`, `hsl(210 40% 50%)`. */
|
|
18
|
+
export declare function looksLikeColor(text: string): boolean;
|
|
11
19
|
export declare function isTranslatableText(text: string): boolean;
|
|
12
20
|
export declare function extractPlaceholders(text: string): string[];
|
|
13
21
|
/**
|
|
@@ -6,6 +6,8 @@ exports.toCamelCase = toCamelCase;
|
|
|
6
6
|
exports.inferNamespace = inferNamespace;
|
|
7
7
|
exports.generateUniqueKey = generateUniqueKey;
|
|
8
8
|
exports.truncateForKey = truncateForKey;
|
|
9
|
+
exports.looksLikeFilePath = looksLikeFilePath;
|
|
10
|
+
exports.looksLikeColor = looksLikeColor;
|
|
9
11
|
exports.isTranslatableText = isTranslatableText;
|
|
10
12
|
exports.extractPlaceholders = extractPlaceholders;
|
|
11
13
|
exports.protectPlaceholders = protectPlaceholders;
|
|
@@ -50,7 +52,8 @@ function toCamelCase(text) {
|
|
|
50
52
|
}
|
|
51
53
|
function inferNamespace(filePath) {
|
|
52
54
|
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
53
|
-
|
|
55
|
+
// Platform variants share their keys: app-tabs.web.tsx and app-tabs.tsx are one screen.
|
|
56
|
+
const pathWithoutExt = normalizedPath.replace(/\.(ts|tsx|js|jsx)$/, '').replace(/\.(web|ios|android|native)$/, '');
|
|
54
57
|
const parts = pathWithoutExt.split('/').filter(Boolean);
|
|
55
58
|
const prefixesToRemove = ['src', 'app', 'pages', 'components', 'features', 'modules'];
|
|
56
59
|
while (parts.length > 0 && prefixesToRemove.includes(parts[0].toLowerCase())) {
|
|
@@ -85,6 +88,24 @@ function truncateForKey(text, maxWords = 5) {
|
|
|
85
88
|
return text;
|
|
86
89
|
return words.slice(0, maxWords).join(' ');
|
|
87
90
|
}
|
|
91
|
+
const FILE_EXTENSION = /\.(?:[cm]?[jt]sx?|json|md|mdx|css|scss|sass|less|html?|vue|svelte|py|rb|go|rs|java|kt|swift|ya?ml|toml|lock|txt|png|jpe?g|gif|svg|webp|pdf|zip)$/i;
|
|
92
|
+
/**
|
|
93
|
+
* A file name or path, not copy: `src/app/index.tsx`, `app/index.tsx`,
|
|
94
|
+
* `index.tsx`, `@scope/pkg/file.js`. Product names that end like one
|
|
95
|
+
* ("Node.js", "Next.js") are capitalized and stay translatable.
|
|
96
|
+
*/
|
|
97
|
+
function looksLikeFilePath(text) {
|
|
98
|
+
if (/\s/.test(text) || !FILE_EXTENSION.test(text))
|
|
99
|
+
return false;
|
|
100
|
+
if (text.includes('/'))
|
|
101
|
+
return true;
|
|
102
|
+
return /^[\p{Ll}\d_@-][\w@.-]*$/u.test(text);
|
|
103
|
+
}
|
|
104
|
+
/** A style color, not copy: `#13111E`, `rgba(255,255,255,0.72)`, `hsl(210 40% 50%)`. */
|
|
105
|
+
function looksLikeColor(text) {
|
|
106
|
+
const s = text.trim();
|
|
107
|
+
return /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(s) || /^(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch|color)\([^()]*\)$/i.test(s);
|
|
108
|
+
}
|
|
88
109
|
function isTranslatableText(text) {
|
|
89
110
|
const trimmed = text.trim();
|
|
90
111
|
if (!trimmed)
|
|
@@ -103,6 +124,13 @@ function isTranslatableText(text) {
|
|
|
103
124
|
return false;
|
|
104
125
|
if (/^[.#][a-zA-Z]/.test(trimmed))
|
|
105
126
|
return false;
|
|
127
|
+
if (looksLikeFilePath(trimmed))
|
|
128
|
+
return false;
|
|
129
|
+
if (looksLikeColor(trimmed))
|
|
130
|
+
return false;
|
|
131
|
+
// An identifier or code, not words: `22AAAAA0000A1Z5` (a GST number), `INV-2024-001`, `SKU12345`.
|
|
132
|
+
if (/^(?=[^\s]*\d)(?=[^\s]*[A-Z])[A-Z0-9_-]{6,}$/.test(trimmed))
|
|
133
|
+
return false;
|
|
106
134
|
if (/^\p{Ll}[\p{L}\p{N}_]*$/u.test(trimmed) && trimmed.length > 20)
|
|
107
135
|
return false;
|
|
108
136
|
return /[\p{L}]/u.test(trimmed);
|
package/dist/utils/ui-copy.d.ts
CHANGED
|
@@ -17,16 +17,17 @@ export declare const DATA_KEYS: Set<string>;
|
|
|
17
17
|
export declare function isMultiWordCopy(text: string): boolean;
|
|
18
18
|
/**
|
|
19
19
|
* Copy without a key to say so: a sentence or Title Case phrase, or a single
|
|
20
|
-
* capitalized word ("Overview", "
|
|
21
|
-
*
|
|
20
|
+
* capitalized word ("Overview", "Mid-tier"), maybe followed by a number or
|
|
21
|
+
* range ("Growing (11–50)"). Data looks different: lowercase tokens
|
|
22
|
+
* ("active"), kebab/snake/camelCase, ALLCAPS, or anything with digits,
|
|
22
23
|
* underscores, slashes or dots in a single token.
|
|
23
24
|
*/
|
|
24
25
|
export declare function looksLikeCopy(text: string): boolean;
|
|
25
26
|
/** Whether a string stored under `name` (a key or a variable) is UI copy. */
|
|
26
27
|
export declare function isUiCopyForName(name: string | null, text: string): boolean;
|
|
27
28
|
/**
|
|
28
|
-
* For a string literal in an object property value, array element
|
|
29
|
-
* variable/assignment initializer: is it UI copy? Other positions (JSX,
|
|
29
|
+
* For a string literal in an object property value, array element,
|
|
30
|
+
* variable/assignment initializer or default value: is it UI copy? Other positions (JSX,
|
|
30
31
|
* returns, conditionals, …) aren't initializers and are left as they are.
|
|
31
32
|
*/
|
|
32
33
|
export declare function isUiCopyLiteral(node: t.Node, parent: t.Node | null | undefined, text: string): boolean;
|
package/dist/utils/ui-copy.js
CHANGED
|
@@ -73,15 +73,16 @@ function isMultiWordCopy(text) {
|
|
|
73
73
|
}
|
|
74
74
|
/**
|
|
75
75
|
* Copy without a key to say so: a sentence or Title Case phrase, or a single
|
|
76
|
-
* capitalized word ("Overview", "
|
|
77
|
-
*
|
|
76
|
+
* capitalized word ("Overview", "Mid-tier"), maybe followed by a number or
|
|
77
|
+
* range ("Growing (11–50)"). Data looks different: lowercase tokens
|
|
78
|
+
* ("active"), kebab/snake/camelCase, ALLCAPS, or anything with digits,
|
|
78
79
|
* underscores, slashes or dots in a single token.
|
|
79
80
|
*/
|
|
80
81
|
function looksLikeCopy(text) {
|
|
81
82
|
const trimmed = text.trim();
|
|
82
83
|
if (isMultiWordCopy(trimmed))
|
|
83
84
|
return true;
|
|
84
|
-
return /^\p{Lu}\p{Ll}+[!?.:…]?$/u.test(trimmed);
|
|
85
|
+
return /^\p{Lu}\p{Ll}+(?:-\p{Ll}+)*(?:[!?.:…]|\s+[^\p{L}\s]+)?$/u.test(trimmed);
|
|
85
86
|
}
|
|
86
87
|
/** Whether a string stored under `name` (a key or a variable) is UI copy. */
|
|
87
88
|
function isUiCopyForName(name, text) {
|
|
@@ -104,8 +105,8 @@ function keyName(key) {
|
|
|
104
105
|
return null;
|
|
105
106
|
}
|
|
106
107
|
/**
|
|
107
|
-
* For a string literal in an object property value, array element
|
|
108
|
-
* variable/assignment initializer: is it UI copy? Other positions (JSX,
|
|
108
|
+
* For a string literal in an object property value, array element,
|
|
109
|
+
* variable/assignment initializer or default value: is it UI copy? Other positions (JSX,
|
|
109
110
|
* returns, conditionals, …) aren't initializers and are left as they are.
|
|
110
111
|
*/
|
|
111
112
|
function isUiCopyLiteral(node, parent, text) {
|
|
@@ -120,6 +121,10 @@ function isUiCopyLiteral(node, parent, text) {
|
|
|
120
121
|
const name = t.isIdentifier(left) ? left.name : t.isMemberExpression(left) && !left.computed && t.isIdentifier(left.property) ? left.property.name : null;
|
|
121
122
|
return isUiCopyForName(name, text);
|
|
122
123
|
}
|
|
124
|
+
// Default values: `function Badge({ type = 'default', title = 'New' })`.
|
|
125
|
+
if (t.isAssignmentPattern(parent) && parent.right === node) {
|
|
126
|
+
return isUiCopyForName(t.isIdentifier(parent.left) ? parent.left.name : null, text);
|
|
127
|
+
}
|
|
123
128
|
if (t.isArrayExpression(parent))
|
|
124
129
|
return looksLikeCopy(text);
|
|
125
130
|
return true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@babelize/engine",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Babelize translation pipeline (extract, keygen, translate, inject). Infrastructure is injected through provider/store interfaces.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"babelize",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"@babel/parser": "^7.28.5",
|
|
33
33
|
"@babel/traverse": "^7.28.5",
|
|
34
34
|
"@babel/types": "^7.29.8",
|
|
35
|
-
"@babelize/types": "^0.1.
|
|
35
|
+
"@babelize/types": "^0.1.3",
|
|
36
36
|
"@vue/compiler-dom": "^3.5",
|
|
37
37
|
"@vue/compiler-sfc": "^3.5",
|
|
38
38
|
"diff": "^8.0.4",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"gettext-parser": "^9.1.1",
|
|
42
42
|
"magic-string": "^0.30",
|
|
43
43
|
"node-html-parser": "^8.0.4",
|
|
44
|
+
"recast": "^0.23",
|
|
44
45
|
"zod": "^3.22.4"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|