@verbaly/compiler 0.24.0 → 0.25.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 +2 -2
- package/dist/cli.js +30 -7
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +21 -5
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -50,11 +50,11 @@ Catalogs are **flat JSON**: most TMS platforms (Crowdin, Lokalise, Phrase, …)
|
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
52
|
npx verbaly export # verbaly-export/<locale>.xlf (XLIFF 2.0, source + target per unit)
|
|
53
|
-
npx verbaly export --format csv # spreadsheet-friendly: key,source,target
|
|
53
|
+
npx verbaly export --format csv # spreadsheet-friendly: key,source,target,location
|
|
54
54
|
npx verbaly import verbaly-export/es.xlf # fill the catalog back
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
`export` writes one file per target locale with the source text alongside the current translation (`--missing` exports only the untranslated entries). `import` reads XLIFF 2.0/1.2 or CSV back and **validates every entry like `translate` does**: a translation that drops a `{param}`, a variant block or an `<em>` tag is rejected and reported, so a translator's typo can't break your UI. Existing translations are kept unless `--overwrite`; `--dry-run` previews everything.
|
|
57
|
+
`export` writes one file per target locale with the source text alongside the current translation (`--missing` exports only the untranslated entries). Every entry carries **where the text lives in your source** (XLIFF `location` notes, a `location` column in CSV), so translators and TMS tools see the context instead of guessing it. `import` reads XLIFF 2.0/1.2 or CSV back and **validates every entry like `translate` does**: a translation that drops a `{param}`, a variant block or an `<em>` tag is rejected and reported, so a translator's typo can't break your UI. Existing translations are kept unless `--overwrite`; `--dry-run` previews everything.
|
|
58
58
|
|
|
59
59
|
## 📱 Mobile resources
|
|
60
60
|
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
2
3
|
import { parseArgs } from "node:util";
|
|
3
4
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, watch, writeFileSync } from "node:fs";
|
|
4
|
-
import {
|
|
5
|
-
import { RICH_TAGS, createVerbaly, normalizeLink, parse, parseTags, safeAttribute } from "verbaly";
|
|
5
|
+
import { RICH_TAGS, createVerbaly, localeDirection, normalizeLink, parse, parseTags, safeAttribute } from "verbaly";
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
7
|
import { glob } from "tinyglobby";
|
|
8
8
|
import { parse as parse$1 } from "@babel/parser";
|
|
@@ -427,6 +427,15 @@ var MessageRegistry = class {
|
|
|
427
427
|
}
|
|
428
428
|
return out;
|
|
429
429
|
}
|
|
430
|
+
origins() {
|
|
431
|
+
const out = this.usedKeys();
|
|
432
|
+
for (const analysis of this.files.values()) for (const msg of analysis.tagged) {
|
|
433
|
+
const files = out.get(msg.key) ?? [];
|
|
434
|
+
if (!files.includes(msg.file)) files.push(msg.file);
|
|
435
|
+
out.set(msg.key, files);
|
|
436
|
+
}
|
|
437
|
+
return out;
|
|
438
|
+
}
|
|
430
439
|
};
|
|
431
440
|
//#endregion
|
|
432
441
|
//#region src/key.ts
|
|
@@ -1128,7 +1137,8 @@ function exportCatalogs(cfg, catalogs, options = {}) {
|
|
|
1128
1137
|
const all = Object.keys(source).filter((key) => source[key]).sort().map((key) => ({
|
|
1129
1138
|
key,
|
|
1130
1139
|
source: source[key],
|
|
1131
|
-
target: catalog[key] ?? ""
|
|
1140
|
+
target: catalog[key] ?? "",
|
|
1141
|
+
location: options.origins?.[key]
|
|
1132
1142
|
}));
|
|
1133
1143
|
const untranslated = all.filter((entry) => !entry.target);
|
|
1134
1144
|
const entries = options.missing ? untranslated : all;
|
|
@@ -1224,8 +1234,13 @@ function parseExchangeFile(file, localeOverride) {
|
|
|
1224
1234
|
throw new Error(`[verbaly] ${file}: unsupported format, expected .xlf, .xliff or .csv.`);
|
|
1225
1235
|
}
|
|
1226
1236
|
function toXliff(sourceLocale, locale, entries) {
|
|
1227
|
-
const units = entries.map(({ key, source, target }) => [
|
|
1237
|
+
const units = entries.map(({ key, source, target, location }) => [
|
|
1228
1238
|
` <unit id="${escapeXml(key)}">`,
|
|
1239
|
+
...location?.length ? [
|
|
1240
|
+
" <notes>",
|
|
1241
|
+
...location.map((file) => ` <note category="location">${escapeXml(file)}</note>`),
|
|
1242
|
+
" </notes>"
|
|
1243
|
+
] : [],
|
|
1229
1244
|
` <segment state="${target ? "translated" : "initial"}">`,
|
|
1230
1245
|
` <source>${escapeXml(source)}</source>`,
|
|
1231
1246
|
` <target>${escapeXml(target)}</target>`,
|
|
@@ -1275,8 +1290,8 @@ function unescapeXml(text) {
|
|
|
1275
1290
|
}
|
|
1276
1291
|
function toCsv(entries) {
|
|
1277
1292
|
return [
|
|
1278
|
-
"key,source,target",
|
|
1279
|
-
...entries.map(({ key, source, target }) => `${csvField(key)},${csvField(source)},${csvField(target)}`),
|
|
1293
|
+
"key,source,target,location",
|
|
1294
|
+
...entries.map(({ key, source, target, location }) => `${csvField(key)},${csvField(source)},${csvField(target)},${csvField(location?.join("; ") ?? "")}`),
|
|
1280
1295
|
""
|
|
1281
1296
|
].join("\r\n");
|
|
1282
1297
|
}
|
|
@@ -1378,6 +1393,7 @@ function renderHtml(html, options) {
|
|
|
1378
1393
|
const chunkStart = m.index + 1 + rawName.length;
|
|
1379
1394
|
if (tagName === "html" && options.setLang !== false) {
|
|
1380
1395
|
setAttribute(ms, html, chunkStart, openEnd, attrChunk, "lang", options.locale);
|
|
1396
|
+
setAttribute(ms, html, chunkStart, openEnd, attrChunk, "dir", localeDirection(options.locale));
|
|
1381
1397
|
continue;
|
|
1382
1398
|
}
|
|
1383
1399
|
const attrs = parseAttrs(attrChunk);
|
|
@@ -1876,7 +1892,8 @@ async function runCli(args = process.argv.slice(2)) {
|
|
|
1876
1892
|
locales: values.locales?.split(","),
|
|
1877
1893
|
format,
|
|
1878
1894
|
out: values.out,
|
|
1879
|
-
missing: values.missing
|
|
1895
|
+
missing: values.missing,
|
|
1896
|
+
origins: isMobileFormat(format) ? void 0 : await collectOrigins(cfg)
|
|
1880
1897
|
});
|
|
1881
1898
|
if (result.files.length === 0) {
|
|
1882
1899
|
console.log("[verbaly] no target locales to export (add locales to your config)");
|
|
@@ -1982,6 +1999,12 @@ function rejectStrayFlags(command, values) {
|
|
|
1982
1999
|
process.exitCode = 1;
|
|
1983
2000
|
return true;
|
|
1984
2001
|
}
|
|
2002
|
+
async function collectOrigins(cfg) {
|
|
2003
|
+
const registry = await extractProject(cfg);
|
|
2004
|
+
const origins = {};
|
|
2005
|
+
for (const [key, files] of registry.origins()) origins[key] = files.map((file) => relative(cfg.root, file).replaceAll("\\", "/"));
|
|
2006
|
+
return origins;
|
|
2007
|
+
}
|
|
1985
2008
|
async function resolveProvider(cfg, model) {
|
|
1986
2009
|
const configured = cfg.translate.provider;
|
|
1987
2010
|
if (typeof configured === "function") return configured;
|