@verbaly/compiler 0.20.0 → 0.21.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/dist/{claude-mpjdqqnY.js → claude-C-ETlqsW.js} +2 -2
- package/dist/{claude-mpjdqqnY.js.map → claude-C-ETlqsW.js.map} +1 -1
- package/dist/cli.js +69 -70
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +18 -11
- package/dist/index.js +101 -93
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -148,17 +148,31 @@ interface RuntimeModuleOptions {
|
|
|
148
148
|
}
|
|
149
149
|
declare function generateRuntimeModule(cfg: ResolvedConfig, options?: RuntimeModuleOptions): string;
|
|
150
150
|
declare function generateLocaleModule(catalog: Catalog): string;
|
|
151
|
-
declare function generateDts(
|
|
152
|
-
declare function writeDts(cfg: ResolvedConfig,
|
|
151
|
+
declare function generateDts(catalog: Catalog): string;
|
|
152
|
+
declare function writeDts(cfg: ResolvedConfig, catalog: Catalog): void;
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/transform.d.ts
|
|
155
|
+
interface TransformResult {
|
|
156
|
+
code: string;
|
|
157
|
+
map: ReturnType<MagicString['generateMap']>;
|
|
158
|
+
}
|
|
159
|
+
declare function transformCode(code: string, file: string, analysis?: Analysis): TransformResult | null;
|
|
153
160
|
//#endregion
|
|
154
161
|
//#region src/plugin.d.ts
|
|
162
|
+
interface PluginOptions extends VerbalyConfig {
|
|
163
|
+
failOnMissing?: boolean;
|
|
164
|
+
}
|
|
155
165
|
declare const RESOLVED_VIRTUAL_ID: string;
|
|
156
166
|
declare const LOCALE_MODULE_PREFIX = "\0virtual:verbaly/locale/";
|
|
157
167
|
declare const SOURCE_FILE_RE: RegExp;
|
|
158
168
|
declare function resolveVirtualId(id: string): string | undefined;
|
|
159
169
|
declare function loadVirtualModule(id: string, cfg: ResolvedConfig, catalogs: Catalogs): string | undefined;
|
|
160
170
|
declare function isTransformTarget(id: string): boolean;
|
|
161
|
-
declare function
|
|
171
|
+
declare function transformSource(code: string, id: string, registry: MessageRegistry): {
|
|
172
|
+
analysis: Analysis;
|
|
173
|
+
result: TransformResult | null;
|
|
174
|
+
};
|
|
175
|
+
declare function runBuildGate(cfg: ResolvedConfig, registry: MessageRegistry, failOnMissing?: boolean): void;
|
|
162
176
|
//#endregion
|
|
163
177
|
//#region src/doctor.d.ts
|
|
164
178
|
interface DoctorEntry {
|
|
@@ -295,12 +309,5 @@ interface RenderSiteResult {
|
|
|
295
309
|
}
|
|
296
310
|
declare function renderSite(cfg: ResolvedConfig, options?: RenderSiteOptions): Promise<RenderSiteResult>;
|
|
297
311
|
//#endregion
|
|
298
|
-
|
|
299
|
-
interface TransformResult {
|
|
300
|
-
code: string;
|
|
301
|
-
map: ReturnType<MagicString['generateMap']>;
|
|
302
|
-
}
|
|
303
|
-
declare function transformCode(code: string, file: string, analysis?: Analysis): TransformResult | null;
|
|
304
|
-
//#endregion
|
|
305
|
-
export { type Alternate, type Analysis, type AnalyzeOptions, type Catalog, type Catalogs, type CheckResult, type ClaudeProviderOptions, type DoctorEntry, type DoctorResult, type ExchangeFormat, type ExportOptions, type ExportResult, type ExportedFile, type ImportOptions, type ImportResult, type InitOptions, type InitResult, LOCALE_MODULE_PREFIX, MessageRegistry, type MissingEntry, PSEUDO_LOCALE, type ParamType, RESOLVED_VIRTUAL_ID, type RenderConfig, type RenderHtmlOptions, type RenderHtmlResult, type RenderSiteOptions, type RenderSiteResult, type ResolvedConfig, type RuntimeModuleOptions, SFC_FILE_RE, SOURCE_FILE_RE, 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, analyzeFile, analyzeSfc, catalogPath, check, claudeProvider, collectParams, detectBundler, doctor, exportCatalogs, extractProject, findConfigFile, formatCheckResult, generateDts, generateLocaleModule, generateRuntimeModule, importCatalogs, init, isTransformTarget, loadCatalogs, loadConfig, loadConfigFile, loadVirtualModule, parseExchangeFile, pruneCatalogs, pseudoCatalogs, pseudoLocalize, readCatalog, renderHtml, renderParamType, renderSite, resolveConfig, resolveVirtualId, runBuildGate, serializeCatalog, stableKey, structureMatches, syncCatalogs, targetLocales, transformCode, translateCatalogs, writeCatalog, writeDts };
|
|
312
|
+
export { type Alternate, type Analysis, type AnalyzeOptions, type Catalog, type Catalogs, type CheckResult, type ClaudeProviderOptions, type DoctorEntry, type DoctorResult, type ExchangeFormat, type ExportOptions, type ExportResult, type ExportedFile, type ImportOptions, type ImportResult, type InitOptions, type InitResult, LOCALE_MODULE_PREFIX, MessageRegistry, type MissingEntry, PSEUDO_LOCALE, type ParamType, type PluginOptions, RESOLVED_VIRTUAL_ID, type RenderConfig, type RenderHtmlOptions, type RenderHtmlResult, type RenderSiteOptions, type RenderSiteResult, type ResolvedConfig, type RuntimeModuleOptions, SFC_FILE_RE, SOURCE_FILE_RE, 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, analyzeFile, analyzeSfc, catalogPath, check, claudeProvider, collectParams, detectBundler, doctor, exportCatalogs, extractProject, findConfigFile, formatCheckResult, generateDts, generateLocaleModule, generateRuntimeModule, importCatalogs, init, isTransformTarget, loadCatalogs, loadConfig, loadConfigFile, loadVirtualModule, parseExchangeFile, pruneCatalogs, pseudoCatalogs, pseudoLocalize, readCatalog, renderHtml, renderParamType, renderSite, resolveConfig, resolveVirtualId, runBuildGate, serializeCatalog, stableKey, structureMatches, syncCatalogs, targetLocales, transformCode, transformSource, translateCatalogs, writeCatalog, writeDts };
|
|
306
313
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -4,8 +4,8 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync
|
|
|
4
4
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
5
5
|
import { RICH_TAGS, createVerbaly, parse as parse$1, parseTags, safeHref } from "verbaly";
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
|
-
import { glob } from "tinyglobby";
|
|
8
7
|
import MagicString from "magic-string";
|
|
8
|
+
import { glob } from "tinyglobby";
|
|
9
9
|
//#region src/key.ts
|
|
10
10
|
function stableKey(message) {
|
|
11
11
|
return createHash("sha256").update(message).digest("base64url").slice(0, 8);
|
|
@@ -69,6 +69,20 @@ function handleTrans(code, node, file, tagged, usedKeys, names) {
|
|
|
69
69
|
const attrs = opening.attributes;
|
|
70
70
|
const idAttr = attrs.find((a) => a.type === "JSXAttribute" && a.name.name === "id");
|
|
71
71
|
const children = node.children;
|
|
72
|
+
const push = (key, built) => tagged.push({
|
|
73
|
+
key,
|
|
74
|
+
message: built.text,
|
|
75
|
+
params: built.params,
|
|
76
|
+
start: node.start,
|
|
77
|
+
end: node.end,
|
|
78
|
+
tagStart: nameNode.start,
|
|
79
|
+
tagEnd: nameNode.end,
|
|
80
|
+
file,
|
|
81
|
+
jsx: {
|
|
82
|
+
name: nameNode.name,
|
|
83
|
+
components: built.components
|
|
84
|
+
}
|
|
85
|
+
});
|
|
72
86
|
if (idAttr) {
|
|
73
87
|
const value = idAttr.value;
|
|
74
88
|
if (value?.type !== "StringLiteral") return;
|
|
@@ -76,20 +90,7 @@ function handleTrans(code, node, file, tagged, usedKeys, names) {
|
|
|
76
90
|
if (attrs.length === 1 && children?.length) {
|
|
77
91
|
const built = buildTransMessage(code, children, names);
|
|
78
92
|
if (built?.text.trim()) {
|
|
79
|
-
|
|
80
|
-
key: id,
|
|
81
|
-
message: built.text,
|
|
82
|
-
params: built.params,
|
|
83
|
-
start: node.start,
|
|
84
|
-
end: node.end,
|
|
85
|
-
tagStart: nameNode.start,
|
|
86
|
-
tagEnd: nameNode.end,
|
|
87
|
-
file,
|
|
88
|
-
jsx: {
|
|
89
|
-
name: nameNode.name,
|
|
90
|
-
components: built.components
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
+
push(id, built);
|
|
93
94
|
return;
|
|
94
95
|
}
|
|
95
96
|
}
|
|
@@ -103,20 +104,7 @@ function handleTrans(code, node, file, tagged, usedKeys, names) {
|
|
|
103
104
|
if (!children?.length) return;
|
|
104
105
|
const built = buildTransMessage(code, children, names);
|
|
105
106
|
if (!built || !built.text.trim()) return;
|
|
106
|
-
|
|
107
|
-
key: stableKey(built.text),
|
|
108
|
-
message: built.text,
|
|
109
|
-
params: built.params,
|
|
110
|
-
start: node.start,
|
|
111
|
-
end: node.end,
|
|
112
|
-
tagStart: nameNode.start,
|
|
113
|
-
tagEnd: nameNode.end,
|
|
114
|
-
file,
|
|
115
|
-
jsx: {
|
|
116
|
-
name: nameNode.name,
|
|
117
|
-
components: built.components
|
|
118
|
-
}
|
|
119
|
-
});
|
|
107
|
+
push(stableKey(built.text), built);
|
|
120
108
|
}
|
|
121
109
|
function explicitId(tag, names) {
|
|
122
110
|
if (tag.type !== "CallExpression") return void 0;
|
|
@@ -272,8 +260,9 @@ function uniqueName(base, source, taken) {
|
|
|
272
260
|
}
|
|
273
261
|
function walk(node, visit) {
|
|
274
262
|
visit(node);
|
|
275
|
-
for (const
|
|
263
|
+
for (const key in node) {
|
|
276
264
|
if (SKIP_KEYS.has(key)) continue;
|
|
265
|
+
const value = node[key];
|
|
277
266
|
if (Array.isArray(value)) {
|
|
278
267
|
for (const item of value) if (isNode(item)) walk(item, visit);
|
|
279
268
|
} else if (isNode(value)) walk(value, visit);
|
|
@@ -454,13 +443,13 @@ function formatCheckResult(result) {
|
|
|
454
443
|
if (result.missing.length > 0) {
|
|
455
444
|
lines.push("missing translations:");
|
|
456
445
|
for (const entry of result.missing) {
|
|
457
|
-
const hint = entry.source ?
|
|
446
|
+
const hint = entry.source ? `: "${truncate(entry.source, 40)}"` : "";
|
|
458
447
|
lines.push(` [${entry.locale}] ${entry.key}${hint}`);
|
|
459
448
|
}
|
|
460
449
|
}
|
|
461
450
|
if (result.unknown.length > 0) {
|
|
462
451
|
lines.push("unknown keys (not in any catalog):");
|
|
463
|
-
for (const entry of result.unknown) lines.push(` ${entry.key}
|
|
452
|
+
for (const entry of result.unknown) lines.push(` ${entry.key} (used in ${entry.files.join(", ")})`);
|
|
464
453
|
}
|
|
465
454
|
return lines.join("\n");
|
|
466
455
|
}
|
|
@@ -507,11 +496,15 @@ function visit(nodes, out) {
|
|
|
507
496
|
}
|
|
508
497
|
function renderParamType(types) {
|
|
509
498
|
if (types.has("unknown") || types.size === 0) return "unknown";
|
|
510
|
-
const
|
|
511
|
-
if (types.has("number"))
|
|
512
|
-
if (types.has("string"))
|
|
513
|
-
if (types.has("date"))
|
|
514
|
-
|
|
499
|
+
const members = /* @__PURE__ */ new Set();
|
|
500
|
+
if (types.has("number")) members.add("number");
|
|
501
|
+
if (types.has("string")) members.add("string");
|
|
502
|
+
if (types.has("date")) for (const m of [
|
|
503
|
+
"Date",
|
|
504
|
+
"number",
|
|
505
|
+
"string"
|
|
506
|
+
]) members.add(m);
|
|
507
|
+
return [...members].join(" | ");
|
|
515
508
|
}
|
|
516
509
|
//#endregion
|
|
517
510
|
//#region src/codegen.ts
|
|
@@ -578,9 +571,9 @@ ${options.extraExports ?? ""}`;
|
|
|
578
571
|
function generateLocaleModule(catalog) {
|
|
579
572
|
return `export default ${JSON.stringify(catalog)};\n`;
|
|
580
573
|
}
|
|
581
|
-
function generateDts(
|
|
574
|
+
function generateDts(catalog) {
|
|
582
575
|
const lines = [];
|
|
583
|
-
for (const [key, message] of
|
|
576
|
+
for (const [key, message] of Object.entries(catalog).sort(([a], [b]) => a.localeCompare(b))) {
|
|
584
577
|
const params = collectParams(message);
|
|
585
578
|
if (params.size === 0) lines.push(` ${JSON.stringify(key)}: never;`);
|
|
586
579
|
else {
|
|
@@ -629,8 +622,13 @@ declare module 'virtual:verbaly/locale/*' {
|
|
|
629
622
|
}
|
|
630
623
|
`;
|
|
631
624
|
}
|
|
632
|
-
function writeDts(cfg,
|
|
633
|
-
|
|
625
|
+
function writeDts(cfg, catalog) {
|
|
626
|
+
const file = join(cfg.root, "verbaly.d.ts");
|
|
627
|
+
const content = generateDts(catalog);
|
|
628
|
+
try {
|
|
629
|
+
if (readFileSync(file, "utf8") === content) return;
|
|
630
|
+
} catch {}
|
|
631
|
+
writeFileSync(file, content);
|
|
634
632
|
}
|
|
635
633
|
//#endregion
|
|
636
634
|
//#region src/pseudo.ts
|
|
@@ -801,7 +799,7 @@ async function loadTsConfig(path) {
|
|
|
801
799
|
const { mod } = await bundleRequire({ filepath: path });
|
|
802
800
|
return mod.default ?? {};
|
|
803
801
|
} catch (error) {
|
|
804
|
-
if (isModuleNotFound(error, "esbuild")) throw new Error(`[verbaly] ${path} needs esbuild
|
|
802
|
+
if (isModuleNotFound(error, "esbuild")) throw new Error(`[verbaly] ${path} needs esbuild: install it with pnpm add -D esbuild`, { cause: error });
|
|
805
803
|
throw error;
|
|
806
804
|
}
|
|
807
805
|
}
|
|
@@ -822,6 +820,35 @@ function definedOnly(config) {
|
|
|
822
820
|
return Object.fromEntries(Object.entries(config).filter(([, value]) => value !== void 0));
|
|
823
821
|
}
|
|
824
822
|
//#endregion
|
|
823
|
+
//#region src/transform.ts
|
|
824
|
+
function quote(text, single) {
|
|
825
|
+
if (!single) return JSON.stringify(text);
|
|
826
|
+
return `'${text.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
827
|
+
}
|
|
828
|
+
function transformCode(code, file, analysis) {
|
|
829
|
+
const { tagged } = analysis ?? analyzeFile(code, file);
|
|
830
|
+
if (tagged.length === 0) return null;
|
|
831
|
+
const s = new MagicString(code);
|
|
832
|
+
for (const msg of tagged) {
|
|
833
|
+
const seen = /* @__PURE__ */ new Set();
|
|
834
|
+
const entries = msg.params.filter((p) => !seen.has(p.name) && seen.add(p.name));
|
|
835
|
+
const pairs = entries.map((p) => `${quote(p.name, msg.singleQuote)}: ${code.slice(p.start, p.end)}`).join(", ");
|
|
836
|
+
if (msg.jsx) {
|
|
837
|
+
const values = entries.length ? ` values={{ ${pairs} }}` : "";
|
|
838
|
+
const components = msg.jsx.components.length ? ` components={{ ${msg.jsx.components.map((c) => `${JSON.stringify(c.name)}: ${c.source}`).join(", ")} }}` : "";
|
|
839
|
+
s.overwrite(msg.start, msg.end, `<${msg.jsx.name} id=${JSON.stringify(msg.key)}${values}${components} />`);
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
const tagSource = code.slice(msg.tagStart, msg.tagEnd);
|
|
843
|
+
const args = entries.length ? `, { ${pairs} }` : "";
|
|
844
|
+
s.overwrite(msg.start, msg.end, `${tagSource}(${quote(msg.key, msg.singleQuote)}${args})`);
|
|
845
|
+
}
|
|
846
|
+
return {
|
|
847
|
+
code: s.toString(),
|
|
848
|
+
map: s.generateMap({ hires: true })
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
//#endregion
|
|
825
852
|
//#region src/plugin.ts
|
|
826
853
|
const RESOLVED_VIRTUAL_ID = "\0virtual:verbaly";
|
|
827
854
|
const LOCALE_MODULE_PREFIX = `${RESOLVED_VIRTUAL_ID}/locale/`;
|
|
@@ -836,7 +863,16 @@ function loadVirtualModule(id, cfg, catalogs) {
|
|
|
836
863
|
function isTransformTarget(id) {
|
|
837
864
|
return SOURCE_FILE_RE.test(id) && !id.includes("node_modules") && !id.startsWith("\0");
|
|
838
865
|
}
|
|
839
|
-
function
|
|
866
|
+
function transformSource(code, id, registry) {
|
|
867
|
+
const analysis = analyzeFile(code, id);
|
|
868
|
+
registry.update(id, analysis);
|
|
869
|
+
return {
|
|
870
|
+
analysis,
|
|
871
|
+
result: transformCode(code, id, analysis) ?? null
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
function runBuildGate(cfg, registry, failOnMissing) {
|
|
875
|
+
if (failOnMissing === false) return;
|
|
840
876
|
const result = check(cfg, loadCatalogs(cfg), registry);
|
|
841
877
|
if (!result.ok) throw new Error(`[verbaly] build blocked\n${formatCheckResult(result)}\nRun \`npx verbaly extract\` and fill the missing translations.`);
|
|
842
878
|
}
|
|
@@ -855,7 +891,7 @@ var MessageRegistry = class {
|
|
|
855
891
|
for (const analysis of this.files.values()) for (const msg of analysis.tagged) {
|
|
856
892
|
const existing = out.get(msg.key);
|
|
857
893
|
if (existing) {
|
|
858
|
-
if (existing.message !== msg.message) console.warn(`[verbaly] key collision "${msg.key}": ${JSON.stringify(existing.message)} vs ${JSON.stringify(msg.message)}
|
|
894
|
+
if (existing.message !== msg.message) console.warn(`[verbaly] key collision "${msg.key}": ${JSON.stringify(existing.message)} vs ${JSON.stringify(msg.message)}, second dropped.`);
|
|
859
895
|
continue;
|
|
860
896
|
}
|
|
861
897
|
out.set(msg.key, msg);
|
|
@@ -987,27 +1023,27 @@ function init(options = {}) {
|
|
|
987
1023
|
const PREVIEW = 5;
|
|
988
1024
|
async function doctor(cfg) {
|
|
989
1025
|
const entries = [];
|
|
990
|
-
const ok = (
|
|
1026
|
+
const ok = (name, message) => entries.push({
|
|
991
1027
|
level: "ok",
|
|
992
|
-
check,
|
|
1028
|
+
check: name,
|
|
993
1029
|
message
|
|
994
1030
|
});
|
|
995
|
-
const warn = (
|
|
1031
|
+
const warn = (name, message, fix) => entries.push({
|
|
996
1032
|
level: "warn",
|
|
997
|
-
check,
|
|
1033
|
+
check: name,
|
|
998
1034
|
message,
|
|
999
1035
|
fix
|
|
1000
1036
|
});
|
|
1001
|
-
const error = (
|
|
1037
|
+
const error = (name, message, fix) => entries.push({
|
|
1002
1038
|
level: "error",
|
|
1003
|
-
check,
|
|
1039
|
+
check: name,
|
|
1004
1040
|
message,
|
|
1005
1041
|
fix
|
|
1006
1042
|
});
|
|
1007
1043
|
const rel = (path) => relative(cfg.root, path).replaceAll("\\", "/");
|
|
1008
1044
|
const configFile = findConfigFile(cfg.root);
|
|
1009
1045
|
if (configFile) ok("config", `${configFile} found`);
|
|
1010
|
-
else warn("config", "no config file
|
|
1046
|
+
else warn("config", "no config file, running on defaults", "run `npx verbaly init`");
|
|
1011
1047
|
const catalogs = {};
|
|
1012
1048
|
let catalogsHealthy = true;
|
|
1013
1049
|
if (!existsSync(cfg.dir)) {
|
|
@@ -1040,14 +1076,14 @@ async function doctor(cfg) {
|
|
|
1040
1076
|
const deps = readDeps(cfg.root);
|
|
1041
1077
|
const bundler = detectBundler(cfg.root);
|
|
1042
1078
|
const wired = deps["@verbaly/vite"] || deps["@verbaly/unplugin"];
|
|
1043
|
-
if (!bundler) ok("plugin", "no bundler detected
|
|
1079
|
+
if (!bundler) ok("plugin", "no bundler detected, the CLI flow (extract/check) applies");
|
|
1044
1080
|
else if (wired) ok("plugin", `${deps["@verbaly/vite"] ? "@verbaly/vite" : "@verbaly/unplugin"} installed for ${bundler}`);
|
|
1045
1081
|
else if (bundler === "vite") warn("plugin", "vite detected but @verbaly/vite is not installed", "pnpm add -D @verbaly/vite and add verbaly() to the plugins in vite.config");
|
|
1046
1082
|
else warn("plugin", `${bundler} detected but @verbaly/unplugin is not installed`, `pnpm add -D @verbaly/unplugin and add the verbaly ${bundler} plugin to your build config`);
|
|
1047
1083
|
if (source) {
|
|
1048
1084
|
const dtsPath = join(cfg.root, "verbaly.d.ts");
|
|
1049
1085
|
if (!existsSync(dtsPath)) warn("types", "verbaly.d.ts has not been generated", "run `npx verbaly extract`");
|
|
1050
|
-
else if (readFileSync(dtsPath, "utf8") !== generateDts(
|
|
1086
|
+
else if (readFileSync(dtsPath, "utf8") !== generateDts(source)) warn("types", "verbaly.d.ts is stale", "run `npx verbaly extract`");
|
|
1051
1087
|
else ok("types", "verbaly.d.ts is up to date");
|
|
1052
1088
|
}
|
|
1053
1089
|
const registry = await extractProject(cfg);
|
|
@@ -1060,10 +1096,10 @@ async function doctor(cfg) {
|
|
|
1060
1096
|
}
|
|
1061
1097
|
if (catalogsHealthy) {
|
|
1062
1098
|
const result = check(cfg, catalogs, registry);
|
|
1063
|
-
if (result.unknown.length > 0) error("keys", `${result.unknown.length} unknown ${result.unknown.length === 1 ? "key" : "keys"} used in code (${preview(result.unknown.map((u) => u.key))})`, "fix the key or add it to the catalogs
|
|
1099
|
+
if (result.unknown.length > 0) error("keys", `${result.unknown.length} unknown ${result.unknown.length === 1 ? "key" : "keys"} used in code (${preview(result.unknown.map((u) => u.key))})`, "fix the key or add it to the catalogs (`npx verbaly check` for details)");
|
|
1064
1100
|
if (result.missing.length > 0) {
|
|
1065
1101
|
const locales = [...new Set(result.missing.map((m) => m.locale))];
|
|
1066
|
-
warn("translations", `${result.missing.length} missing ${result.missing.length === 1 ? "translation" : "translations"} (${locales.join(", ")})`, "run `npx verbaly translate` or fill the catalogs
|
|
1102
|
+
warn("translations", `${result.missing.length} missing ${result.missing.length === 1 ? "translation" : "translations"} (${locales.join(", ")})`, "run `npx verbaly translate` or fill the catalogs (`npx verbaly check` for details)");
|
|
1067
1103
|
}
|
|
1068
1104
|
if (result.ok) ok("translations", "all translations complete");
|
|
1069
1105
|
}
|
|
@@ -1155,19 +1191,20 @@ function exportCatalogs(cfg, catalogs, options = {}) {
|
|
|
1155
1191
|
mkdirSync(dir, { recursive: true });
|
|
1156
1192
|
for (const locale of targets) {
|
|
1157
1193
|
const catalog = catalogs[locale] ?? {};
|
|
1158
|
-
|
|
1194
|
+
const all = Object.keys(source).filter((key) => source[key]).sort().map((key) => ({
|
|
1159
1195
|
key,
|
|
1160
1196
|
source: source[key],
|
|
1161
1197
|
target: catalog[key] ?? ""
|
|
1162
1198
|
}));
|
|
1163
|
-
|
|
1199
|
+
const untranslated = all.filter((entry) => !entry.target);
|
|
1200
|
+
const entries = options.missing ? untranslated : all;
|
|
1164
1201
|
const path = join(dir, `${locale}.${format === "csv" ? "csv" : "xlf"}`);
|
|
1165
1202
|
writeFileSync(path, format === "csv" ? toCsv(entries) : toXliff(cfg.sourceLocale, locale, entries));
|
|
1166
1203
|
files.push({
|
|
1167
1204
|
locale,
|
|
1168
1205
|
path,
|
|
1169
1206
|
total: entries.length,
|
|
1170
|
-
untranslated:
|
|
1207
|
+
untranslated: untranslated.length
|
|
1171
1208
|
});
|
|
1172
1209
|
}
|
|
1173
1210
|
return {
|
|
@@ -1187,8 +1224,8 @@ function importCatalogs(cfg, catalogs, files, options = {}) {
|
|
|
1187
1224
|
for (const file of files) {
|
|
1188
1225
|
const parsed = parseExchangeFile(file, options.locale);
|
|
1189
1226
|
const locale = parsed.locale;
|
|
1190
|
-
if (!/^[a-zA-Z]{2,3}([-_][a-zA-Z0-9]+)*$/.test(locale)) throw new Error(`[verbaly] ${file}: "${locale}" doesn't look like a locale
|
|
1191
|
-
if (locale === cfg.sourceLocale) throw new Error(`[verbaly] ${file} targets the source locale "${locale}"
|
|
1227
|
+
if (!/^[a-zA-Z]{2,3}([-_][a-zA-Z0-9]+)*$/.test(locale)) throw new Error(`[verbaly] ${file}: "${locale}" doesn't look like a locale, pass --locale <id>.`);
|
|
1228
|
+
if (locale === cfg.sourceLocale) throw new Error(`[verbaly] ${file} targets the source locale "${locale}": import fills translations, not the source. Pass --locale if the detection is wrong.`);
|
|
1192
1229
|
const catalog = catalogs[locale] ??= {};
|
|
1193
1230
|
for (const [key, text] of Object.entries(parsed.entries)) {
|
|
1194
1231
|
if (!text.trim()) continue;
|
|
@@ -1215,7 +1252,7 @@ function parseExchangeFile(file, localeOverride) {
|
|
|
1215
1252
|
if (/\.(xlf|xliff)$/i.test(file)) {
|
|
1216
1253
|
const parsed = parseXliff(content);
|
|
1217
1254
|
const locale = localeOverride ?? parsed.locale;
|
|
1218
|
-
if (!locale) throw new Error(`[verbaly] ${file} has no trgLang/target-language
|
|
1255
|
+
if (!locale) throw new Error(`[verbaly] ${file} has no trgLang/target-language, pass --locale <id>.`);
|
|
1219
1256
|
return {
|
|
1220
1257
|
locale,
|
|
1221
1258
|
entries: parsed.entries
|
|
@@ -1225,7 +1262,7 @@ function parseExchangeFile(file, localeOverride) {
|
|
|
1225
1262
|
locale: localeOverride ?? basename(file).replace(/\.csv$/i, ""),
|
|
1226
1263
|
entries: parseCsv(content)
|
|
1227
1264
|
};
|
|
1228
|
-
throw new Error(`[verbaly] ${file}: unsupported format
|
|
1265
|
+
throw new Error(`[verbaly] ${file}: unsupported format, expected .xlf, .xliff or .csv.`);
|
|
1229
1266
|
}
|
|
1230
1267
|
function toXliff(sourceLocale, locale, entries) {
|
|
1231
1268
|
const units = entries.map(({ key, source, target }) => [
|
|
@@ -1371,7 +1408,7 @@ async function loadSdk(load = () => import("@anthropic-ai/sdk")) {
|
|
|
1371
1408
|
try {
|
|
1372
1409
|
return (await load()).default;
|
|
1373
1410
|
} catch (error) {
|
|
1374
|
-
if (isModuleNotFound(error, "@anthropic-ai/sdk")) throw new Error("[verbaly] the claude translate provider needs @anthropic-ai/sdk
|
|
1411
|
+
if (isModuleNotFound(error, "@anthropic-ai/sdk")) throw new Error("[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", { cause: error });
|
|
1375
1412
|
throw error;
|
|
1376
1413
|
}
|
|
1377
1414
|
}
|
|
@@ -1646,35 +1683,6 @@ function decodeEntities(text) {
|
|
|
1646
1683
|
return text.replace(/"/g, "\"").replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
1647
1684
|
}
|
|
1648
1685
|
//#endregion
|
|
1649
|
-
|
|
1650
|
-
function quote(text, single) {
|
|
1651
|
-
if (!single) return JSON.stringify(text);
|
|
1652
|
-
return `'${text.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
1653
|
-
}
|
|
1654
|
-
function transformCode(code, file, analysis) {
|
|
1655
|
-
const { tagged } = analysis ?? analyzeFile(code, file);
|
|
1656
|
-
if (tagged.length === 0) return null;
|
|
1657
|
-
const s = new MagicString(code);
|
|
1658
|
-
for (const msg of tagged) {
|
|
1659
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1660
|
-
const entries = msg.params.filter((p) => !seen.has(p.name) && seen.add(p.name));
|
|
1661
|
-
const pairs = entries.map((p) => `${quote(p.name, msg.singleQuote)}: ${code.slice(p.start, p.end)}`).join(", ");
|
|
1662
|
-
if (msg.jsx) {
|
|
1663
|
-
const values = entries.length ? ` values={{ ${pairs} }}` : "";
|
|
1664
|
-
const components = msg.jsx.components.length ? ` components={{ ${msg.jsx.components.map((c) => `${JSON.stringify(c.name)}: ${c.source}`).join(", ")} }}` : "";
|
|
1665
|
-
s.overwrite(msg.start, msg.end, `<${msg.jsx.name} id=${JSON.stringify(msg.key)}${values}${components} />`);
|
|
1666
|
-
continue;
|
|
1667
|
-
}
|
|
1668
|
-
const tagSource = code.slice(msg.tagStart, msg.tagEnd);
|
|
1669
|
-
const args = entries.length ? `, { ${pairs} }` : "";
|
|
1670
|
-
s.overwrite(msg.start, msg.end, `${tagSource}(${quote(msg.key, msg.singleQuote)}${args})`);
|
|
1671
|
-
}
|
|
1672
|
-
return {
|
|
1673
|
-
code: s.toString(),
|
|
1674
|
-
map: s.generateMap({ hires: true })
|
|
1675
|
-
};
|
|
1676
|
-
}
|
|
1677
|
-
//#endregion
|
|
1678
|
-
export { LOCALE_MODULE_PREFIX, MessageRegistry, PSEUDO_LOCALE, RESOLVED_VIRTUAL_ID, SFC_FILE_RE, SOURCE_FILE_RE, VIRTUAL_ID, analyze, analyzeFile, analyzeSfc, catalogPath, check, claudeProvider, collectParams, detectBundler, doctor, exportCatalogs, extractProject, findConfigFile, formatCheckResult, generateDts, generateLocaleModule, generateRuntimeModule, importCatalogs, init, isTransformTarget, loadCatalogs, loadConfig, loadConfigFile, loadVirtualModule, parseExchangeFile, pruneCatalogs, pseudoCatalogs, pseudoLocalize, readCatalog, renderHtml, renderParamType, renderSite, resolveConfig, resolveVirtualId, runBuildGate, serializeCatalog, stableKey, structureMatches, syncCatalogs, targetLocales, transformCode, translateCatalogs, writeCatalog, writeDts };
|
|
1686
|
+
export { LOCALE_MODULE_PREFIX, MessageRegistry, PSEUDO_LOCALE, RESOLVED_VIRTUAL_ID, SFC_FILE_RE, SOURCE_FILE_RE, VIRTUAL_ID, analyze, analyzeFile, analyzeSfc, catalogPath, check, claudeProvider, collectParams, detectBundler, doctor, exportCatalogs, extractProject, findConfigFile, formatCheckResult, generateDts, generateLocaleModule, generateRuntimeModule, importCatalogs, init, isTransformTarget, loadCatalogs, loadConfig, loadConfigFile, loadVirtualModule, parseExchangeFile, pruneCatalogs, pseudoCatalogs, pseudoLocalize, readCatalog, renderHtml, renderParamType, renderSite, resolveConfig, resolveVirtualId, runBuildGate, serializeCatalog, stableKey, structureMatches, syncCatalogs, targetLocales, transformCode, transformSource, translateCatalogs, writeCatalog, writeDts };
|
|
1679
1687
|
|
|
1680
1688
|
//# sourceMappingURL=index.js.map
|