@saidksi/localizer-core 0.1.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 (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +164 -0
  3. package/dist/ai/anthropic.d.ts +17 -0
  4. package/dist/ai/anthropic.d.ts.map +1 -0
  5. package/dist/ai/anthropic.js +58 -0
  6. package/dist/ai/anthropic.js.map +1 -0
  7. package/dist/ai/dedup.d.ts +19 -0
  8. package/dist/ai/dedup.d.ts.map +1 -0
  9. package/dist/ai/dedup.js +119 -0
  10. package/dist/ai/dedup.js.map +1 -0
  11. package/dist/ai/index.d.ts +65 -0
  12. package/dist/ai/index.d.ts.map +1 -0
  13. package/dist/ai/index.js +464 -0
  14. package/dist/ai/index.js.map +1 -0
  15. package/dist/ai/openai.d.ts +11 -0
  16. package/dist/ai/openai.d.ts.map +1 -0
  17. package/dist/ai/openai.js +62 -0
  18. package/dist/ai/openai.js.map +1 -0
  19. package/dist/ai/prompts.d.ts +20 -0
  20. package/dist/ai/prompts.d.ts.map +1 -0
  21. package/dist/ai/prompts.js +151 -0
  22. package/dist/ai/prompts.js.map +1 -0
  23. package/dist/cache/index.d.ts +69 -0
  24. package/dist/cache/index.d.ts.map +1 -0
  25. package/dist/cache/index.js +129 -0
  26. package/dist/cache/index.js.map +1 -0
  27. package/dist/index.d.ts +8 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +14 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/rewriter/index.d.ts +31 -0
  32. package/dist/rewriter/index.d.ts.map +1 -0
  33. package/dist/rewriter/index.js +128 -0
  34. package/dist/rewriter/index.js.map +1 -0
  35. package/dist/rewriter/transforms.d.ts +38 -0
  36. package/dist/rewriter/transforms.d.ts.map +1 -0
  37. package/dist/rewriter/transforms.js +189 -0
  38. package/dist/rewriter/transforms.js.map +1 -0
  39. package/dist/rewriter/ts-morph.d.ts +19 -0
  40. package/dist/rewriter/ts-morph.d.ts.map +1 -0
  41. package/dist/rewriter/ts-morph.js +121 -0
  42. package/dist/rewriter/ts-morph.js.map +1 -0
  43. package/dist/scanner/babel.d.ts +3 -0
  44. package/dist/scanner/babel.d.ts.map +1 -0
  45. package/dist/scanner/babel.js +504 -0
  46. package/dist/scanner/babel.js.map +1 -0
  47. package/dist/scanner/filters.d.ts +38 -0
  48. package/dist/scanner/filters.d.ts.map +1 -0
  49. package/dist/scanner/filters.js +133 -0
  50. package/dist/scanner/filters.js.map +1 -0
  51. package/dist/scanner/index.d.ts +22 -0
  52. package/dist/scanner/index.d.ts.map +1 -0
  53. package/dist/scanner/index.js +82 -0
  54. package/dist/scanner/index.js.map +1 -0
  55. package/dist/scanner/typescript.d.ts +3 -0
  56. package/dist/scanner/typescript.d.ts.map +1 -0
  57. package/dist/scanner/typescript.js +542 -0
  58. package/dist/scanner/typescript.js.map +1 -0
  59. package/dist/types.d.ts +205 -0
  60. package/dist/types.d.ts.map +1 -0
  61. package/dist/types.js +3 -0
  62. package/dist/types.js.map +1 -0
  63. package/dist/validator/index.d.ts +65 -0
  64. package/dist/validator/index.d.ts.map +1 -0
  65. package/dist/validator/index.js +237 -0
  66. package/dist/validator/index.js.map +1 -0
  67. package/package.json +65 -0
@@ -0,0 +1,189 @@
1
+ export const LIBRARY_ADAPTERS = {
2
+ "react-i18next": {
3
+ importSource: "react-i18next",
4
+ importSpecifier: "useTranslation",
5
+ importStatement: 'import { useTranslation } from "react-i18next";',
6
+ hookStatement: "const { t } = useTranslation();",
7
+ hookDetectionString: "useTranslation(",
8
+ callFor: (key) => `t('${key}')`,
9
+ },
10
+ "i18next": {
11
+ importSource: "react-i18next",
12
+ importSpecifier: "useTranslation",
13
+ importStatement: 'import { useTranslation } from "react-i18next";',
14
+ hookStatement: "const { t } = useTranslation();",
15
+ hookDetectionString: "useTranslation(",
16
+ callFor: (key) => `t('${key}')`,
17
+ },
18
+ "next-intl": {
19
+ importSource: "next-intl",
20
+ importSpecifier: "useTranslations",
21
+ importStatement: 'import { useTranslations } from "next-intl";',
22
+ hookStatement: "const t = useTranslations();",
23
+ hookDetectionString: "useTranslations(",
24
+ callFor: (key) => `t('${key}')`,
25
+ },
26
+ "react-intl": {
27
+ importSource: "react-intl",
28
+ importSpecifier: "useIntl",
29
+ importStatement: 'import { useIntl } from "react-intl";',
30
+ hookStatement: "const intl = useIntl();",
31
+ hookDetectionString: "useIntl(",
32
+ callFor: (key) => `intl.formatMessage({ id: '${key}' })`,
33
+ },
34
+ "vue-i18n": {
35
+ importSource: "vue-i18n",
36
+ importSpecifier: "useI18n",
37
+ importStatement: 'import { useI18n } from "vue-i18n";',
38
+ hookStatement: "const { t } = useI18n();",
39
+ hookDetectionString: "useI18n(",
40
+ callFor: (key) => `t('${key}')`,
41
+ },
42
+ };
43
+ export function getAdapter(library) {
44
+ return LIBRARY_ADAPTERS[library];
45
+ }
46
+ // ─── Positional string replacement ───────────────────────────────────────────
47
+ /**
48
+ * Apply all resolved string replacements to the source text.
49
+ *
50
+ * Strategy:
51
+ * - Sort results bottom-to-top, right-to-left so earlier replacements
52
+ * don't shift the positions of later ones on the same line.
53
+ * - For each result, locate the exact source span from its NodeType and
54
+ * replace it with the appropriate t() call expression.
55
+ */
56
+ export function applyStringReplacements(source, results, adapter) {
57
+ const resolved = results.filter((r) => r.resolvedKey !== null);
58
+ if (resolved.length === 0)
59
+ return { modified: source, count: 0 };
60
+ // Sort: bottom → top, right → left
61
+ const sorted = [...resolved].sort((a, b) => {
62
+ if (b.line !== a.line)
63
+ return b.line - a.line;
64
+ return b.column - a.column;
65
+ });
66
+ const lines = source.split("\n");
67
+ let count = 0;
68
+ for (const result of sorted) {
69
+ const lineIdx = result.line - 1; // convert to 0-based
70
+ const lineStr = lines[lineIdx];
71
+ if (lineStr === undefined)
72
+ continue;
73
+ const key = result.resolvedKey;
74
+ const call = adapter.callFor(key);
75
+ let replaced = null;
76
+ switch (result.nodeType) {
77
+ case "JSXText": {
78
+ // Bare text: Welcome back → {t('key')}
79
+ // Search for the trimmed value starting at (or after) the column
80
+ const idx = lineStr.indexOf(result.value, result.column);
81
+ if (idx !== -1) {
82
+ replaced =
83
+ lineStr.substring(0, idx) +
84
+ `{${call}}` +
85
+ lineStr.substring(idx + result.value.length);
86
+ }
87
+ break;
88
+ }
89
+ case "JSXAttribute": {
90
+ // Quoted attribute value: "Enter email" or 'Enter email' → {t('key')}
91
+ // Try both quote styles; column points to the opening quote
92
+ for (const q of ['"', "'"]) {
93
+ const target = `${q}${result.value}${q}`;
94
+ const idx = lineStr.indexOf(target, result.column);
95
+ if (idx !== -1) {
96
+ replaced =
97
+ lineStr.substring(0, idx) +
98
+ `{${call}}` +
99
+ lineStr.substring(idx + target.length);
100
+ break;
101
+ }
102
+ }
103
+ break;
104
+ }
105
+ case "TemplateLiteral": {
106
+ if (result.context === "Template literal (static part)") {
107
+ // Static quasi from a dynamic template: `prefix ${expr} suffix`
108
+ // Replace just the static text span with ${t('key')} (stays inside backticks)
109
+ const idx = lineStr.indexOf(result.value, result.column);
110
+ if (idx !== -1) {
111
+ replaced =
112
+ lineStr.substring(0, idx) +
113
+ `\${${call}}` +
114
+ lineStr.substring(idx + result.value.length);
115
+ }
116
+ }
117
+ else {
118
+ // Standalone static template literal: `Hello world` → t('key')
119
+ const target = `\`${result.value}\``;
120
+ const idx = lineStr.indexOf(target, result.column);
121
+ if (idx !== -1) {
122
+ replaced =
123
+ lineStr.substring(0, idx) +
124
+ call +
125
+ lineStr.substring(idx + target.length);
126
+ }
127
+ }
128
+ break;
129
+ }
130
+ case "StringLiteral": {
131
+ // Skip module-level strings — t() is not available outside component functions
132
+ if (result.isModuleLevel)
133
+ break;
134
+ // Non-JSX string inside component: setError("..."), return "..." etc. → t('key')
135
+ for (const q of ['"', "'"]) {
136
+ const target = `${q}${result.value}${q}`;
137
+ const idx = lineStr.indexOf(target, result.column);
138
+ if (idx !== -1) {
139
+ replaced =
140
+ lineStr.substring(0, idx) +
141
+ call +
142
+ lineStr.substring(idx + target.length);
143
+ break;
144
+ }
145
+ }
146
+ break;
147
+ }
148
+ case "JSXInterpolation": {
149
+ // Replace the entire children span with a single t() call that passes
150
+ // the interpolation variables as an object:
151
+ // You have {taskCount} pending tasks
152
+ // → {t('tasks.pending_count', { taskCount: taskCount })}
153
+ if (!result.rawSpan || !result.interpolations?.length)
154
+ break;
155
+ // Build the options object: { taskCount: taskCount, userName: userName }
156
+ const argsStr = result.interpolations
157
+ .map(({ placeholder, expression }) => placeholder === expression
158
+ ? placeholder // shorthand: { taskCount }
159
+ : `${placeholder}: ${expression}`)
160
+ .join(", ");
161
+ const callWithArgs = adapter.callFor(key).replace(/\)$/, `, { ${argsStr} })`);
162
+ // rawSpan may include surrounding newlines/whitespace from JSX formatting
163
+ // (the scanner's firstChild.getStart() lands on the '\n' after the opening
164
+ // tag's '>'). Trim first, then search on the reported line.
165
+ // Try column-anchored search first; fall back to scanning from col 0
166
+ // because result.column may point to the prior line's position.
167
+ const rawSpanTrimmed = result.rawSpan.trim();
168
+ // If trimmed span still contains a newline the content truly spans
169
+ // multiple lines — indexOf on a single lineStr will return -1 safely.
170
+ let idx = lineStr.indexOf(rawSpanTrimmed, result.column);
171
+ if (idx === -1)
172
+ idx = lineStr.indexOf(rawSpanTrimmed, 0);
173
+ if (idx !== -1) {
174
+ replaced =
175
+ lineStr.substring(0, idx) +
176
+ `{${callWithArgs}}` +
177
+ lineStr.substring(idx + rawSpanTrimmed.length);
178
+ }
179
+ break;
180
+ }
181
+ }
182
+ if (replaced !== null) {
183
+ lines[lineIdx] = replaced;
184
+ count++;
185
+ }
186
+ }
187
+ return { modified: lines.join("\n"), count };
188
+ }
189
+ //# sourceMappingURL=transforms.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transforms.js","sourceRoot":"","sources":["../../src/rewriter/transforms.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { LibraryAdapter } from "./transforms.js";
2
+ /**
3
+ * Ensure the i18n library import exists in the file.
4
+ * If the import (or specifier) is already present, the source is returned unchanged.
5
+ * Otherwise the import is added after the last existing import declaration.
6
+ */
7
+ export declare function ensureImport(source: string, filePath: string, adapter: LibraryAdapter): string;
8
+ /**
9
+ * Ensure the translation hook declaration exists in the first component function.
10
+ * If the hook call is already present anywhere in the file, the source is returned unchanged.
11
+ * Otherwise the hook statement is inserted at the top of the component function body.
12
+ */
13
+ export declare function ensureHook(source: string, filePath: string, adapter: LibraryAdapter, namespace?: string): string;
14
+ /**
15
+ * Apply both import and hook injection in a single pass.
16
+ * Import is added first so the hook injection sees the updated source.
17
+ */
18
+ export declare function ensureTranslationBoilerplate(source: string, filePath: string, adapter: LibraryAdapter, namespace?: string): string;
19
+ //# sourceMappingURL=ts-morph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ts-morph.d.ts","sourceRoot":"","sources":["../../src/rewriter/ts-morph.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAItD;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,cAAc,GACtB,MAAM,CAoCR;AA8CD;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,cAAc,EACvB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAkCR;AAID;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,cAAc,EACvB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAIR"}
@@ -0,0 +1,121 @@
1
+ import { Project, Node } from "ts-morph";
2
+ // ─── Import injection ─────────────────────────────────────────────────────────
3
+ /**
4
+ * Ensure the i18n library import exists in the file.
5
+ * If the import (or specifier) is already present, the source is returned unchanged.
6
+ * Otherwise the import is added after the last existing import declaration.
7
+ */
8
+ export function ensureImport(source, filePath, adapter) {
9
+ const project = new Project({ useInMemoryFileSystem: true });
10
+ const sf = project.createSourceFile(filePath, source, { overwrite: true });
11
+ // Check if the import already exists
12
+ const existing = sf.getImportDeclaration((d) => d.getModuleSpecifierValue() === adapter.importSource &&
13
+ d.getNamedImports().some((n) => n.getName() === adapter.importSpecifier));
14
+ if (existing)
15
+ return source; // already imported
16
+ // Check if the module is imported but our specifier is missing
17
+ const partialImport = sf.getImportDeclaration((d) => d.getModuleSpecifierValue() === adapter.importSource);
18
+ if (partialImport) {
19
+ // Add our specifier to the existing import
20
+ partialImport.addNamedImport(adapter.importSpecifier);
21
+ }
22
+ else {
23
+ // Add a brand new import declaration after all existing imports
24
+ const imports = sf.getImportDeclarations();
25
+ const insertIndex = imports.length > 0
26
+ ? imports[imports.length - 1].getChildIndex() + 1
27
+ : 0;
28
+ sf.insertImportDeclaration(insertIndex, {
29
+ namedImports: [adapter.importSpecifier],
30
+ moduleSpecifier: adapter.importSource,
31
+ });
32
+ }
33
+ return sf.getFullText();
34
+ }
35
+ // ─── Hook injection ───────────────────────────────────────────────────────────
36
+ /**
37
+ * Find the first component function in the source file.
38
+ * Looks for:
39
+ * 1. Exported function declarations: `export function Login()`
40
+ * 2. Default export functions: `export default function()`
41
+ * 3. Exported arrow function variable declarations: `export const Login = () => {}`
42
+ * 4. Any arrow function variable declaration: `const Login = () => {}`
43
+ * 5. Fallback: any function declaration
44
+ *
45
+ * Returns null if no suitable function is found.
46
+ */
47
+ function findComponentFunction(sf) {
48
+ // 1. Named exported function declarations
49
+ for (const fn of sf.getFunctions()) {
50
+ if (fn.isExported() || fn.isDefaultExport())
51
+ return fn;
52
+ }
53
+ // 2. Exported arrow function variable declarations
54
+ for (const varStmt of sf.getVariableStatements()) {
55
+ if (!varStmt.isExported())
56
+ continue;
57
+ for (const decl of varStmt.getDeclarations()) {
58
+ const init = decl.getInitializer();
59
+ if (init && Node.isArrowFunction(init)) {
60
+ return init;
61
+ }
62
+ }
63
+ }
64
+ // 3. Any arrow function variable declaration
65
+ for (const varStmt of sf.getVariableStatements()) {
66
+ for (const decl of varStmt.getDeclarations()) {
67
+ const init = decl.getInitializer();
68
+ if (init && Node.isArrowFunction(init)) {
69
+ return init;
70
+ }
71
+ }
72
+ }
73
+ // 4. Fallback: first function declaration in file
74
+ return sf.getFunctions()[0] ?? null;
75
+ }
76
+ /**
77
+ * Ensure the translation hook declaration exists in the first component function.
78
+ * If the hook call is already present anywhere in the file, the source is returned unchanged.
79
+ * Otherwise the hook statement is inserted at the top of the component function body.
80
+ */
81
+ export function ensureHook(source, filePath, adapter, namespace) {
82
+ // Fast path: hook already present in source text
83
+ if (source.includes(adapter.hookDetectionString))
84
+ return source;
85
+ const project = new Project({ useInMemoryFileSystem: true });
86
+ const sf = project.createSourceFile(filePath, source, { overwrite: true });
87
+ const fn = findComponentFunction(sf);
88
+ if (!fn)
89
+ return source; // no function found — leave as-is
90
+ const body = Node.isArrowFunction(fn)
91
+ ? fn.getBody()
92
+ : fn.getBody();
93
+ if (!body)
94
+ return source;
95
+ // For arrow functions with expression bodies (no braces), convert to block first
96
+ if (!Node.isBlock(body)) {
97
+ // e.g. `const X = () => <div/>` — not common in real components, skip
98
+ return source;
99
+ }
100
+ // Build the hook statement, using namespace if provided and if the hook accepts it
101
+ // Only useTranslation, useTranslations, and useI18n accept namespace argument.
102
+ // react-intl's useIntl() does not accept a namespace argument.
103
+ const supportsNamespace = adapter.importSpecifier !== "useIntl";
104
+ const hookStatement = namespace && supportsNamespace
105
+ ? adapter.hookStatement.replace("()", `('${namespace}')`)
106
+ : adapter.hookStatement;
107
+ // Insert hook at the very beginning of the function body (index 0)
108
+ body.insertStatements(0, [hookStatement]);
109
+ return sf.getFullText();
110
+ }
111
+ // ─── Combined ────────────────────────────────────────────────────────────────
112
+ /**
113
+ * Apply both import and hook injection in a single pass.
114
+ * Import is added first so the hook injection sees the updated source.
115
+ */
116
+ export function ensureTranslationBoilerplate(source, filePath, adapter, namespace) {
117
+ let result = ensureImport(source, filePath, adapter);
118
+ result = ensureHook(result, filePath, adapter, namespace);
119
+ return result;
120
+ }
121
+ //# sourceMappingURL=ts-morph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ts-morph.js","sourceRoot":"","sources":["../../src/rewriter/ts-morph.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAc,MAAM,UAAU,CAAC;AAGrD,iFAAiF;AAEjF;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAC1B,MAAc,EACd,QAAgB,EAChB,OAAuB;IAEvB,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,EAAE,qBAAqB,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,MAAM,EAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE3E,qCAAqC;IACrC,MAAM,QAAQ,GAAG,EAAE,CAAC,oBAAoB,CACtC,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,uBAAuB,EAAE,KAAK,OAAO,CAAC,YAAY;QACpD,CAAC,CAAC,eAAe,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,KAAK,OAAO,CAAC,eAAe,CAAC,CAC3E,CAAC;IAEF,IAAI,QAAQ;QAAE,OAAO,MAAM,CAAC,CAAC,mBAAmB;IAEhD,+DAA+D;IAC/D,MAAM,aAAa,GAAG,EAAE,CAAC,oBAAoB,CAC3C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,uBAAuB,EAAE,KAAK,OAAO,CAAC,YAAY,CAC5D,CAAC;IAEF,IAAI,aAAa,EAAE,CAAC;QAClB,2CAA2C;QAC3C,aAAa,CAAC,cAAc,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IACxD,CAAC;SAAM,CAAC;QACN,gEAAgE;QAChE,MAAM,OAAO,GAAG,EAAE,CAAC,qBAAqB,EAAE,CAAC;QAC3C,MAAM,WAAW,GACf,OAAO,CAAC,MAAM,GAAG,CAAC;YAChB,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC,aAAa,EAAE,GAAG,CAAC;YAClD,CAAC,CAAC,CAAC,CAAC;QAER,EAAE,CAAC,uBAAuB,CAAC,WAAW,EAAE;YACtC,YAAY,EAAE,CAAC,OAAO,CAAC,eAAe,CAAC;YACvC,eAAe,EAAE,OAAO,CAAC,YAAY;SACtC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,EAAE,CAAC,WAAW,EAAE,CAAC;AAC1B,CAAC;AAED,iFAAiF;AAEjF;;;;;;;;;;GAUG;AACH,SAAS,qBAAqB,CAAC,EAA2C;IACxE,0CAA0C;IAC1C,KAAK,MAAM,EAAE,IAAI,EAAE,CAAC,YAAY,EAAE,EAAE,CAAC;QACnC,IAAI,EAAE,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,eAAe,EAAE;YAAE,OAAO,EAAE,CAAC;IACzD,CAAC;IAED,mDAAmD;IACnD,KAAK,MAAM,OAAO,IAAI,EAAE,CAAC,qBAAqB,EAAE,EAAE,CAAC;QACjD,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE;YAAE,SAAS;QACpC,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC;YAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;YACnC,IAAI,IAAI,IAAI,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC;gBACvC,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;IAED,6CAA6C;IAC7C,KAAK,MAAM,OAAO,IAAI,EAAE,CAAC,qBAAqB,EAAE,EAAE,CAAC;QACjD,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC;YAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;YACnC,IAAI,IAAI,IAAI,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC;gBACvC,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;IAED,kDAAkD;IAClD,OAAO,EAAE,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;AACtC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CACxB,MAAc,EACd,QAAgB,EAChB,OAAuB,EACvB,SAAkB;IAElB,iDAAiD;IACjD,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,mBAAmB,CAAC;QAAE,OAAO,MAAM,CAAC;IAEhE,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,EAAE,qBAAqB,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,MAAM,EAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE3E,MAAM,EAAE,GAAG,qBAAqB,CAAC,EAAE,CAAC,CAAC;IACrC,IAAI,CAAC,EAAE;QAAE,OAAO,MAAM,CAAC,CAAC,kCAAkC;IAE1D,MAAM,IAAI,GAAG,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC;QACnC,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE;QACd,CAAC,CAAE,EAAiD,CAAC,OAAO,EAAE,CAAC;IAEjE,IAAI,CAAC,IAAI;QAAE,OAAO,MAAM,CAAC;IAEzB,iFAAiF;IACjF,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,sEAAsE;QACtE,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,mFAAmF;IACnF,+EAA+E;IAC/E,+DAA+D;IAC/D,MAAM,iBAAiB,GAAG,OAAO,CAAC,eAAe,KAAK,SAAS,CAAC;IAChE,MAAM,aAAa,GAAG,SAAS,IAAI,iBAAiB;QAClD,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,SAAS,IAAI,CAAC;QACzD,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC;IAE1B,mEAAmE;IACnE,IAAI,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC;IAE1C,OAAO,EAAE,CAAC,WAAW,EAAE,CAAC;AAC1B,CAAC;AAED,gFAAgF;AAEhF;;;GAGG;AACH,MAAM,UAAU,4BAA4B,CAC1C,MAAc,EACd,QAAgB,EAChB,OAAuB,EACvB,SAAkB;IAElB,IAAI,MAAM,GAAG,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IACrD,MAAM,GAAG,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;IAC1D,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { ScanResult, LocalizeConfig } from "../types.js";
2
+ export declare function scanWithBabel(filePath: string, source: string, config: LocalizeConfig): ScanResult[];
3
+ //# sourceMappingURL=babel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"babel.d.ts","sourceRoot":"","sources":["../../src/scanner/babel.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAY,cAAc,EAAoB,MAAM,aAAa,CAAC;AA2R1F,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,cAAc,GACrB,UAAU,EAAE,CAsTd"}