@babelize/vite 0.0.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/README.md +215 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +311 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# @babelize/vite
|
|
2
|
+
|
|
3
|
+
**Vite plugin for Babelize** — automatically discover strings, translate them, and generate a lockfile at build time. Zero configuration required.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
// vite.config.ts
|
|
7
|
+
import babelize from "@babelize/vite";
|
|
8
|
+
|
|
9
|
+
export default defineConfig({
|
|
10
|
+
plugins: [
|
|
11
|
+
react(),
|
|
12
|
+
babelize({
|
|
13
|
+
locales: ["ja", "fr", "de"],
|
|
14
|
+
}),
|
|
15
|
+
],
|
|
16
|
+
});
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bun add @babelize/vite -D
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## How It Works
|
|
30
|
+
|
|
31
|
+
At build time, the plugin:
|
|
32
|
+
|
|
33
|
+
1. **Scans** every source file for `babelize("...")`, `babelize.p(...)`, and `<BabelizeTag>` calls using AST analysis
|
|
34
|
+
2. **Extracts** all unique strings and plural templates
|
|
35
|
+
3. **Translates** them via the Babelize API for each configured locale
|
|
36
|
+
4. **Generates** `babelize-lock.json` with all translations
|
|
37
|
+
|
|
38
|
+
At runtime, the lockfile is served via a virtual module `virtual:babelize-lockfile` so the SDK can load it without any manual import path configuration.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
### 1. Configure the plugin
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
// vite.config.ts
|
|
48
|
+
import babelize from "@babelize/vite";
|
|
49
|
+
|
|
50
|
+
export default defineConfig({
|
|
51
|
+
plugins: [
|
|
52
|
+
react(),
|
|
53
|
+
babelize({
|
|
54
|
+
locales: ["ja", "fr", "de"],
|
|
55
|
+
}),
|
|
56
|
+
],
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 2. Set your API key
|
|
61
|
+
|
|
62
|
+
```env
|
|
63
|
+
# .env
|
|
64
|
+
BABELIZE_API_KEY=blz_sk_xxxxxxxxx
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 3. Use the lockfile in your app
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
// src/main.tsx
|
|
71
|
+
import { babelize } from "@babelize/sdk";
|
|
72
|
+
import lockfile from "virtual:babelize-lockfile";
|
|
73
|
+
|
|
74
|
+
lockfile && babelize.loadLockfile(lockfile);
|
|
75
|
+
babelize.init(import.meta.env.VITE_BABELIZE_API_KEY);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 4. Build
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
bun run build
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The lockfile is automatically generated at `babelize-lock.json` in your project root.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Options
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
interface BabelizePluginOptions {
|
|
92
|
+
apiKey?: string;
|
|
93
|
+
locales?: string[];
|
|
94
|
+
output?: string;
|
|
95
|
+
functionName?: string;
|
|
96
|
+
apiUrl?: string;
|
|
97
|
+
mock?: boolean;
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
| Option | Type | Default | Description |
|
|
102
|
+
|--------|------|---------|-------------|
|
|
103
|
+
| `locales` | `string[]` | — | Target locales to translate to (e.g. `["ja", "fr"]`) |
|
|
104
|
+
| `apiKey` | `string` | `process.env.BABELIZE_API_KEY` | Babelize API key |
|
|
105
|
+
| `functionName` | `string` | `"babelize"` | Function name to scan for |
|
|
106
|
+
| `output` | `string` | `"babelize-lock.json"` | Output path for the lockfile |
|
|
107
|
+
| `apiUrl` | `string` | `"https://api.babelize.co/api"` | API base URL |
|
|
108
|
+
| `mock` | `boolean` | `false` | Generate fake translations (`"Welcome [ja]"`) for testing without API key |
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## What Gets Scanned
|
|
113
|
+
|
|
114
|
+
The plugin scans these patterns in your source files:
|
|
115
|
+
|
|
116
|
+
### `babelize("static string")`
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
babelize("Welcome") // ✓ extracted
|
|
120
|
+
babelize("Hello {name}", { name }) // ✓ extracted (variables stripped)
|
|
121
|
+
babelize(dynamicVariable) // ✗ skipped (can't statically analyze)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### `babelize.p(singular, plural, count)`
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
babelize.p("{count} item", "{count} items", items.length)
|
|
128
|
+
// ✓ extracted as plural with forms ["one", "other"]
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### `babelize.plural({...}, vars)`
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
babelize.plural({ one: "1 item", other: "{count} items" }, { count })
|
|
135
|
+
// ✓ extracted as plural with explicit forms
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### `<BabelizeTag template="..." />`
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
<BabelizeTag
|
|
142
|
+
template="Welcome to {Babelize}, a platform {for localization}"
|
|
143
|
+
segments={{ Babelize: <strong>Babelize</strong> }}
|
|
144
|
+
/>
|
|
145
|
+
// ✓ template extracted as a single translation unit
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Lockfile Output
|
|
151
|
+
|
|
152
|
+
The plugin generates `babelize-lock.json` in your project root:
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"version": 1,
|
|
157
|
+
"meta": {
|
|
158
|
+
"generatedAt": "2026-07-30T12:00:00Z",
|
|
159
|
+
"locales": ["ja", "fr", "de"],
|
|
160
|
+
"totalStrings": 42
|
|
161
|
+
},
|
|
162
|
+
"strings": {
|
|
163
|
+
"Welcome": {
|
|
164
|
+
"ja": "ようこそ",
|
|
165
|
+
"fr": "Bienvenue"
|
|
166
|
+
},
|
|
167
|
+
"Hello {name}": {
|
|
168
|
+
"ja": "こんにちは {name}",
|
|
169
|
+
"fr": "Bonjour {name}"
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
"plurals": {
|
|
173
|
+
"{count} item": {
|
|
174
|
+
"forms": ["one", "other"],
|
|
175
|
+
"ja": { "other": "{count} 項目" },
|
|
176
|
+
"fr": { "one": "{count} élément", "other": "{count} éléments" }
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Virtual Module
|
|
185
|
+
|
|
186
|
+
The plugin provides a virtual module `virtual:babelize-lockfile` that serves the current lockfile content. If a lockfile exists from a previous build, its data is used. If not, an empty lockfile is returned.
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
// Type declaration for the virtual module
|
|
190
|
+
declare module "virtual:babelize-lockfile" {
|
|
191
|
+
const data: {
|
|
192
|
+
version: number;
|
|
193
|
+
strings: Record<string, Record<string, string>>;
|
|
194
|
+
plurals?: Record<string, any>;
|
|
195
|
+
};
|
|
196
|
+
export default data;
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## TypeScript
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
import type { BabelizePluginOptions } from "@babelize/vite";
|
|
206
|
+
|
|
207
|
+
interface BabelizePluginOptions {
|
|
208
|
+
apiKey?: string;
|
|
209
|
+
locales?: string[];
|
|
210
|
+
output?: string;
|
|
211
|
+
functionName?: string;
|
|
212
|
+
apiUrl?: string;
|
|
213
|
+
mock?: boolean;
|
|
214
|
+
}
|
|
215
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
interface BabelizePluginOptions {
|
|
4
|
+
apiKey?: string;
|
|
5
|
+
locales?: string[];
|
|
6
|
+
output?: string;
|
|
7
|
+
functionName?: string;
|
|
8
|
+
apiUrl?: string;
|
|
9
|
+
mock?: boolean;
|
|
10
|
+
}
|
|
11
|
+
declare function babelize(options?: BabelizePluginOptions): Plugin;
|
|
12
|
+
|
|
13
|
+
export { type BabelizePluginOptions, babelize, babelize as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from "fs";
|
|
3
|
+
import { resolve, extname } from "path";
|
|
4
|
+
|
|
5
|
+
// src/scanner.ts
|
|
6
|
+
import { createRequire } from "module";
|
|
7
|
+
import { parse } from "@babel/parser";
|
|
8
|
+
var _require = createRequire(import.meta.url);
|
|
9
|
+
var _traverse = _require("@babel/traverse");
|
|
10
|
+
var traverse = _traverse.default?.default ?? _traverse.default ?? _traverse;
|
|
11
|
+
var DEFAULT_FUNCTION_NAME = "babelize";
|
|
12
|
+
var PLURAL_FORMS = ["zero", "one", "two", "few", "many", "other"];
|
|
13
|
+
function extractLiteralArg(node) {
|
|
14
|
+
if (!node) return null;
|
|
15
|
+
if (node.type === "StringLiteral") return node.value;
|
|
16
|
+
if (node.type === "TemplateLiteral") {
|
|
17
|
+
if (node.expressions.length > 0) {
|
|
18
|
+
let pattern = "";
|
|
19
|
+
for (let i = 0; i < node.quasis.length; i++) {
|
|
20
|
+
pattern += node.quasis[i].value.cooked ?? "";
|
|
21
|
+
if (i < node.expressions.length) {
|
|
22
|
+
const expr = node.expressions[i];
|
|
23
|
+
if (expr.type === "Identifier") {
|
|
24
|
+
pattern += `{${expr.name}}`;
|
|
25
|
+
} else {
|
|
26
|
+
pattern += "{*}";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return pattern;
|
|
31
|
+
}
|
|
32
|
+
return node.quasis[0]?.value?.cooked ?? null;
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
function extractStrings(source, options) {
|
|
37
|
+
const fnName = options?.functionName ?? DEFAULT_FUNCTION_NAME;
|
|
38
|
+
const strings = [];
|
|
39
|
+
let ast;
|
|
40
|
+
try {
|
|
41
|
+
ast = parse(source, {
|
|
42
|
+
sourceType: "module",
|
|
43
|
+
plugins: ["typescript", "jsx", "decorators-legacy"]
|
|
44
|
+
});
|
|
45
|
+
} catch {
|
|
46
|
+
return strings;
|
|
47
|
+
}
|
|
48
|
+
traverse(ast, {
|
|
49
|
+
CallExpression(path) {
|
|
50
|
+
const callee = path.node.callee;
|
|
51
|
+
if (callee.type === "Identifier" && callee.name === fnName) {
|
|
52
|
+
const arg = path.node.arguments[0];
|
|
53
|
+
const val = extractLiteralArg(arg);
|
|
54
|
+
if (val) strings.push(val);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
JSXOpeningElement(path) {
|
|
59
|
+
const name = path.node.name;
|
|
60
|
+
if (name.type !== "JSXIdentifier" || name.name !== "BabelizeTag") return;
|
|
61
|
+
const templateAttr = path.node.attributes.find(
|
|
62
|
+
(attr) => attr.type === "JSXAttribute" && attr.name?.name === "template"
|
|
63
|
+
);
|
|
64
|
+
if (!templateAttr?.value) return;
|
|
65
|
+
const extractLiteral = (node) => {
|
|
66
|
+
if (node.type === "StringLiteral") return node.value;
|
|
67
|
+
if (node.type === "TemplateLiteral") {
|
|
68
|
+
if (node.expressions.length > 0) return null;
|
|
69
|
+
return node.quasis[0]?.value?.cooked ?? null;
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
};
|
|
73
|
+
const value = templateAttr.value.type === "JSXExpressionContainer" ? extractLiteral(templateAttr.value.expression) : extractLiteral(templateAttr.value);
|
|
74
|
+
if (value) strings.push(value);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
return strings;
|
|
78
|
+
}
|
|
79
|
+
function extractPlurals(source, options) {
|
|
80
|
+
const fnName = options?.functionName ?? DEFAULT_FUNCTION_NAME;
|
|
81
|
+
const plurals = [];
|
|
82
|
+
let ast;
|
|
83
|
+
try {
|
|
84
|
+
ast = parse(source, {
|
|
85
|
+
sourceType: "module",
|
|
86
|
+
plugins: ["typescript", "jsx", "decorators-legacy"]
|
|
87
|
+
});
|
|
88
|
+
} catch {
|
|
89
|
+
return plurals;
|
|
90
|
+
}
|
|
91
|
+
traverse(ast, {
|
|
92
|
+
CallExpression(path) {
|
|
93
|
+
const callee = path.node.callee;
|
|
94
|
+
if (callee.type === "MemberExpression" && callee.object.type === "Identifier" && callee.object.name === fnName && callee.property.type === "Identifier" && (callee.property.name === "p" || callee.property.name === "plural")) {
|
|
95
|
+
const singular = extractLiteralArg(path.node.arguments[0]);
|
|
96
|
+
const plural = extractLiteralArg(path.node.arguments[1]);
|
|
97
|
+
if (singular && plural) {
|
|
98
|
+
plurals.push({
|
|
99
|
+
canonical: singular,
|
|
100
|
+
forms: ["one", "other"],
|
|
101
|
+
templates: [singular, plural]
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (callee.type === "MemberExpression" && callee.object.type === "Identifier" && callee.object.name === fnName && callee.property.type === "Identifier" && callee.property.name === "plural") {
|
|
106
|
+
const formsArg = path.node.arguments[0];
|
|
107
|
+
if (formsArg?.type !== "ObjectExpression") return;
|
|
108
|
+
const formNames = [];
|
|
109
|
+
const templates = [];
|
|
110
|
+
for (const prop of formsArg.properties) {
|
|
111
|
+
if (prop.type !== "ObjectProperty") continue;
|
|
112
|
+
const key = prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "StringLiteral" ? prop.key.value : null;
|
|
113
|
+
if (!key || !PLURAL_FORMS.includes(key)) continue;
|
|
114
|
+
const val = extractLiteralArg(prop.value);
|
|
115
|
+
if (val) {
|
|
116
|
+
formNames.push(key);
|
|
117
|
+
templates.push(val);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const otherIdx = formNames.indexOf("other");
|
|
121
|
+
const canonical = otherIdx >= 0 ? templates[otherIdx] : templates[0];
|
|
122
|
+
if (canonical) {
|
|
123
|
+
plurals.push({ canonical, forms: formNames, templates });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
return plurals;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/lockfile.ts
|
|
132
|
+
function createLockfile(strings, translationsByLocale, plurals, pluralTranslationsByLocale) {
|
|
133
|
+
const locales = Object.keys(translationsByLocale);
|
|
134
|
+
const uniqueStrings = [...new Set(strings)];
|
|
135
|
+
const stringsMap = {};
|
|
136
|
+
for (const str of uniqueStrings) {
|
|
137
|
+
stringsMap[str] = {};
|
|
138
|
+
for (const locale of locales) {
|
|
139
|
+
stringsMap[str][locale] = translationsByLocale[locale]?.[str] ?? str;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const result = {
|
|
143
|
+
version: 1,
|
|
144
|
+
meta: {
|
|
145
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
146
|
+
locales,
|
|
147
|
+
totalStrings: uniqueStrings.length
|
|
148
|
+
},
|
|
149
|
+
strings: stringsMap
|
|
150
|
+
};
|
|
151
|
+
if (plurals && plurals.length > 0) {
|
|
152
|
+
const pluralsMap = {};
|
|
153
|
+
for (const plural of plurals) {
|
|
154
|
+
pluralsMap[plural.canonical] = { forms: plural.forms };
|
|
155
|
+
for (const locale of locales) {
|
|
156
|
+
const localeTranslations = pluralTranslationsByLocale?.[locale]?.[plural.canonical];
|
|
157
|
+
if (localeTranslations) {
|
|
158
|
+
pluralsMap[plural.canonical][locale] = {};
|
|
159
|
+
for (const form of plural.forms) {
|
|
160
|
+
pluralsMap[plural.canonical][locale][form] = localeTranslations[form] ?? plural.templates[plural.forms.indexOf(form)];
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
pluralsMap[plural.canonical][locale] = {};
|
|
164
|
+
for (let i = 0; i < plural.forms.length; i++) {
|
|
165
|
+
pluralsMap[plural.canonical][locale][plural.forms[i]] = plural.templates[i];
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
result.plurals = pluralsMap;
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/index.ts
|
|
176
|
+
var DEFAULT_OUTPUT = "babelize-lock.json";
|
|
177
|
+
var DEFAULT_API_URL = "https://api.babelize.co/api";
|
|
178
|
+
var VALID_EXTS = /* @__PURE__ */ new Set([".tsx", ".ts", ".jsx", ".js"]);
|
|
179
|
+
var VIRTUAL_MODULE_ID = "virtual:babelize-lockfile";
|
|
180
|
+
var RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
|
|
181
|
+
function collectSourceFiles(dir) {
|
|
182
|
+
const files = [];
|
|
183
|
+
try {
|
|
184
|
+
const entries = readdirSync(dir);
|
|
185
|
+
for (const entry of entries) {
|
|
186
|
+
if (entry === "node_modules" || entry === "dist" || entry === ".git") continue;
|
|
187
|
+
const fullPath = resolve(dir, entry);
|
|
188
|
+
if (statSync(fullPath).isDirectory()) {
|
|
189
|
+
files.push(...collectSourceFiles(fullPath));
|
|
190
|
+
} else if (VALID_EXTS.has(extname(entry))) {
|
|
191
|
+
files.push(fullPath);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
} catch {
|
|
195
|
+
}
|
|
196
|
+
return files;
|
|
197
|
+
}
|
|
198
|
+
function babelize(options) {
|
|
199
|
+
const mock = options?.mock ?? false;
|
|
200
|
+
const apiKey = options?.apiKey ?? process.env.BABELIZE_API_KEY;
|
|
201
|
+
const locales = options?.locales;
|
|
202
|
+
const output = options?.output ?? DEFAULT_OUTPUT;
|
|
203
|
+
const fnName = options?.functionName ?? "babelize";
|
|
204
|
+
const apiUrl = options?.apiUrl ?? DEFAULT_API_URL;
|
|
205
|
+
let config;
|
|
206
|
+
function generateMockTranslations(strings, locales2) {
|
|
207
|
+
const result = {};
|
|
208
|
+
for (const locale of locales2) {
|
|
209
|
+
result[locale] = {};
|
|
210
|
+
for (const str of strings) {
|
|
211
|
+
result[locale][str] = `${str} [${locale}]`;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
name: "babelize",
|
|
218
|
+
resolveId(id) {
|
|
219
|
+
if (id === VIRTUAL_MODULE_ID) {
|
|
220
|
+
return RESOLVED_VIRTUAL_MODULE_ID;
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
load(id) {
|
|
224
|
+
if (id !== RESOLVED_VIRTUAL_MODULE_ID) return;
|
|
225
|
+
const outputPath = resolve(config.root, output);
|
|
226
|
+
let lockfile = { version: 1, strings: {} };
|
|
227
|
+
if (existsSync(outputPath)) {
|
|
228
|
+
try {
|
|
229
|
+
lockfile = JSON.parse(readFileSync(outputPath, "utf-8"));
|
|
230
|
+
} catch {
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return `export default ${JSON.stringify(lockfile)};`;
|
|
234
|
+
},
|
|
235
|
+
configResolved(resolvedConfig) {
|
|
236
|
+
config = resolvedConfig;
|
|
237
|
+
},
|
|
238
|
+
async closeBundle() {
|
|
239
|
+
const srcDir = resolve(config.root, "src");
|
|
240
|
+
if (!existsSync(srcDir)) return;
|
|
241
|
+
const sourceFiles = collectSourceFiles(srcDir);
|
|
242
|
+
if (sourceFiles.length === 0) return;
|
|
243
|
+
const allStrings = [];
|
|
244
|
+
const allPlurals = [];
|
|
245
|
+
for (const file of sourceFiles) {
|
|
246
|
+
try {
|
|
247
|
+
const code = readFileSync(file, "utf-8");
|
|
248
|
+
const strings = extractStrings(code, { functionName: fnName });
|
|
249
|
+
allStrings.push(...strings);
|
|
250
|
+
const plurals = extractPlurals(code, { functionName: fnName });
|
|
251
|
+
allPlurals.push(...plurals);
|
|
252
|
+
} catch {
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (allStrings.length === 0 && allPlurals.length === 0) return;
|
|
256
|
+
const uniqueStrings = [...new Set(allStrings)];
|
|
257
|
+
const uniquePlurals = allPlurals.filter(
|
|
258
|
+
(p, i, arr) => arr.findIndex((x) => x.canonical === p.canonical) === i
|
|
259
|
+
);
|
|
260
|
+
const translationsByLocale = {};
|
|
261
|
+
const pluralTranslationsByLocale = {};
|
|
262
|
+
if (locales && locales.length > 0) {
|
|
263
|
+
if (mock) {
|
|
264
|
+
Object.assign(translationsByLocale, generateMockTranslations(uniqueStrings, locales));
|
|
265
|
+
} else if (apiKey) {
|
|
266
|
+
for (const locale of locales) {
|
|
267
|
+
try {
|
|
268
|
+
const response = await fetch(`${apiUrl}/v1/translate`, {
|
|
269
|
+
method: "POST",
|
|
270
|
+
headers: {
|
|
271
|
+
"Content-Type": "application/json",
|
|
272
|
+
Authorization: `Bearer ${apiKey}`
|
|
273
|
+
},
|
|
274
|
+
body: JSON.stringify({
|
|
275
|
+
locale,
|
|
276
|
+
strings: uniqueStrings,
|
|
277
|
+
plurals: uniquePlurals.map((p) => ({
|
|
278
|
+
canonical: p.canonical,
|
|
279
|
+
forms: p.forms,
|
|
280
|
+
templates: p.templates
|
|
281
|
+
}))
|
|
282
|
+
})
|
|
283
|
+
});
|
|
284
|
+
if (response.ok) {
|
|
285
|
+
const data = await response.json();
|
|
286
|
+
translationsByLocale[locale] = data.translations;
|
|
287
|
+
if (data.plurals) {
|
|
288
|
+
pluralTranslationsByLocale[locale] = data.plurals;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
} catch {
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
const lockfile = createLockfile(
|
|
297
|
+
uniqueStrings,
|
|
298
|
+
translationsByLocale,
|
|
299
|
+
uniquePlurals,
|
|
300
|
+
pluralTranslationsByLocale
|
|
301
|
+
);
|
|
302
|
+
const outputPath = resolve(config.root, output);
|
|
303
|
+
writeFileSync(outputPath, JSON.stringify(lockfile, null, 2));
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
var index_default = babelize;
|
|
308
|
+
export {
|
|
309
|
+
babelize,
|
|
310
|
+
index_default as default
|
|
311
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@babelize/vite",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Vite plugin for Babelize — auto-discover strings and generate translation lockfile at build time",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": ["dist"],
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup src/index.ts --format esm --dts --clean --external vite --external @babel/parser --external @babel/traverse",
|
|
22
|
+
"dev": "tsup src/index.ts --format esm --dts --watch --external vite --external @babel/parser --external @babel/traverse",
|
|
23
|
+
"typecheck": "tsc --noEmit"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"vite": "^5.x || ^6.x"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@babel/parser": "^7.28.5",
|
|
30
|
+
"@babel/traverse": "^7.28.5"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/babel__traverse": "^7.28.0",
|
|
34
|
+
"tsup": "^8.4.0",
|
|
35
|
+
"typescript": "^5.7.3",
|
|
36
|
+
"vite": "^6.x"
|
|
37
|
+
}
|
|
38
|
+
}
|