@verbaly/compiler 0.7.0 → 0.9.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.
- package/README.md +43 -34
- package/dist/claude-RQATA6OI.js +63 -0
- package/dist/claude-RQATA6OI.js.map +1 -0
- package/dist/cli.js +154 -11
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +34 -1
- package/dist/index.js +173 -19
- package/dist/index.js.map +1 -1
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -1,34 +1,43 @@
|
|
|
1
|
-
<p align="center">
|
|
2
|
-
<img src="https://raw.githubusercontent.com/AronSoto/verbaly/develop/assets/logo-light.png" alt="Verbaly" width="300" />
|
|
3
|
-
</p>
|
|
4
|
-
|
|
5
|
-
<p align="center"><em>Message extraction, type-safe codegen and CLI for Verbaly.</em></p>
|
|
6
|
-
|
|
7
|
-
<p align="center">
|
|
8
|
-
<a href="https://www.npmjs.com/package/@verbaly/compiler"><img src="https://img.shields.io/npm/v/@verbaly/compiler?logo=npm&color=cb3837" alt="npm version" /></a>
|
|
9
|
-
<a href="https://github.com/AronSoto/verbaly/blob/develop/LICENSE"><img src="https://img.shields.io/npm/l/@verbaly/compiler?color=blue" alt="Apache-2.0" /></a>
|
|
10
|
-
</p>
|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
The compiler behind [Verbaly](https://github.com/AronSoto/verbaly): AST extraction of `t\`...\``**and JSX`<Trans>` children** into stable hashed keys
|
|
15
|
-
|
|
16
|
-
> Most projects don't install this directly — [`@verbaly/vite`](https://www.npmjs.com/package/@verbaly/vite) wraps it with zero config. Reach for it when scripting extraction/checks yourself.
|
|
17
|
-
|
|
18
|
-
## CLI
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
npx verbaly extract # sync catalogs + types
|
|
22
|
-
npx verbaly check # exit 1 if anything is missing (CI)
|
|
23
|
-
npx verbaly extract --prune # drop orphaned keys
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/AronSoto/verbaly/develop/assets/logo-light.png" alt="Verbaly" width="300" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center"><em>Message extraction, type-safe codegen and CLI for Verbaly.</em></p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://www.npmjs.com/package/@verbaly/compiler"><img src="https://img.shields.io/npm/v/@verbaly/compiler?logo=npm&color=cb3837" alt="npm version" /></a>
|
|
9
|
+
<a href="https://github.com/AronSoto/verbaly/blob/develop/LICENSE"><img src="https://img.shields.io/npm/l/@verbaly/compiler?color=blue" alt="Apache-2.0" /></a>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
The compiler behind [Verbaly](https://github.com/AronSoto/verbaly): AST extraction of `t\`...\``**and JSX`<Trans>` children** into stable hashed keys — or **readable keys** via `` t.id('inbox.title')`…` `` and `<Trans id="inbox.title">…</Trans>` — flat JSON catalog sync, and typed codegen. It also ships the **`verbaly` CLI**.
|
|
15
|
+
|
|
16
|
+
> Most projects don't install this directly — [`@verbaly/vite`](https://www.npmjs.com/package/@verbaly/vite) wraps it with zero config. Reach for it when scripting extraction/checks yourself.
|
|
17
|
+
|
|
18
|
+
## CLI
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx verbaly extract # sync catalogs + types
|
|
22
|
+
npx verbaly check # exit 1 if anything is missing (CI)
|
|
23
|
+
npx verbaly extract --prune # drop orphaned keys
|
|
24
|
+
npx verbaly translate # fill missing translations via Claude (or your provider)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Reads `verbaly.config.{js,mjs,ts,mts,json}` (TS configs need `esbuild` installed). Generates `locales/<locale>.json` (flat, portable — no proprietary format) and `verbaly.d.ts` with params typed per key.
|
|
28
|
+
|
|
29
|
+
## Machine translation
|
|
30
|
+
|
|
31
|
+
`verbaly translate` fills the `""` holes `check` reports. The default provider uses Claude via the official SDK — install it as a dev dependency (translation is a build-time step, not an app runtime dependency): `pnpm add -D @anthropic-ai/sdk` (or `npm i -D`), plus `ANTHROPIC_API_KEY`. Default model is `claude-sonnet-5` (balanced quality/cost); override with `translate.model` in config or `--model <id>`. Placeholders, variants and tags are validated after translation — anything not preserved verbatim stays `""` so `check` keeps failing. Plug your own provider in `verbaly.config.ts`:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
translate: { provider: async ({ sourceLocale, targetLocale, messages }) => ({ ...translated }) }
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
📖 Docs: **https://verbaly-web.vercel.app/docs/cli**
|
|
38
|
+
|
|
39
|
+
> ⚠️ Early development (`0.x`) — API not stable yet.
|
|
40
|
+
|
|
41
|
+
## License
|
|
42
|
+
|
|
43
|
+
[Apache-2.0](https://github.com/AronSoto/verbaly/blob/develop/LICENSE) © Aron Soto
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// src/providers/claude.ts
|
|
2
|
+
var DEFAULT_MODEL = "claude-sonnet-5";
|
|
3
|
+
var SYSTEM = `You translate UI strings for the Verbaly i18n library.
|
|
4
|
+
Rules:
|
|
5
|
+
- Translate only the human-readable text, naturally for the target locale.
|
|
6
|
+
- Preserve verbatim: placeholders like {name}, format specs like {price:currency/EUR}, variant blocks like {count | one: ... | other: # ...} (translate only the text inside each variant, keep keys and # as-is), ICU syntax, named tags like <em>...</em> and escapes {{ }} || ##.
|
|
7
|
+
- Keys are opaque identifiers: return exactly the same keys, never translate or rename them.`;
|
|
8
|
+
function claudeProvider(options = {}) {
|
|
9
|
+
return async (request) => {
|
|
10
|
+
const Anthropic = await loadSdk();
|
|
11
|
+
const client = new Anthropic(options.apiKey ? { apiKey: options.apiKey } : {});
|
|
12
|
+
const response = await client.messages.create({
|
|
13
|
+
model: options.model ?? DEFAULT_MODEL,
|
|
14
|
+
max_tokens: options.maxTokens ?? 16e3,
|
|
15
|
+
thinking: { type: "disabled" },
|
|
16
|
+
system: SYSTEM,
|
|
17
|
+
messages: [{ role: "user", content: buildPrompt(request) }],
|
|
18
|
+
output_config: { format: batchFormat(request) }
|
|
19
|
+
});
|
|
20
|
+
const text = response.content.find((block) => block.type === "text")?.text ?? "{}";
|
|
21
|
+
return JSON.parse(text);
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function buildPrompt(request) {
|
|
25
|
+
return `Translate each value from "${request.sourceLocale}" to "${request.targetLocale}". Return a JSON object with the same keys and translated values.
|
|
26
|
+
|
|
27
|
+
` + JSON.stringify(request.messages, null, 2);
|
|
28
|
+
}
|
|
29
|
+
function batchFormat(request) {
|
|
30
|
+
const keys = Object.keys(request.messages);
|
|
31
|
+
return {
|
|
32
|
+
type: "json_schema",
|
|
33
|
+
schema: {
|
|
34
|
+
type: "object",
|
|
35
|
+
properties: Object.fromEntries(keys.map((key) => [key, { type: "string" }])),
|
|
36
|
+
required: keys,
|
|
37
|
+
additionalProperties: false
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
async function loadSdk() {
|
|
42
|
+
try {
|
|
43
|
+
const mod = await import("@anthropic-ai/sdk");
|
|
44
|
+
return mod.default;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (isModuleNotFound(error, "@anthropic-ai/sdk")) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
"[verbaly] the claude translate provider needs @anthropic-ai/sdk \u2014 install it as a dev dependency (e.g. `pnpm add -D @anthropic-ai/sdk` / `npm i -D @anthropic-ai/sdk`) and set ANTHROPIC_API_KEY",
|
|
49
|
+
{ cause: error }
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function isModuleNotFound(error, name) {
|
|
56
|
+
return error instanceof Error && error.code === "ERR_MODULE_NOT_FOUND" && error.message.includes(name);
|
|
57
|
+
}
|
|
58
|
+
export {
|
|
59
|
+
batchFormat,
|
|
60
|
+
buildPrompt,
|
|
61
|
+
claudeProvider
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=claude-RQATA6OI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/providers/claude.ts"],"sourcesContent":["import type { TranslateProvider, TranslateRequest } from '../translate';\r\n\r\nexport interface ClaudeProviderOptions {\r\n model?: string;\r\n apiKey?: string;\r\n maxTokens?: number;\r\n}\r\n\r\n// balanced default for translation\r\nconst DEFAULT_MODEL = 'claude-sonnet-5';\r\n\r\nconst SYSTEM = `You translate UI strings for the Verbaly i18n library.\r\nRules:\r\n- Translate only the human-readable text, naturally for the target locale.\r\n- Preserve verbatim: placeholders like {name}, format specs like {price:currency/EUR}, variant blocks like {count | one: ... | other: # ...} (translate only the text inside each variant, keep keys and # as-is), ICU syntax, named tags like <em>...</em> and escapes {{ }} || ##.\r\n- Keys are opaque identifiers: return exactly the same keys, never translate or rename them.`;\r\n\r\nexport function claudeProvider(options: ClaudeProviderOptions = {}): TranslateProvider {\r\n return async (request: TranslateRequest) => {\r\n const Anthropic = await loadSdk();\r\n const client = new Anthropic(options.apiKey ? { apiKey: options.apiKey } : {});\r\n const response = await client.messages.create({\r\n model: options.model ?? DEFAULT_MODEL,\r\n max_tokens: options.maxTokens ?? 16000,\r\n thinking: { type: 'disabled' },\r\n system: SYSTEM,\r\n messages: [{ role: 'user', content: buildPrompt(request) }],\r\n output_config: { format: batchFormat(request) },\r\n });\r\n const text = response.content.find((block) => block.type === 'text')?.text ?? '{}';\r\n return JSON.parse(text) as Record<string, string>;\r\n };\r\n}\r\n\r\nexport function buildPrompt(request: TranslateRequest): string {\r\n return (\r\n `Translate each value from \"${request.sourceLocale}\" to \"${request.targetLocale}\". ` +\r\n `Return a JSON object with the same keys and translated values.\\n\\n` +\r\n JSON.stringify(request.messages, null, 2)\r\n );\r\n}\r\n\r\nexport function batchFormat(request: TranslateRequest) {\r\n const keys = Object.keys(request.messages);\r\n return {\r\n type: 'json_schema' as const,\r\n schema: {\r\n type: 'object',\r\n properties: Object.fromEntries(keys.map((key) => [key, { type: 'string' }])),\r\n required: keys,\r\n additionalProperties: false,\r\n },\r\n };\r\n}\r\n\r\nasync function loadSdk(): Promise<typeof import('@anthropic-ai/sdk').default> {\r\n try {\r\n const mod = await import('@anthropic-ai/sdk');\r\n return mod.default;\r\n } catch (error) {\r\n if (isModuleNotFound(error, '@anthropic-ai/sdk')) {\r\n throw new Error(\r\n '[verbaly] the claude translate provider needs @anthropic-ai/sdk — install it as a dev dependency (e.g. `pnpm add -D @anthropic-ai/sdk` / `npm i -D @anthropic-ai/sdk`) and set ANTHROPIC_API_KEY',\r\n { cause: error },\r\n );\r\n }\r\n throw error;\r\n }\r\n}\r\n\r\nfunction isModuleNotFound(error: unknown, name: string): boolean {\r\n return (\r\n error instanceof Error &&\r\n (error as { code?: string }).code === 'ERR_MODULE_NOT_FOUND' &&\r\n error.message.includes(name)\r\n );\r\n}\r\n"],"mappings":";AASA,IAAM,gBAAgB;AAEtB,IAAM,SAAS;AAAA;AAAA;AAAA;AAAA;AAMR,SAAS,eAAe,UAAiC,CAAC,GAAsB;AACrF,SAAO,OAAO,YAA8B;AAC1C,UAAM,YAAY,MAAM,QAAQ;AAChC,UAAM,SAAS,IAAI,UAAU,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC,CAAC;AAC7E,UAAM,WAAW,MAAM,OAAO,SAAS,OAAO;AAAA,MAC5C,OAAO,QAAQ,SAAS;AAAA,MACxB,YAAY,QAAQ,aAAa;AAAA,MACjC,UAAU,EAAE,MAAM,WAAW;AAAA,MAC7B,QAAQ;AAAA,MACR,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,YAAY,OAAO,EAAE,CAAC;AAAA,MAC1D,eAAe,EAAE,QAAQ,YAAY,OAAO,EAAE;AAAA,IAChD,CAAC;AACD,UAAM,OAAO,SAAS,QAAQ,KAAK,CAAC,UAAU,MAAM,SAAS,MAAM,GAAG,QAAQ;AAC9E,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB;AACF;AAEO,SAAS,YAAY,SAAmC;AAC7D,SACE,8BAA8B,QAAQ,YAAY,SAAS,QAAQ,YAAY;AAAA;AAAA,IAE/E,KAAK,UAAU,QAAQ,UAAU,MAAM,CAAC;AAE5C;AAEO,SAAS,YAAY,SAA2B;AACrD,QAAM,OAAO,OAAO,KAAK,QAAQ,QAAQ;AACzC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,YAAY,OAAO,YAAY,KAAK,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,SAAS,CAAC,CAAC,CAAC;AAAA,MAC3E,UAAU;AAAA,MACV,sBAAsB;AAAA,IACxB;AAAA,EACF;AACF;AAEA,eAAe,UAA+D;AAC5E,MAAI;AACF,UAAM,MAAM,MAAM,OAAO,mBAAmB;AAC5C,WAAO,IAAI;AAAA,EACb,SAAS,OAAO;AACd,QAAI,iBAAiB,OAAO,mBAAmB,GAAG;AAChD,YAAM,IAAI;AAAA,QACR;AAAA,QACA,EAAE,OAAO,MAAM;AAAA,MACjB;AAAA,IACF;AACA,UAAM;AAAA,EACR;AACF;AAEA,SAAS,iBAAiB,OAAgB,MAAuB;AAC/D,SACE,iBAAiB,SAChB,MAA4B,SAAS,0BACtC,MAAM,QAAQ,SAAS,IAAI;AAE/B;","names":[]}
|
package/dist/cli.js
CHANGED
|
@@ -153,6 +153,11 @@ ${lines.join("\n")}
|
|
|
153
153
|
...args: [VerbalyMessages[K]] extends [never] ? [] : [VerbalyMessages[K]]
|
|
154
154
|
): string;
|
|
155
155
|
export function t(strings: TemplateStringsArray, ...values: unknown[]): string;
|
|
156
|
+
export namespace t {
|
|
157
|
+
export function id(
|
|
158
|
+
key: string,
|
|
159
|
+
): (strings: TemplateStringsArray, ...values: unknown[]) => string;
|
|
160
|
+
}
|
|
156
161
|
|
|
157
162
|
export function setLocale(locale: string): Promise<void>;
|
|
158
163
|
export function getLocale(): string;
|
|
@@ -189,7 +194,8 @@ function resolveConfig(config = {}) {
|
|
|
189
194
|
sourceLocale,
|
|
190
195
|
locales: [...locales],
|
|
191
196
|
include: config.include ?? ["src/**/*.{js,jsx,ts,tsx,mjs,mts}"],
|
|
192
|
-
exclude: config.exclude ?? ["**/node_modules/**", "**/dist/**"]
|
|
197
|
+
exclude: config.exclude ?? ["**/node_modules/**", "**/dist/**"],
|
|
198
|
+
translate: config.translate ?? {}
|
|
193
199
|
};
|
|
194
200
|
}
|
|
195
201
|
async function loadConfigFile(root) {
|
|
@@ -264,18 +270,19 @@ function analyze(code, file) {
|
|
|
264
270
|
walk(ast.program, (node) => {
|
|
265
271
|
if (node.type === "TaggedTemplateExpression") {
|
|
266
272
|
const tag = node.tag;
|
|
267
|
-
|
|
273
|
+
const explicit = explicitId(tag);
|
|
274
|
+
if (!explicit && !isTReference(tag)) return;
|
|
268
275
|
const quasi = node.quasi;
|
|
269
276
|
const message = buildMessage(code, quasi);
|
|
270
277
|
if (!message) return;
|
|
271
278
|
tagged.push({
|
|
272
|
-
key: stableKey(message.text),
|
|
279
|
+
key: explicit ? explicit.key : stableKey(message.text),
|
|
273
280
|
message: message.text,
|
|
274
281
|
params: message.params,
|
|
275
282
|
start: node.start,
|
|
276
283
|
end: node.end,
|
|
277
|
-
tagStart: tag.start,
|
|
278
|
-
tagEnd: tag.end,
|
|
284
|
+
tagStart: explicit ? explicit.refStart : tag.start,
|
|
285
|
+
tagEnd: explicit ? explicit.refEnd : tag.end,
|
|
279
286
|
file
|
|
280
287
|
});
|
|
281
288
|
} else if (node.type === "CallExpression") {
|
|
@@ -298,13 +305,32 @@ function handleTrans(code, node, file, tagged, usedKeys) {
|
|
|
298
305
|
if (nameNode.type !== "JSXIdentifier" || nameNode.name !== "Trans") return;
|
|
299
306
|
const attrs = opening.attributes;
|
|
300
307
|
const idAttr = attrs.find((a) => a.type === "JSXAttribute" && a.name.name === "id");
|
|
308
|
+
const children = node.children;
|
|
301
309
|
if (idAttr) {
|
|
302
310
|
const value = idAttr.value;
|
|
303
|
-
if (value?.type
|
|
311
|
+
if (value?.type !== "StringLiteral") return;
|
|
312
|
+
const id = value.value;
|
|
313
|
+
if (attrs.length === 1 && children?.length) {
|
|
314
|
+
const built2 = buildTransMessage(code, children);
|
|
315
|
+
if (built2?.text.trim()) {
|
|
316
|
+
tagged.push({
|
|
317
|
+
key: id,
|
|
318
|
+
message: built2.text,
|
|
319
|
+
params: built2.params,
|
|
320
|
+
start: node.start,
|
|
321
|
+
end: node.end,
|
|
322
|
+
tagStart: nameNode.start,
|
|
323
|
+
tagEnd: nameNode.end,
|
|
324
|
+
file,
|
|
325
|
+
jsx: { name: nameNode.name, components: built2.components }
|
|
326
|
+
});
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
usedKeys.push({ key: id, file });
|
|
304
331
|
return;
|
|
305
332
|
}
|
|
306
333
|
if (attrs.length > 0) return;
|
|
307
|
-
const children = node.children;
|
|
308
334
|
if (!children?.length) return;
|
|
309
335
|
const built = buildTransMessage(code, children);
|
|
310
336
|
if (!built || !built.text.trim()) return;
|
|
@@ -320,6 +346,19 @@ function handleTrans(code, node, file, tagged, usedKeys) {
|
|
|
320
346
|
jsx: { name: nameNode.name, components: built.components }
|
|
321
347
|
});
|
|
322
348
|
}
|
|
349
|
+
function explicitId(tag) {
|
|
350
|
+
if (tag.type !== "CallExpression") return void 0;
|
|
351
|
+
const callee = tag.callee;
|
|
352
|
+
if (callee.type !== "MemberExpression" || callee.computed) return void 0;
|
|
353
|
+
const prop = callee.property;
|
|
354
|
+
if (prop.type !== "Identifier" || prop.name !== "id") return void 0;
|
|
355
|
+
const obj = callee.object;
|
|
356
|
+
if (!isTReference(obj)) return void 0;
|
|
357
|
+
const args = tag.arguments;
|
|
358
|
+
const first = args[0];
|
|
359
|
+
if (args.length !== 1 || first?.type !== "StringLiteral") return void 0;
|
|
360
|
+
return { key: first.value, refStart: obj.start, refEnd: obj.end };
|
|
361
|
+
}
|
|
323
362
|
function buildTransMessage(code, children) {
|
|
324
363
|
const params = [];
|
|
325
364
|
const components = [];
|
|
@@ -541,21 +580,84 @@ function pruneCatalogs(cfg, catalogs, registry) {
|
|
|
541
580
|
return removed;
|
|
542
581
|
}
|
|
543
582
|
|
|
583
|
+
// src/translate.ts
|
|
584
|
+
async function translateCatalogs(cfg, catalogs, provider, options = {}) {
|
|
585
|
+
const batchSize = options.batchSize ?? 20;
|
|
586
|
+
const targets = (options.locales ?? cfg.locales).filter(
|
|
587
|
+
(locale) => locale !== cfg.sourceLocale && cfg.locales.includes(locale)
|
|
588
|
+
);
|
|
589
|
+
const source = catalogs[cfg.sourceLocale] ?? {};
|
|
590
|
+
const result = { translated: {}, invalid: {}, pending: {} };
|
|
591
|
+
for (const locale of targets) {
|
|
592
|
+
const catalog = catalogs[locale] ??= {};
|
|
593
|
+
const missing = Object.keys(source).filter((key) => source[key] && !catalog[key]);
|
|
594
|
+
if (missing.length === 0) continue;
|
|
595
|
+
if (options.dryRun) {
|
|
596
|
+
result.pending[locale] = missing;
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
for (let i = 0; i < missing.length; i += batchSize) {
|
|
600
|
+
const keys = missing.slice(i, i + batchSize);
|
|
601
|
+
const messages = Object.fromEntries(keys.map((key) => [key, source[key]]));
|
|
602
|
+
const out = await provider({
|
|
603
|
+
sourceLocale: cfg.sourceLocale,
|
|
604
|
+
targetLocale: locale,
|
|
605
|
+
messages
|
|
606
|
+
});
|
|
607
|
+
for (const key of keys) {
|
|
608
|
+
const text = out[key];
|
|
609
|
+
if (typeof text === "string" && text.trim() && structureMatches(source[key], text)) {
|
|
610
|
+
catalog[key] = text;
|
|
611
|
+
(result.translated[locale] ??= []).push(key);
|
|
612
|
+
} else {
|
|
613
|
+
(result.invalid[locale] ??= []).push(key);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return result;
|
|
619
|
+
}
|
|
620
|
+
function structureMatches(source, translated) {
|
|
621
|
+
return sameMembers(paramNames(source), paramNames(translated)) && sameMembers(tagTokens(source), tagTokens(translated));
|
|
622
|
+
}
|
|
623
|
+
function paramNames(message) {
|
|
624
|
+
try {
|
|
625
|
+
return [...collectParams(message).keys()].sort();
|
|
626
|
+
} catch {
|
|
627
|
+
return ["\0invalid"];
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
var TAG = /<(\/?)([a-zA-Z][\w-]*)(\/?)>/g;
|
|
631
|
+
function tagTokens(message) {
|
|
632
|
+
const out = [];
|
|
633
|
+
for (const match of message.matchAll(TAG)) {
|
|
634
|
+
out.push(`${match[1]}${match[2]}${match[3]}`);
|
|
635
|
+
}
|
|
636
|
+
return out.sort();
|
|
637
|
+
}
|
|
638
|
+
function sameMembers(a, b) {
|
|
639
|
+
return a.length === b.length && a.every((value, i) => value === b[i]);
|
|
640
|
+
}
|
|
641
|
+
|
|
544
642
|
// src/cli.ts
|
|
545
643
|
var HELP = `verbaly \u2014 i18n compiler
|
|
546
644
|
|
|
547
645
|
Usage:
|
|
548
|
-
verbaly extract
|
|
549
|
-
verbaly check
|
|
646
|
+
verbaly extract scan sources, update catalogs and types
|
|
647
|
+
verbaly check verify translations are complete (CI)
|
|
648
|
+
verbaly translate fill missing translations via a provider (default: claude)
|
|
550
649
|
|
|
551
650
|
Options:
|
|
552
651
|
--root <path> project root (default: cwd)
|
|
553
652
|
--dir <path> catalogs directory (default: locales)
|
|
554
653
|
--source <locale> source locale (default: en)
|
|
555
|
-
--locales <csv> extra locales
|
|
654
|
+
--locales <csv> extra locales; for translate: target locales to fill
|
|
556
655
|
--prune drop keys no longer referenced (extract)
|
|
656
|
+
--model <id> model override for the claude provider (translate)
|
|
657
|
+
--dry-run list what would be translated, write nothing (translate)
|
|
557
658
|
|
|
558
|
-
Config file: verbaly.config.{js,mjs,json} at root (flags win).
|
|
659
|
+
Config file: verbaly.config.{js,mjs,ts,mts,json} at root (flags win).
|
|
660
|
+
The claude provider needs @anthropic-ai/sdk installed and ANTHROPIC_API_KEY set.
|
|
559
661
|
`;
|
|
560
662
|
async function main() {
|
|
561
663
|
const { positionals, values } = parseArgs({
|
|
@@ -566,6 +668,8 @@ async function main() {
|
|
|
566
668
|
source: { type: "string" },
|
|
567
669
|
locales: { type: "string" },
|
|
568
670
|
prune: { type: "boolean" },
|
|
671
|
+
model: { type: "string" },
|
|
672
|
+
"dry-run": { type: "boolean" },
|
|
569
673
|
help: { type: "boolean", short: "h" }
|
|
570
674
|
}
|
|
571
675
|
});
|
|
@@ -613,10 +717,49 @@ ${formatCheckResult(result)}`);
|
|
|
613
717
|
process.exitCode = 1;
|
|
614
718
|
return;
|
|
615
719
|
}
|
|
720
|
+
if (command === "translate") {
|
|
721
|
+
const catalogs = loadCatalogs(cfg);
|
|
722
|
+
const provider = await resolveProvider(cfg, values.model);
|
|
723
|
+
const result = await translateCatalogs(cfg, catalogs, provider, {
|
|
724
|
+
locales: values.locales?.split(","),
|
|
725
|
+
batchSize: cfg.translate.batchSize,
|
|
726
|
+
dryRun: values["dry-run"]
|
|
727
|
+
});
|
|
728
|
+
if (values["dry-run"]) {
|
|
729
|
+
const entries = Object.entries(result.pending);
|
|
730
|
+
if (entries.length === 0) {
|
|
731
|
+
console.log("[verbaly] nothing to translate \u2713");
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
for (const [locale, keys] of entries) {
|
|
735
|
+
console.log(` ${locale}: ${keys.length} missing \u2014 ${keys.join(", ")}`);
|
|
736
|
+
}
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
for (const locale of Object.keys(result.translated)) {
|
|
740
|
+
writeCatalog(cfg, locale, catalogs[locale] ?? {});
|
|
741
|
+
console.log(` ${locale}: +${result.translated[locale].length} translated`);
|
|
742
|
+
}
|
|
743
|
+
for (const [locale, keys] of Object.entries(result.invalid)) {
|
|
744
|
+
console.warn(
|
|
745
|
+
` ${locale}: ${keys.length} rejected (params/tags not preserved): ${keys.join(", ")}`
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
if (Object.keys(result.translated).length === 0 && Object.keys(result.invalid).length === 0) {
|
|
749
|
+
console.log("[verbaly] nothing to translate \u2713");
|
|
750
|
+
}
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
616
753
|
console.error(`[verbaly] unknown command "${command}"
|
|
617
754
|
${HELP}`);
|
|
618
755
|
process.exitCode = 1;
|
|
619
756
|
}
|
|
757
|
+
async function resolveProvider(cfg, model) {
|
|
758
|
+
const configured = cfg.translate.provider;
|
|
759
|
+
if (typeof configured === "function") return configured;
|
|
760
|
+
const { claudeProvider } = await import("./claude-RQATA6OI.js");
|
|
761
|
+
return claudeProvider({ model: model ?? cfg.translate.model });
|
|
762
|
+
}
|
|
620
763
|
main().catch((error) => {
|
|
621
764
|
console.error("[verbaly]", error instanceof Error ? error.message : error);
|
|
622
765
|
process.exitCode = 1;
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/catalog.ts","../src/check.ts","../src/codegen.ts","../src/params.ts","../src/config.ts","../src/extract.ts","../src/analyze.ts","../src/key.ts","../src/registry.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { parseArgs } from 'node:util';\nimport { loadCatalogs, writeCatalog } from './catalog';\nimport { check, formatCheckResult } from './check';\nimport { writeDts } from './codegen';\nimport { loadConfig } from './config';\nimport { extractProject, pruneCatalogs, syncCatalogs } from './extract';\n\nconst HELP = `verbaly — i18n compiler\n\nUsage:\n verbaly extract scan sources, update catalogs and types\n verbaly check verify translations are complete (CI)\n\nOptions:\n --root <path> project root (default: cwd)\n --dir <path> catalogs directory (default: locales)\n --source <locale> source locale (default: en)\n --locales <csv> extra locales (default: from catalog files)\n --prune drop keys no longer referenced (extract)\n\nConfig file: verbaly.config.{js,mjs,json} at root (flags win).\n`;\n\nasync function main(): Promise<void> {\n const { positionals, values } = parseArgs({\n allowPositionals: true,\n options: {\n root: { type: 'string' },\n dir: { type: 'string' },\n source: { type: 'string' },\n locales: { type: 'string' },\n prune: { type: 'boolean' },\n help: { type: 'boolean', short: 'h' },\n },\n });\n\n const command = positionals[0];\n if (values.help || !command) {\n console.log(HELP);\n process.exitCode = command ? 0 : 1;\n return;\n }\n\n const cfg = await loadConfig(values.root ?? process.cwd(), {\n dir: values.dir,\n sourceLocale: values.source,\n locales: values.locales?.split(','),\n });\n\n if (command === 'extract') {\n const registry = await extractProject(cfg);\n const catalogs = loadCatalogs(cfg);\n if (values.prune) {\n const removed = pruneCatalogs(cfg, catalogs, registry);\n for (const [locale, keys] of Object.entries(removed)) {\n console.log(` ${locale}: -${keys.length} pruned`);\n }\n }\n const { added } = syncCatalogs(cfg, catalogs, registry);\n for (const locale of cfg.locales) {\n writeCatalog(cfg, locale, catalogs[locale] ?? {});\n }\n writeDts(cfg, new Map(Object.entries(catalogs[cfg.sourceLocale] ?? {})));\n const total = registry.messages().size;\n console.log(`[verbaly] ${total} messages · locales: ${cfg.locales.join(', ')}`);\n for (const [locale, keys] of Object.entries(added)) {\n console.log(` ${locale}: +${keys.length}`);\n }\n return;\n }\n\n if (command === 'check') {\n const registry = await extractProject(cfg);\n const result = check(cfg, loadCatalogs(cfg), registry);\n if (result.ok) {\n console.log('[verbaly] all translations complete ✓');\n return;\n }\n console.error(`[verbaly] check failed\\n${formatCheckResult(result)}`);\n process.exitCode = 1;\n return;\n }\n\n console.error(`[verbaly] unknown command \"${command}\"\\n${HELP}`);\n process.exitCode = 1;\n}\n\nmain().catch((error: unknown) => {\n console.error('[verbaly]', error instanceof Error ? error.message : error);\n process.exitCode = 1;\n});\n","import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { ResolvedConfig } from './config';\n\nexport type Catalog = Record<string, string>;\nexport type Catalogs = Record<string, Catalog>;\n\nexport function catalogPath(cfg: ResolvedConfig, locale: string): string {\n return join(cfg.dir, `${locale}.json`);\n}\n\nexport function readCatalog(cfg: ResolvedConfig, locale: string): Catalog {\n try {\n return JSON.parse(readFileSync(catalogPath(cfg, locale), 'utf8')) as Catalog;\n } catch {\n return {};\n }\n}\n\nexport function loadCatalogs(cfg: ResolvedConfig): Catalogs {\n const catalogs: Catalogs = {};\n for (const locale of cfg.locales) catalogs[locale] = readCatalog(cfg, locale);\n return catalogs;\n}\n\nexport function serializeCatalog(catalog: Catalog): string {\n const sorted: Catalog = {};\n for (const key of Object.keys(catalog).sort()) {\n const value = catalog[key];\n if (value !== undefined) sorted[key] = value;\n }\n return JSON.stringify(sorted, null, 2) + '\\n';\n}\n\nexport function writeCatalog(cfg: ResolvedConfig, locale: string, catalog: Catalog): string {\n const serialized = serializeCatalog(catalog);\n mkdirSync(cfg.dir, { recursive: true });\n writeFileSync(catalogPath(cfg, locale), serialized);\n return serialized;\n}\n","import type { Catalogs } from './catalog';\nimport type { ResolvedConfig } from './config';\nimport type { MessageRegistry } from './registry';\n\nexport interface MissingEntry {\n locale: string;\n key: string;\n source?: string;\n}\n\nexport interface UnknownEntry {\n key: string;\n files: string[];\n}\n\nexport interface CheckResult {\n ok: boolean;\n missing: MissingEntry[];\n unknown: UnknownEntry[];\n}\n\nexport function check(\n cfg: ResolvedConfig,\n catalogs: Catalogs,\n registry: MessageRegistry,\n): CheckResult {\n const source = catalogs[cfg.sourceLocale] ?? {};\n const extracted = registry.messages();\n\n const unknown: UnknownEntry[] = [];\n for (const [key, files] of registry.usedKeys()) {\n const known =\n extracted.has(key) || cfg.locales.some((locale) => catalogs[locale]?.[key] !== undefined);\n if (!known) unknown.push({ key, files });\n }\n\n const needed = new Set<string>([...extracted.keys(), ...Object.keys(source)]);\n\n const missing: MissingEntry[] = [];\n for (const key of extracted.keys()) {\n if (!source[key]) {\n missing.push({ locale: cfg.sourceLocale, key, source: extracted.get(key)?.message });\n }\n }\n for (const locale of cfg.locales) {\n if (locale === cfg.sourceLocale) continue;\n for (const key of needed) {\n if (!catalogs[locale]?.[key]) {\n missing.push({ locale, key, source: source[key] ?? extracted.get(key)?.message });\n }\n }\n }\n\n return { ok: missing.length === 0 && unknown.length === 0, missing, unknown };\n}\n\nexport function formatCheckResult(result: CheckResult): string {\n const lines: string[] = [];\n if (result.missing.length > 0) {\n lines.push('missing translations:');\n for (const entry of result.missing) {\n const hint = entry.source ? ` — \"${truncate(entry.source, 40)}\"` : '';\n lines.push(` [${entry.locale}] ${entry.key}${hint}`);\n }\n }\n if (result.unknown.length > 0) {\n lines.push('unknown keys (not in any catalog):');\n for (const entry of result.unknown) {\n lines.push(` ${entry.key} — used in ${entry.files.join(', ')}`);\n }\n }\n return lines.join('\\n');\n}\n\nfunction truncate(text: string, max: number): string {\n return text.length > max ? text.slice(0, max - 1) + '…' : text;\n}\n","import { writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { Catalog } from './catalog';\nimport type { ResolvedConfig } from './config';\nimport { collectParams, renderParamType } from './params';\n\nexport const VIRTUAL_ID = 'virtual:verbaly';\n\nexport function generateRuntimeModule(cfg: ResolvedConfig): string {\n const others = cfg.locales.filter((locale) => locale !== cfg.sourceLocale);\n const src = JSON.stringify(cfg.sourceLocale);\n const loaders = others\n .map((locale) => ` ${JSON.stringify(locale)}: () => import('${VIRTUAL_ID}/locale/${locale}'),`)\n .join('\\n');\n\n return `import { createVerbaly } from 'verbaly';\nimport source from '${VIRTUAL_ID}/locale/${cfg.sourceLocale}';\n\nconst loaders = {\n${loaders}\n};\nconst loaded = new Set([${src}]);\n\nconst v = createVerbaly({\n locale: ${src},\n fallback: ${src},\n messages: { [${src}]: source },\n});\n\nexport const verbaly = v;\nexport const t = v.t;\n\nexport function getLocale() {\n return v.locale;\n}\n\nexport function subscribe(listener) {\n return v.subscribe(listener);\n}\n\nexport async function setLocale(locale) {\n const target = loaders[locale] ? locale : locale.split('-')[0];\n if (!loaded.has(target) && loaders[target]) {\n const mod = await loaders[target]();\n v.addMessages(target, mod.default);\n loaded.add(target);\n }\n v.setLocale(locale);\n}\n`;\n}\n\nexport function generateLocaleModule(catalog: Catalog): string {\n return `export default ${JSON.stringify(catalog)};\\n`;\n}\n\nexport function generateDts(entries: Map<string, string>): string {\n const lines: string[] = [];\n for (const [key, message] of [...entries.entries()].sort(([a], [b]) => a.localeCompare(b))) {\n const params = collectParams(message);\n if (params.size === 0) {\n lines.push(` ${JSON.stringify(key)}: never;`);\n } else {\n const fields = [...params.entries()]\n .map(([name, types]) => `${JSON.stringify(name)}: ${renderParamType(types)}`)\n .join('; ');\n lines.push(` ${JSON.stringify(key)}: { ${fields} };`);\n }\n }\n\n return `// generated by verbaly — do not edit\ndeclare module 'virtual:verbaly' {\n export interface VerbalyMessages {\n${lines.join('\\n')}\n }\n export type VerbalyKey = keyof VerbalyMessages & string;\n\n export const verbaly: import('verbaly').Verbaly<VerbalyKey>;\n\n export function t<K extends VerbalyKey>(\n key: K,\n ...args: [VerbalyMessages[K]] extends [never] ? [] : [VerbalyMessages[K]]\n ): string;\n export function t(strings: TemplateStringsArray, ...values: unknown[]): string;\n\n export function setLocale(locale: string): Promise<void>;\n export function getLocale(): string;\n export function subscribe(listener: () => void): () => void;\n}\n\ndeclare module 'virtual:verbaly/locale/*' {\n const messages: Record<string, string>;\n export default messages;\n}\n`;\n}\n\nexport function writeDts(cfg: ResolvedConfig, entries: Map<string, string>): void {\n writeFileSync(join(cfg.root, 'verbaly.d.ts'), generateDts(entries));\n}\n","import { parse, type MessageNode } from 'verbaly';\n\nexport type ParamType = 'number' | 'string' | 'date' | 'unknown';\n\nconst PLURAL_KEYS = new Set(['zero', 'one', 'two', 'few', 'many']);\nconst NUMBER_FORMATS = new Set(['number', 'integer', 'percent', 'currency']);\nconst DATE_FORMATS = new Set(['date', 'time']);\n\nexport function collectParams(message: string): Map<string, Set<ParamType>> {\n const out = new Map<string, Set<ParamType>>();\n visit(parse(message), out);\n return out;\n}\n\nfunction visit(nodes: MessageNode[], out: Map<string, Set<ParamType>>): void {\n for (const node of nodes) {\n if (node.kind !== 'param') continue;\n const types = out.get(node.name) ?? new Set<ParamType>();\n out.set(node.name, types);\n\n if (node.variants) {\n for (const [key, body] of node.variants) {\n if (PLURAL_KEYS.has(key) || /^=\\d+$/.test(key)) types.add('number');\n else if (key !== 'other') types.add('string');\n visit(body, out);\n }\n if (types.size === 0) types.add('string');\n } else if (node.format && NUMBER_FORMATS.has(node.format)) {\n types.add('number');\n } else if (node.format && DATE_FORMATS.has(node.format)) {\n types.add('date');\n } else {\n types.add('unknown');\n }\n }\n}\n\nexport function renderParamType(types: Set<ParamType>): string {\n if (types.has('unknown') || types.size === 0) return 'unknown';\n const parts: string[] = [];\n if (types.has('number')) parts.push('number');\n if (types.has('string')) parts.push('string');\n if (types.has('date')) parts.push('Date | number | string');\n return parts.join(' | ');\n}\n","import { existsSync, readFileSync, readdirSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nexport interface VerbalyConfig {\n root?: string;\n dir?: string;\n sourceLocale?: string;\n locales?: string[];\n include?: string[];\n exclude?: string[];\n}\n\nexport interface ResolvedConfig {\n root: string;\n dir: string;\n sourceLocale: string;\n locales: string[];\n include: string[];\n exclude: string[];\n}\n\nexport function resolveConfig(config: VerbalyConfig = {}): ResolvedConfig {\n const root = resolve(config.root ?? process.cwd());\n const dir = resolve(root, config.dir ?? 'locales');\n const sourceLocale = config.sourceLocale ?? 'en';\n\n const locales = new Set<string>([sourceLocale, ...(config.locales ?? [])]);\n if (existsSync(dir)) {\n for (const file of readdirSync(dir)) {\n if (file.endsWith('.json')) locales.add(file.slice(0, -5));\n }\n }\n\n return {\n root,\n dir,\n sourceLocale,\n locales: [...locales],\n include: config.include ?? ['src/**/*.{js,jsx,ts,tsx,mjs,mts}'],\n exclude: config.exclude ?? ['**/node_modules/**', '**/dist/**'],\n };\n}\n\nexport async function loadConfigFile(root: string): Promise<VerbalyConfig> {\n for (const name of ['verbaly.config.js', 'verbaly.config.mjs']) {\n const path = join(root, name);\n if (existsSync(path)) {\n const mod = (await import(pathToFileURL(path).href)) as { default?: VerbalyConfig };\n return mod.default ?? {};\n }\n }\n for (const name of ['verbaly.config.ts', 'verbaly.config.mts']) {\n const path = join(root, name);\n if (existsSync(path)) return loadTsConfig(path);\n }\n const jsonPath = join(root, 'verbaly.config.json');\n if (existsSync(jsonPath)) {\n return JSON.parse(readFileSync(jsonPath, 'utf8')) as VerbalyConfig;\n }\n return {};\n}\n\nasync function loadTsConfig(path: string): Promise<VerbalyConfig> {\n try {\n const { bundleRequire } = await import('bundle-require');\n const { mod } = await bundleRequire<{ default?: VerbalyConfig }>({ filepath: path });\n return mod.default ?? {};\n } catch (error) {\n if (isModuleNotFound(error, 'esbuild')) {\n throw new Error(`[verbaly] ${path} needs esbuild — install it: pnpm add -D esbuild`, {\n cause: error,\n });\n }\n throw error;\n }\n}\n\nfunction isModuleNotFound(error: unknown, name: string): boolean {\n return (\n error instanceof Error &&\n (error as { code?: string }).code === 'ERR_MODULE_NOT_FOUND' &&\n error.message.includes(name)\n );\n}\n\n// file config + inline overrides\nexport async function loadConfig(\n root: string,\n overrides: VerbalyConfig = {},\n): Promise<ResolvedConfig> {\n const fileConfig = await loadConfigFile(root);\n return resolveConfig({ ...fileConfig, ...definedOnly(overrides), root });\n}\n\nfunction definedOnly(config: VerbalyConfig): VerbalyConfig {\n return Object.fromEntries(\n Object.entries(config).filter(([, value]) => value !== undefined),\n ) as VerbalyConfig;\n}\n","import { readFileSync } from 'node:fs';\nimport { glob } from 'tinyglobby';\nimport { analyze } from './analyze';\nimport type { Catalogs } from './catalog';\nimport type { ResolvedConfig } from './config';\nimport { MessageRegistry } from './registry';\n\nexport async function extractProject(cfg: ResolvedConfig): Promise<MessageRegistry> {\n const files = await glob(cfg.include, {\n cwd: cfg.root,\n ignore: cfg.exclude,\n absolute: true,\n });\n const registry = new MessageRegistry();\n for (const file of files) {\n registry.update(file, analyze(readFileSync(file, 'utf8'), file));\n }\n return registry;\n}\n\nexport interface SyncResult {\n added: Record<string, string[]>;\n}\n\n// fills source texts + '' placeholders\nexport function syncCatalogs(\n cfg: ResolvedConfig,\n catalogs: Catalogs,\n registry: MessageRegistry,\n): SyncResult {\n const added: Record<string, string[]> = {};\n const source = (catalogs[cfg.sourceLocale] ??= {});\n\n for (const [key, msg] of registry.messages()) {\n if (source[key] !== msg.message) {\n source[key] = msg.message;\n (added[cfg.sourceLocale] ??= []).push(key);\n }\n }\n\n const needed = Object.keys(source);\n for (const locale of cfg.locales) {\n if (locale === cfg.sourceLocale) continue;\n const catalog = (catalogs[locale] ??= {});\n for (const key of needed) {\n if (catalog[key] === undefined) {\n catalog[key] = '';\n (added[locale] ??= []).push(key);\n }\n }\n }\n return { added };\n}\n\n// drops keys no longer referenced\nexport function pruneCatalogs(\n cfg: ResolvedConfig,\n catalogs: Catalogs,\n registry: MessageRegistry,\n): Record<string, string[]> {\n const keep = new Set([...registry.messages().keys(), ...registry.usedKeys().keys()]);\n const removed: Record<string, string[]> = {};\n for (const locale of cfg.locales) {\n const catalog = catalogs[locale];\n if (!catalog) continue;\n for (const key of Object.keys(catalog)) {\n if (!keep.has(key)) {\n delete catalog[key];\n (removed[locale] ??= []).push(key);\n }\n }\n }\n return removed;\n}\n","import { parse } from '@babel/parser';\nimport { stableKey } from './key';\n\nexport interface TaggedParam {\n name: string;\n start: number;\n end: number;\n}\n\nexport interface TransComponent {\n name: string;\n source: string;\n}\n\nexport interface TaggedMessage {\n key: string;\n message: string;\n params: TaggedParam[];\n start: number;\n end: number;\n tagStart: number;\n tagEnd: number;\n file: string;\n jsx?: { name: string; components: TransComponent[] };\n}\n\nexport interface UsedKey {\n key: string;\n file: string;\n}\n\nexport interface Analysis {\n tagged: TaggedMessage[];\n usedKeys: UsedKey[];\n}\n\ninterface AstNode {\n type: string;\n start: number;\n end: number;\n [key: string]: unknown;\n}\n\nconst SKIP_KEYS = new Set(['loc', 'leadingComments', 'trailingComments', 'innerComments', 'extra']);\n\nexport function analyze(code: string, file: string): Analysis {\n const jsx = /\\.[jt]sx$/.test(file);\n const ast = parse(code, {\n sourceType: 'module',\n errorRecovery: true,\n plugins: jsx ? ['typescript', 'jsx'] : ['typescript'],\n });\n\n const tagged: TaggedMessage[] = [];\n const usedKeys: UsedKey[] = [];\n\n walk(ast.program as unknown as AstNode, (node) => {\n if (node.type === 'TaggedTemplateExpression') {\n const tag = node.tag as AstNode;\n if (!isTReference(tag)) return;\n const quasi = node.quasi as AstNode;\n const message = buildMessage(code, quasi);\n if (!message) return;\n tagged.push({\n key: stableKey(message.text),\n message: message.text,\n params: message.params,\n start: node.start,\n end: node.end,\n tagStart: tag.start,\n tagEnd: tag.end,\n file,\n });\n } else if (node.type === 'CallExpression') {\n const callee = node.callee as AstNode;\n if (!isTReference(callee)) return;\n const args = node.arguments as AstNode[];\n const first = args[0];\n if (first?.type === 'StringLiteral') {\n usedKeys.push({ key: first.value as string, file });\n }\n } else if (node.type === 'JSXElement') {\n handleTrans(code, node, file, tagged, usedKeys);\n }\n });\n\n return { tagged, usedKeys };\n}\n\nfunction handleTrans(\n code: string,\n node: AstNode,\n file: string,\n tagged: TaggedMessage[],\n usedKeys: UsedKey[],\n): void {\n const opening = node.openingElement as AstNode;\n const nameNode = opening.name as AstNode;\n if (nameNode.type !== 'JSXIdentifier' || nameNode.name !== 'Trans') return;\n\n const attrs = opening.attributes as AstNode[];\n const idAttr = attrs.find((a) => a.type === 'JSXAttribute' && (a.name as AstNode).name === 'id');\n if (idAttr) {\n const value = idAttr.value as AstNode | null;\n if (value?.type === 'StringLiteral') usedKeys.push({ key: value.value as string, file });\n return; // runtime-first, untouched\n }\n if (attrs.length > 0) return; // hand-written props → don't guess\n\n const children = node.children as AstNode[] | undefined;\n if (!children?.length) return;\n\n const built = buildTransMessage(code, children);\n if (!built || !built.text.trim()) return;\n\n tagged.push({\n key: stableKey(built.text),\n message: built.text,\n params: built.params,\n start: node.start,\n end: node.end,\n tagStart: nameNode.start,\n tagEnd: nameNode.end,\n file,\n jsx: { name: nameNode.name as string, components: built.components },\n });\n}\n\ninterface BuiltTrans {\n text: string;\n params: TaggedParam[];\n components: TransComponent[];\n}\n\nfunction buildTransMessage(code: string, children: AstNode[]): BuiltTrans | undefined {\n const params: TaggedParam[] = [];\n const components: TransComponent[] = [];\n const takenParams = new Map<string, string>();\n const takenTags = new Map<string, string>();\n\n function walkChildren(nodes: AstNode[]): string | undefined {\n let text = '';\n for (const child of nodes) {\n if (child.type === 'JSXText') {\n text += escapeText(cleanJsxText(child.value as string));\n } else if (child.type === 'JSXExpressionContainer') {\n const expr = child.expression as AstNode;\n if (expr.type === 'JSXEmptyExpression') continue;\n if (expr.type === 'StringLiteral') {\n text += escapeText(expr.value as string);\n continue;\n }\n if (expr.type === 'TaggedTemplateExpression') return undefined; // overlap hazard\n const source = code.slice(expr.start, expr.end);\n const name = uniqueName(deriveName(expr, params.length), source, takenParams);\n params.push({ name, start: expr.start, end: expr.end });\n text += `{${name}}`;\n } else if (child.type === 'JSXElement') {\n const opening = child.openingElement as AstNode;\n const nameNode = opening.name as AstNode;\n if (nameNode.type !== 'JSXIdentifier' || nameNode.name === 'Trans') return undefined;\n const source = selfClosedSource(code, opening);\n const tag = uniqueName((nameNode.name as string).toLowerCase(), source, takenTags);\n if (!components.some((c) => c.name === tag)) components.push({ name: tag, source });\n if (!child.closingElement) {\n text += `<${tag}/>`;\n } else {\n const inner = walkChildren(child.children as AstNode[]);\n if (inner === undefined) return undefined;\n text += `<${tag}>${inner}</${tag}>`;\n }\n } else {\n return undefined; // fragments / spread children → bail\n }\n }\n return text;\n }\n\n const text = walkChildren(children);\n if (text === undefined) return undefined;\n return { text, params, components };\n}\n\n// JSX text semantics: newline-indent boundaries removed, interior joins = one space\nfunction cleanJsxText(raw: string): string {\n const lines = raw.split(/\\r\\n|[\\r\\n]/);\n let out = '';\n for (let i = 0; i < lines.length; i++) {\n let line = lines[i]!.replace(/\\t/g, ' ');\n if (i !== 0) line = line.replace(/^ +/, '');\n if (i !== lines.length - 1) line = line.replace(/ +$/, '');\n if (!line) continue;\n if (out && i !== 0) out += ' ';\n out += line;\n }\n return out;\n}\n\nfunction selfClosedSource(code: string, opening: AstNode): string {\n const src = code.slice(opening.start, opening.end);\n return src.endsWith('/>') ? src : `${src.slice(0, -1).trimEnd()} />`;\n}\n\nfunction isTReference(node: AstNode): boolean {\n if (node.type === 'Identifier') return node.name === 't';\n if (node.type === 'MemberExpression' && !node.computed) {\n const prop = node.property as AstNode;\n return prop.type === 'Identifier' && prop.name === 't';\n }\n return false;\n}\n\ninterface BuiltMessage {\n text: string;\n params: TaggedParam[];\n}\n\nfunction buildMessage(code: string, quasi: AstNode): BuiltMessage | undefined {\n const quasis = quasi.quasis as AstNode[];\n const expressions = quasi.expressions as AstNode[];\n const params: TaggedParam[] = [];\n const taken = new Map<string, string>();\n\n let text = escapeText(cookedValue(quasis[0]));\n\n for (let i = 0; i < expressions.length; i++) {\n const expr = expressions[i];\n if (!expr) return undefined;\n const source = code.slice(expr.start, expr.end);\n const name = uniqueName(deriveName(expr, i), source, taken);\n params.push({ name, start: expr.start, end: expr.end });\n text += `{${name}}` + escapeText(cookedValue(quasis[i + 1]));\n }\n return { text, params };\n}\n\nfunction cookedValue(element: AstNode | undefined): string {\n if (!element) return '';\n const value = element.value as { cooked?: string; raw: string };\n return value.cooked ?? value.raw;\n}\n\n// literal braces → escaped\nfunction escapeText(text: string): string {\n return text.replace(/[{}]/g, (m) => m + m);\n}\n\nfunction deriveName(expr: AstNode, index: number): string {\n if (expr.type === 'Identifier') return expr.name as string;\n if (expr.type === 'MemberExpression' && !expr.computed) {\n const prop = expr.property as AstNode;\n if (prop.type === 'Identifier') return prop.name as string;\n }\n return `_${index}`;\n}\n\nfunction uniqueName(base: string, source: string, taken: Map<string, string>): string {\n let name = base;\n let n = 2;\n while (taken.has(name) && taken.get(name) !== source) {\n name = `${base}${n}`;\n n += 1;\n }\n taken.set(name, source);\n return name;\n}\n\nfunction walk(node: AstNode, visit: (node: AstNode) => void): void {\n visit(node);\n for (const [key, value] of Object.entries(node)) {\n if (SKIP_KEYS.has(key)) continue;\n if (Array.isArray(value)) {\n for (const item of value) {\n if (isNode(item)) walk(item, visit);\n }\n } else if (isNode(value)) {\n walk(value, visit);\n }\n }\n}\n\nfunction isNode(value: unknown): value is AstNode {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { type?: unknown }).type === 'string'\n );\n}\n","import { createHash } from 'node:crypto';\n\nexport function stableKey(message: string): string {\n return createHash('sha256').update(message).digest('base64url').slice(0, 8);\n}\n","import type { Analysis, TaggedMessage } from './analyze';\n\nexport class MessageRegistry {\n private files = new Map<string, Analysis>();\n\n update(file: string, analysis: Analysis): void {\n this.files.set(file, analysis);\n }\n\n remove(file: string): void {\n this.files.delete(file);\n }\n\n messages(): Map<string, TaggedMessage> {\n const out = new Map<string, TaggedMessage>();\n for (const analysis of this.files.values()) {\n for (const msg of analysis.tagged) {\n const existing = out.get(msg.key);\n if (existing) {\n if (existing.message !== msg.message) {\n console.warn(\n `[verbaly] key collision \"${msg.key}\": ` +\n `${JSON.stringify(existing.message)} vs ${JSON.stringify(msg.message)} — second dropped.`,\n );\n }\n continue;\n }\n out.set(msg.key, msg);\n }\n }\n return out;\n }\n\n usedKeys(): Map<string, string[]> {\n const out = new Map<string, string[]>();\n for (const analysis of this.files.values()) {\n for (const used of analysis.usedKeys) {\n const files = out.get(used.key) ?? [];\n if (!files.includes(used.file)) files.push(used.file);\n out.set(used.key, files);\n }\n }\n return out;\n }\n}\n"],"mappings":"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","names":["writeFileSync","join","writeFileSync","join","readFileSync","join","readFileSync","parse","parse","text","visit","readFileSync"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/catalog.ts","../src/check.ts","../src/codegen.ts","../src/params.ts","../src/config.ts","../src/extract.ts","../src/analyze.ts","../src/key.ts","../src/registry.ts","../src/translate.ts"],"sourcesContent":["#!/usr/bin/env node\r\nimport { parseArgs } from 'node:util';\r\nimport { loadCatalogs, writeCatalog } from './catalog';\r\nimport { check, formatCheckResult } from './check';\r\nimport { writeDts } from './codegen';\r\nimport { loadConfig } from './config';\r\nimport { extractProject, pruneCatalogs, syncCatalogs } from './extract';\r\nimport { translateCatalogs, type TranslateProvider } from './translate';\r\nimport type { ResolvedConfig } from './config';\r\n\r\nconst HELP = `verbaly — i18n compiler\r\n\r\nUsage:\r\n verbaly extract scan sources, update catalogs and types\r\n verbaly check verify translations are complete (CI)\r\n verbaly translate fill missing translations via a provider (default: claude)\r\n\r\nOptions:\r\n --root <path> project root (default: cwd)\r\n --dir <path> catalogs directory (default: locales)\r\n --source <locale> source locale (default: en)\r\n --locales <csv> extra locales; for translate: target locales to fill\r\n --prune drop keys no longer referenced (extract)\r\n --model <id> model override for the claude provider (translate)\r\n --dry-run list what would be translated, write nothing (translate)\r\n\r\nConfig file: verbaly.config.{js,mjs,ts,mts,json} at root (flags win).\r\nThe claude provider needs @anthropic-ai/sdk installed and ANTHROPIC_API_KEY set.\r\n`;\r\n\r\nasync function main(): Promise<void> {\r\n const { positionals, values } = parseArgs({\r\n allowPositionals: true,\r\n options: {\r\n root: { type: 'string' },\r\n dir: { type: 'string' },\r\n source: { type: 'string' },\r\n locales: { type: 'string' },\r\n prune: { type: 'boolean' },\r\n model: { type: 'string' },\r\n 'dry-run': { type: 'boolean' },\r\n help: { type: 'boolean', short: 'h' },\r\n },\r\n });\r\n\r\n const command = positionals[0];\r\n if (values.help || !command) {\r\n console.log(HELP);\r\n process.exitCode = command ? 0 : 1;\r\n return;\r\n }\r\n\r\n const cfg = await loadConfig(values.root ?? process.cwd(), {\r\n dir: values.dir,\r\n sourceLocale: values.source,\r\n locales: values.locales?.split(','),\r\n });\r\n\r\n if (command === 'extract') {\r\n const registry = await extractProject(cfg);\r\n const catalogs = loadCatalogs(cfg);\r\n if (values.prune) {\r\n const removed = pruneCatalogs(cfg, catalogs, registry);\r\n for (const [locale, keys] of Object.entries(removed)) {\r\n console.log(` ${locale}: -${keys.length} pruned`);\r\n }\r\n }\r\n const { added } = syncCatalogs(cfg, catalogs, registry);\r\n for (const locale of cfg.locales) {\r\n writeCatalog(cfg, locale, catalogs[locale] ?? {});\r\n }\r\n writeDts(cfg, new Map(Object.entries(catalogs[cfg.sourceLocale] ?? {})));\r\n const total = registry.messages().size;\r\n console.log(`[verbaly] ${total} messages · locales: ${cfg.locales.join(', ')}`);\r\n for (const [locale, keys] of Object.entries(added)) {\r\n console.log(` ${locale}: +${keys.length}`);\r\n }\r\n return;\r\n }\r\n\r\n if (command === 'check') {\r\n const registry = await extractProject(cfg);\r\n const result = check(cfg, loadCatalogs(cfg), registry);\r\n if (result.ok) {\r\n console.log('[verbaly] all translations complete ✓');\r\n return;\r\n }\r\n console.error(`[verbaly] check failed\\n${formatCheckResult(result)}`);\r\n process.exitCode = 1;\r\n return;\r\n }\r\n\r\n if (command === 'translate') {\r\n const catalogs = loadCatalogs(cfg);\r\n const provider = await resolveProvider(cfg, values.model);\r\n const result = await translateCatalogs(cfg, catalogs, provider, {\r\n locales: values.locales?.split(','),\r\n batchSize: cfg.translate.batchSize,\r\n dryRun: values['dry-run'],\r\n });\r\n\r\n if (values['dry-run']) {\r\n const entries = Object.entries(result.pending);\r\n if (entries.length === 0) {\r\n console.log('[verbaly] nothing to translate ✓');\r\n return;\r\n }\r\n for (const [locale, keys] of entries) {\r\n console.log(` ${locale}: ${keys.length} missing — ${keys.join(', ')}`);\r\n }\r\n return;\r\n }\r\n\r\n for (const locale of Object.keys(result.translated)) {\r\n writeCatalog(cfg, locale, catalogs[locale] ?? {});\r\n console.log(` ${locale}: +${result.translated[locale]!.length} translated`);\r\n }\r\n for (const [locale, keys] of Object.entries(result.invalid)) {\r\n console.warn(\r\n ` ${locale}: ${keys.length} rejected (params/tags not preserved): ${keys.join(', ')}`,\r\n );\r\n }\r\n if (Object.keys(result.translated).length === 0 && Object.keys(result.invalid).length === 0) {\r\n console.log('[verbaly] nothing to translate ✓');\r\n }\r\n return;\r\n }\r\n\r\n console.error(`[verbaly] unknown command \"${command}\"\\n${HELP}`);\r\n process.exitCode = 1;\r\n}\r\n\r\nasync function resolveProvider(\r\n cfg: ResolvedConfig,\r\n model: string | undefined,\r\n): Promise<TranslateProvider> {\r\n const configured = cfg.translate.provider;\r\n if (typeof configured === 'function') return configured;\r\n const { claudeProvider } = await import('./providers/claude');\r\n return claudeProvider({ model: model ?? cfg.translate.model });\r\n}\r\n\r\nmain().catch((error: unknown) => {\r\n console.error('[verbaly]', error instanceof Error ? error.message : error);\r\n process.exitCode = 1;\r\n});\r\n","import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { ResolvedConfig } from './config';\n\nexport type Catalog = Record<string, string>;\nexport type Catalogs = Record<string, Catalog>;\n\nexport function catalogPath(cfg: ResolvedConfig, locale: string): string {\n return join(cfg.dir, `${locale}.json`);\n}\n\nexport function readCatalog(cfg: ResolvedConfig, locale: string): Catalog {\n try {\n return JSON.parse(readFileSync(catalogPath(cfg, locale), 'utf8')) as Catalog;\n } catch {\n return {};\n }\n}\n\nexport function loadCatalogs(cfg: ResolvedConfig): Catalogs {\n const catalogs: Catalogs = {};\n for (const locale of cfg.locales) catalogs[locale] = readCatalog(cfg, locale);\n return catalogs;\n}\n\nexport function serializeCatalog(catalog: Catalog): string {\n const sorted: Catalog = {};\n for (const key of Object.keys(catalog).sort()) {\n const value = catalog[key];\n if (value !== undefined) sorted[key] = value;\n }\n return JSON.stringify(sorted, null, 2) + '\\n';\n}\n\nexport function writeCatalog(cfg: ResolvedConfig, locale: string, catalog: Catalog): string {\n const serialized = serializeCatalog(catalog);\n mkdirSync(cfg.dir, { recursive: true });\n writeFileSync(catalogPath(cfg, locale), serialized);\n return serialized;\n}\n","import type { Catalogs } from './catalog';\nimport type { ResolvedConfig } from './config';\nimport type { MessageRegistry } from './registry';\n\nexport interface MissingEntry {\n locale: string;\n key: string;\n source?: string;\n}\n\nexport interface UnknownEntry {\n key: string;\n files: string[];\n}\n\nexport interface CheckResult {\n ok: boolean;\n missing: MissingEntry[];\n unknown: UnknownEntry[];\n}\n\nexport function check(\n cfg: ResolvedConfig,\n catalogs: Catalogs,\n registry: MessageRegistry,\n): CheckResult {\n const source = catalogs[cfg.sourceLocale] ?? {};\n const extracted = registry.messages();\n\n const unknown: UnknownEntry[] = [];\n for (const [key, files] of registry.usedKeys()) {\n const known =\n extracted.has(key) || cfg.locales.some((locale) => catalogs[locale]?.[key] !== undefined);\n if (!known) unknown.push({ key, files });\n }\n\n const needed = new Set<string>([...extracted.keys(), ...Object.keys(source)]);\n\n const missing: MissingEntry[] = [];\n for (const key of extracted.keys()) {\n if (!source[key]) {\n missing.push({ locale: cfg.sourceLocale, key, source: extracted.get(key)?.message });\n }\n }\n for (const locale of cfg.locales) {\n if (locale === cfg.sourceLocale) continue;\n for (const key of needed) {\n if (!catalogs[locale]?.[key]) {\n missing.push({ locale, key, source: source[key] ?? extracted.get(key)?.message });\n }\n }\n }\n\n return { ok: missing.length === 0 && unknown.length === 0, missing, unknown };\n}\n\nexport function formatCheckResult(result: CheckResult): string {\n const lines: string[] = [];\n if (result.missing.length > 0) {\n lines.push('missing translations:');\n for (const entry of result.missing) {\n const hint = entry.source ? ` — \"${truncate(entry.source, 40)}\"` : '';\n lines.push(` [${entry.locale}] ${entry.key}${hint}`);\n }\n }\n if (result.unknown.length > 0) {\n lines.push('unknown keys (not in any catalog):');\n for (const entry of result.unknown) {\n lines.push(` ${entry.key} — used in ${entry.files.join(', ')}`);\n }\n }\n return lines.join('\\n');\n}\n\nfunction truncate(text: string, max: number): string {\n return text.length > max ? text.slice(0, max - 1) + '…' : text;\n}\n","import { writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { Catalog } from './catalog';\nimport type { ResolvedConfig } from './config';\nimport { collectParams, renderParamType } from './params';\n\nexport const VIRTUAL_ID = 'virtual:verbaly';\n\nexport function generateRuntimeModule(cfg: ResolvedConfig): string {\n const others = cfg.locales.filter((locale) => locale !== cfg.sourceLocale);\n const src = JSON.stringify(cfg.sourceLocale);\n const loaders = others\n .map(\n (locale) => ` ${JSON.stringify(locale)}: () => import('${VIRTUAL_ID}/locale/${locale}'),`,\n )\n .join('\\n');\n\n return `import { createVerbaly } from 'verbaly';\nimport source from '${VIRTUAL_ID}/locale/${cfg.sourceLocale}';\n\nconst v = createVerbaly({\n locale: ${src},\n fallback: ${src},\n messages: { [${src}]: source },\n loaders: {\n${loaders}\n },\n});\n\nexport const verbaly = v;\nexport const t = v.t;\n\nexport function getLocale() {\n return v.locale;\n}\n\nexport function subscribe(listener) {\n return v.subscribe(listener);\n}\n\nexport async function setLocale(locale) {\n await v.loadLocale(locale);\n v.setLocale(locale);\n}\n`;\n}\n\nexport function generateLocaleModule(catalog: Catalog): string {\n return `export default ${JSON.stringify(catalog)};\\n`;\n}\n\nexport function generateDts(entries: Map<string, string>): string {\n const lines: string[] = [];\n for (const [key, message] of [...entries.entries()].sort(([a], [b]) => a.localeCompare(b))) {\n const params = collectParams(message);\n if (params.size === 0) {\n lines.push(` ${JSON.stringify(key)}: never;`);\n } else {\n const fields = [...params.entries()]\n .map(([name, types]) => `${JSON.stringify(name)}: ${renderParamType(types)}`)\n .join('; ');\n lines.push(` ${JSON.stringify(key)}: { ${fields} };`);\n }\n }\n\n return `// generated by verbaly — do not edit\ndeclare module 'virtual:verbaly' {\n export interface VerbalyMessages {\n${lines.join('\\n')}\n }\n export type VerbalyKey = keyof VerbalyMessages & string;\n\n export const verbaly: import('verbaly').Verbaly<VerbalyKey>;\n\n export function t<K extends VerbalyKey>(\n key: K,\n ...args: [VerbalyMessages[K]] extends [never] ? [] : [VerbalyMessages[K]]\n ): string;\n export function t(strings: TemplateStringsArray, ...values: unknown[]): string;\n export namespace t {\n export function id(\n key: string,\n ): (strings: TemplateStringsArray, ...values: unknown[]) => string;\n }\n\n export function setLocale(locale: string): Promise<void>;\n export function getLocale(): string;\n export function subscribe(listener: () => void): () => void;\n}\n\ndeclare module 'virtual:verbaly/locale/*' {\n const messages: Record<string, string>;\n export default messages;\n}\n`;\n}\n\nexport function writeDts(cfg: ResolvedConfig, entries: Map<string, string>): void {\n writeFileSync(join(cfg.root, 'verbaly.d.ts'), generateDts(entries));\n}\n","import { parse, type MessageNode } from 'verbaly';\n\nexport type ParamType = 'number' | 'string' | 'date' | 'unknown';\n\nconst PLURAL_KEYS = new Set(['zero', 'one', 'two', 'few', 'many']);\nconst NUMBER_FORMATS = new Set(['number', 'integer', 'percent', 'currency']);\nconst DATE_FORMATS = new Set(['date', 'time']);\n\nexport function collectParams(message: string): Map<string, Set<ParamType>> {\n const out = new Map<string, Set<ParamType>>();\n visit(parse(message), out);\n return out;\n}\n\nfunction visit(nodes: MessageNode[], out: Map<string, Set<ParamType>>): void {\n for (const node of nodes) {\n if (node.kind !== 'param') continue;\n const types = out.get(node.name) ?? new Set<ParamType>();\n out.set(node.name, types);\n\n if (node.variants) {\n for (const [key, body] of node.variants) {\n if (PLURAL_KEYS.has(key) || /^=\\d+$/.test(key)) types.add('number');\n else if (key !== 'other') types.add('string');\n visit(body, out);\n }\n if (types.size === 0) types.add('string');\n } else if (node.format && NUMBER_FORMATS.has(node.format)) {\n types.add('number');\n } else if (node.format && DATE_FORMATS.has(node.format)) {\n types.add('date');\n } else {\n types.add('unknown');\n }\n }\n}\n\nexport function renderParamType(types: Set<ParamType>): string {\n if (types.has('unknown') || types.size === 0) return 'unknown';\n const parts: string[] = [];\n if (types.has('number')) parts.push('number');\n if (types.has('string')) parts.push('string');\n if (types.has('date')) parts.push('Date | number | string');\n return parts.join(' | ');\n}\n","import { existsSync, readFileSync, readdirSync } from 'node:fs';\r\nimport { join, resolve } from 'node:path';\r\nimport { pathToFileURL } from 'node:url';\r\nimport type { TranslateProvider } from './translate';\r\n\r\nexport interface TranslateConfig {\r\n provider?: 'claude' | TranslateProvider;\r\n model?: string;\r\n batchSize?: number;\r\n}\r\n\r\nexport interface VerbalyConfig {\r\n root?: string;\r\n dir?: string;\r\n sourceLocale?: string;\r\n locales?: string[];\r\n include?: string[];\r\n exclude?: string[];\r\n translate?: TranslateConfig;\r\n}\r\n\r\nexport interface ResolvedConfig {\r\n root: string;\r\n dir: string;\r\n sourceLocale: string;\r\n locales: string[];\r\n include: string[];\r\n exclude: string[];\r\n translate: TranslateConfig;\r\n}\r\n\r\nexport function resolveConfig(config: VerbalyConfig = {}): ResolvedConfig {\r\n const root = resolve(config.root ?? process.cwd());\r\n const dir = resolve(root, config.dir ?? 'locales');\r\n const sourceLocale = config.sourceLocale ?? 'en';\r\n\r\n const locales = new Set<string>([sourceLocale, ...(config.locales ?? [])]);\r\n if (existsSync(dir)) {\r\n for (const file of readdirSync(dir)) {\r\n if (file.endsWith('.json')) locales.add(file.slice(0, -5));\r\n }\r\n }\r\n\r\n return {\r\n root,\r\n dir,\r\n sourceLocale,\r\n locales: [...locales],\r\n include: config.include ?? ['src/**/*.{js,jsx,ts,tsx,mjs,mts}'],\r\n exclude: config.exclude ?? ['**/node_modules/**', '**/dist/**'],\r\n translate: config.translate ?? {},\r\n };\r\n}\r\n\r\nexport async function loadConfigFile(root: string): Promise<VerbalyConfig> {\r\n for (const name of ['verbaly.config.js', 'verbaly.config.mjs']) {\r\n const path = join(root, name);\r\n if (existsSync(path)) {\r\n const mod = (await import(pathToFileURL(path).href)) as { default?: VerbalyConfig };\r\n return mod.default ?? {};\r\n }\r\n }\r\n for (const name of ['verbaly.config.ts', 'verbaly.config.mts']) {\r\n const path = join(root, name);\r\n if (existsSync(path)) return loadTsConfig(path);\r\n }\r\n const jsonPath = join(root, 'verbaly.config.json');\r\n if (existsSync(jsonPath)) {\r\n return JSON.parse(readFileSync(jsonPath, 'utf8')) as VerbalyConfig;\r\n }\r\n return {};\r\n}\r\n\r\nasync function loadTsConfig(path: string): Promise<VerbalyConfig> {\r\n try {\r\n const { bundleRequire } = await import('bundle-require');\r\n const { mod } = await bundleRequire<{ default?: VerbalyConfig }>({ filepath: path });\r\n return mod.default ?? {};\r\n } catch (error) {\r\n if (isModuleNotFound(error, 'esbuild')) {\r\n throw new Error(`[verbaly] ${path} needs esbuild — install it: pnpm add -D esbuild`, {\r\n cause: error,\r\n });\r\n }\r\n throw error;\r\n }\r\n}\r\n\r\nfunction isModuleNotFound(error: unknown, name: string): boolean {\r\n return (\r\n error instanceof Error &&\r\n (error as { code?: string }).code === 'ERR_MODULE_NOT_FOUND' &&\r\n error.message.includes(name)\r\n );\r\n}\r\n\r\n// file config + inline overrides\r\nexport async function loadConfig(\r\n root: string,\r\n overrides: VerbalyConfig = {},\r\n): Promise<ResolvedConfig> {\r\n const fileConfig = await loadConfigFile(root);\r\n return resolveConfig({ ...fileConfig, ...definedOnly(overrides), root });\r\n}\r\n\r\nfunction definedOnly(config: VerbalyConfig): VerbalyConfig {\r\n return Object.fromEntries(\r\n Object.entries(config).filter(([, value]) => value !== undefined),\r\n ) as VerbalyConfig;\r\n}\r\n","import { readFileSync } from 'node:fs';\nimport { glob } from 'tinyglobby';\nimport { analyze } from './analyze';\nimport type { Catalogs } from './catalog';\nimport type { ResolvedConfig } from './config';\nimport { MessageRegistry } from './registry';\n\nexport async function extractProject(cfg: ResolvedConfig): Promise<MessageRegistry> {\n const files = await glob(cfg.include, {\n cwd: cfg.root,\n ignore: cfg.exclude,\n absolute: true,\n });\n const registry = new MessageRegistry();\n for (const file of files) {\n registry.update(file, analyze(readFileSync(file, 'utf8'), file));\n }\n return registry;\n}\n\nexport interface SyncResult {\n added: Record<string, string[]>;\n}\n\n// fills source texts + '' placeholders\nexport function syncCatalogs(\n cfg: ResolvedConfig,\n catalogs: Catalogs,\n registry: MessageRegistry,\n): SyncResult {\n const added: Record<string, string[]> = {};\n const source = (catalogs[cfg.sourceLocale] ??= {});\n\n for (const [key, msg] of registry.messages()) {\n if (source[key] !== msg.message) {\n source[key] = msg.message;\n (added[cfg.sourceLocale] ??= []).push(key);\n }\n }\n\n const needed = Object.keys(source);\n for (const locale of cfg.locales) {\n if (locale === cfg.sourceLocale) continue;\n const catalog = (catalogs[locale] ??= {});\n for (const key of needed) {\n if (catalog[key] === undefined) {\n catalog[key] = '';\n (added[locale] ??= []).push(key);\n }\n }\n }\n return { added };\n}\n\n// drops keys no longer referenced\nexport function pruneCatalogs(\n cfg: ResolvedConfig,\n catalogs: Catalogs,\n registry: MessageRegistry,\n): Record<string, string[]> {\n const keep = new Set([...registry.messages().keys(), ...registry.usedKeys().keys()]);\n const removed: Record<string, string[]> = {};\n for (const locale of cfg.locales) {\n const catalog = catalogs[locale];\n if (!catalog) continue;\n for (const key of Object.keys(catalog)) {\n if (!keep.has(key)) {\n delete catalog[key];\n (removed[locale] ??= []).push(key);\n }\n }\n }\n return removed;\n}\n","import { parse } from '@babel/parser';\r\nimport { stableKey } from './key';\r\n\r\nexport interface TaggedParam {\r\n name: string;\r\n start: number;\r\n end: number;\r\n}\r\n\r\nexport interface TransComponent {\r\n name: string;\r\n source: string;\r\n}\r\n\r\nexport interface TaggedMessage {\r\n key: string;\r\n message: string;\r\n params: TaggedParam[];\r\n start: number;\r\n end: number;\r\n tagStart: number;\r\n tagEnd: number;\r\n file: string;\r\n jsx?: { name: string; components: TransComponent[] };\r\n}\r\n\r\nexport interface UsedKey {\r\n key: string;\r\n file: string;\r\n}\r\n\r\nexport interface Analysis {\r\n tagged: TaggedMessage[];\r\n usedKeys: UsedKey[];\r\n}\r\n\r\ninterface AstNode {\r\n type: string;\r\n start: number;\r\n end: number;\r\n [key: string]: unknown;\r\n}\r\n\r\nconst SKIP_KEYS = new Set(['loc', 'leadingComments', 'trailingComments', 'innerComments', 'extra']);\r\n\r\nexport function analyze(code: string, file: string): Analysis {\r\n const jsx = /\\.[jt]sx$/.test(file);\r\n const ast = parse(code, {\r\n sourceType: 'module',\r\n errorRecovery: true,\r\n plugins: jsx ? ['typescript', 'jsx'] : ['typescript'],\r\n });\r\n\r\n const tagged: TaggedMessage[] = [];\r\n const usedKeys: UsedKey[] = [];\r\n\r\n walk(ast.program as unknown as AstNode, (node) => {\r\n if (node.type === 'TaggedTemplateExpression') {\r\n const tag = node.tag as AstNode;\r\n const explicit = explicitId(tag);\r\n if (!explicit && !isTReference(tag)) return;\r\n const quasi = node.quasi as AstNode;\r\n const message = buildMessage(code, quasi);\r\n if (!message) return;\r\n tagged.push({\r\n key: explicit ? explicit.key : stableKey(message.text),\r\n message: message.text,\r\n params: message.params,\r\n start: node.start,\r\n end: node.end,\r\n tagStart: explicit ? explicit.refStart : tag.start,\r\n tagEnd: explicit ? explicit.refEnd : tag.end,\r\n file,\r\n });\r\n } else if (node.type === 'CallExpression') {\r\n const callee = node.callee as AstNode;\r\n if (!isTReference(callee)) return;\r\n const args = node.arguments as AstNode[];\r\n const first = args[0];\r\n if (first?.type === 'StringLiteral') {\r\n usedKeys.push({ key: first.value as string, file });\r\n }\r\n } else if (node.type === 'JSXElement') {\r\n handleTrans(code, node, file, tagged, usedKeys);\r\n }\r\n });\r\n\r\n return { tagged, usedKeys };\r\n}\r\n\r\nfunction handleTrans(\r\n code: string,\r\n node: AstNode,\r\n file: string,\r\n tagged: TaggedMessage[],\r\n usedKeys: UsedKey[],\r\n): void {\r\n const opening = node.openingElement as AstNode;\r\n const nameNode = opening.name as AstNode;\r\n if (nameNode.type !== 'JSXIdentifier' || nameNode.name !== 'Trans') return;\r\n\r\n const attrs = opening.attributes as AstNode[];\r\n const idAttr = attrs.find((a) => a.type === 'JSXAttribute' && (a.name as AstNode).name === 'id');\r\n const children = node.children as AstNode[] | undefined;\r\n\r\n if (idAttr) {\r\n const value = idAttr.value as AstNode | null;\r\n if (value?.type !== 'StringLiteral') return;\r\n const id = value.value as string;\r\n // id + children → extract under the explicit key\r\n if (attrs.length === 1 && children?.length) {\r\n const built = buildTransMessage(code, children);\r\n if (built?.text.trim()) {\r\n tagged.push({\r\n key: id,\r\n message: built.text,\r\n params: built.params,\r\n start: node.start,\r\n end: node.end,\r\n tagStart: nameNode.start,\r\n tagEnd: nameNode.end,\r\n file,\r\n jsx: { name: nameNode.name as string, components: built.components },\r\n });\r\n return;\r\n }\r\n }\r\n usedKeys.push({ key: id, file });\r\n return; // runtime-first, untouched\r\n }\r\n if (attrs.length > 0) return; // hand-written props → don't guess\r\n\r\n if (!children?.length) return;\r\n\r\n const built = buildTransMessage(code, children);\r\n if (!built || !built.text.trim()) return;\r\n\r\n tagged.push({\r\n key: stableKey(built.text),\r\n message: built.text,\r\n params: built.params,\r\n start: node.start,\r\n end: node.end,\r\n tagStart: nameNode.start,\r\n tagEnd: nameNode.end,\r\n file,\r\n jsx: { name: nameNode.name as string, components: built.components },\r\n });\r\n}\r\n\r\n// t.id('key')`…` → explicit readable key\r\nfunction explicitId(tag: AstNode): { key: string; refStart: number; refEnd: number } | undefined {\r\n if (tag.type !== 'CallExpression') return undefined;\r\n const callee = tag.callee as AstNode;\r\n if (callee.type !== 'MemberExpression' || callee.computed) return undefined;\r\n const prop = callee.property as AstNode;\r\n if (prop.type !== 'Identifier' || prop.name !== 'id') return undefined;\r\n const obj = callee.object as AstNode;\r\n if (!isTReference(obj)) return undefined;\r\n const args = tag.arguments as AstNode[];\r\n const first = args[0];\r\n if (args.length !== 1 || first?.type !== 'StringLiteral') return undefined;\r\n return { key: first.value as string, refStart: obj.start, refEnd: obj.end };\r\n}\r\n\r\ninterface BuiltTrans {\r\n text: string;\r\n params: TaggedParam[];\r\n components: TransComponent[];\r\n}\r\n\r\nfunction buildTransMessage(code: string, children: AstNode[]): BuiltTrans | undefined {\r\n const params: TaggedParam[] = [];\r\n const components: TransComponent[] = [];\r\n const takenParams = new Map<string, string>();\r\n const takenTags = new Map<string, string>();\r\n\r\n function walkChildren(nodes: AstNode[]): string | undefined {\r\n let text = '';\r\n for (const child of nodes) {\r\n if (child.type === 'JSXText') {\r\n text += escapeText(cleanJsxText(child.value as string));\r\n } else if (child.type === 'JSXExpressionContainer') {\r\n const expr = child.expression as AstNode;\r\n if (expr.type === 'JSXEmptyExpression') continue;\r\n if (expr.type === 'StringLiteral') {\r\n text += escapeText(expr.value as string);\r\n continue;\r\n }\r\n if (expr.type === 'TaggedTemplateExpression') return undefined; // overlap hazard\r\n const source = code.slice(expr.start, expr.end);\r\n const name = uniqueName(deriveName(expr, params.length), source, takenParams);\r\n params.push({ name, start: expr.start, end: expr.end });\r\n text += `{${name}}`;\r\n } else if (child.type === 'JSXElement') {\r\n const opening = child.openingElement as AstNode;\r\n const nameNode = opening.name as AstNode;\r\n if (nameNode.type !== 'JSXIdentifier' || nameNode.name === 'Trans') return undefined;\r\n const source = selfClosedSource(code, opening);\r\n const tag = uniqueName((nameNode.name as string).toLowerCase(), source, takenTags);\r\n if (!components.some((c) => c.name === tag)) components.push({ name: tag, source });\r\n if (!child.closingElement) {\r\n text += `<${tag}/>`;\r\n } else {\r\n const inner = walkChildren(child.children as AstNode[]);\r\n if (inner === undefined) return undefined;\r\n text += `<${tag}>${inner}</${tag}>`;\r\n }\r\n } else {\r\n return undefined; // fragments / spread children → bail\r\n }\r\n }\r\n return text;\r\n }\r\n\r\n const text = walkChildren(children);\r\n if (text === undefined) return undefined;\r\n return { text, params, components };\r\n}\r\n\r\n// JSX text semantics: newline-indent boundaries removed, interior joins = one space\r\nfunction cleanJsxText(raw: string): string {\r\n const lines = raw.split(/\\r\\n|[\\r\\n]/);\r\n let out = '';\r\n for (let i = 0; i < lines.length; i++) {\r\n let line = lines[i]!.replace(/\\t/g, ' ');\r\n if (i !== 0) line = line.replace(/^ +/, '');\r\n if (i !== lines.length - 1) line = line.replace(/ +$/, '');\r\n if (!line) continue;\r\n if (out && i !== 0) out += ' ';\r\n out += line;\r\n }\r\n return out;\r\n}\r\n\r\nfunction selfClosedSource(code: string, opening: AstNode): string {\r\n const src = code.slice(opening.start, opening.end);\r\n return src.endsWith('/>') ? src : `${src.slice(0, -1).trimEnd()} />`;\r\n}\r\n\r\nfunction isTReference(node: AstNode): boolean {\r\n if (node.type === 'Identifier') return node.name === 't';\r\n if (node.type === 'MemberExpression' && !node.computed) {\r\n const prop = node.property as AstNode;\r\n return prop.type === 'Identifier' && prop.name === 't';\r\n }\r\n return false;\r\n}\r\n\r\ninterface BuiltMessage {\r\n text: string;\r\n params: TaggedParam[];\r\n}\r\n\r\nfunction buildMessage(code: string, quasi: AstNode): BuiltMessage | undefined {\r\n const quasis = quasi.quasis as AstNode[];\r\n const expressions = quasi.expressions as AstNode[];\r\n const params: TaggedParam[] = [];\r\n const taken = new Map<string, string>();\r\n\r\n let text = escapeText(cookedValue(quasis[0]));\r\n\r\n for (let i = 0; i < expressions.length; i++) {\r\n const expr = expressions[i];\r\n if (!expr) return undefined;\r\n const source = code.slice(expr.start, expr.end);\r\n const name = uniqueName(deriveName(expr, i), source, taken);\r\n params.push({ name, start: expr.start, end: expr.end });\r\n text += `{${name}}` + escapeText(cookedValue(quasis[i + 1]));\r\n }\r\n return { text, params };\r\n}\r\n\r\nfunction cookedValue(element: AstNode | undefined): string {\r\n if (!element) return '';\r\n const value = element.value as { cooked?: string; raw: string };\r\n return value.cooked ?? value.raw;\r\n}\r\n\r\n// literal braces → escaped\r\nfunction escapeText(text: string): string {\r\n return text.replace(/[{}]/g, (m) => m + m);\r\n}\r\n\r\nfunction deriveName(expr: AstNode, index: number): string {\r\n if (expr.type === 'Identifier') return expr.name as string;\r\n if (expr.type === 'MemberExpression' && !expr.computed) {\r\n const prop = expr.property as AstNode;\r\n if (prop.type === 'Identifier') return prop.name as string;\r\n }\r\n return `_${index}`;\r\n}\r\n\r\nfunction uniqueName(base: string, source: string, taken: Map<string, string>): string {\r\n let name = base;\r\n let n = 2;\r\n while (taken.has(name) && taken.get(name) !== source) {\r\n name = `${base}${n}`;\r\n n += 1;\r\n }\r\n taken.set(name, source);\r\n return name;\r\n}\r\n\r\nfunction walk(node: AstNode, visit: (node: AstNode) => void): void {\r\n visit(node);\r\n for (const [key, value] of Object.entries(node)) {\r\n if (SKIP_KEYS.has(key)) continue;\r\n if (Array.isArray(value)) {\r\n for (const item of value) {\r\n if (isNode(item)) walk(item, visit);\r\n }\r\n } else if (isNode(value)) {\r\n walk(value, visit);\r\n }\r\n }\r\n}\r\n\r\nfunction isNode(value: unknown): value is AstNode {\r\n return (\r\n typeof value === 'object' &&\r\n value !== null &&\r\n typeof (value as { type?: unknown }).type === 'string'\r\n );\r\n}\r\n","import { createHash } from 'node:crypto';\n\nexport function stableKey(message: string): string {\n return createHash('sha256').update(message).digest('base64url').slice(0, 8);\n}\n","import type { Analysis, TaggedMessage } from './analyze';\n\nexport class MessageRegistry {\n private files = new Map<string, Analysis>();\n\n update(file: string, analysis: Analysis): void {\n this.files.set(file, analysis);\n }\n\n remove(file: string): void {\n this.files.delete(file);\n }\n\n messages(): Map<string, TaggedMessage> {\n const out = new Map<string, TaggedMessage>();\n for (const analysis of this.files.values()) {\n for (const msg of analysis.tagged) {\n const existing = out.get(msg.key);\n if (existing) {\n if (existing.message !== msg.message) {\n console.warn(\n `[verbaly] key collision \"${msg.key}\": ` +\n `${JSON.stringify(existing.message)} vs ${JSON.stringify(msg.message)} — second dropped.`,\n );\n }\n continue;\n }\n out.set(msg.key, msg);\n }\n }\n return out;\n }\n\n usedKeys(): Map<string, string[]> {\n const out = new Map<string, string[]>();\n for (const analysis of this.files.values()) {\n for (const used of analysis.usedKeys) {\n const files = out.get(used.key) ?? [];\n if (!files.includes(used.file)) files.push(used.file);\n out.set(used.key, files);\n }\n }\n return out;\n }\n}\n","import type { Catalogs } from './catalog';\nimport type { ResolvedConfig } from './config';\nimport { collectParams } from './params';\n\nexport interface TranslateRequest {\n sourceLocale: string;\n targetLocale: string;\n messages: Record<string, string>;\n}\n\nexport type TranslateProvider = (request: TranslateRequest) => Promise<Record<string, string>>;\n\nexport interface TranslateOptions {\n locales?: string[];\n batchSize?: number;\n dryRun?: boolean;\n}\n\nexport interface TranslateResult {\n translated: Record<string, string[]>;\n invalid: Record<string, string[]>;\n pending: Record<string, string[]>;\n}\n\n// fills '' entries via the provider; invalid translations stay '' (check keeps failing)\nexport async function translateCatalogs(\n cfg: ResolvedConfig,\n catalogs: Catalogs,\n provider: TranslateProvider,\n options: TranslateOptions = {},\n): Promise<TranslateResult> {\n const batchSize = options.batchSize ?? 20;\n const targets = (options.locales ?? cfg.locales).filter(\n (locale) => locale !== cfg.sourceLocale && cfg.locales.includes(locale),\n );\n const source = catalogs[cfg.sourceLocale] ?? {};\n const result: TranslateResult = { translated: {}, invalid: {}, pending: {} };\n\n for (const locale of targets) {\n const catalog = (catalogs[locale] ??= {});\n const missing = Object.keys(source).filter((key) => source[key] && !catalog[key]);\n if (missing.length === 0) continue;\n\n if (options.dryRun) {\n result.pending[locale] = missing;\n continue;\n }\n\n for (let i = 0; i < missing.length; i += batchSize) {\n const keys = missing.slice(i, i + batchSize);\n const messages = Object.fromEntries(keys.map((key) => [key, source[key]!]));\n const out = await provider({\n sourceLocale: cfg.sourceLocale,\n targetLocale: locale,\n messages,\n });\n for (const key of keys) {\n const text = out[key];\n if (typeof text === 'string' && text.trim() && structureMatches(source[key]!, text)) {\n catalog[key] = text;\n (result.translated[locale] ??= []).push(key);\n } else {\n (result.invalid[locale] ??= []).push(key);\n }\n }\n }\n }\n return result;\n}\n\n// params and tags must survive translation verbatim\nexport function structureMatches(source: string, translated: string): boolean {\n return sameMembers(paramNames(source), paramNames(translated)) && sameMembers(tagTokens(source), tagTokens(translated));\n}\n\nfunction paramNames(message: string): string[] {\n try {\n return [...collectParams(message).keys()].sort();\n } catch {\n return ['\u0000invalid'];\n }\n}\n\nconst TAG = /<(\\/?)([a-zA-Z][\\w-]*)(\\/?)>/g;\n\nfunction tagTokens(message: string): string[] {\n const out: string[] = [];\n for (const match of message.matchAll(TAG)) {\n out.push(`${match[1]}${match[2]}${match[3]}`);\n }\n return out.sort();\n}\n\nfunction sameMembers(a: string[], b: string[]): boolean {\n return a.length === b.length && a.every((value, i) => value === b[i]);\n}\n"],"mappings":"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","names":["writeFileSync","join","writeFileSync","join","readFileSync","join","readFileSync","parse","parse","built","text","visit","readFileSync"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -33,6 +33,30 @@ interface Analysis {
|
|
|
33
33
|
}
|
|
34
34
|
declare function analyze(code: string, file: string): Analysis;
|
|
35
35
|
|
|
36
|
+
interface TranslateRequest {
|
|
37
|
+
sourceLocale: string;
|
|
38
|
+
targetLocale: string;
|
|
39
|
+
messages: Record<string, string>;
|
|
40
|
+
}
|
|
41
|
+
type TranslateProvider = (request: TranslateRequest) => Promise<Record<string, string>>;
|
|
42
|
+
interface TranslateOptions {
|
|
43
|
+
locales?: string[];
|
|
44
|
+
batchSize?: number;
|
|
45
|
+
dryRun?: boolean;
|
|
46
|
+
}
|
|
47
|
+
interface TranslateResult {
|
|
48
|
+
translated: Record<string, string[]>;
|
|
49
|
+
invalid: Record<string, string[]>;
|
|
50
|
+
pending: Record<string, string[]>;
|
|
51
|
+
}
|
|
52
|
+
declare function translateCatalogs(cfg: ResolvedConfig, catalogs: Catalogs, provider: TranslateProvider, options?: TranslateOptions): Promise<TranslateResult>;
|
|
53
|
+
declare function structureMatches(source: string, translated: string): boolean;
|
|
54
|
+
|
|
55
|
+
interface TranslateConfig {
|
|
56
|
+
provider?: 'claude' | TranslateProvider;
|
|
57
|
+
model?: string;
|
|
58
|
+
batchSize?: number;
|
|
59
|
+
}
|
|
36
60
|
interface VerbalyConfig {
|
|
37
61
|
root?: string;
|
|
38
62
|
dir?: string;
|
|
@@ -40,6 +64,7 @@ interface VerbalyConfig {
|
|
|
40
64
|
locales?: string[];
|
|
41
65
|
include?: string[];
|
|
42
66
|
exclude?: string[];
|
|
67
|
+
translate?: TranslateConfig;
|
|
43
68
|
}
|
|
44
69
|
interface ResolvedConfig {
|
|
45
70
|
root: string;
|
|
@@ -48,6 +73,7 @@ interface ResolvedConfig {
|
|
|
48
73
|
locales: string[];
|
|
49
74
|
include: string[];
|
|
50
75
|
exclude: string[];
|
|
76
|
+
translate: TranslateConfig;
|
|
51
77
|
}
|
|
52
78
|
declare function resolveConfig(config?: VerbalyConfig): ResolvedConfig;
|
|
53
79
|
declare function loadConfigFile(root: string): Promise<VerbalyConfig>;
|
|
@@ -105,10 +131,17 @@ type ParamType = 'number' | 'string' | 'date' | 'unknown';
|
|
|
105
131
|
declare function collectParams(message: string): Map<string, Set<ParamType>>;
|
|
106
132
|
declare function renderParamType(types: Set<ParamType>): string;
|
|
107
133
|
|
|
134
|
+
interface ClaudeProviderOptions {
|
|
135
|
+
model?: string;
|
|
136
|
+
apiKey?: string;
|
|
137
|
+
maxTokens?: number;
|
|
138
|
+
}
|
|
139
|
+
declare function claudeProvider(options?: ClaudeProviderOptions): TranslateProvider;
|
|
140
|
+
|
|
108
141
|
interface TransformResult {
|
|
109
142
|
code: string;
|
|
110
143
|
map: ReturnType<MagicString['generateMap']>;
|
|
111
144
|
}
|
|
112
145
|
declare function transformCode(code: string, file: string, analysis?: Analysis): TransformResult | null;
|
|
113
146
|
|
|
114
|
-
export { type Analysis, type Catalog, type Catalogs, type CheckResult, MessageRegistry, type MissingEntry, type ParamType, type ResolvedConfig, type SyncResult, type TaggedMessage, type TaggedParam, type TransComponent, type TransformResult, type UnknownEntry, type UsedKey, VIRTUAL_ID, type VerbalyConfig, analyze, catalogPath, check, collectParams, extractProject, formatCheckResult, generateDts, generateLocaleModule, generateRuntimeModule, loadCatalogs, loadConfig, loadConfigFile, pruneCatalogs, readCatalog, renderParamType, resolveConfig, serializeCatalog, stableKey, syncCatalogs, transformCode, writeCatalog, writeDts };
|
|
147
|
+
export { type Analysis, type Catalog, type Catalogs, type CheckResult, type ClaudeProviderOptions, MessageRegistry, type MissingEntry, type ParamType, type ResolvedConfig, type SyncResult, type TaggedMessage, type TaggedParam, type TransComponent, type TransformResult, type TranslateConfig, type TranslateOptions, type TranslateProvider, type TranslateRequest, type TranslateResult, type UnknownEntry, type UsedKey, VIRTUAL_ID, type VerbalyConfig, analyze, catalogPath, check, claudeProvider, collectParams, extractProject, formatCheckResult, generateDts, generateLocaleModule, generateRuntimeModule, loadCatalogs, loadConfig, loadConfigFile, pruneCatalogs, readCatalog, renderParamType, resolveConfig, serializeCatalog, stableKey, structureMatches, syncCatalogs, transformCode, translateCatalogs, writeCatalog, writeDts };
|
package/dist/index.js
CHANGED
|
@@ -21,18 +21,19 @@ function analyze(code, file) {
|
|
|
21
21
|
walk(ast.program, (node) => {
|
|
22
22
|
if (node.type === "TaggedTemplateExpression") {
|
|
23
23
|
const tag = node.tag;
|
|
24
|
-
|
|
24
|
+
const explicit = explicitId(tag);
|
|
25
|
+
if (!explicit && !isTReference(tag)) return;
|
|
25
26
|
const quasi = node.quasi;
|
|
26
27
|
const message = buildMessage(code, quasi);
|
|
27
28
|
if (!message) return;
|
|
28
29
|
tagged.push({
|
|
29
|
-
key: stableKey(message.text),
|
|
30
|
+
key: explicit ? explicit.key : stableKey(message.text),
|
|
30
31
|
message: message.text,
|
|
31
32
|
params: message.params,
|
|
32
33
|
start: node.start,
|
|
33
34
|
end: node.end,
|
|
34
|
-
tagStart: tag.start,
|
|
35
|
-
tagEnd: tag.end,
|
|
35
|
+
tagStart: explicit ? explicit.refStart : tag.start,
|
|
36
|
+
tagEnd: explicit ? explicit.refEnd : tag.end,
|
|
36
37
|
file
|
|
37
38
|
});
|
|
38
39
|
} else if (node.type === "CallExpression") {
|
|
@@ -55,13 +56,32 @@ function handleTrans(code, node, file, tagged, usedKeys) {
|
|
|
55
56
|
if (nameNode.type !== "JSXIdentifier" || nameNode.name !== "Trans") return;
|
|
56
57
|
const attrs = opening.attributes;
|
|
57
58
|
const idAttr = attrs.find((a) => a.type === "JSXAttribute" && a.name.name === "id");
|
|
59
|
+
const children = node.children;
|
|
58
60
|
if (idAttr) {
|
|
59
61
|
const value = idAttr.value;
|
|
60
|
-
if (value?.type
|
|
62
|
+
if (value?.type !== "StringLiteral") return;
|
|
63
|
+
const id = value.value;
|
|
64
|
+
if (attrs.length === 1 && children?.length) {
|
|
65
|
+
const built2 = buildTransMessage(code, children);
|
|
66
|
+
if (built2?.text.trim()) {
|
|
67
|
+
tagged.push({
|
|
68
|
+
key: id,
|
|
69
|
+
message: built2.text,
|
|
70
|
+
params: built2.params,
|
|
71
|
+
start: node.start,
|
|
72
|
+
end: node.end,
|
|
73
|
+
tagStart: nameNode.start,
|
|
74
|
+
tagEnd: nameNode.end,
|
|
75
|
+
file,
|
|
76
|
+
jsx: { name: nameNode.name, components: built2.components }
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
usedKeys.push({ key: id, file });
|
|
61
82
|
return;
|
|
62
83
|
}
|
|
63
84
|
if (attrs.length > 0) return;
|
|
64
|
-
const children = node.children;
|
|
65
85
|
if (!children?.length) return;
|
|
66
86
|
const built = buildTransMessage(code, children);
|
|
67
87
|
if (!built || !built.text.trim()) return;
|
|
@@ -77,6 +97,19 @@ function handleTrans(code, node, file, tagged, usedKeys) {
|
|
|
77
97
|
jsx: { name: nameNode.name, components: built.components }
|
|
78
98
|
});
|
|
79
99
|
}
|
|
100
|
+
function explicitId(tag) {
|
|
101
|
+
if (tag.type !== "CallExpression") return void 0;
|
|
102
|
+
const callee = tag.callee;
|
|
103
|
+
if (callee.type !== "MemberExpression" || callee.computed) return void 0;
|
|
104
|
+
const prop = callee.property;
|
|
105
|
+
if (prop.type !== "Identifier" || prop.name !== "id") return void 0;
|
|
106
|
+
const obj = callee.object;
|
|
107
|
+
if (!isTReference(obj)) return void 0;
|
|
108
|
+
const args = tag.arguments;
|
|
109
|
+
const first = args[0];
|
|
110
|
+
if (args.length !== 1 || first?.type !== "StringLiteral") return void 0;
|
|
111
|
+
return { key: first.value, refStart: obj.start, refEnd: obj.end };
|
|
112
|
+
}
|
|
80
113
|
function buildTransMessage(code, children) {
|
|
81
114
|
const params = [];
|
|
82
115
|
const components = [];
|
|
@@ -336,19 +369,19 @@ var VIRTUAL_ID = "virtual:verbaly";
|
|
|
336
369
|
function generateRuntimeModule(cfg) {
|
|
337
370
|
const others = cfg.locales.filter((locale) => locale !== cfg.sourceLocale);
|
|
338
371
|
const src = JSON.stringify(cfg.sourceLocale);
|
|
339
|
-
const loaders = others.map(
|
|
372
|
+
const loaders = others.map(
|
|
373
|
+
(locale) => ` ${JSON.stringify(locale)}: () => import('${VIRTUAL_ID}/locale/${locale}'),`
|
|
374
|
+
).join("\n");
|
|
340
375
|
return `import { createVerbaly } from 'verbaly';
|
|
341
376
|
import source from '${VIRTUAL_ID}/locale/${cfg.sourceLocale}';
|
|
342
377
|
|
|
343
|
-
const loaders = {
|
|
344
|
-
${loaders}
|
|
345
|
-
};
|
|
346
|
-
const loaded = new Set([${src}]);
|
|
347
|
-
|
|
348
378
|
const v = createVerbaly({
|
|
349
379
|
locale: ${src},
|
|
350
380
|
fallback: ${src},
|
|
351
381
|
messages: { [${src}]: source },
|
|
382
|
+
loaders: {
|
|
383
|
+
${loaders}
|
|
384
|
+
},
|
|
352
385
|
});
|
|
353
386
|
|
|
354
387
|
export const verbaly = v;
|
|
@@ -363,12 +396,7 @@ export function subscribe(listener) {
|
|
|
363
396
|
}
|
|
364
397
|
|
|
365
398
|
export async function setLocale(locale) {
|
|
366
|
-
|
|
367
|
-
if (!loaded.has(target) && loaders[target]) {
|
|
368
|
-
const mod = await loaders[target]();
|
|
369
|
-
v.addMessages(target, mod.default);
|
|
370
|
-
loaded.add(target);
|
|
371
|
-
}
|
|
399
|
+
await v.loadLocale(locale);
|
|
372
400
|
v.setLocale(locale);
|
|
373
401
|
}
|
|
374
402
|
`;
|
|
@@ -402,6 +430,11 @@ ${lines.join("\n")}
|
|
|
402
430
|
...args: [VerbalyMessages[K]] extends [never] ? [] : [VerbalyMessages[K]]
|
|
403
431
|
): string;
|
|
404
432
|
export function t(strings: TemplateStringsArray, ...values: unknown[]): string;
|
|
433
|
+
export namespace t {
|
|
434
|
+
export function id(
|
|
435
|
+
key: string,
|
|
436
|
+
): (strings: TemplateStringsArray, ...values: unknown[]) => string;
|
|
437
|
+
}
|
|
405
438
|
|
|
406
439
|
export function setLocale(locale: string): Promise<void>;
|
|
407
440
|
export function getLocale(): string;
|
|
@@ -438,7 +471,8 @@ function resolveConfig(config = {}) {
|
|
|
438
471
|
sourceLocale,
|
|
439
472
|
locales: [...locales],
|
|
440
473
|
include: config.include ?? ["src/**/*.{js,jsx,ts,tsx,mjs,mts}"],
|
|
441
|
-
exclude: config.exclude ?? ["**/node_modules/**", "**/dist/**"]
|
|
474
|
+
exclude: config.exclude ?? ["**/node_modules/**", "**/dist/**"],
|
|
475
|
+
translate: config.translate ?? {}
|
|
442
476
|
};
|
|
443
477
|
}
|
|
444
478
|
async function loadConfigFile(root) {
|
|
@@ -581,6 +615,64 @@ function pruneCatalogs(cfg, catalogs, registry) {
|
|
|
581
615
|
return removed;
|
|
582
616
|
}
|
|
583
617
|
|
|
618
|
+
// src/providers/claude.ts
|
|
619
|
+
var DEFAULT_MODEL = "claude-sonnet-5";
|
|
620
|
+
var SYSTEM = `You translate UI strings for the Verbaly i18n library.
|
|
621
|
+
Rules:
|
|
622
|
+
- Translate only the human-readable text, naturally for the target locale.
|
|
623
|
+
- Preserve verbatim: placeholders like {name}, format specs like {price:currency/EUR}, variant blocks like {count | one: ... | other: # ...} (translate only the text inside each variant, keep keys and # as-is), ICU syntax, named tags like <em>...</em> and escapes {{ }} || ##.
|
|
624
|
+
- Keys are opaque identifiers: return exactly the same keys, never translate or rename them.`;
|
|
625
|
+
function claudeProvider(options = {}) {
|
|
626
|
+
return async (request) => {
|
|
627
|
+
const Anthropic = await loadSdk();
|
|
628
|
+
const client = new Anthropic(options.apiKey ? { apiKey: options.apiKey } : {});
|
|
629
|
+
const response = await client.messages.create({
|
|
630
|
+
model: options.model ?? DEFAULT_MODEL,
|
|
631
|
+
max_tokens: options.maxTokens ?? 16e3,
|
|
632
|
+
thinking: { type: "disabled" },
|
|
633
|
+
system: SYSTEM,
|
|
634
|
+
messages: [{ role: "user", content: buildPrompt(request) }],
|
|
635
|
+
output_config: { format: batchFormat(request) }
|
|
636
|
+
});
|
|
637
|
+
const text = response.content.find((block) => block.type === "text")?.text ?? "{}";
|
|
638
|
+
return JSON.parse(text);
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
function buildPrompt(request) {
|
|
642
|
+
return `Translate each value from "${request.sourceLocale}" to "${request.targetLocale}". Return a JSON object with the same keys and translated values.
|
|
643
|
+
|
|
644
|
+
` + JSON.stringify(request.messages, null, 2);
|
|
645
|
+
}
|
|
646
|
+
function batchFormat(request) {
|
|
647
|
+
const keys = Object.keys(request.messages);
|
|
648
|
+
return {
|
|
649
|
+
type: "json_schema",
|
|
650
|
+
schema: {
|
|
651
|
+
type: "object",
|
|
652
|
+
properties: Object.fromEntries(keys.map((key) => [key, { type: "string" }])),
|
|
653
|
+
required: keys,
|
|
654
|
+
additionalProperties: false
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
async function loadSdk() {
|
|
659
|
+
try {
|
|
660
|
+
const mod = await import("@anthropic-ai/sdk");
|
|
661
|
+
return mod.default;
|
|
662
|
+
} catch (error) {
|
|
663
|
+
if (isModuleNotFound2(error, "@anthropic-ai/sdk")) {
|
|
664
|
+
throw new Error(
|
|
665
|
+
"[verbaly] the claude translate provider needs @anthropic-ai/sdk \u2014 install it as a dev dependency (e.g. `pnpm add -D @anthropic-ai/sdk` / `npm i -D @anthropic-ai/sdk`) and set ANTHROPIC_API_KEY",
|
|
666
|
+
{ cause: error }
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
throw error;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
function isModuleNotFound2(error, name) {
|
|
673
|
+
return error instanceof Error && error.code === "ERR_MODULE_NOT_FOUND" && error.message.includes(name);
|
|
674
|
+
}
|
|
675
|
+
|
|
584
676
|
// src/transform.ts
|
|
585
677
|
import MagicString from "magic-string";
|
|
586
678
|
function transformCode(code, file, analysis) {
|
|
@@ -607,12 +699,72 @@ function transformCode(code, file, analysis) {
|
|
|
607
699
|
}
|
|
608
700
|
return { code: s.toString(), map: s.generateMap({ hires: true }) };
|
|
609
701
|
}
|
|
702
|
+
|
|
703
|
+
// src/translate.ts
|
|
704
|
+
async function translateCatalogs(cfg, catalogs, provider, options = {}) {
|
|
705
|
+
const batchSize = options.batchSize ?? 20;
|
|
706
|
+
const targets = (options.locales ?? cfg.locales).filter(
|
|
707
|
+
(locale) => locale !== cfg.sourceLocale && cfg.locales.includes(locale)
|
|
708
|
+
);
|
|
709
|
+
const source = catalogs[cfg.sourceLocale] ?? {};
|
|
710
|
+
const result = { translated: {}, invalid: {}, pending: {} };
|
|
711
|
+
for (const locale of targets) {
|
|
712
|
+
const catalog = catalogs[locale] ??= {};
|
|
713
|
+
const missing = Object.keys(source).filter((key) => source[key] && !catalog[key]);
|
|
714
|
+
if (missing.length === 0) continue;
|
|
715
|
+
if (options.dryRun) {
|
|
716
|
+
result.pending[locale] = missing;
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
for (let i = 0; i < missing.length; i += batchSize) {
|
|
720
|
+
const keys = missing.slice(i, i + batchSize);
|
|
721
|
+
const messages = Object.fromEntries(keys.map((key) => [key, source[key]]));
|
|
722
|
+
const out = await provider({
|
|
723
|
+
sourceLocale: cfg.sourceLocale,
|
|
724
|
+
targetLocale: locale,
|
|
725
|
+
messages
|
|
726
|
+
});
|
|
727
|
+
for (const key of keys) {
|
|
728
|
+
const text = out[key];
|
|
729
|
+
if (typeof text === "string" && text.trim() && structureMatches(source[key], text)) {
|
|
730
|
+
catalog[key] = text;
|
|
731
|
+
(result.translated[locale] ??= []).push(key);
|
|
732
|
+
} else {
|
|
733
|
+
(result.invalid[locale] ??= []).push(key);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
return result;
|
|
739
|
+
}
|
|
740
|
+
function structureMatches(source, translated) {
|
|
741
|
+
return sameMembers(paramNames(source), paramNames(translated)) && sameMembers(tagTokens(source), tagTokens(translated));
|
|
742
|
+
}
|
|
743
|
+
function paramNames(message) {
|
|
744
|
+
try {
|
|
745
|
+
return [...collectParams(message).keys()].sort();
|
|
746
|
+
} catch {
|
|
747
|
+
return ["\0invalid"];
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
var TAG = /<(\/?)([a-zA-Z][\w-]*)(\/?)>/g;
|
|
751
|
+
function tagTokens(message) {
|
|
752
|
+
const out = [];
|
|
753
|
+
for (const match of message.matchAll(TAG)) {
|
|
754
|
+
out.push(`${match[1]}${match[2]}${match[3]}`);
|
|
755
|
+
}
|
|
756
|
+
return out.sort();
|
|
757
|
+
}
|
|
758
|
+
function sameMembers(a, b) {
|
|
759
|
+
return a.length === b.length && a.every((value, i) => value === b[i]);
|
|
760
|
+
}
|
|
610
761
|
export {
|
|
611
762
|
MessageRegistry,
|
|
612
763
|
VIRTUAL_ID,
|
|
613
764
|
analyze,
|
|
614
765
|
catalogPath,
|
|
615
766
|
check,
|
|
767
|
+
claudeProvider,
|
|
616
768
|
collectParams,
|
|
617
769
|
extractProject,
|
|
618
770
|
formatCheckResult,
|
|
@@ -628,8 +780,10 @@ export {
|
|
|
628
780
|
resolveConfig,
|
|
629
781
|
serializeCatalog,
|
|
630
782
|
stableKey,
|
|
783
|
+
structureMatches,
|
|
631
784
|
syncCatalogs,
|
|
632
785
|
transformCode,
|
|
786
|
+
translateCatalogs,
|
|
633
787
|
writeCatalog,
|
|
634
788
|
writeDts
|
|
635
789
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/analyze.ts","../src/key.ts","../src/catalog.ts","../src/check.ts","../src/codegen.ts","../src/params.ts","../src/config.ts","../src/extract.ts","../src/registry.ts","../src/transform.ts"],"sourcesContent":["import { parse } from '@babel/parser';\nimport { stableKey } from './key';\n\nexport interface TaggedParam {\n name: string;\n start: number;\n end: number;\n}\n\nexport interface TransComponent {\n name: string;\n source: string;\n}\n\nexport interface TaggedMessage {\n key: string;\n message: string;\n params: TaggedParam[];\n start: number;\n end: number;\n tagStart: number;\n tagEnd: number;\n file: string;\n jsx?: { name: string; components: TransComponent[] };\n}\n\nexport interface UsedKey {\n key: string;\n file: string;\n}\n\nexport interface Analysis {\n tagged: TaggedMessage[];\n usedKeys: UsedKey[];\n}\n\ninterface AstNode {\n type: string;\n start: number;\n end: number;\n [key: string]: unknown;\n}\n\nconst SKIP_KEYS = new Set(['loc', 'leadingComments', 'trailingComments', 'innerComments', 'extra']);\n\nexport function analyze(code: string, file: string): Analysis {\n const jsx = /\\.[jt]sx$/.test(file);\n const ast = parse(code, {\n sourceType: 'module',\n errorRecovery: true,\n plugins: jsx ? ['typescript', 'jsx'] : ['typescript'],\n });\n\n const tagged: TaggedMessage[] = [];\n const usedKeys: UsedKey[] = [];\n\n walk(ast.program as unknown as AstNode, (node) => {\n if (node.type === 'TaggedTemplateExpression') {\n const tag = node.tag as AstNode;\n if (!isTReference(tag)) return;\n const quasi = node.quasi as AstNode;\n const message = buildMessage(code, quasi);\n if (!message) return;\n tagged.push({\n key: stableKey(message.text),\n message: message.text,\n params: message.params,\n start: node.start,\n end: node.end,\n tagStart: tag.start,\n tagEnd: tag.end,\n file,\n });\n } else if (node.type === 'CallExpression') {\n const callee = node.callee as AstNode;\n if (!isTReference(callee)) return;\n const args = node.arguments as AstNode[];\n const first = args[0];\n if (first?.type === 'StringLiteral') {\n usedKeys.push({ key: first.value as string, file });\n }\n } else if (node.type === 'JSXElement') {\n handleTrans(code, node, file, tagged, usedKeys);\n }\n });\n\n return { tagged, usedKeys };\n}\n\nfunction handleTrans(\n code: string,\n node: AstNode,\n file: string,\n tagged: TaggedMessage[],\n usedKeys: UsedKey[],\n): void {\n const opening = node.openingElement as AstNode;\n const nameNode = opening.name as AstNode;\n if (nameNode.type !== 'JSXIdentifier' || nameNode.name !== 'Trans') return;\n\n const attrs = opening.attributes as AstNode[];\n const idAttr = attrs.find((a) => a.type === 'JSXAttribute' && (a.name as AstNode).name === 'id');\n if (idAttr) {\n const value = idAttr.value as AstNode | null;\n if (value?.type === 'StringLiteral') usedKeys.push({ key: value.value as string, file });\n return; // runtime-first, untouched\n }\n if (attrs.length > 0) return; // hand-written props → don't guess\n\n const children = node.children as AstNode[] | undefined;\n if (!children?.length) return;\n\n const built = buildTransMessage(code, children);\n if (!built || !built.text.trim()) return;\n\n tagged.push({\n key: stableKey(built.text),\n message: built.text,\n params: built.params,\n start: node.start,\n end: node.end,\n tagStart: nameNode.start,\n tagEnd: nameNode.end,\n file,\n jsx: { name: nameNode.name as string, components: built.components },\n });\n}\n\ninterface BuiltTrans {\n text: string;\n params: TaggedParam[];\n components: TransComponent[];\n}\n\nfunction buildTransMessage(code: string, children: AstNode[]): BuiltTrans | undefined {\n const params: TaggedParam[] = [];\n const components: TransComponent[] = [];\n const takenParams = new Map<string, string>();\n const takenTags = new Map<string, string>();\n\n function walkChildren(nodes: AstNode[]): string | undefined {\n let text = '';\n for (const child of nodes) {\n if (child.type === 'JSXText') {\n text += escapeText(cleanJsxText(child.value as string));\n } else if (child.type === 'JSXExpressionContainer') {\n const expr = child.expression as AstNode;\n if (expr.type === 'JSXEmptyExpression') continue;\n if (expr.type === 'StringLiteral') {\n text += escapeText(expr.value as string);\n continue;\n }\n if (expr.type === 'TaggedTemplateExpression') return undefined; // overlap hazard\n const source = code.slice(expr.start, expr.end);\n const name = uniqueName(deriveName(expr, params.length), source, takenParams);\n params.push({ name, start: expr.start, end: expr.end });\n text += `{${name}}`;\n } else if (child.type === 'JSXElement') {\n const opening = child.openingElement as AstNode;\n const nameNode = opening.name as AstNode;\n if (nameNode.type !== 'JSXIdentifier' || nameNode.name === 'Trans') return undefined;\n const source = selfClosedSource(code, opening);\n const tag = uniqueName((nameNode.name as string).toLowerCase(), source, takenTags);\n if (!components.some((c) => c.name === tag)) components.push({ name: tag, source });\n if (!child.closingElement) {\n text += `<${tag}/>`;\n } else {\n const inner = walkChildren(child.children as AstNode[]);\n if (inner === undefined) return undefined;\n text += `<${tag}>${inner}</${tag}>`;\n }\n } else {\n return undefined; // fragments / spread children → bail\n }\n }\n return text;\n }\n\n const text = walkChildren(children);\n if (text === undefined) return undefined;\n return { text, params, components };\n}\n\n// JSX text semantics: newline-indent boundaries removed, interior joins = one space\nfunction cleanJsxText(raw: string): string {\n const lines = raw.split(/\\r\\n|[\\r\\n]/);\n let out = '';\n for (let i = 0; i < lines.length; i++) {\n let line = lines[i]!.replace(/\\t/g, ' ');\n if (i !== 0) line = line.replace(/^ +/, '');\n if (i !== lines.length - 1) line = line.replace(/ +$/, '');\n if (!line) continue;\n if (out && i !== 0) out += ' ';\n out += line;\n }\n return out;\n}\n\nfunction selfClosedSource(code: string, opening: AstNode): string {\n const src = code.slice(opening.start, opening.end);\n return src.endsWith('/>') ? src : `${src.slice(0, -1).trimEnd()} />`;\n}\n\nfunction isTReference(node: AstNode): boolean {\n if (node.type === 'Identifier') return node.name === 't';\n if (node.type === 'MemberExpression' && !node.computed) {\n const prop = node.property as AstNode;\n return prop.type === 'Identifier' && prop.name === 't';\n }\n return false;\n}\n\ninterface BuiltMessage {\n text: string;\n params: TaggedParam[];\n}\n\nfunction buildMessage(code: string, quasi: AstNode): BuiltMessage | undefined {\n const quasis = quasi.quasis as AstNode[];\n const expressions = quasi.expressions as AstNode[];\n const params: TaggedParam[] = [];\n const taken = new Map<string, string>();\n\n let text = escapeText(cookedValue(quasis[0]));\n\n for (let i = 0; i < expressions.length; i++) {\n const expr = expressions[i];\n if (!expr) return undefined;\n const source = code.slice(expr.start, expr.end);\n const name = uniqueName(deriveName(expr, i), source, taken);\n params.push({ name, start: expr.start, end: expr.end });\n text += `{${name}}` + escapeText(cookedValue(quasis[i + 1]));\n }\n return { text, params };\n}\n\nfunction cookedValue(element: AstNode | undefined): string {\n if (!element) return '';\n const value = element.value as { cooked?: string; raw: string };\n return value.cooked ?? value.raw;\n}\n\n// literal braces → escaped\nfunction escapeText(text: string): string {\n return text.replace(/[{}]/g, (m) => m + m);\n}\n\nfunction deriveName(expr: AstNode, index: number): string {\n if (expr.type === 'Identifier') return expr.name as string;\n if (expr.type === 'MemberExpression' && !expr.computed) {\n const prop = expr.property as AstNode;\n if (prop.type === 'Identifier') return prop.name as string;\n }\n return `_${index}`;\n}\n\nfunction uniqueName(base: string, source: string, taken: Map<string, string>): string {\n let name = base;\n let n = 2;\n while (taken.has(name) && taken.get(name) !== source) {\n name = `${base}${n}`;\n n += 1;\n }\n taken.set(name, source);\n return name;\n}\n\nfunction walk(node: AstNode, visit: (node: AstNode) => void): void {\n visit(node);\n for (const [key, value] of Object.entries(node)) {\n if (SKIP_KEYS.has(key)) continue;\n if (Array.isArray(value)) {\n for (const item of value) {\n if (isNode(item)) walk(item, visit);\n }\n } else if (isNode(value)) {\n walk(value, visit);\n }\n }\n}\n\nfunction isNode(value: unknown): value is AstNode {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { type?: unknown }).type === 'string'\n );\n}\n","import { createHash } from 'node:crypto';\n\nexport function stableKey(message: string): string {\n return createHash('sha256').update(message).digest('base64url').slice(0, 8);\n}\n","import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { ResolvedConfig } from './config';\n\nexport type Catalog = Record<string, string>;\nexport type Catalogs = Record<string, Catalog>;\n\nexport function catalogPath(cfg: ResolvedConfig, locale: string): string {\n return join(cfg.dir, `${locale}.json`);\n}\n\nexport function readCatalog(cfg: ResolvedConfig, locale: string): Catalog {\n try {\n return JSON.parse(readFileSync(catalogPath(cfg, locale), 'utf8')) as Catalog;\n } catch {\n return {};\n }\n}\n\nexport function loadCatalogs(cfg: ResolvedConfig): Catalogs {\n const catalogs: Catalogs = {};\n for (const locale of cfg.locales) catalogs[locale] = readCatalog(cfg, locale);\n return catalogs;\n}\n\nexport function serializeCatalog(catalog: Catalog): string {\n const sorted: Catalog = {};\n for (const key of Object.keys(catalog).sort()) {\n const value = catalog[key];\n if (value !== undefined) sorted[key] = value;\n }\n return JSON.stringify(sorted, null, 2) + '\\n';\n}\n\nexport function writeCatalog(cfg: ResolvedConfig, locale: string, catalog: Catalog): string {\n const serialized = serializeCatalog(catalog);\n mkdirSync(cfg.dir, { recursive: true });\n writeFileSync(catalogPath(cfg, locale), serialized);\n return serialized;\n}\n","import type { Catalogs } from './catalog';\nimport type { ResolvedConfig } from './config';\nimport type { MessageRegistry } from './registry';\n\nexport interface MissingEntry {\n locale: string;\n key: string;\n source?: string;\n}\n\nexport interface UnknownEntry {\n key: string;\n files: string[];\n}\n\nexport interface CheckResult {\n ok: boolean;\n missing: MissingEntry[];\n unknown: UnknownEntry[];\n}\n\nexport function check(\n cfg: ResolvedConfig,\n catalogs: Catalogs,\n registry: MessageRegistry,\n): CheckResult {\n const source = catalogs[cfg.sourceLocale] ?? {};\n const extracted = registry.messages();\n\n const unknown: UnknownEntry[] = [];\n for (const [key, files] of registry.usedKeys()) {\n const known =\n extracted.has(key) || cfg.locales.some((locale) => catalogs[locale]?.[key] !== undefined);\n if (!known) unknown.push({ key, files });\n }\n\n const needed = new Set<string>([...extracted.keys(), ...Object.keys(source)]);\n\n const missing: MissingEntry[] = [];\n for (const key of extracted.keys()) {\n if (!source[key]) {\n missing.push({ locale: cfg.sourceLocale, key, source: extracted.get(key)?.message });\n }\n }\n for (const locale of cfg.locales) {\n if (locale === cfg.sourceLocale) continue;\n for (const key of needed) {\n if (!catalogs[locale]?.[key]) {\n missing.push({ locale, key, source: source[key] ?? extracted.get(key)?.message });\n }\n }\n }\n\n return { ok: missing.length === 0 && unknown.length === 0, missing, unknown };\n}\n\nexport function formatCheckResult(result: CheckResult): string {\n const lines: string[] = [];\n if (result.missing.length > 0) {\n lines.push('missing translations:');\n for (const entry of result.missing) {\n const hint = entry.source ? ` — \"${truncate(entry.source, 40)}\"` : '';\n lines.push(` [${entry.locale}] ${entry.key}${hint}`);\n }\n }\n if (result.unknown.length > 0) {\n lines.push('unknown keys (not in any catalog):');\n for (const entry of result.unknown) {\n lines.push(` ${entry.key} — used in ${entry.files.join(', ')}`);\n }\n }\n return lines.join('\\n');\n}\n\nfunction truncate(text: string, max: number): string {\n return text.length > max ? text.slice(0, max - 1) + '…' : text;\n}\n","import { writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { Catalog } from './catalog';\nimport type { ResolvedConfig } from './config';\nimport { collectParams, renderParamType } from './params';\n\nexport const VIRTUAL_ID = 'virtual:verbaly';\n\nexport function generateRuntimeModule(cfg: ResolvedConfig): string {\n const others = cfg.locales.filter((locale) => locale !== cfg.sourceLocale);\n const src = JSON.stringify(cfg.sourceLocale);\n const loaders = others\n .map((locale) => ` ${JSON.stringify(locale)}: () => import('${VIRTUAL_ID}/locale/${locale}'),`)\n .join('\\n');\n\n return `import { createVerbaly } from 'verbaly';\nimport source from '${VIRTUAL_ID}/locale/${cfg.sourceLocale}';\n\nconst loaders = {\n${loaders}\n};\nconst loaded = new Set([${src}]);\n\nconst v = createVerbaly({\n locale: ${src},\n fallback: ${src},\n messages: { [${src}]: source },\n});\n\nexport const verbaly = v;\nexport const t = v.t;\n\nexport function getLocale() {\n return v.locale;\n}\n\nexport function subscribe(listener) {\n return v.subscribe(listener);\n}\n\nexport async function setLocale(locale) {\n const target = loaders[locale] ? locale : locale.split('-')[0];\n if (!loaded.has(target) && loaders[target]) {\n const mod = await loaders[target]();\n v.addMessages(target, mod.default);\n loaded.add(target);\n }\n v.setLocale(locale);\n}\n`;\n}\n\nexport function generateLocaleModule(catalog: Catalog): string {\n return `export default ${JSON.stringify(catalog)};\\n`;\n}\n\nexport function generateDts(entries: Map<string, string>): string {\n const lines: string[] = [];\n for (const [key, message] of [...entries.entries()].sort(([a], [b]) => a.localeCompare(b))) {\n const params = collectParams(message);\n if (params.size === 0) {\n lines.push(` ${JSON.stringify(key)}: never;`);\n } else {\n const fields = [...params.entries()]\n .map(([name, types]) => `${JSON.stringify(name)}: ${renderParamType(types)}`)\n .join('; ');\n lines.push(` ${JSON.stringify(key)}: { ${fields} };`);\n }\n }\n\n return `// generated by verbaly — do not edit\ndeclare module 'virtual:verbaly' {\n export interface VerbalyMessages {\n${lines.join('\\n')}\n }\n export type VerbalyKey = keyof VerbalyMessages & string;\n\n export const verbaly: import('verbaly').Verbaly<VerbalyKey>;\n\n export function t<K extends VerbalyKey>(\n key: K,\n ...args: [VerbalyMessages[K]] extends [never] ? [] : [VerbalyMessages[K]]\n ): string;\n export function t(strings: TemplateStringsArray, ...values: unknown[]): string;\n\n export function setLocale(locale: string): Promise<void>;\n export function getLocale(): string;\n export function subscribe(listener: () => void): () => void;\n}\n\ndeclare module 'virtual:verbaly/locale/*' {\n const messages: Record<string, string>;\n export default messages;\n}\n`;\n}\n\nexport function writeDts(cfg: ResolvedConfig, entries: Map<string, string>): void {\n writeFileSync(join(cfg.root, 'verbaly.d.ts'), generateDts(entries));\n}\n","import { parse, type MessageNode } from 'verbaly';\n\nexport type ParamType = 'number' | 'string' | 'date' | 'unknown';\n\nconst PLURAL_KEYS = new Set(['zero', 'one', 'two', 'few', 'many']);\nconst NUMBER_FORMATS = new Set(['number', 'integer', 'percent', 'currency']);\nconst DATE_FORMATS = new Set(['date', 'time']);\n\nexport function collectParams(message: string): Map<string, Set<ParamType>> {\n const out = new Map<string, Set<ParamType>>();\n visit(parse(message), out);\n return out;\n}\n\nfunction visit(nodes: MessageNode[], out: Map<string, Set<ParamType>>): void {\n for (const node of nodes) {\n if (node.kind !== 'param') continue;\n const types = out.get(node.name) ?? new Set<ParamType>();\n out.set(node.name, types);\n\n if (node.variants) {\n for (const [key, body] of node.variants) {\n if (PLURAL_KEYS.has(key) || /^=\\d+$/.test(key)) types.add('number');\n else if (key !== 'other') types.add('string');\n visit(body, out);\n }\n if (types.size === 0) types.add('string');\n } else if (node.format && NUMBER_FORMATS.has(node.format)) {\n types.add('number');\n } else if (node.format && DATE_FORMATS.has(node.format)) {\n types.add('date');\n } else {\n types.add('unknown');\n }\n }\n}\n\nexport function renderParamType(types: Set<ParamType>): string {\n if (types.has('unknown') || types.size === 0) return 'unknown';\n const parts: string[] = [];\n if (types.has('number')) parts.push('number');\n if (types.has('string')) parts.push('string');\n if (types.has('date')) parts.push('Date | number | string');\n return parts.join(' | ');\n}\n","import { existsSync, readFileSync, readdirSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nexport interface VerbalyConfig {\n root?: string;\n dir?: string;\n sourceLocale?: string;\n locales?: string[];\n include?: string[];\n exclude?: string[];\n}\n\nexport interface ResolvedConfig {\n root: string;\n dir: string;\n sourceLocale: string;\n locales: string[];\n include: string[];\n exclude: string[];\n}\n\nexport function resolveConfig(config: VerbalyConfig = {}): ResolvedConfig {\n const root = resolve(config.root ?? process.cwd());\n const dir = resolve(root, config.dir ?? 'locales');\n const sourceLocale = config.sourceLocale ?? 'en';\n\n const locales = new Set<string>([sourceLocale, ...(config.locales ?? [])]);\n if (existsSync(dir)) {\n for (const file of readdirSync(dir)) {\n if (file.endsWith('.json')) locales.add(file.slice(0, -5));\n }\n }\n\n return {\n root,\n dir,\n sourceLocale,\n locales: [...locales],\n include: config.include ?? ['src/**/*.{js,jsx,ts,tsx,mjs,mts}'],\n exclude: config.exclude ?? ['**/node_modules/**', '**/dist/**'],\n };\n}\n\nexport async function loadConfigFile(root: string): Promise<VerbalyConfig> {\n for (const name of ['verbaly.config.js', 'verbaly.config.mjs']) {\n const path = join(root, name);\n if (existsSync(path)) {\n const mod = (await import(pathToFileURL(path).href)) as { default?: VerbalyConfig };\n return mod.default ?? {};\n }\n }\n for (const name of ['verbaly.config.ts', 'verbaly.config.mts']) {\n const path = join(root, name);\n if (existsSync(path)) return loadTsConfig(path);\n }\n const jsonPath = join(root, 'verbaly.config.json');\n if (existsSync(jsonPath)) {\n return JSON.parse(readFileSync(jsonPath, 'utf8')) as VerbalyConfig;\n }\n return {};\n}\n\nasync function loadTsConfig(path: string): Promise<VerbalyConfig> {\n try {\n const { bundleRequire } = await import('bundle-require');\n const { mod } = await bundleRequire<{ default?: VerbalyConfig }>({ filepath: path });\n return mod.default ?? {};\n } catch (error) {\n if (isModuleNotFound(error, 'esbuild')) {\n throw new Error(`[verbaly] ${path} needs esbuild — install it: pnpm add -D esbuild`, {\n cause: error,\n });\n }\n throw error;\n }\n}\n\nfunction isModuleNotFound(error: unknown, name: string): boolean {\n return (\n error instanceof Error &&\n (error as { code?: string }).code === 'ERR_MODULE_NOT_FOUND' &&\n error.message.includes(name)\n );\n}\n\n// file config + inline overrides\nexport async function loadConfig(\n root: string,\n overrides: VerbalyConfig = {},\n): Promise<ResolvedConfig> {\n const fileConfig = await loadConfigFile(root);\n return resolveConfig({ ...fileConfig, ...definedOnly(overrides), root });\n}\n\nfunction definedOnly(config: VerbalyConfig): VerbalyConfig {\n return Object.fromEntries(\n Object.entries(config).filter(([, value]) => value !== undefined),\n ) as VerbalyConfig;\n}\n","import { readFileSync } from 'node:fs';\nimport { glob } from 'tinyglobby';\nimport { analyze } from './analyze';\nimport type { Catalogs } from './catalog';\nimport type { ResolvedConfig } from './config';\nimport { MessageRegistry } from './registry';\n\nexport async function extractProject(cfg: ResolvedConfig): Promise<MessageRegistry> {\n const files = await glob(cfg.include, {\n cwd: cfg.root,\n ignore: cfg.exclude,\n absolute: true,\n });\n const registry = new MessageRegistry();\n for (const file of files) {\n registry.update(file, analyze(readFileSync(file, 'utf8'), file));\n }\n return registry;\n}\n\nexport interface SyncResult {\n added: Record<string, string[]>;\n}\n\n// fills source texts + '' placeholders\nexport function syncCatalogs(\n cfg: ResolvedConfig,\n catalogs: Catalogs,\n registry: MessageRegistry,\n): SyncResult {\n const added: Record<string, string[]> = {};\n const source = (catalogs[cfg.sourceLocale] ??= {});\n\n for (const [key, msg] of registry.messages()) {\n if (source[key] !== msg.message) {\n source[key] = msg.message;\n (added[cfg.sourceLocale] ??= []).push(key);\n }\n }\n\n const needed = Object.keys(source);\n for (const locale of cfg.locales) {\n if (locale === cfg.sourceLocale) continue;\n const catalog = (catalogs[locale] ??= {});\n for (const key of needed) {\n if (catalog[key] === undefined) {\n catalog[key] = '';\n (added[locale] ??= []).push(key);\n }\n }\n }\n return { added };\n}\n\n// drops keys no longer referenced\nexport function pruneCatalogs(\n cfg: ResolvedConfig,\n catalogs: Catalogs,\n registry: MessageRegistry,\n): Record<string, string[]> {\n const keep = new Set([...registry.messages().keys(), ...registry.usedKeys().keys()]);\n const removed: Record<string, string[]> = {};\n for (const locale of cfg.locales) {\n const catalog = catalogs[locale];\n if (!catalog) continue;\n for (const key of Object.keys(catalog)) {\n if (!keep.has(key)) {\n delete catalog[key];\n (removed[locale] ??= []).push(key);\n }\n }\n }\n return removed;\n}\n","import type { Analysis, TaggedMessage } from './analyze';\n\nexport class MessageRegistry {\n private files = new Map<string, Analysis>();\n\n update(file: string, analysis: Analysis): void {\n this.files.set(file, analysis);\n }\n\n remove(file: string): void {\n this.files.delete(file);\n }\n\n messages(): Map<string, TaggedMessage> {\n const out = new Map<string, TaggedMessage>();\n for (const analysis of this.files.values()) {\n for (const msg of analysis.tagged) {\n const existing = out.get(msg.key);\n if (existing) {\n if (existing.message !== msg.message) {\n console.warn(\n `[verbaly] key collision \"${msg.key}\": ` +\n `${JSON.stringify(existing.message)} vs ${JSON.stringify(msg.message)} — second dropped.`,\n );\n }\n continue;\n }\n out.set(msg.key, msg);\n }\n }\n return out;\n }\n\n usedKeys(): Map<string, string[]> {\n const out = new Map<string, string[]>();\n for (const analysis of this.files.values()) {\n for (const used of analysis.usedKeys) {\n const files = out.get(used.key) ?? [];\n if (!files.includes(used.file)) files.push(used.file);\n out.set(used.key, files);\n }\n }\n return out;\n }\n}\n","import MagicString from 'magic-string';\nimport { analyze, type Analysis } from './analyze';\n\nexport interface TransformResult {\n code: string;\n map: ReturnType<MagicString['generateMap']>;\n}\n\nexport function transformCode(\n code: string,\n file: string,\n analysis?: Analysis,\n): TransformResult | null {\n const { tagged } = analysis ?? analyze(code, file);\n if (tagged.length === 0) return null;\n\n const s = new MagicString(code);\n for (const msg of tagged) {\n const seen = new Set<string>();\n const entries = msg.params.filter((p) => !seen.has(p.name) && seen.add(p.name));\n const pairs = entries\n .map((p) => `${JSON.stringify(p.name)}: ${code.slice(p.start, p.end)}`)\n .join(', ');\n\n if (msg.jsx) {\n const values = entries.length ? ` values={{ ${pairs} }}` : '';\n const components = msg.jsx.components.length\n ? ` components={{ ${msg.jsx.components\n .map((c) => `${JSON.stringify(c.name)}: ${c.source}`)\n .join(', ')} }}`\n : '';\n s.overwrite(\n msg.start,\n msg.end,\n `<${msg.jsx.name} id=${JSON.stringify(msg.key)}${values}${components} />`,\n );\n continue;\n }\n\n const tagSource = code.slice(msg.tagStart, msg.tagEnd);\n const args = entries.length ? `, { ${pairs} }` : '';\n s.overwrite(msg.start, msg.end, `${tagSource}(${JSON.stringify(msg.key)}${args})`);\n }\n return { code: s.toString(), map: s.generateMap({ hires: true }) };\n}\n"],"mappings":"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","names":["text","visit","writeFileSync","join","parse","writeFileSync","join","readFileSync","join","readFileSync","readFileSync"]}
|
|
1
|
+
{"version":3,"sources":["../src/analyze.ts","../src/key.ts","../src/catalog.ts","../src/check.ts","../src/codegen.ts","../src/params.ts","../src/config.ts","../src/extract.ts","../src/registry.ts","../src/providers/claude.ts","../src/transform.ts","../src/translate.ts"],"sourcesContent":["import { parse } from '@babel/parser';\r\nimport { stableKey } from './key';\r\n\r\nexport interface TaggedParam {\r\n name: string;\r\n start: number;\r\n end: number;\r\n}\r\n\r\nexport interface TransComponent {\r\n name: string;\r\n source: string;\r\n}\r\n\r\nexport interface TaggedMessage {\r\n key: string;\r\n message: string;\r\n params: TaggedParam[];\r\n start: number;\r\n end: number;\r\n tagStart: number;\r\n tagEnd: number;\r\n file: string;\r\n jsx?: { name: string; components: TransComponent[] };\r\n}\r\n\r\nexport interface UsedKey {\r\n key: string;\r\n file: string;\r\n}\r\n\r\nexport interface Analysis {\r\n tagged: TaggedMessage[];\r\n usedKeys: UsedKey[];\r\n}\r\n\r\ninterface AstNode {\r\n type: string;\r\n start: number;\r\n end: number;\r\n [key: string]: unknown;\r\n}\r\n\r\nconst SKIP_KEYS = new Set(['loc', 'leadingComments', 'trailingComments', 'innerComments', 'extra']);\r\n\r\nexport function analyze(code: string, file: string): Analysis {\r\n const jsx = /\\.[jt]sx$/.test(file);\r\n const ast = parse(code, {\r\n sourceType: 'module',\r\n errorRecovery: true,\r\n plugins: jsx ? ['typescript', 'jsx'] : ['typescript'],\r\n });\r\n\r\n const tagged: TaggedMessage[] = [];\r\n const usedKeys: UsedKey[] = [];\r\n\r\n walk(ast.program as unknown as AstNode, (node) => {\r\n if (node.type === 'TaggedTemplateExpression') {\r\n const tag = node.tag as AstNode;\r\n const explicit = explicitId(tag);\r\n if (!explicit && !isTReference(tag)) return;\r\n const quasi = node.quasi as AstNode;\r\n const message = buildMessage(code, quasi);\r\n if (!message) return;\r\n tagged.push({\r\n key: explicit ? explicit.key : stableKey(message.text),\r\n message: message.text,\r\n params: message.params,\r\n start: node.start,\r\n end: node.end,\r\n tagStart: explicit ? explicit.refStart : tag.start,\r\n tagEnd: explicit ? explicit.refEnd : tag.end,\r\n file,\r\n });\r\n } else if (node.type === 'CallExpression') {\r\n const callee = node.callee as AstNode;\r\n if (!isTReference(callee)) return;\r\n const args = node.arguments as AstNode[];\r\n const first = args[0];\r\n if (first?.type === 'StringLiteral') {\r\n usedKeys.push({ key: first.value as string, file });\r\n }\r\n } else if (node.type === 'JSXElement') {\r\n handleTrans(code, node, file, tagged, usedKeys);\r\n }\r\n });\r\n\r\n return { tagged, usedKeys };\r\n}\r\n\r\nfunction handleTrans(\r\n code: string,\r\n node: AstNode,\r\n file: string,\r\n tagged: TaggedMessage[],\r\n usedKeys: UsedKey[],\r\n): void {\r\n const opening = node.openingElement as AstNode;\r\n const nameNode = opening.name as AstNode;\r\n if (nameNode.type !== 'JSXIdentifier' || nameNode.name !== 'Trans') return;\r\n\r\n const attrs = opening.attributes as AstNode[];\r\n const idAttr = attrs.find((a) => a.type === 'JSXAttribute' && (a.name as AstNode).name === 'id');\r\n const children = node.children as AstNode[] | undefined;\r\n\r\n if (idAttr) {\r\n const value = idAttr.value as AstNode | null;\r\n if (value?.type !== 'StringLiteral') return;\r\n const id = value.value as string;\r\n // id + children → extract under the explicit key\r\n if (attrs.length === 1 && children?.length) {\r\n const built = buildTransMessage(code, children);\r\n if (built?.text.trim()) {\r\n tagged.push({\r\n key: id,\r\n message: built.text,\r\n params: built.params,\r\n start: node.start,\r\n end: node.end,\r\n tagStart: nameNode.start,\r\n tagEnd: nameNode.end,\r\n file,\r\n jsx: { name: nameNode.name as string, components: built.components },\r\n });\r\n return;\r\n }\r\n }\r\n usedKeys.push({ key: id, file });\r\n return; // runtime-first, untouched\r\n }\r\n if (attrs.length > 0) return; // hand-written props → don't guess\r\n\r\n if (!children?.length) return;\r\n\r\n const built = buildTransMessage(code, children);\r\n if (!built || !built.text.trim()) return;\r\n\r\n tagged.push({\r\n key: stableKey(built.text),\r\n message: built.text,\r\n params: built.params,\r\n start: node.start,\r\n end: node.end,\r\n tagStart: nameNode.start,\r\n tagEnd: nameNode.end,\r\n file,\r\n jsx: { name: nameNode.name as string, components: built.components },\r\n });\r\n}\r\n\r\n// t.id('key')`…` → explicit readable key\r\nfunction explicitId(tag: AstNode): { key: string; refStart: number; refEnd: number } | undefined {\r\n if (tag.type !== 'CallExpression') return undefined;\r\n const callee = tag.callee as AstNode;\r\n if (callee.type !== 'MemberExpression' || callee.computed) return undefined;\r\n const prop = callee.property as AstNode;\r\n if (prop.type !== 'Identifier' || prop.name !== 'id') return undefined;\r\n const obj = callee.object as AstNode;\r\n if (!isTReference(obj)) return undefined;\r\n const args = tag.arguments as AstNode[];\r\n const first = args[0];\r\n if (args.length !== 1 || first?.type !== 'StringLiteral') return undefined;\r\n return { key: first.value as string, refStart: obj.start, refEnd: obj.end };\r\n}\r\n\r\ninterface BuiltTrans {\r\n text: string;\r\n params: TaggedParam[];\r\n components: TransComponent[];\r\n}\r\n\r\nfunction buildTransMessage(code: string, children: AstNode[]): BuiltTrans | undefined {\r\n const params: TaggedParam[] = [];\r\n const components: TransComponent[] = [];\r\n const takenParams = new Map<string, string>();\r\n const takenTags = new Map<string, string>();\r\n\r\n function walkChildren(nodes: AstNode[]): string | undefined {\r\n let text = '';\r\n for (const child of nodes) {\r\n if (child.type === 'JSXText') {\r\n text += escapeText(cleanJsxText(child.value as string));\r\n } else if (child.type === 'JSXExpressionContainer') {\r\n const expr = child.expression as AstNode;\r\n if (expr.type === 'JSXEmptyExpression') continue;\r\n if (expr.type === 'StringLiteral') {\r\n text += escapeText(expr.value as string);\r\n continue;\r\n }\r\n if (expr.type === 'TaggedTemplateExpression') return undefined; // overlap hazard\r\n const source = code.slice(expr.start, expr.end);\r\n const name = uniqueName(deriveName(expr, params.length), source, takenParams);\r\n params.push({ name, start: expr.start, end: expr.end });\r\n text += `{${name}}`;\r\n } else if (child.type === 'JSXElement') {\r\n const opening = child.openingElement as AstNode;\r\n const nameNode = opening.name as AstNode;\r\n if (nameNode.type !== 'JSXIdentifier' || nameNode.name === 'Trans') return undefined;\r\n const source = selfClosedSource(code, opening);\r\n const tag = uniqueName((nameNode.name as string).toLowerCase(), source, takenTags);\r\n if (!components.some((c) => c.name === tag)) components.push({ name: tag, source });\r\n if (!child.closingElement) {\r\n text += `<${tag}/>`;\r\n } else {\r\n const inner = walkChildren(child.children as AstNode[]);\r\n if (inner === undefined) return undefined;\r\n text += `<${tag}>${inner}</${tag}>`;\r\n }\r\n } else {\r\n return undefined; // fragments / spread children → bail\r\n }\r\n }\r\n return text;\r\n }\r\n\r\n const text = walkChildren(children);\r\n if (text === undefined) return undefined;\r\n return { text, params, components };\r\n}\r\n\r\n// JSX text semantics: newline-indent boundaries removed, interior joins = one space\r\nfunction cleanJsxText(raw: string): string {\r\n const lines = raw.split(/\\r\\n|[\\r\\n]/);\r\n let out = '';\r\n for (let i = 0; i < lines.length; i++) {\r\n let line = lines[i]!.replace(/\\t/g, ' ');\r\n if (i !== 0) line = line.replace(/^ +/, '');\r\n if (i !== lines.length - 1) line = line.replace(/ +$/, '');\r\n if (!line) continue;\r\n if (out && i !== 0) out += ' ';\r\n out += line;\r\n }\r\n return out;\r\n}\r\n\r\nfunction selfClosedSource(code: string, opening: AstNode): string {\r\n const src = code.slice(opening.start, opening.end);\r\n return src.endsWith('/>') ? src : `${src.slice(0, -1).trimEnd()} />`;\r\n}\r\n\r\nfunction isTReference(node: AstNode): boolean {\r\n if (node.type === 'Identifier') return node.name === 't';\r\n if (node.type === 'MemberExpression' && !node.computed) {\r\n const prop = node.property as AstNode;\r\n return prop.type === 'Identifier' && prop.name === 't';\r\n }\r\n return false;\r\n}\r\n\r\ninterface BuiltMessage {\r\n text: string;\r\n params: TaggedParam[];\r\n}\r\n\r\nfunction buildMessage(code: string, quasi: AstNode): BuiltMessage | undefined {\r\n const quasis = quasi.quasis as AstNode[];\r\n const expressions = quasi.expressions as AstNode[];\r\n const params: TaggedParam[] = [];\r\n const taken = new Map<string, string>();\r\n\r\n let text = escapeText(cookedValue(quasis[0]));\r\n\r\n for (let i = 0; i < expressions.length; i++) {\r\n const expr = expressions[i];\r\n if (!expr) return undefined;\r\n const source = code.slice(expr.start, expr.end);\r\n const name = uniqueName(deriveName(expr, i), source, taken);\r\n params.push({ name, start: expr.start, end: expr.end });\r\n text += `{${name}}` + escapeText(cookedValue(quasis[i + 1]));\r\n }\r\n return { text, params };\r\n}\r\n\r\nfunction cookedValue(element: AstNode | undefined): string {\r\n if (!element) return '';\r\n const value = element.value as { cooked?: string; raw: string };\r\n return value.cooked ?? value.raw;\r\n}\r\n\r\n// literal braces → escaped\r\nfunction escapeText(text: string): string {\r\n return text.replace(/[{}]/g, (m) => m + m);\r\n}\r\n\r\nfunction deriveName(expr: AstNode, index: number): string {\r\n if (expr.type === 'Identifier') return expr.name as string;\r\n if (expr.type === 'MemberExpression' && !expr.computed) {\r\n const prop = expr.property as AstNode;\r\n if (prop.type === 'Identifier') return prop.name as string;\r\n }\r\n return `_${index}`;\r\n}\r\n\r\nfunction uniqueName(base: string, source: string, taken: Map<string, string>): string {\r\n let name = base;\r\n let n = 2;\r\n while (taken.has(name) && taken.get(name) !== source) {\r\n name = `${base}${n}`;\r\n n += 1;\r\n }\r\n taken.set(name, source);\r\n return name;\r\n}\r\n\r\nfunction walk(node: AstNode, visit: (node: AstNode) => void): void {\r\n visit(node);\r\n for (const [key, value] of Object.entries(node)) {\r\n if (SKIP_KEYS.has(key)) continue;\r\n if (Array.isArray(value)) {\r\n for (const item of value) {\r\n if (isNode(item)) walk(item, visit);\r\n }\r\n } else if (isNode(value)) {\r\n walk(value, visit);\r\n }\r\n }\r\n}\r\n\r\nfunction isNode(value: unknown): value is AstNode {\r\n return (\r\n typeof value === 'object' &&\r\n value !== null &&\r\n typeof (value as { type?: unknown }).type === 'string'\r\n );\r\n}\r\n","import { createHash } from 'node:crypto';\n\nexport function stableKey(message: string): string {\n return createHash('sha256').update(message).digest('base64url').slice(0, 8);\n}\n","import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { ResolvedConfig } from './config';\n\nexport type Catalog = Record<string, string>;\nexport type Catalogs = Record<string, Catalog>;\n\nexport function catalogPath(cfg: ResolvedConfig, locale: string): string {\n return join(cfg.dir, `${locale}.json`);\n}\n\nexport function readCatalog(cfg: ResolvedConfig, locale: string): Catalog {\n try {\n return JSON.parse(readFileSync(catalogPath(cfg, locale), 'utf8')) as Catalog;\n } catch {\n return {};\n }\n}\n\nexport function loadCatalogs(cfg: ResolvedConfig): Catalogs {\n const catalogs: Catalogs = {};\n for (const locale of cfg.locales) catalogs[locale] = readCatalog(cfg, locale);\n return catalogs;\n}\n\nexport function serializeCatalog(catalog: Catalog): string {\n const sorted: Catalog = {};\n for (const key of Object.keys(catalog).sort()) {\n const value = catalog[key];\n if (value !== undefined) sorted[key] = value;\n }\n return JSON.stringify(sorted, null, 2) + '\\n';\n}\n\nexport function writeCatalog(cfg: ResolvedConfig, locale: string, catalog: Catalog): string {\n const serialized = serializeCatalog(catalog);\n mkdirSync(cfg.dir, { recursive: true });\n writeFileSync(catalogPath(cfg, locale), serialized);\n return serialized;\n}\n","import type { Catalogs } from './catalog';\nimport type { ResolvedConfig } from './config';\nimport type { MessageRegistry } from './registry';\n\nexport interface MissingEntry {\n locale: string;\n key: string;\n source?: string;\n}\n\nexport interface UnknownEntry {\n key: string;\n files: string[];\n}\n\nexport interface CheckResult {\n ok: boolean;\n missing: MissingEntry[];\n unknown: UnknownEntry[];\n}\n\nexport function check(\n cfg: ResolvedConfig,\n catalogs: Catalogs,\n registry: MessageRegistry,\n): CheckResult {\n const source = catalogs[cfg.sourceLocale] ?? {};\n const extracted = registry.messages();\n\n const unknown: UnknownEntry[] = [];\n for (const [key, files] of registry.usedKeys()) {\n const known =\n extracted.has(key) || cfg.locales.some((locale) => catalogs[locale]?.[key] !== undefined);\n if (!known) unknown.push({ key, files });\n }\n\n const needed = new Set<string>([...extracted.keys(), ...Object.keys(source)]);\n\n const missing: MissingEntry[] = [];\n for (const key of extracted.keys()) {\n if (!source[key]) {\n missing.push({ locale: cfg.sourceLocale, key, source: extracted.get(key)?.message });\n }\n }\n for (const locale of cfg.locales) {\n if (locale === cfg.sourceLocale) continue;\n for (const key of needed) {\n if (!catalogs[locale]?.[key]) {\n missing.push({ locale, key, source: source[key] ?? extracted.get(key)?.message });\n }\n }\n }\n\n return { ok: missing.length === 0 && unknown.length === 0, missing, unknown };\n}\n\nexport function formatCheckResult(result: CheckResult): string {\n const lines: string[] = [];\n if (result.missing.length > 0) {\n lines.push('missing translations:');\n for (const entry of result.missing) {\n const hint = entry.source ? ` — \"${truncate(entry.source, 40)}\"` : '';\n lines.push(` [${entry.locale}] ${entry.key}${hint}`);\n }\n }\n if (result.unknown.length > 0) {\n lines.push('unknown keys (not in any catalog):');\n for (const entry of result.unknown) {\n lines.push(` ${entry.key} — used in ${entry.files.join(', ')}`);\n }\n }\n return lines.join('\\n');\n}\n\nfunction truncate(text: string, max: number): string {\n return text.length > max ? text.slice(0, max - 1) + '…' : text;\n}\n","import { writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { Catalog } from './catalog';\nimport type { ResolvedConfig } from './config';\nimport { collectParams, renderParamType } from './params';\n\nexport const VIRTUAL_ID = 'virtual:verbaly';\n\nexport function generateRuntimeModule(cfg: ResolvedConfig): string {\n const others = cfg.locales.filter((locale) => locale !== cfg.sourceLocale);\n const src = JSON.stringify(cfg.sourceLocale);\n const loaders = others\n .map(\n (locale) => ` ${JSON.stringify(locale)}: () => import('${VIRTUAL_ID}/locale/${locale}'),`,\n )\n .join('\\n');\n\n return `import { createVerbaly } from 'verbaly';\nimport source from '${VIRTUAL_ID}/locale/${cfg.sourceLocale}';\n\nconst v = createVerbaly({\n locale: ${src},\n fallback: ${src},\n messages: { [${src}]: source },\n loaders: {\n${loaders}\n },\n});\n\nexport const verbaly = v;\nexport const t = v.t;\n\nexport function getLocale() {\n return v.locale;\n}\n\nexport function subscribe(listener) {\n return v.subscribe(listener);\n}\n\nexport async function setLocale(locale) {\n await v.loadLocale(locale);\n v.setLocale(locale);\n}\n`;\n}\n\nexport function generateLocaleModule(catalog: Catalog): string {\n return `export default ${JSON.stringify(catalog)};\\n`;\n}\n\nexport function generateDts(entries: Map<string, string>): string {\n const lines: string[] = [];\n for (const [key, message] of [...entries.entries()].sort(([a], [b]) => a.localeCompare(b))) {\n const params = collectParams(message);\n if (params.size === 0) {\n lines.push(` ${JSON.stringify(key)}: never;`);\n } else {\n const fields = [...params.entries()]\n .map(([name, types]) => `${JSON.stringify(name)}: ${renderParamType(types)}`)\n .join('; ');\n lines.push(` ${JSON.stringify(key)}: { ${fields} };`);\n }\n }\n\n return `// generated by verbaly — do not edit\ndeclare module 'virtual:verbaly' {\n export interface VerbalyMessages {\n${lines.join('\\n')}\n }\n export type VerbalyKey = keyof VerbalyMessages & string;\n\n export const verbaly: import('verbaly').Verbaly<VerbalyKey>;\n\n export function t<K extends VerbalyKey>(\n key: K,\n ...args: [VerbalyMessages[K]] extends [never] ? [] : [VerbalyMessages[K]]\n ): string;\n export function t(strings: TemplateStringsArray, ...values: unknown[]): string;\n export namespace t {\n export function id(\n key: string,\n ): (strings: TemplateStringsArray, ...values: unknown[]) => string;\n }\n\n export function setLocale(locale: string): Promise<void>;\n export function getLocale(): string;\n export function subscribe(listener: () => void): () => void;\n}\n\ndeclare module 'virtual:verbaly/locale/*' {\n const messages: Record<string, string>;\n export default messages;\n}\n`;\n}\n\nexport function writeDts(cfg: ResolvedConfig, entries: Map<string, string>): void {\n writeFileSync(join(cfg.root, 'verbaly.d.ts'), generateDts(entries));\n}\n","import { parse, type MessageNode } from 'verbaly';\n\nexport type ParamType = 'number' | 'string' | 'date' | 'unknown';\n\nconst PLURAL_KEYS = new Set(['zero', 'one', 'two', 'few', 'many']);\nconst NUMBER_FORMATS = new Set(['number', 'integer', 'percent', 'currency']);\nconst DATE_FORMATS = new Set(['date', 'time']);\n\nexport function collectParams(message: string): Map<string, Set<ParamType>> {\n const out = new Map<string, Set<ParamType>>();\n visit(parse(message), out);\n return out;\n}\n\nfunction visit(nodes: MessageNode[], out: Map<string, Set<ParamType>>): void {\n for (const node of nodes) {\n if (node.kind !== 'param') continue;\n const types = out.get(node.name) ?? new Set<ParamType>();\n out.set(node.name, types);\n\n if (node.variants) {\n for (const [key, body] of node.variants) {\n if (PLURAL_KEYS.has(key) || /^=\\d+$/.test(key)) types.add('number');\n else if (key !== 'other') types.add('string');\n visit(body, out);\n }\n if (types.size === 0) types.add('string');\n } else if (node.format && NUMBER_FORMATS.has(node.format)) {\n types.add('number');\n } else if (node.format && DATE_FORMATS.has(node.format)) {\n types.add('date');\n } else {\n types.add('unknown');\n }\n }\n}\n\nexport function renderParamType(types: Set<ParamType>): string {\n if (types.has('unknown') || types.size === 0) return 'unknown';\n const parts: string[] = [];\n if (types.has('number')) parts.push('number');\n if (types.has('string')) parts.push('string');\n if (types.has('date')) parts.push('Date | number | string');\n return parts.join(' | ');\n}\n","import { existsSync, readFileSync, readdirSync } from 'node:fs';\r\nimport { join, resolve } from 'node:path';\r\nimport { pathToFileURL } from 'node:url';\r\nimport type { TranslateProvider } from './translate';\r\n\r\nexport interface TranslateConfig {\r\n provider?: 'claude' | TranslateProvider;\r\n model?: string;\r\n batchSize?: number;\r\n}\r\n\r\nexport interface VerbalyConfig {\r\n root?: string;\r\n dir?: string;\r\n sourceLocale?: string;\r\n locales?: string[];\r\n include?: string[];\r\n exclude?: string[];\r\n translate?: TranslateConfig;\r\n}\r\n\r\nexport interface ResolvedConfig {\r\n root: string;\r\n dir: string;\r\n sourceLocale: string;\r\n locales: string[];\r\n include: string[];\r\n exclude: string[];\r\n translate: TranslateConfig;\r\n}\r\n\r\nexport function resolveConfig(config: VerbalyConfig = {}): ResolvedConfig {\r\n const root = resolve(config.root ?? process.cwd());\r\n const dir = resolve(root, config.dir ?? 'locales');\r\n const sourceLocale = config.sourceLocale ?? 'en';\r\n\r\n const locales = new Set<string>([sourceLocale, ...(config.locales ?? [])]);\r\n if (existsSync(dir)) {\r\n for (const file of readdirSync(dir)) {\r\n if (file.endsWith('.json')) locales.add(file.slice(0, -5));\r\n }\r\n }\r\n\r\n return {\r\n root,\r\n dir,\r\n sourceLocale,\r\n locales: [...locales],\r\n include: config.include ?? ['src/**/*.{js,jsx,ts,tsx,mjs,mts}'],\r\n exclude: config.exclude ?? ['**/node_modules/**', '**/dist/**'],\r\n translate: config.translate ?? {},\r\n };\r\n}\r\n\r\nexport async function loadConfigFile(root: string): Promise<VerbalyConfig> {\r\n for (const name of ['verbaly.config.js', 'verbaly.config.mjs']) {\r\n const path = join(root, name);\r\n if (existsSync(path)) {\r\n const mod = (await import(pathToFileURL(path).href)) as { default?: VerbalyConfig };\r\n return mod.default ?? {};\r\n }\r\n }\r\n for (const name of ['verbaly.config.ts', 'verbaly.config.mts']) {\r\n const path = join(root, name);\r\n if (existsSync(path)) return loadTsConfig(path);\r\n }\r\n const jsonPath = join(root, 'verbaly.config.json');\r\n if (existsSync(jsonPath)) {\r\n return JSON.parse(readFileSync(jsonPath, 'utf8')) as VerbalyConfig;\r\n }\r\n return {};\r\n}\r\n\r\nasync function loadTsConfig(path: string): Promise<VerbalyConfig> {\r\n try {\r\n const { bundleRequire } = await import('bundle-require');\r\n const { mod } = await bundleRequire<{ default?: VerbalyConfig }>({ filepath: path });\r\n return mod.default ?? {};\r\n } catch (error) {\r\n if (isModuleNotFound(error, 'esbuild')) {\r\n throw new Error(`[verbaly] ${path} needs esbuild — install it: pnpm add -D esbuild`, {\r\n cause: error,\r\n });\r\n }\r\n throw error;\r\n }\r\n}\r\n\r\nfunction isModuleNotFound(error: unknown, name: string): boolean {\r\n return (\r\n error instanceof Error &&\r\n (error as { code?: string }).code === 'ERR_MODULE_NOT_FOUND' &&\r\n error.message.includes(name)\r\n );\r\n}\r\n\r\n// file config + inline overrides\r\nexport async function loadConfig(\r\n root: string,\r\n overrides: VerbalyConfig = {},\r\n): Promise<ResolvedConfig> {\r\n const fileConfig = await loadConfigFile(root);\r\n return resolveConfig({ ...fileConfig, ...definedOnly(overrides), root });\r\n}\r\n\r\nfunction definedOnly(config: VerbalyConfig): VerbalyConfig {\r\n return Object.fromEntries(\r\n Object.entries(config).filter(([, value]) => value !== undefined),\r\n ) as VerbalyConfig;\r\n}\r\n","import { readFileSync } from 'node:fs';\nimport { glob } from 'tinyglobby';\nimport { analyze } from './analyze';\nimport type { Catalogs } from './catalog';\nimport type { ResolvedConfig } from './config';\nimport { MessageRegistry } from './registry';\n\nexport async function extractProject(cfg: ResolvedConfig): Promise<MessageRegistry> {\n const files = await glob(cfg.include, {\n cwd: cfg.root,\n ignore: cfg.exclude,\n absolute: true,\n });\n const registry = new MessageRegistry();\n for (const file of files) {\n registry.update(file, analyze(readFileSync(file, 'utf8'), file));\n }\n return registry;\n}\n\nexport interface SyncResult {\n added: Record<string, string[]>;\n}\n\n// fills source texts + '' placeholders\nexport function syncCatalogs(\n cfg: ResolvedConfig,\n catalogs: Catalogs,\n registry: MessageRegistry,\n): SyncResult {\n const added: Record<string, string[]> = {};\n const source = (catalogs[cfg.sourceLocale] ??= {});\n\n for (const [key, msg] of registry.messages()) {\n if (source[key] !== msg.message) {\n source[key] = msg.message;\n (added[cfg.sourceLocale] ??= []).push(key);\n }\n }\n\n const needed = Object.keys(source);\n for (const locale of cfg.locales) {\n if (locale === cfg.sourceLocale) continue;\n const catalog = (catalogs[locale] ??= {});\n for (const key of needed) {\n if (catalog[key] === undefined) {\n catalog[key] = '';\n (added[locale] ??= []).push(key);\n }\n }\n }\n return { added };\n}\n\n// drops keys no longer referenced\nexport function pruneCatalogs(\n cfg: ResolvedConfig,\n catalogs: Catalogs,\n registry: MessageRegistry,\n): Record<string, string[]> {\n const keep = new Set([...registry.messages().keys(), ...registry.usedKeys().keys()]);\n const removed: Record<string, string[]> = {};\n for (const locale of cfg.locales) {\n const catalog = catalogs[locale];\n if (!catalog) continue;\n for (const key of Object.keys(catalog)) {\n if (!keep.has(key)) {\n delete catalog[key];\n (removed[locale] ??= []).push(key);\n }\n }\n }\n return removed;\n}\n","import type { Analysis, TaggedMessage } from './analyze';\n\nexport class MessageRegistry {\n private files = new Map<string, Analysis>();\n\n update(file: string, analysis: Analysis): void {\n this.files.set(file, analysis);\n }\n\n remove(file: string): void {\n this.files.delete(file);\n }\n\n messages(): Map<string, TaggedMessage> {\n const out = new Map<string, TaggedMessage>();\n for (const analysis of this.files.values()) {\n for (const msg of analysis.tagged) {\n const existing = out.get(msg.key);\n if (existing) {\n if (existing.message !== msg.message) {\n console.warn(\n `[verbaly] key collision \"${msg.key}\": ` +\n `${JSON.stringify(existing.message)} vs ${JSON.stringify(msg.message)} — second dropped.`,\n );\n }\n continue;\n }\n out.set(msg.key, msg);\n }\n }\n return out;\n }\n\n usedKeys(): Map<string, string[]> {\n const out = new Map<string, string[]>();\n for (const analysis of this.files.values()) {\n for (const used of analysis.usedKeys) {\n const files = out.get(used.key) ?? [];\n if (!files.includes(used.file)) files.push(used.file);\n out.set(used.key, files);\n }\n }\n return out;\n }\n}\n","import type { TranslateProvider, TranslateRequest } from '../translate';\r\n\r\nexport interface ClaudeProviderOptions {\r\n model?: string;\r\n apiKey?: string;\r\n maxTokens?: number;\r\n}\r\n\r\n// balanced default for translation\r\nconst DEFAULT_MODEL = 'claude-sonnet-5';\r\n\r\nconst SYSTEM = `You translate UI strings for the Verbaly i18n library.\r\nRules:\r\n- Translate only the human-readable text, naturally for the target locale.\r\n- Preserve verbatim: placeholders like {name}, format specs like {price:currency/EUR}, variant blocks like {count | one: ... | other: # ...} (translate only the text inside each variant, keep keys and # as-is), ICU syntax, named tags like <em>...</em> and escapes {{ }} || ##.\r\n- Keys are opaque identifiers: return exactly the same keys, never translate or rename them.`;\r\n\r\nexport function claudeProvider(options: ClaudeProviderOptions = {}): TranslateProvider {\r\n return async (request: TranslateRequest) => {\r\n const Anthropic = await loadSdk();\r\n const client = new Anthropic(options.apiKey ? { apiKey: options.apiKey } : {});\r\n const response = await client.messages.create({\r\n model: options.model ?? DEFAULT_MODEL,\r\n max_tokens: options.maxTokens ?? 16000,\r\n thinking: { type: 'disabled' },\r\n system: SYSTEM,\r\n messages: [{ role: 'user', content: buildPrompt(request) }],\r\n output_config: { format: batchFormat(request) },\r\n });\r\n const text = response.content.find((block) => block.type === 'text')?.text ?? '{}';\r\n return JSON.parse(text) as Record<string, string>;\r\n };\r\n}\r\n\r\nexport function buildPrompt(request: TranslateRequest): string {\r\n return (\r\n `Translate each value from \"${request.sourceLocale}\" to \"${request.targetLocale}\". ` +\r\n `Return a JSON object with the same keys and translated values.\\n\\n` +\r\n JSON.stringify(request.messages, null, 2)\r\n );\r\n}\r\n\r\nexport function batchFormat(request: TranslateRequest) {\r\n const keys = Object.keys(request.messages);\r\n return {\r\n type: 'json_schema' as const,\r\n schema: {\r\n type: 'object',\r\n properties: Object.fromEntries(keys.map((key) => [key, { type: 'string' }])),\r\n required: keys,\r\n additionalProperties: false,\r\n },\r\n };\r\n}\r\n\r\nasync function loadSdk(): Promise<typeof import('@anthropic-ai/sdk').default> {\r\n try {\r\n const mod = await import('@anthropic-ai/sdk');\r\n return mod.default;\r\n } catch (error) {\r\n if (isModuleNotFound(error, '@anthropic-ai/sdk')) {\r\n throw new Error(\r\n '[verbaly] the claude translate provider needs @anthropic-ai/sdk — install it as a dev dependency (e.g. `pnpm add -D @anthropic-ai/sdk` / `npm i -D @anthropic-ai/sdk`) and set ANTHROPIC_API_KEY',\r\n { cause: error },\r\n );\r\n }\r\n throw error;\r\n }\r\n}\r\n\r\nfunction isModuleNotFound(error: unknown, name: string): boolean {\r\n return (\r\n error instanceof Error &&\r\n (error as { code?: string }).code === 'ERR_MODULE_NOT_FOUND' &&\r\n error.message.includes(name)\r\n );\r\n}\r\n","import MagicString from 'magic-string';\r\nimport { analyze, type Analysis } from './analyze';\r\n\r\nexport interface TransformResult {\r\n code: string;\r\n map: ReturnType<MagicString['generateMap']>;\r\n}\r\n\r\nexport function transformCode(\r\n code: string,\r\n file: string,\r\n analysis?: Analysis,\r\n): TransformResult | null {\r\n const { tagged } = analysis ?? analyze(code, file);\r\n if (tagged.length === 0) return null;\r\n\r\n const s = new MagicString(code);\r\n for (const msg of tagged) {\r\n const seen = new Set<string>();\r\n const entries = msg.params.filter((p) => !seen.has(p.name) && seen.add(p.name));\r\n const pairs = entries\r\n .map((p) => `${JSON.stringify(p.name)}: ${code.slice(p.start, p.end)}`)\r\n .join(', ');\r\n\r\n if (msg.jsx) {\r\n const values = entries.length ? ` values={{ ${pairs} }}` : '';\r\n const components = msg.jsx.components.length\r\n ? ` components={{ ${msg.jsx.components\r\n .map((c) => `${JSON.stringify(c.name)}: ${c.source}`)\r\n .join(', ')} }}`\r\n : '';\r\n s.overwrite(\r\n msg.start,\r\n msg.end,\r\n `<${msg.jsx.name} id=${JSON.stringify(msg.key)}${values}${components} />`,\r\n );\r\n continue;\r\n }\r\n\r\n const tagSource = code.slice(msg.tagStart, msg.tagEnd);\r\n const args = entries.length ? `, { ${pairs} }` : '';\r\n s.overwrite(msg.start, msg.end, `${tagSource}(${JSON.stringify(msg.key)}${args})`);\r\n }\r\n return { code: s.toString(), map: s.generateMap({ hires: true }) };\r\n}\r\n","import type { Catalogs } from './catalog';\nimport type { ResolvedConfig } from './config';\nimport { collectParams } from './params';\n\nexport interface TranslateRequest {\n sourceLocale: string;\n targetLocale: string;\n messages: Record<string, string>;\n}\n\nexport type TranslateProvider = (request: TranslateRequest) => Promise<Record<string, string>>;\n\nexport interface TranslateOptions {\n locales?: string[];\n batchSize?: number;\n dryRun?: boolean;\n}\n\nexport interface TranslateResult {\n translated: Record<string, string[]>;\n invalid: Record<string, string[]>;\n pending: Record<string, string[]>;\n}\n\n// fills '' entries via the provider; invalid translations stay '' (check keeps failing)\nexport async function translateCatalogs(\n cfg: ResolvedConfig,\n catalogs: Catalogs,\n provider: TranslateProvider,\n options: TranslateOptions = {},\n): Promise<TranslateResult> {\n const batchSize = options.batchSize ?? 20;\n const targets = (options.locales ?? cfg.locales).filter(\n (locale) => locale !== cfg.sourceLocale && cfg.locales.includes(locale),\n );\n const source = catalogs[cfg.sourceLocale] ?? {};\n const result: TranslateResult = { translated: {}, invalid: {}, pending: {} };\n\n for (const locale of targets) {\n const catalog = (catalogs[locale] ??= {});\n const missing = Object.keys(source).filter((key) => source[key] && !catalog[key]);\n if (missing.length === 0) continue;\n\n if (options.dryRun) {\n result.pending[locale] = missing;\n continue;\n }\n\n for (let i = 0; i < missing.length; i += batchSize) {\n const keys = missing.slice(i, i + batchSize);\n const messages = Object.fromEntries(keys.map((key) => [key, source[key]!]));\n const out = await provider({\n sourceLocale: cfg.sourceLocale,\n targetLocale: locale,\n messages,\n });\n for (const key of keys) {\n const text = out[key];\n if (typeof text === 'string' && text.trim() && structureMatches(source[key]!, text)) {\n catalog[key] = text;\n (result.translated[locale] ??= []).push(key);\n } else {\n (result.invalid[locale] ??= []).push(key);\n }\n }\n }\n }\n return result;\n}\n\n// params and tags must survive translation verbatim\nexport function structureMatches(source: string, translated: string): boolean {\n return sameMembers(paramNames(source), paramNames(translated)) && sameMembers(tagTokens(source), tagTokens(translated));\n}\n\nfunction paramNames(message: string): string[] {\n try {\n return [...collectParams(message).keys()].sort();\n } catch {\n return ['\u0000invalid'];\n }\n}\n\nconst TAG = /<(\\/?)([a-zA-Z][\\w-]*)(\\/?)>/g;\n\nfunction tagTokens(message: string): string[] {\n const out: string[] = [];\n for (const match of message.matchAll(TAG)) {\n out.push(`${match[1]}${match[2]}${match[3]}`);\n }\n return out.sort();\n}\n\nfunction sameMembers(a: string[], b: string[]): boolean {\n return a.length === b.length && a.every((value, i) => value === b[i]);\n}\n"],"mappings":"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","names":["built","text","visit","writeFileSync","join","parse","writeFileSync","join","readFileSync","join","readFileSync","readFileSync","isModuleNotFound"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@verbaly/compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Message extraction and type-safe codegen for Verbaly.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"i18n",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"url": "git+https://github.com/AronSoto/verbaly.git",
|
|
16
16
|
"directory": "packages/compiler"
|
|
17
17
|
},
|
|
18
|
-
"homepage": "https://
|
|
18
|
+
"homepage": "https://verbaly-web.vercel.app/docs/cli",
|
|
19
19
|
"bugs": {
|
|
20
20
|
"url": "https://github.com/AronSoto/verbaly/issues"
|
|
21
21
|
},
|
|
@@ -46,17 +46,22 @@
|
|
|
46
46
|
"bundle-require": "^5.1.0",
|
|
47
47
|
"magic-string": "^0.30.21",
|
|
48
48
|
"tinyglobby": "^0.2.17",
|
|
49
|
-
"verbaly": "0.
|
|
49
|
+
"verbaly": "0.9.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
+
"@anthropic-ai/sdk": ">=0.110.0",
|
|
52
53
|
"esbuild": ">=0.18"
|
|
53
54
|
},
|
|
54
55
|
"peerDependenciesMeta": {
|
|
56
|
+
"@anthropic-ai/sdk": {
|
|
57
|
+
"optional": true
|
|
58
|
+
},
|
|
55
59
|
"esbuild": {
|
|
56
60
|
"optional": true
|
|
57
61
|
}
|
|
58
62
|
},
|
|
59
63
|
"devDependencies": {
|
|
64
|
+
"@anthropic-ai/sdk": "^0.110.0",
|
|
60
65
|
"esbuild": "^0.28.1"
|
|
61
66
|
},
|
|
62
67
|
"scripts": {
|