@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.
Files changed (55) hide show
  1. package/dist/analysis/analyze.js +1 -1
  2. package/dist/config.d.ts +3 -3
  3. package/dist/detection/stack.d.ts +3 -1
  4. package/dist/detection/stack.js +13 -6
  5. package/dist/injection/StrategyFactory.d.ts +5 -1
  6. package/dist/injection/StrategyFactory.js +7 -2
  7. package/dist/injection/html/html-injector.d.ts +2 -0
  8. package/dist/injection/html/html-injector.js +55 -6
  9. package/dist/injection/strategies/VanillaStrategy.d.ts +8 -0
  10. package/dist/injection/strategies/VanillaStrategy.js +10 -3
  11. package/dist/local/translate-core.d.ts +2 -2
  12. package/dist/local/translate-core.js +2 -2
  13. package/dist/modules/extractor/extract.js +67 -10
  14. package/dist/modules/finalization/dependencies.d.ts +10 -0
  15. package/dist/modules/finalization/dependencies.js +23 -0
  16. package/dist/modules/finalization/finalize.js +47 -43
  17. package/dist/modules/finalization/nextjs-finalize.js +2 -1
  18. package/dist/modules/finalization/react-native-finalize.d.ts +45 -0
  19. package/dist/modules/finalization/react-native-finalize.js +324 -0
  20. package/dist/modules/finalization/static-runtime.d.ts +2 -2
  21. package/dist/modules/finalization/static-runtime.js +10 -5
  22. package/dist/modules/format-injection/index.js +38 -9
  23. package/dist/modules/format-injection/inject-formats.d.ts +3 -2
  24. package/dist/modules/format-injection/inject-formats.js +4 -3
  25. package/dist/modules/injection/inject.d.ts +14 -1
  26. package/dist/modules/injection/inject.js +307 -154
  27. package/dist/modules/sync-detector/sync-detector.js +11 -4
  28. package/dist/modules/translator/line-breaks.d.ts +8 -0
  29. package/dist/modules/translator/line-breaks.js +63 -0
  30. package/dist/modules/translator/translate.js +5 -2
  31. package/dist/modules/workspace/validate.d.ts +13 -1
  32. package/dist/modules/workspace/validate.js +95 -58
  33. package/dist/runtime/types.d.ts +22 -0
  34. package/dist/runtime/types.js +33 -0
  35. package/dist/utils/ast-helpers.d.ts +30 -2
  36. package/dist/utils/ast-helpers.js +355 -42
  37. package/dist/utils/extraction-options.js +1 -1
  38. package/dist/utils/file-utils.js +4 -1
  39. package/dist/utils/i18n-config-path.d.ts +18 -0
  40. package/dist/utils/i18n-config-path.js +64 -0
  41. package/dist/utils/i18next-icu.js +1 -1
  42. package/dist/utils/jsx-message.d.ts +16 -1
  43. package/dist/utils/jsx-message.js +89 -9
  44. package/dist/utils/key-usage.js +1 -1
  45. package/dist/utils/metadata.d.ts +5 -1
  46. package/dist/utils/metadata.js +19 -1
  47. package/dist/utils/module-constants.d.ts +21 -3
  48. package/dist/utils/module-constants.js +123 -25
  49. package/dist/utils/react-native.d.ts +17 -0
  50. package/dist/utils/react-native.js +108 -0
  51. package/dist/utils/string-utils.d.ts +8 -0
  52. package/dist/utils/string-utils.js +29 -1
  53. package/dist/utils/ui-copy.d.ts +5 -4
  54. package/dist/utils/ui-copy.js +10 -5
  55. package/package.json +3 -2
@@ -41,7 +41,19 @@ const inject_formats_1 = require("./inject-formats");
41
41
  const ast_helpers_1 = require("../../utils/ast-helpers");
42
42
  const file_utils_1 = require("../../utils/file-utils");
43
43
  const types_1 = require("../../runtime/types");
44
+ const StrategyFactory_1 = require("../../injection/StrategyFactory");
44
45
  const generator_1 = require("@babel/generator");
