@verbaly/compiler 0.26.0 → 0.28.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 +4 -3
- package/dist/{claude-C-ETlqsW.js → claude-h9WA0ppg.js} +4 -2
- package/dist/claude-h9WA0ppg.js.map +1 -0
- package/dist/cli.js +461 -42
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +15 -3
- package/dist/index.js +400 -32
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/claude-C-ETlqsW.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -48,12 +48,14 @@ interface TranslateRequest {
|
|
|
48
48
|
sourceLocale: string;
|
|
49
49
|
targetLocale: string;
|
|
50
50
|
messages: Record<string, string>;
|
|
51
|
+
origins?: Record<string, string[]>;
|
|
51
52
|
}
|
|
52
53
|
type TranslateProvider = (request: TranslateRequest) => Promise<Record<string, string>>;
|
|
53
54
|
interface TranslateOptions {
|
|
54
55
|
locales?: string[];
|
|
55
56
|
batchSize?: number;
|
|
56
57
|
dryRun?: boolean;
|
|
58
|
+
origins?: Record<string, string[]>;
|
|
57
59
|
}
|
|
58
60
|
interface TranslateResult {
|
|
59
61
|
translated: Record<string, string[]>;
|
|
@@ -192,8 +194,17 @@ interface DoctorResult {
|
|
|
192
194
|
}
|
|
193
195
|
declare function doctor(cfg: ResolvedConfig): Promise<DoctorResult>;
|
|
194
196
|
//#endregion
|
|
197
|
+
//#region src/drafts.d.ts
|
|
198
|
+
declare const DRAFTS_FILE = ".verbaly-drafts.json";
|
|
199
|
+
type Drafts = Record<string, string[]>;
|
|
200
|
+
declare function loadDrafts(cfg: ResolvedConfig): Drafts;
|
|
201
|
+
declare function saveDrafts(cfg: ResolvedConfig, drafts: Drafts): void;
|
|
202
|
+
declare function markDrafts(drafts: Drafts, locale: string, keys: string[]): void;
|
|
203
|
+
declare function clearDrafts(drafts: Drafts, locale: string, keys?: string[]): void;
|
|
204
|
+
declare function effectiveDrafts(drafts: Drafts, catalogs: Catalogs): Drafts;
|
|
205
|
+
//#endregion
|
|
195
206
|
//#region src/exchange.d.ts
|
|
196
|
-
type ExchangeFormat = 'xliff' | 'csv';
|
|
207
|
+
type ExchangeFormat = 'xliff' | 'csv' | 'po';
|
|
197
208
|
type MobileFormat = 'android-xml' | 'ios-strings';
|
|
198
209
|
type ExportFormat = ExchangeFormat | MobileFormat;
|
|
199
210
|
declare function isMobileFormat(format: ExportFormat): format is MobileFormat;
|
|
@@ -323,13 +334,14 @@ interface LocaleStatus {
|
|
|
323
334
|
locale: string;
|
|
324
335
|
translated: number;
|
|
325
336
|
total: number;
|
|
337
|
+
drafts: number;
|
|
326
338
|
}
|
|
327
339
|
interface StatusResult {
|
|
328
340
|
messages: number;
|
|
329
341
|
source: string;
|
|
330
342
|
locales: LocaleStatus[];
|
|
331
343
|
}
|
|
332
|
-
declare function status(cfg: ResolvedConfig, catalogs: Catalogs, registry: MessageRegistry): StatusResult;
|
|
344
|
+
declare function status(cfg: ResolvedConfig, catalogs: Catalogs, registry: MessageRegistry, drafts?: Drafts): StatusResult;
|
|
333
345
|
declare function formatStatusResult(result: StatusResult): string;
|
|
334
346
|
//#endregion
|
|
335
347
|
//#region src/watch.d.ts
|
|
@@ -369,5 +381,5 @@ interface WrapCodeResult {
|
|
|
369
381
|
}
|
|
370
382
|
declare function wrapCode(code: string, file: string): WrapCodeResult;
|
|
371
383
|
//#endregion
|
|
372
|
-
export { type Alternate, type Analysis, type AnalyzeOptions, type Catalog, type Catalogs, type CheckResult, type ClaudeProviderOptions, type DoctorEntry, type DoctorResult, type ExchangeFormat, type ExportFormat, type ExportOptions, type ExportResult, type ExportedFile, type ImportOptions, type ImportResult, type InitOptions, type InitResult, LOCALE_MODULE_PREFIX, type LocaleStatus, MessageRegistry, type MissingEntry, type MobileFormat, 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 StatusResult, 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, type WatchProjectOptions, type WrapCodeResult, type WrapEntry, type WrapOptions, type WrapResult, type WrapSkip, analyze, analyzeFile, analyzeSfc, catalogPath, check, claudeProvider, collectParams, createSourceFilter, detectBundler, doctor, exportCatalogs, extractProject, findConfigFile, formatCheckResult, formatStatusResult, generateDts, generateLocaleModule, generateRuntimeModule, githubCheckAnnotations, importCatalogs, init, isMobileFormat, isTransformTarget, loadCatalogs, loadConfig, loadConfigFile, loadVirtualModule, parseExchangeFile, pruneCatalogs, pseudoCatalogs, pseudoLocalize, readCatalog, renderHtml, renderParamType, renderSite, resolveConfig, resolveVirtualId, runBuildGate, serializeCatalog, stableKey, status, structureMatches, syncCatalogs, targetLocales, transformCode, transformSource, translateCatalogs, watchProject, wrapCode, wrapProject, writeCatalog, writeDts };
|
|
384
|
+
export { type Alternate, type Analysis, type AnalyzeOptions, type Catalog, type Catalogs, type CheckResult, type ClaudeProviderOptions, DRAFTS_FILE, type DoctorEntry, type DoctorResult, type Drafts, type ExchangeFormat, type ExportFormat, type ExportOptions, type ExportResult, type ExportedFile, type ImportOptions, type ImportResult, type InitOptions, type InitResult, LOCALE_MODULE_PREFIX, type LocaleStatus, MessageRegistry, type MissingEntry, type MobileFormat, 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 StatusResult, 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, type WatchProjectOptions, type WrapCodeResult, type WrapEntry, type WrapOptions, type WrapResult, type WrapSkip, analyze, analyzeFile, analyzeSfc, catalogPath, check, claudeProvider, clearDrafts, collectParams, createSourceFilter, detectBundler, doctor, effectiveDrafts, exportCatalogs, extractProject, findConfigFile, formatCheckResult, formatStatusResult, generateDts, generateLocaleModule, generateRuntimeModule, githubCheckAnnotations, importCatalogs, init, isMobileFormat, isTransformTarget, loadCatalogs, loadConfig, loadConfigFile, loadDrafts, loadVirtualModule, markDrafts, parseExchangeFile, pruneCatalogs, pseudoCatalogs, pseudoLocalize, readCatalog, renderHtml, renderParamType, renderSite, resolveConfig, resolveVirtualId, runBuildGate, saveDrafts, serializeCatalog, stableKey, status, structureMatches, syncCatalogs, targetLocales, transformCode, transformSource, translateCatalogs, watchProject, wrapCode, wrapProject, writeCatalog, writeDts };
|
|
373
385
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -768,7 +768,7 @@ function pseudoLocalize(message) {
|
|
|
768
768
|
}
|
|
769
769
|
const ch = message[i];
|
|
770
770
|
if (ch === "{") {
|
|
771
|
-
const end = matchBrace(message, i);
|
|
771
|
+
const end = matchBrace$1(message, i);
|
|
772
772
|
out += message.slice(i, end);
|
|
773
773
|
i = end;
|
|
774
774
|
continue;
|
|
@@ -792,7 +792,7 @@ function pseudoLocalize(message) {
|
|
|
792
792
|
const pad = "~".repeat(Math.ceil(letters / 3));
|
|
793
793
|
return `⟦${out}${pad ? " " + pad : ""}⟧`;
|
|
794
794
|
}
|
|
795
|
-
function matchBrace(message, start) {
|
|
795
|
+
function matchBrace$1(message, start) {
|
|
796
796
|
let depth = 0;
|
|
797
797
|
for (let i = start; i < message.length; i++) {
|
|
798
798
|
const two = message.slice(i, i + 2);
|
|
@@ -824,7 +824,7 @@ function resolveConfig(config = {}) {
|
|
|
824
824
|
const sourceLocale = config.sourceLocale ?? "en";
|
|
825
825
|
const locales = /* @__PURE__ */ new Set([sourceLocale, ...config.locales ?? []]);
|
|
826
826
|
if (existsSync(dir)) {
|
|
827
|
-
for (const file of readdirSync(dir)) if (file.endsWith(".json") && file !== `en-XA.json`) locales.add(file.slice(0, -5));
|
|
827
|
+
for (const file of readdirSync(dir)) if (file.endsWith(".json") && !file.startsWith(".") && file !== `en-XA.json`) locales.add(file.slice(0, -5));
|
|
828
828
|
}
|
|
829
829
|
return {
|
|
830
830
|
root,
|
|
@@ -1211,6 +1211,282 @@ function readDeps(root) {
|
|
|
1211
1211
|
}
|
|
1212
1212
|
}
|
|
1213
1213
|
//#endregion
|
|
1214
|
+
//#region src/drafts.ts
|
|
1215
|
+
const DRAFTS_FILE = ".verbaly-drafts.json";
|
|
1216
|
+
function draftsPath(cfg) {
|
|
1217
|
+
return join(cfg.dir, DRAFTS_FILE);
|
|
1218
|
+
}
|
|
1219
|
+
function loadDrafts(cfg) {
|
|
1220
|
+
let content;
|
|
1221
|
+
try {
|
|
1222
|
+
content = readFileSync(draftsPath(cfg), "utf8");
|
|
1223
|
+
} catch {
|
|
1224
|
+
return {};
|
|
1225
|
+
}
|
|
1226
|
+
try {
|
|
1227
|
+
return JSON.parse(content.replace(/^\uFEFF/, ""));
|
|
1228
|
+
} catch (error) {
|
|
1229
|
+
throw new Error(`[verbaly] ${draftsPath(cfg)} is not valid JSON, fix or delete the file`, { cause: error });
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
function serializeDrafts(drafts) {
|
|
1233
|
+
const sorted = {};
|
|
1234
|
+
for (const locale of Object.keys(drafts).sort()) {
|
|
1235
|
+
const keys = [...new Set(drafts[locale])].sort();
|
|
1236
|
+
if (keys.length) sorted[locale] = keys;
|
|
1237
|
+
}
|
|
1238
|
+
return JSON.stringify(sorted, null, 2) + "\n";
|
|
1239
|
+
}
|
|
1240
|
+
function saveDrafts(cfg, drafts) {
|
|
1241
|
+
const serialized = serializeDrafts(drafts);
|
|
1242
|
+
const path = draftsPath(cfg);
|
|
1243
|
+
try {
|
|
1244
|
+
if (readFileSync(path, "utf8") === serialized) return;
|
|
1245
|
+
} catch {
|
|
1246
|
+
mkdirSync(cfg.dir, { recursive: true });
|
|
1247
|
+
}
|
|
1248
|
+
writeFileSync(path, serialized);
|
|
1249
|
+
}
|
|
1250
|
+
function markDrafts(drafts, locale, keys) {
|
|
1251
|
+
if (!keys.length) return;
|
|
1252
|
+
drafts[locale] = [.../* @__PURE__ */ new Set([...drafts[locale] ?? [], ...keys])];
|
|
1253
|
+
}
|
|
1254
|
+
function clearDrafts(drafts, locale, keys) {
|
|
1255
|
+
if (!drafts[locale]) return;
|
|
1256
|
+
if (!keys) {
|
|
1257
|
+
delete drafts[locale];
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
const drop = new Set(keys);
|
|
1261
|
+
drafts[locale] = drafts[locale].filter((key) => !drop.has(key));
|
|
1262
|
+
if (!drafts[locale].length) delete drafts[locale];
|
|
1263
|
+
}
|
|
1264
|
+
function effectiveDrafts(drafts, catalogs) {
|
|
1265
|
+
const out = {};
|
|
1266
|
+
for (const [locale, keys] of Object.entries(drafts)) {
|
|
1267
|
+
const catalog = catalogs[locale] ?? {};
|
|
1268
|
+
const live = keys.filter((key) => catalog[key]);
|
|
1269
|
+
if (live.length) out[locale] = live;
|
|
1270
|
+
}
|
|
1271
|
+
return out;
|
|
1272
|
+
}
|
|
1273
|
+
//#endregion
|
|
1274
|
+
//#region src/inline.ts
|
|
1275
|
+
const OPEN_TAG = /^<([a-zA-Z][a-zA-Z0-9-]*)\s*(\/?)>/;
|
|
1276
|
+
function messageToInline(text) {
|
|
1277
|
+
return renderParts(parseParts(text, 0).parts, /* @__PURE__ */ new Map());
|
|
1278
|
+
}
|
|
1279
|
+
function inlineToMessage(xml) {
|
|
1280
|
+
let out = "";
|
|
1281
|
+
let text = "";
|
|
1282
|
+
let i = 0;
|
|
1283
|
+
const flush = () => {
|
|
1284
|
+
out += unescapeXml(text);
|
|
1285
|
+
text = "";
|
|
1286
|
+
};
|
|
1287
|
+
while (i < xml.length) {
|
|
1288
|
+
if (xml[i] === "<") {
|
|
1289
|
+
const slice = xml.slice(i);
|
|
1290
|
+
const ph = /^<ph\b([^>]*?)\/\s*>/.exec(slice) ?? /^<ph\b([^>]*)>\s*<\/ph\s*>/.exec(slice);
|
|
1291
|
+
if (ph) {
|
|
1292
|
+
flush();
|
|
1293
|
+
out += attr(ph[1], "disp") ?? `{${attr(ph[1], "id") ?? "ph"}}`;
|
|
1294
|
+
i += ph[0].length;
|
|
1295
|
+
continue;
|
|
1296
|
+
}
|
|
1297
|
+
const pc = /^<pc\b([^>]*)>/.exec(slice);
|
|
1298
|
+
if (pc) {
|
|
1299
|
+
const close = findPcClose(xml, i + pc[0].length);
|
|
1300
|
+
if (close) {
|
|
1301
|
+
flush();
|
|
1302
|
+
const id = attr(pc[1], "id") ?? "pc";
|
|
1303
|
+
out += attr(pc[1], "dispStart") ?? `<${id}>`;
|
|
1304
|
+
out += inlineToMessage(xml.slice(i + pc[0].length, close.innerEnd));
|
|
1305
|
+
out += attr(pc[1], "dispEnd") ?? `</${id}>`;
|
|
1306
|
+
i = close.end;
|
|
1307
|
+
continue;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
text += xml[i];
|
|
1312
|
+
i += 1;
|
|
1313
|
+
}
|
|
1314
|
+
flush();
|
|
1315
|
+
return out;
|
|
1316
|
+
}
|
|
1317
|
+
function escapeXml(text) {
|
|
1318
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1319
|
+
}
|
|
1320
|
+
function unescapeXml(text) {
|
|
1321
|
+
return text.replace(/&(lt|gt|amp|quot|apos|#x?[0-9a-fA-F]+);/g, (_, entity) => {
|
|
1322
|
+
if (entity === "lt") return "<";
|
|
1323
|
+
if (entity === "gt") return ">";
|
|
1324
|
+
if (entity === "amp") return "&";
|
|
1325
|
+
if (entity === "quot") return "\"";
|
|
1326
|
+
if (entity === "apos") return "'";
|
|
1327
|
+
const code = entity.startsWith("#x") ? parseInt(entity.slice(2), 16) : parseInt(entity.slice(1), 10);
|
|
1328
|
+
return String.fromCodePoint(code);
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
function parseParts(text, pos, closeTag) {
|
|
1332
|
+
const parts = [];
|
|
1333
|
+
let plain = "";
|
|
1334
|
+
const flush = () => {
|
|
1335
|
+
if (plain) parts.push({
|
|
1336
|
+
kind: "text",
|
|
1337
|
+
text: plain
|
|
1338
|
+
});
|
|
1339
|
+
plain = "";
|
|
1340
|
+
};
|
|
1341
|
+
let i = pos;
|
|
1342
|
+
while (i < text.length) {
|
|
1343
|
+
const pair = text[i] + (text[i + 1] ?? "");
|
|
1344
|
+
if (pair === "{{" || pair === "}}") {
|
|
1345
|
+
plain += pair;
|
|
1346
|
+
i += 2;
|
|
1347
|
+
continue;
|
|
1348
|
+
}
|
|
1349
|
+
if (text[i] === "{") {
|
|
1350
|
+
const end = matchBrace(text, i);
|
|
1351
|
+
if (end === -1) {
|
|
1352
|
+
plain += "{";
|
|
1353
|
+
i += 1;
|
|
1354
|
+
continue;
|
|
1355
|
+
}
|
|
1356
|
+
const src = text.slice(i, end + 1);
|
|
1357
|
+
if (hasTopLevelPipe(src)) plain += src;
|
|
1358
|
+
else {
|
|
1359
|
+
flush();
|
|
1360
|
+
parts.push({
|
|
1361
|
+
kind: "ph",
|
|
1362
|
+
name: paramName(src),
|
|
1363
|
+
src
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
i = end + 1;
|
|
1367
|
+
continue;
|
|
1368
|
+
}
|
|
1369
|
+
if (text[i] === "<") {
|
|
1370
|
+
if (closeTag && text.startsWith(`</${closeTag}>`, i)) {
|
|
1371
|
+
flush();
|
|
1372
|
+
return {
|
|
1373
|
+
parts,
|
|
1374
|
+
end: i + closeTag.length + 3,
|
|
1375
|
+
closed: true
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
const open = OPEN_TAG.exec(text.slice(i));
|
|
1379
|
+
if (open) {
|
|
1380
|
+
const [openSrc, name, selfClose] = open;
|
|
1381
|
+
if (selfClose) {
|
|
1382
|
+
flush();
|
|
1383
|
+
parts.push({
|
|
1384
|
+
kind: "ph",
|
|
1385
|
+
name: idSafe(name),
|
|
1386
|
+
src: openSrc
|
|
1387
|
+
});
|
|
1388
|
+
i += openSrc.length;
|
|
1389
|
+
continue;
|
|
1390
|
+
}
|
|
1391
|
+
const inner = parseParts(text, i + openSrc.length, name);
|
|
1392
|
+
if (inner.closed) {
|
|
1393
|
+
flush();
|
|
1394
|
+
parts.push({
|
|
1395
|
+
kind: "pc",
|
|
1396
|
+
name: idSafe(name),
|
|
1397
|
+
openSrc,
|
|
1398
|
+
closeSrc: `</${name}>`,
|
|
1399
|
+
children: inner.parts
|
|
1400
|
+
});
|
|
1401
|
+
i = inner.end;
|
|
1402
|
+
continue;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
plain += "<";
|
|
1406
|
+
i += 1;
|
|
1407
|
+
continue;
|
|
1408
|
+
}
|
|
1409
|
+
plain += text[i];
|
|
1410
|
+
i += 1;
|
|
1411
|
+
}
|
|
1412
|
+
flush();
|
|
1413
|
+
return {
|
|
1414
|
+
parts,
|
|
1415
|
+
end: i,
|
|
1416
|
+
closed: false
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
function matchBrace(text, start) {
|
|
1420
|
+
let depth = 0;
|
|
1421
|
+
for (let i = start; i < text.length; i++) {
|
|
1422
|
+
const pair = text[i] + (text[i + 1] ?? "");
|
|
1423
|
+
if (pair === "{{" || pair === "}}") {
|
|
1424
|
+
i += 1;
|
|
1425
|
+
continue;
|
|
1426
|
+
}
|
|
1427
|
+
if (text[i] === "{") depth += 1;
|
|
1428
|
+
else if (text[i] === "}") {
|
|
1429
|
+
depth -= 1;
|
|
1430
|
+
if (depth === 0) return i;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
return -1;
|
|
1434
|
+
}
|
|
1435
|
+
function hasTopLevelPipe(src) {
|
|
1436
|
+
let depth = 0;
|
|
1437
|
+
for (let i = 0; i < src.length; i++) {
|
|
1438
|
+
const pair = src[i] + (src[i + 1] ?? "");
|
|
1439
|
+
if (pair === "{{" || pair === "}}" || pair === "||") {
|
|
1440
|
+
i += 1;
|
|
1441
|
+
continue;
|
|
1442
|
+
}
|
|
1443
|
+
if (src[i] === "{") depth += 1;
|
|
1444
|
+
else if (src[i] === "}") depth -= 1;
|
|
1445
|
+
else if (src[i] === "|" && depth === 1) return true;
|
|
1446
|
+
}
|
|
1447
|
+
return false;
|
|
1448
|
+
}
|
|
1449
|
+
function paramName(src) {
|
|
1450
|
+
const name = /^\{\s*([^{}|:\s]+)/.exec(src)?.[1];
|
|
1451
|
+
return name ? idSafe(name) : "ph";
|
|
1452
|
+
}
|
|
1453
|
+
function idSafe(name) {
|
|
1454
|
+
return name.replace(/[^a-zA-Z0-9._-]/g, "_") || "ph";
|
|
1455
|
+
}
|
|
1456
|
+
function renderParts(parts, taken) {
|
|
1457
|
+
let out = "";
|
|
1458
|
+
for (const part of parts) if (part.kind === "text") out += escapeXml(part.text);
|
|
1459
|
+
else if (part.kind === "ph") out += `<ph id="${uniqueId(part.name, taken)}" disp="${escapeXml(part.src)}"/>`;
|
|
1460
|
+
else {
|
|
1461
|
+
const id = uniqueId(part.name, taken);
|
|
1462
|
+
out += `<pc id="${id}" dispStart="${escapeXml(part.openSrc)}" dispEnd="${escapeXml(part.closeSrc)}">`;
|
|
1463
|
+
out += renderParts(part.children, taken);
|
|
1464
|
+
out += "</pc>";
|
|
1465
|
+
}
|
|
1466
|
+
return out;
|
|
1467
|
+
}
|
|
1468
|
+
function uniqueId(name, taken) {
|
|
1469
|
+
const count = taken.get(name) ?? 0;
|
|
1470
|
+
taken.set(name, count + 1);
|
|
1471
|
+
return count === 0 ? name : `${name}${count + 1}`;
|
|
1472
|
+
}
|
|
1473
|
+
function findPcClose(xml, from) {
|
|
1474
|
+
const tags = /<pc\b[^>]*>|<\/pc\s*>/g;
|
|
1475
|
+
tags.lastIndex = from;
|
|
1476
|
+
let depth = 1;
|
|
1477
|
+
for (let match = tags.exec(xml); match; match = tags.exec(xml)) {
|
|
1478
|
+
depth += match[0].startsWith("</") ? -1 : 1;
|
|
1479
|
+
if (depth === 0) return {
|
|
1480
|
+
innerEnd: match.index,
|
|
1481
|
+
end: tags.lastIndex
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
function attr(attrs, name) {
|
|
1486
|
+
const value = new RegExp(`\\b${name}\\s*=\\s*"([^"]*)"`).exec(attrs)?.[1];
|
|
1487
|
+
return value === void 0 ? void 0 : unescapeXml(value);
|
|
1488
|
+
}
|
|
1489
|
+
//#endregion
|
|
1214
1490
|
//#region src/mobile.ts
|
|
1215
1491
|
function androidValuesDir(locale, sourceLocale) {
|
|
1216
1492
|
if (locale === sourceLocale) return "values";
|
|
@@ -1250,6 +1526,97 @@ function iosText(text) {
|
|
|
1250
1526
|
return text.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n").replace(/\t/g, "\\t");
|
|
1251
1527
|
}
|
|
1252
1528
|
//#endregion
|
|
1529
|
+
//#region src/po.ts
|
|
1530
|
+
function toPo(sourceLocale, locale, entries) {
|
|
1531
|
+
return [
|
|
1532
|
+
[
|
|
1533
|
+
"msgid \"\"",
|
|
1534
|
+
"msgstr \"\"",
|
|
1535
|
+
`${poString("Project-Id-Version: verbaly\n")}`,
|
|
1536
|
+
`${poString("MIME-Version: 1.0\n")}`,
|
|
1537
|
+
`${poString("Content-Type: text/plain; charset=UTF-8\n")}`,
|
|
1538
|
+
`${poString("Content-Transfer-Encoding: 8bit\n")}`,
|
|
1539
|
+
`${poString(`Language: ${locale}\n`)}`,
|
|
1540
|
+
`${poString(`X-Source-Language: ${sourceLocale}\n`)}`
|
|
1541
|
+
].join("\n"),
|
|
1542
|
+
...entries.map(({ key, source, target, location }) => [
|
|
1543
|
+
...location?.length ? [`#: ${location.join(" ")}`] : [],
|
|
1544
|
+
`msgctxt ${poString(key)}`,
|
|
1545
|
+
`msgid ${poString(source)}`,
|
|
1546
|
+
`msgstr ${poString(target)}`
|
|
1547
|
+
].join("\n")),
|
|
1548
|
+
""
|
|
1549
|
+
].join("\n\n");
|
|
1550
|
+
}
|
|
1551
|
+
function parsePo(content) {
|
|
1552
|
+
const entries = {};
|
|
1553
|
+
let locale;
|
|
1554
|
+
let msgctxt;
|
|
1555
|
+
let msgid;
|
|
1556
|
+
let msgstr;
|
|
1557
|
+
let fuzzy = false;
|
|
1558
|
+
let field;
|
|
1559
|
+
const finish = () => {
|
|
1560
|
+
if (msgid === "" && msgctxt === void 0) {
|
|
1561
|
+
const lang = /^Language:[ \t]*(.+?)[ \t]*$/m.exec(msgstr ?? "")?.[1];
|
|
1562
|
+
if (lang) locale = lang;
|
|
1563
|
+
} else if (msgctxt !== void 0 && msgid !== void 0) entries[msgctxt] = fuzzy ? "" : msgstr ?? "";
|
|
1564
|
+
msgctxt = msgid = msgstr = field = void 0;
|
|
1565
|
+
fuzzy = false;
|
|
1566
|
+
};
|
|
1567
|
+
for (const raw of content.replace(/^\uFEFF/, "").split(/\r\n|[\r\n]/)) {
|
|
1568
|
+
const line = raw.trim();
|
|
1569
|
+
if (line === "") {
|
|
1570
|
+
finish();
|
|
1571
|
+
continue;
|
|
1572
|
+
}
|
|
1573
|
+
if (line.startsWith("#")) {
|
|
1574
|
+
if (/^#,.*\bfuzzy\b/.test(line)) fuzzy = true;
|
|
1575
|
+
continue;
|
|
1576
|
+
}
|
|
1577
|
+
const keyword = /^(msgctxt|msgid_plural|msgid|msgstr(?:\[\d+\])?)\s+(.*)$/.exec(line);
|
|
1578
|
+
if (keyword) {
|
|
1579
|
+
const [, name, rest] = keyword;
|
|
1580
|
+
const value = poUnquote(rest);
|
|
1581
|
+
if (value === void 0) continue;
|
|
1582
|
+
if (name === "msgctxt") {
|
|
1583
|
+
msgctxt = value;
|
|
1584
|
+
field = "msgctxt";
|
|
1585
|
+
} else if (name === "msgid") {
|
|
1586
|
+
msgid = value;
|
|
1587
|
+
field = "msgid";
|
|
1588
|
+
} else if (name === "msgstr" || name === "msgstr[0]") {
|
|
1589
|
+
msgstr = value;
|
|
1590
|
+
field = "msgstr";
|
|
1591
|
+
} else field = "other";
|
|
1592
|
+
continue;
|
|
1593
|
+
}
|
|
1594
|
+
const continuation = poUnquote(line);
|
|
1595
|
+
if (continuation === void 0 || field === void 0 || field === "other") continue;
|
|
1596
|
+
if (field === "msgctxt") msgctxt = (msgctxt ?? "") + continuation;
|
|
1597
|
+
else if (field === "msgid") msgid = (msgid ?? "") + continuation;
|
|
1598
|
+
else msgstr = (msgstr ?? "") + continuation;
|
|
1599
|
+
}
|
|
1600
|
+
finish();
|
|
1601
|
+
return {
|
|
1602
|
+
locale,
|
|
1603
|
+
entries
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1606
|
+
function poString(text) {
|
|
1607
|
+
return `"${text.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n").replace(/\t/g, "\\t").replace(/\r/g, "\\r")}"`;
|
|
1608
|
+
}
|
|
1609
|
+
function poUnquote(text) {
|
|
1610
|
+
const match = /^"((?:[^"\\]|\\.)*)"$/.exec(text);
|
|
1611
|
+
if (!match) return void 0;
|
|
1612
|
+
return match[1].replace(/\\(.)/g, (_, ch) => {
|
|
1613
|
+
if (ch === "n") return "\n";
|
|
1614
|
+
if (ch === "t") return " ";
|
|
1615
|
+
if (ch === "r") return "\r";
|
|
1616
|
+
return ch;
|
|
1617
|
+
});
|
|
1618
|
+
}
|
|
1619
|
+
//#endregion
|
|
1253
1620
|
//#region src/translate.ts
|
|
1254
1621
|
async function translateCatalogs(cfg, catalogs, provider, options = {}) {
|
|
1255
1622
|
const batchSize = options.batchSize ?? 20;
|
|
@@ -1271,10 +1638,12 @@ async function translateCatalogs(cfg, catalogs, provider, options = {}) {
|
|
|
1271
1638
|
for (let i = 0; i < missing.length; i += batchSize) {
|
|
1272
1639
|
const keys = missing.slice(i, i + batchSize);
|
|
1273
1640
|
const messages = Object.fromEntries(keys.map((key) => [key, source[key]]));
|
|
1641
|
+
const origins = options.origins ? Object.fromEntries(keys.filter((key) => options.origins[key]).map((key) => [key, options.origins[key]])) : void 0;
|
|
1274
1642
|
const out = await provider({
|
|
1275
1643
|
sourceLocale: cfg.sourceLocale,
|
|
1276
1644
|
targetLocale: locale,
|
|
1277
|
-
messages
|
|
1645
|
+
messages,
|
|
1646
|
+
origins
|
|
1278
1647
|
});
|
|
1279
1648
|
for (const key of keys) {
|
|
1280
1649
|
const text = out[key];
|
|
@@ -1329,8 +1698,8 @@ function exportCatalogs(cfg, catalogs, options = {}) {
|
|
|
1329
1698
|
}));
|
|
1330
1699
|
const untranslated = all.filter((entry) => !entry.target);
|
|
1331
1700
|
const entries = options.missing ? untranslated : all;
|
|
1332
|
-
const path = join(dir, `${locale}.${format === "
|
|
1333
|
-
writeFileSync(path, format === "csv" ? toCsv(entries) : toXliff(cfg.sourceLocale, locale, entries));
|
|
1701
|
+
const path = join(dir, `${locale}.${format === "xliff" ? "xlf" : format}`);
|
|
1702
|
+
writeFileSync(path, format === "csv" ? toCsv(entries) : format === "po" ? toPo(cfg.sourceLocale, locale, entries) : toXliff(cfg.sourceLocale, locale, entries));
|
|
1334
1703
|
files.push({
|
|
1335
1704
|
locale,
|
|
1336
1705
|
path,
|
|
@@ -1418,7 +1787,14 @@ function parseExchangeFile(file, localeOverride) {
|
|
|
1418
1787
|
locale: localeOverride ?? basename(file).replace(/\.csv$/i, ""),
|
|
1419
1788
|
entries: parseCsv(content)
|
|
1420
1789
|
};
|
|
1421
|
-
|
|
1790
|
+
if (/\.po$/i.test(file)) {
|
|
1791
|
+
const parsed = parsePo(content);
|
|
1792
|
+
return {
|
|
1793
|
+
locale: localeOverride ?? parsed.locale ?? basename(file).replace(/\.po$/i, ""),
|
|
1794
|
+
entries: parsed.entries
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1797
|
+
throw new Error(`[verbaly] ${file}: unsupported format, expected .xlf, .xliff, .csv or .po.`);
|
|
1422
1798
|
}
|
|
1423
1799
|
function toXliff(sourceLocale, locale, entries) {
|
|
1424
1800
|
const units = entries.map(({ key, source, target, location }) => [
|
|
@@ -1429,8 +1805,8 @@ function toXliff(sourceLocale, locale, entries) {
|
|
|
1429
1805
|
" </notes>"
|
|
1430
1806
|
] : [],
|
|
1431
1807
|
` <segment state="${target ? "translated" : "initial"}">`,
|
|
1432
|
-
` <source>${
|
|
1433
|
-
` <target>${
|
|
1808
|
+
` <source>${messageToInline(source)}</source>`,
|
|
1809
|
+
` <target>${messageToInline(target)}</target>`,
|
|
1434
1810
|
" </segment>",
|
|
1435
1811
|
" </unit>"
|
|
1436
1812
|
].join("\n")).join("\n");
|
|
@@ -1451,30 +1827,16 @@ function parseXliff(content) {
|
|
|
1451
1827
|
const id = /\bid\s*=\s*"([^"]*)"/.exec(match[1])?.[1];
|
|
1452
1828
|
if (!id) continue;
|
|
1453
1829
|
const target = /<target\b[^>]*>([\s\S]*?)<\/target>/.exec(match[2])?.[1];
|
|
1454
|
-
entries[unescapeXml(id)] = target === void 0 ? "" :
|
|
1830
|
+
entries[unescapeXml(id)] = target === void 0 ? "" : inlineToMessage(stripCdata(target));
|
|
1455
1831
|
}
|
|
1456
1832
|
return {
|
|
1457
1833
|
locale,
|
|
1458
1834
|
entries
|
|
1459
1835
|
};
|
|
1460
1836
|
}
|
|
1461
|
-
function escapeXml(text) {
|
|
1462
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1463
|
-
}
|
|
1464
1837
|
function stripCdata(text) {
|
|
1465
1838
|
return text.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, "$1");
|
|
1466
1839
|
}
|
|
1467
|
-
function unescapeXml(text) {
|
|
1468
|
-
return text.replace(/&(lt|gt|amp|quot|apos|#x?[0-9a-fA-F]+);/g, (_, entity) => {
|
|
1469
|
-
if (entity === "lt") return "<";
|
|
1470
|
-
if (entity === "gt") return ">";
|
|
1471
|
-
if (entity === "amp") return "&";
|
|
1472
|
-
if (entity === "quot") return "\"";
|
|
1473
|
-
if (entity === "apos") return "'";
|
|
1474
|
-
const code = entity.startsWith("#x") ? parseInt(entity.slice(2), 16) : parseInt(entity.slice(1), 10);
|
|
1475
|
-
return String.fromCodePoint(code);
|
|
1476
|
-
});
|
|
1477
|
-
}
|
|
1478
1840
|
function toCsv(entries) {
|
|
1479
1841
|
return [
|
|
1480
1842
|
"key,source,target,location",
|
|
@@ -1551,7 +1913,9 @@ function claudeProvider(options = {}) {
|
|
|
1551
1913
|
};
|
|
1552
1914
|
}
|
|
1553
1915
|
function buildPrompt(request) {
|
|
1554
|
-
|
|
1916
|
+
const origins = request.origins;
|
|
1917
|
+
const context = origins && Object.keys(origins).length ? `\n\nWhere each string appears (context for tone and length, do not translate these paths):\n` + Object.entries(origins).map(([key, files]) => ` ${key}: ${files.join(", ")}`).join("\n") : "";
|
|
1918
|
+
return `Translate each value from "${request.sourceLocale}" to "${request.targetLocale}". Return a JSON object with the same keys and translated values.\n\n` + JSON.stringify(request.messages, null, 2) + context;
|
|
1555
1919
|
}
|
|
1556
1920
|
function batchFormat(request) {
|
|
1557
1921
|
const keys = Object.keys(request.messages);
|
|
@@ -1694,7 +2058,8 @@ async function renderSite(cfg, options = {}) {
|
|
|
1694
2058
|
const attribute = options.attribute ?? cfg.render.attribute;
|
|
1695
2059
|
const baseUrl = (options.baseUrl ?? cfg.render.baseUrl)?.replace(/\/+$/, "");
|
|
1696
2060
|
const wantHreflang = (options.hreflang ?? cfg.render.hreflang ?? true) && baseUrl !== void 0;
|
|
1697
|
-
const
|
|
2061
|
+
const sitemap = options.sitemap ?? cfg.render.sitemap ?? false;
|
|
2062
|
+
const wantSitemap = sitemap !== false && baseUrl !== void 0;
|
|
1698
2063
|
const clean = options.clean ?? cfg.render.clean ?? false;
|
|
1699
2064
|
const catalogs = loadCatalogs(cfg);
|
|
1700
2065
|
if (clean) {
|
|
@@ -1737,7 +2102,7 @@ async function renderSite(cfg, options = {}) {
|
|
|
1737
2102
|
writeFileSync(out, result.html);
|
|
1738
2103
|
}
|
|
1739
2104
|
}
|
|
1740
|
-
if (wantSitemap && urls.length) writeFileSync(join(site, typeof
|
|
2105
|
+
if (wantSitemap && urls.length) writeFileSync(join(site, typeof sitemap === "string" ? sitemap : "sitemap-i18n.xml"), buildSitemap(urls));
|
|
1741
2106
|
return {
|
|
1742
2107
|
files: files.length,
|
|
1743
2108
|
locales: [...locales],
|
|
@@ -1845,9 +2210,10 @@ function decodeEntities(text) {
|
|
|
1845
2210
|
}
|
|
1846
2211
|
//#endregion
|
|
1847
2212
|
//#region src/status.ts
|
|
1848
|
-
function status(cfg, catalogs, registry) {
|
|
2213
|
+
function status(cfg, catalogs, registry, drafts = {}) {
|
|
1849
2214
|
const source = catalogs[cfg.sourceLocale] ?? {};
|
|
1850
2215
|
const needed = /* @__PURE__ */ new Set([...registry.messages().keys(), ...Object.keys(source)]);
|
|
2216
|
+
const live = effectiveDrafts(drafts, catalogs);
|
|
1851
2217
|
const locales = [];
|
|
1852
2218
|
for (const locale of cfg.locales) {
|
|
1853
2219
|
if (locale === cfg.sourceLocale) continue;
|
|
@@ -1856,7 +2222,8 @@ function status(cfg, catalogs, registry) {
|
|
|
1856
2222
|
locales.push({
|
|
1857
2223
|
locale,
|
|
1858
2224
|
translated,
|
|
1859
|
-
total: needed.size
|
|
2225
|
+
total: needed.size,
|
|
2226
|
+
drafts: live[locale]?.length ?? 0
|
|
1860
2227
|
});
|
|
1861
2228
|
}
|
|
1862
2229
|
return {
|
|
@@ -1871,10 +2238,11 @@ function formatStatusResult(result) {
|
|
|
1871
2238
|
lines.push(" no target locales (add locales to your config)");
|
|
1872
2239
|
return lines.join("\n");
|
|
1873
2240
|
}
|
|
1874
|
-
for (const { locale, translated, total } of result.locales) {
|
|
2241
|
+
for (const { locale, translated, total, drafts } of result.locales) {
|
|
1875
2242
|
const pct = total === 0 ? 100 : Math.floor(translated / total * 100);
|
|
1876
2243
|
const mark = translated === total ? " ✓" : "";
|
|
1877
|
-
|
|
2244
|
+
const draftNote = drafts > 0 ? `, ${drafts} unreviewed` : "";
|
|
2245
|
+
lines.push(` ${locale}: ${translated}/${total} translated (${pct}%${draftNote})${mark}`);
|
|
1878
2246
|
}
|
|
1879
2247
|
return lines.join("\n");
|
|
1880
2248
|
}
|
|
@@ -2132,6 +2500,6 @@ function line(node) {
|
|
|
2132
2500
|
return node.loc?.start?.line ?? 1;
|
|
2133
2501
|
}
|
|
2134
2502
|
//#endregion
|
|
2135
|
-
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, createSourceFilter, detectBundler, doctor, exportCatalogs, extractProject, findConfigFile, formatCheckResult, formatStatusResult, generateDts, generateLocaleModule, generateRuntimeModule, githubCheckAnnotations, importCatalogs, init, isMobileFormat, isTransformTarget, loadCatalogs, loadConfig, loadConfigFile, loadVirtualModule, parseExchangeFile, pruneCatalogs, pseudoCatalogs, pseudoLocalize, readCatalog, renderHtml, renderParamType, renderSite, resolveConfig, resolveVirtualId, runBuildGate, serializeCatalog, stableKey, status, structureMatches, syncCatalogs, targetLocales, transformCode, transformSource, translateCatalogs, watchProject, wrapCode, wrapProject, writeCatalog, writeDts };
|
|
2503
|
+
export { DRAFTS_FILE, LOCALE_MODULE_PREFIX, MessageRegistry, PSEUDO_LOCALE, RESOLVED_VIRTUAL_ID, SFC_FILE_RE, SOURCE_FILE_RE, VIRTUAL_ID, analyze, analyzeFile, analyzeSfc, catalogPath, check, claudeProvider, clearDrafts, collectParams, createSourceFilter, detectBundler, doctor, effectiveDrafts, exportCatalogs, extractProject, findConfigFile, formatCheckResult, formatStatusResult, generateDts, generateLocaleModule, generateRuntimeModule, githubCheckAnnotations, importCatalogs, init, isMobileFormat, isTransformTarget, loadCatalogs, loadConfig, loadConfigFile, loadDrafts, loadVirtualModule, markDrafts, parseExchangeFile, pruneCatalogs, pseudoCatalogs, pseudoLocalize, readCatalog, renderHtml, renderParamType, renderSite, resolveConfig, resolveVirtualId, runBuildGate, saveDrafts, serializeCatalog, stableKey, status, structureMatches, syncCatalogs, targetLocales, transformCode, transformSource, translateCatalogs, watchProject, wrapCode, wrapProject, writeCatalog, writeDts };
|
|
2136
2504
|
|
|
2137
2505
|
//# sourceMappingURL=index.js.map
|