@babelize/engine 0.1.3 → 0.2.0

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.
Files changed (50) hide show
  1. package/dist/config.d.ts +3 -3
  2. package/dist/detection/stack.d.ts +3 -1
  3. package/dist/detection/stack.js +13 -6
  4. package/dist/injection/StrategyFactory.d.ts +5 -1
  5. package/dist/injection/StrategyFactory.js +7 -2
  6. package/dist/injection/strategies/VanillaStrategy.d.ts +8 -0
  7. package/dist/injection/strategies/VanillaStrategy.js +10 -3
  8. package/dist/local/translate-core.d.ts +1 -1
  9. package/dist/local/translate-core.js +1 -1
  10. package/dist/modules/extractor/extract.js +67 -10
  11. package/dist/modules/finalization/dependencies.d.ts +10 -0
  12. package/dist/modules/finalization/dependencies.js +23 -0
  13. package/dist/modules/finalization/finalize.js +41 -39
  14. package/dist/modules/finalization/nextjs-finalize.js +2 -1
  15. package/dist/modules/finalization/react-native-finalize.d.ts +45 -0
  16. package/dist/modules/finalization/react-native-finalize.js +324 -0
  17. package/dist/modules/format-injection/index.js +38 -9
  18. package/dist/modules/format-injection/inject-formats.d.ts +3 -2
  19. package/dist/modules/format-injection/inject-formats.js +4 -3
  20. package/dist/modules/injection/inject.d.ts +14 -1
  21. package/dist/modules/injection/inject.js +307 -154
  22. package/dist/modules/sync-detector/sync-detector.js +11 -4
  23. package/dist/modules/translator/line-breaks.d.ts +8 -0
  24. package/dist/modules/translator/line-breaks.js +63 -0
  25. package/dist/modules/translator/translate.js +5 -2
  26. package/dist/modules/workspace/validate.d.ts +13 -1
  27. package/dist/modules/workspace/validate.js +95 -58
  28. package/dist/runtime/types.d.ts +22 -0
  29. package/dist/runtime/types.js +33 -0
  30. package/dist/utils/ast-helpers.d.ts +30 -2
  31. package/dist/utils/ast-helpers.js +355 -42
  32. package/dist/utils/extraction-options.js +1 -1
  33. package/dist/utils/file-utils.js +4 -1
  34. package/dist/utils/i18n-config-path.d.ts +18 -0
  35. package/dist/utils/i18n-config-path.js +64 -0
  36. package/dist/utils/i18next-icu.js +1 -1
  37. package/dist/utils/jsx-message.d.ts +16 -1
  38. package/dist/utils/jsx-message.js +89 -9
  39. package/dist/utils/key-usage.js +1 -1
  40. package/dist/utils/metadata.d.ts +5 -1
  41. package/dist/utils/metadata.js +19 -1
  42. package/dist/utils/module-constants.d.ts +21 -3
  43. package/dist/utils/module-constants.js +123 -25
  44. package/dist/utils/react-native.d.ts +17 -0
  45. package/dist/utils/react-native.js +108 -0
  46. package/dist/utils/string-utils.d.ts +8 -0
  47. package/dist/utils/string-utils.js +29 -1
  48. package/dist/utils/ui-copy.d.ts +5 -4
  49. package/dist/utils/ui-copy.js +10 -5
  50. 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
- const pathWithoutExt = normalizedPath.replace(/\.(ts|tsx|js|jsx)$/, '');
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);
@@ -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", "Settings"). Data looks different: lowercase
21
- * tokens ("active"), kebab/snake/camelCase, ALLCAPS, or anything with digits,
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 or
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;
@@ -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", "Settings"). Data looks different: lowercase
77
- * tokens ("active"), kebab/snake/camelCase, ALLCAPS, or anything with digits,
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 or
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",
3
+ "version": "0.2.0",
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.2",
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": {