46
+ /** The local name of `import X from 'i18next'` in a file, if it has one. */
47
+ function i18nextDefaultImport(ast) {
48
+ for (const node of ast.program.body) {
49
+ if (!t.isImportDeclaration(node) || node.source.value !== 'i18next')
50
+ continue;
51
+ const def = node.specifiers.find((s) => t.isImportDefaultSpecifier(s));
52
+ if (def)
53
+ return def.local.name;
54
+ }
55
+ return null;
56
+ }
45
57
  /**
46
58
  * Run format injection for all format strings.
47
59
  *
@@ -66,13 +78,20 @@ async function injectFormats(workspace, formatStrings, formatConfig, dryRun = fa
66
78
  }
67
79
  stringsByFile.get(fileKey).push(s);
68
80
  }
69
- // Build locale expression based on framework
70
- const localeExpr = (0, inject_formats_1.buildLocaleExpression)(workspace.framework || 'unknown');
81
+ // The locale the formatted values follow. react-i18next (React, React
82
+ // Native, Next.js with react-i18next): the app's current language,
83
+ // `i18next.language` — React Native has no navigator.language, and on the
84
+ // web it ignored the language the user picked. Before init it is undefined,
85
+ // which Intl reads as the device default. Elsewhere the browser's language:
86
+ // next-intl's useLocale() and vue-i18n's composable only work in components.
87
+ const deps = { ...workspace.packageJson?.dependencies, ...workspace.packageJson?.devDependencies };
88
+ const followsI18next = StrategyFactory_1.StrategyFactory.getStrategy(workspace.framework || 'unknown', deps).id === 'react-i18next';
89
+ const browserLocaleExpr = (0, inject_formats_1.buildLocaleExpression)('browser');
71
90
  // Find all source files
72
91
  const sourceFiles = [];
73
92
  for (const sourceDir of workspace.sourceDirs) {
74
93
  const dirPath = path.join(workspace.path, sourceDir);
75
- const files = await (0, file_utils_1.findFiles)(dirPath, types_1.SUPPORTED_EXTENSIONS, types_1.EXCLUDED_PATTERNS);
94
+ const files = await (0, file_utils_1.findFiles)(dirPath, types_1.SUPPORTED_EXTENSIONS, (0, types_1.workspaceExcludes)(workspace));
76
95
  sourceFiles.push(...files);
77
96
  }
78
97
  // Build a lookup of format strings by (relative file path, line)
@@ -93,11 +112,17 @@ async function injectFormats(workspace, formatStrings, formatConfig, dryRun = fa
93
112
  try {
94
113
  const originalContent = await (0, file_utils_1.readFile)(filePath);
95
114
  const isTypeScript = filePath.endsWith('.ts') || filePath.endsWith('.tsx');
96
- const ast = (0, ast_helpers_1.parseCode)(originalContent, { isTypeScript });
115
+ const ast = (0, ast_helpers_1.parseCode)(originalContent, { isTypeScript, preserveFormat: true });
97
116
  const injections = [];
98
117
  const lineLookup = formatStringLookup.get(relativePath);
99
118
  if (!lineLookup)
100
119
  continue;
120
+ const existingI18next = followsI18next ? i18nextDefaultImport(ast) : null;
121
+ // A name nothing else in the file binds (`import * as i18next from 'i18next'`, a local `i18next`).
122
+ const i18nextName = existingI18next ?? ((0, ast_helpers_1.bindsTopLevel)(ast, 'i18next') ? 'i18nextInstance' : 'i18next');
123
+ const fileLocaleExpr = followsI18next
124
+ ? t.memberExpression(t.identifier(i18nextName), t.identifier('language'))
125
+ : browserLocaleExpr;
101
126
  // Walk AST and replace matching string literals with Intl.* expressions
102
127
  (0, ast_helpers_1.traverse)(ast, {
103
128
  StringLiteral(strPath) {
@@ -111,7 +136,7 @@ async function injectFormats(workspace, formatStrings, formatConfig, dryRun = fa
111
136
  // Verify the text matches exactly (avoid false positives from partial matches)
112
137
  if (node.value !== formatStr.text)
113
138
  return;
114
- const astExpr = (0, inject_formats_1.buildFormatAstExpression)(formatStr.detectedFormat, formatConfig, localeExpr);
139
+ const astExpr = (0, inject_formats_1.buildFormatAstExpression)(formatStr.detectedFormat, formatConfig, t.cloneNode(fileLocaleExpr));
115
140
  if (!astExpr) {
116
141
  skippedFiles.push({
117
142
  file: relativePath,
@@ -144,7 +169,7 @@ async function injectFormats(workspace, formatStrings, formatConfig, dryRun = fa
144
169
  const text = node.value.trim();
145
170
  if (!text)
146
171
  return;
147
- const astExpr = (0, inject_formats_1.buildFormatAstExpression)(formatStr.detectedFormat, formatConfig, localeExpr);
172
+ const astExpr = (0, inject_formats_1.buildFormatAstExpression)(formatStr.detectedFormat, formatConfig, t.cloneNode(fileLocaleExpr));
148
173
  if (!astExpr) {
149
174
  skippedFiles.push({
150
175
  file: relativePath,
@@ -170,6 +195,10 @@ async function injectFormats(workspace, formatStrings, formatConfig, dryRun = fa
170
195
  });
171
196
  if (injections.length === 0)
172
197
  continue;
198
+ const importAdded = followsI18next && !existingI18next;
199
+ if (importAdded) {
200
+ ast.program.body.unshift(t.importDeclaration([t.importDefaultSpecifier(t.identifier(i18nextName))], t.stringLiteral('i18next')));
201
+ }
173
202
  const modifiedContent = (0, ast_helpers_1.generateCode)(ast, originalContent);
174
203
  if (!dryRun) {
175
204
  await (0, file_utils_1.writeFile)(filePath, modifiedContent);
@@ -179,7 +208,7 @@ async function injectFormats(workspace, formatStrings, formatConfig, dryRun = fa
179
208
  originalContent,
180
209
  modifiedContent,
181
210
  injections,
182
- importAdded: false,
211
+ importAdded,
183
212
  hookAdded: false,
184
213
  });
185
214
  totalInjections += injections.length;
@@ -196,7 +225,7 @@ async function injectFormats(workspace, formatStrings, formatConfig, dryRun = fa
196
225
  const htmlExtensions = ['.html', '.htm'];
197
226
  for (const sourceDir of workspace.sourceDirs) {
198
227
  const dirPath = path.join(workspace.path, sourceDir);
199
- const files = await (0, file_utils_1.findFiles)(dirPath, htmlExtensions, types_1.EXCLUDED_PATTERNS);
228
+ const files = await (0, file_utils_1.findFiles)(dirPath, htmlExtensions, (0, types_1.workspaceExcludes)(workspace));
200
229
  for (const filePath of files) {
201
230
  const relativePath = (0, file_utils_1.getRelativePath)(workspace.path, filePath);
202
231
  const fileStrings = stringsByFile.get(relativePath);
@@ -213,7 +242,7 @@ async function injectFormats(workspace, formatStrings, formatConfig, dryRun = fa
213
242
  const regex = new RegExp(escaped, 'g');
214
243
  if (!regex.test(content))
215
244
  continue;
216
- const astExpr = (0, inject_formats_1.buildFormatAstExpression)(formatStr.detectedFormat, formatConfig, localeExpr);
245
+ const astExpr = (0, inject_formats_1.buildFormatAstExpression)(formatStr.detectedFormat, formatConfig, browserLocaleExpr);
217
246
  if (!astExpr)
218
247
  continue;
219
248
  const replacementCode = (0, generator_1.generate)(astExpr).code;
@@ -26,6 +26,7 @@ export declare function buildFormatInjection(extracted: ExtractedString, formatC
26
26
  */
27
27
  export declare function buildFormatAstExpression(format: DetectedFormat, formatConfig: FormatLocaleConfig, localeExpr: t.Expression): t.Expression | null;
28
28
  /**
29
- * Build a locale expression based on the detected framework.
29
+ * Build a locale expression for an i18n library (an injection strategy id:
30
+ * 'react-i18next', 'next-intl'); anything else gets the browser's language.
30
31
  */
31
- export declare function buildLocaleExpression(framework: string): t.Expression;
32
+ export declare function buildLocaleExpression(library: string): t.Expression;
@@ -194,10 +194,11 @@ function buildNumberAstExpression(format, localeExpr) {
194
194
  return t.callExpression(t.memberExpression(nfNew, t.identifier('format')), [t.numericLiteral(numericValue)]);
195
195
  }
196
196
  /**
197
- * Build a locale expression based on the detected framework.
197
+ * Build a locale expression for an i18n library (an injection strategy id:
198
+ * 'react-i18next', 'next-intl'); anything else gets the browser's language.
198
199
  */
199
- function buildLocaleExpression(framework) {
200
- switch (framework) {
200
+ function buildLocaleExpression(library) {
201
+ switch (library) {
201
202
  case 'react-i18next':
202
203
  return t.memberExpression(t.identifier('i18n'), t.identifier('language'));
203
204
  case 'next-intl':
@@ -14,7 +14,20 @@ positions?: Map<string, Set<number>>): Promise<InjectionResult>;
14
14
  */
15
15
  export declare function injectIntoFile(content: string, filePath: string, keyMapping: Map<string, string>, confidenceThreshold: number, framework: string, existingDeps?: Record<string, string>, forceTypeScript?: boolean,
16
16
  /** extractionId → source offsets of the nodes extraction selected (see ExtractedString.offsets). */
17
- positions?: Map<string, Set<number>>): FileInjection;
17
+ positions?: Map<string, Set<number>>, options?: {
18
+ /**
19
+ * Import path (relative to this file) of the i18n config's module-level `t`.
20
+ * Set when Babelize owns the react-i18next config: strings outside any
21
+ * component the hook can go in use it. Unset, they are left as they are.
22
+ */
23
+ moduleTranslator?: string;
24
+ /**
25
+ * Messages with placeholders are formatted at runtime (ICU): template
26
+ * literals and `+` concatenations become `t(key, { name })`. Off, they
27
+ * are left as they are.
28
+ */
29
+ placeholders?: boolean;
30
+ }): FileInjection;
18
31
  /**
19
32
  * Inject translations into a Vue SFC file.
20
33
  * Uses @vue/compiler-sfc to parse, vue/template-scan to find translatable