@verbaly/compiler 0.14.0 → 0.15.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 +17 -3
- package/dist/cli.js +305 -67
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +41 -3
- package/dist/index.js +249 -66
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RichLink } from "verbaly";
|
|
1
|
+
import { MessageTree, RichLink } from "verbaly";
|
|
2
2
|
import MagicString from "magic-string";
|
|
3
3
|
//#region src/analyze.d.ts
|
|
4
4
|
interface TaggedParam {
|
|
@@ -150,6 +150,44 @@ interface DoctorResult {
|
|
|
150
150
|
}
|
|
151
151
|
declare function doctor(cfg: ResolvedConfig): Promise<DoctorResult>;
|
|
152
152
|
//#endregion
|
|
153
|
+
//#region src/exchange.d.ts
|
|
154
|
+
type ExchangeFormat = 'xliff' | 'csv';
|
|
155
|
+
interface ExportOptions {
|
|
156
|
+
locales?: string[];
|
|
157
|
+
format?: ExchangeFormat;
|
|
158
|
+
out?: string;
|
|
159
|
+
missing?: boolean;
|
|
160
|
+
}
|
|
161
|
+
interface ExportedFile {
|
|
162
|
+
locale: string;
|
|
163
|
+
path: string;
|
|
164
|
+
total: number;
|
|
165
|
+
untranslated: number;
|
|
166
|
+
}
|
|
167
|
+
interface ExportResult {
|
|
168
|
+
format: ExchangeFormat;
|
|
169
|
+
dir: string;
|
|
170
|
+
files: ExportedFile[];
|
|
171
|
+
}
|
|
172
|
+
interface ImportOptions {
|
|
173
|
+
locale?: string;
|
|
174
|
+
overwrite?: boolean;
|
|
175
|
+
dryRun?: boolean;
|
|
176
|
+
}
|
|
177
|
+
interface ImportResult {
|
|
178
|
+
imported: Record<string, string[]>;
|
|
179
|
+
rejected: Record<string, string[]>;
|
|
180
|
+
skipped: Record<string, string[]>;
|
|
181
|
+
unknown: Record<string, string[]>;
|
|
182
|
+
}
|
|
183
|
+
declare function exportCatalogs(cfg: ResolvedConfig, catalogs: Catalogs, options?: ExportOptions): ExportResult;
|
|
184
|
+
declare function importCatalogs(cfg: ResolvedConfig, catalogs: Catalogs, files: string[], options?: ImportOptions): ImportResult;
|
|
185
|
+
interface ParsedFile {
|
|
186
|
+
locale: string;
|
|
187
|
+
entries: Record<string, string>;
|
|
188
|
+
}
|
|
189
|
+
declare function parseExchangeFile(file: string, localeOverride?: string): ParsedFile;
|
|
190
|
+
//#endregion
|
|
153
191
|
//#region src/extract.d.ts
|
|
154
192
|
declare function extractProject(cfg: ResolvedConfig): Promise<MessageRegistry>;
|
|
155
193
|
interface SyncResult {
|
|
@@ -203,7 +241,7 @@ interface Alternate {
|
|
|
203
241
|
}
|
|
204
242
|
interface RenderHtmlOptions {
|
|
205
243
|
locale: string;
|
|
206
|
-
catalogs: Catalogs
|
|
244
|
+
catalogs: Catalogs | Record<string, MessageTree>;
|
|
207
245
|
sourceLocale?: string;
|
|
208
246
|
attribute?: string;
|
|
209
247
|
richTags?: string[];
|
|
@@ -241,5 +279,5 @@ interface TransformResult {
|
|
|
241
279
|
}
|
|
242
280
|
declare function transformCode(code: string, file: string, analysis?: Analysis): TransformResult | null;
|
|
243
281
|
//#endregion
|
|
244
|
-
export { type Alternate, type Analysis, type Catalog, type Catalogs, type CheckResult, type ClaudeProviderOptions, type DoctorEntry, type DoctorResult, type InitOptions, type InitResult, MessageRegistry, type MissingEntry, PSEUDO_LOCALE, type ParamType, type RenderConfig, type RenderHtmlOptions, type RenderHtmlResult, type RenderSiteOptions, type RenderSiteResult, 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, detectBundler, doctor, extractProject, findConfigFile, formatCheckResult, generateDts, generateLocaleModule, generateRuntimeModule, init, loadCatalogs, loadConfig, loadConfigFile, pruneCatalogs, pseudoCatalogs, pseudoLocalize, readCatalog, renderHtml, renderParamType, renderSite, resolveConfig, serializeCatalog, stableKey, structureMatches, syncCatalogs, transformCode, translateCatalogs, writeCatalog, writeDts };
|
|
282
|
+
export { type Alternate, type Analysis, 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, MessageRegistry, type MissingEntry, PSEUDO_LOCALE, type ParamType, type RenderConfig, type RenderHtmlOptions, type RenderHtmlResult, type RenderSiteOptions, type RenderSiteResult, 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, detectBundler, doctor, exportCatalogs, extractProject, findConfigFile, formatCheckResult, generateDts, generateLocaleModule, generateRuntimeModule, importCatalogs, init, loadCatalogs, loadConfig, loadConfigFile, parseExchangeFile, pruneCatalogs, pseudoCatalogs, pseudoLocalize, readCatalog, renderHtml, renderParamType, renderSite, resolveConfig, serializeCatalog, stableKey, structureMatches, syncCatalogs, transformCode, translateCatalogs, writeCatalog, writeDts };
|
|
245
283
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parse } from "@babel/parser";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { dirname, join, relative, resolve } from "node:path";
|
|
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
7
|
import { glob } from "tinyglobby";
|
|
@@ -794,6 +794,247 @@ function readDeps(root) {
|
|
|
794
794
|
}
|
|
795
795
|
}
|
|
796
796
|
//#endregion
|
|
797
|
+
//#region src/translate.ts
|
|
798
|
+
async function translateCatalogs(cfg, catalogs, provider, options = {}) {
|
|
799
|
+
const batchSize = options.batchSize ?? 20;
|
|
800
|
+
const targets = (options.locales ?? cfg.locales).filter((locale) => locale !== cfg.sourceLocale && cfg.locales.includes(locale));
|
|
801
|
+
const source = catalogs[cfg.sourceLocale] ?? {};
|
|
802
|
+
const result = {
|
|
803
|
+
translated: {},
|
|
804
|
+
invalid: {},
|
|
805
|
+
pending: {}
|
|
806
|
+
};
|
|
807
|
+
for (const locale of targets) {
|
|
808
|
+
const catalog = catalogs[locale] ??= {};
|
|
809
|
+
const missing = Object.keys(source).filter((key) => source[key] && !catalog[key]);
|
|
810
|
+
if (missing.length === 0) continue;
|
|
811
|
+
if (options.dryRun) {
|
|
812
|
+
result.pending[locale] = missing;
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
for (let i = 0; i < missing.length; i += batchSize) {
|
|
816
|
+
const keys = missing.slice(i, i + batchSize);
|
|
817
|
+
const messages = Object.fromEntries(keys.map((key) => [key, source[key]]));
|
|
818
|
+
const out = await provider({
|
|
819
|
+
sourceLocale: cfg.sourceLocale,
|
|
820
|
+
targetLocale: locale,
|
|
821
|
+
messages
|
|
822
|
+
});
|
|
823
|
+
for (const key of keys) {
|
|
824
|
+
const text = out[key];
|
|
825
|
+
if (typeof text === "string" && text.trim() && structureMatches(source[key], text)) {
|
|
826
|
+
catalog[key] = text;
|
|
827
|
+
(result.translated[locale] ??= []).push(key);
|
|
828
|
+
} else (result.invalid[locale] ??= []).push(key);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return result;
|
|
833
|
+
}
|
|
834
|
+
function structureMatches(source, translated) {
|
|
835
|
+
return sameMembers(paramNames(source), paramNames(translated)) && sameMembers(tagTokens(source), tagTokens(translated));
|
|
836
|
+
}
|
|
837
|
+
function paramNames(message) {
|
|
838
|
+
try {
|
|
839
|
+
return [...collectParams(message).keys()].sort();
|
|
840
|
+
} catch {
|
|
841
|
+
return ["\0invalid"];
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
const TAG = /<(\/?)([a-zA-Z][\w-]*)(\/?)>/g;
|
|
845
|
+
function tagTokens(message) {
|
|
846
|
+
const out = [];
|
|
847
|
+
for (const match of message.matchAll(TAG)) out.push(`${match[1]}${match[2]}${match[3]}`);
|
|
848
|
+
return out.sort();
|
|
849
|
+
}
|
|
850
|
+
function sameMembers(a, b) {
|
|
851
|
+
return a.length === b.length && a.every((value, i) => value === b[i]);
|
|
852
|
+
}
|
|
853
|
+
//#endregion
|
|
854
|
+
//#region src/exchange.ts
|
|
855
|
+
function exportCatalogs(cfg, catalogs, options = {}) {
|
|
856
|
+
const format = options.format ?? "xliff";
|
|
857
|
+
const dir = resolve(cfg.root, options.out ?? "verbaly-export");
|
|
858
|
+
const source = catalogs[cfg.sourceLocale] ?? {};
|
|
859
|
+
const targets = (options.locales ?? cfg.locales).filter((locale) => locale !== cfg.sourceLocale && cfg.locales.includes(locale));
|
|
860
|
+
const files = [];
|
|
861
|
+
mkdirSync(dir, { recursive: true });
|
|
862
|
+
for (const locale of targets) {
|
|
863
|
+
const catalog = catalogs[locale] ?? {};
|
|
864
|
+
let entries = Object.keys(source).filter((key) => source[key]).sort().map((key) => ({
|
|
865
|
+
key,
|
|
866
|
+
source: source[key],
|
|
867
|
+
target: catalog[key] ?? ""
|
|
868
|
+
}));
|
|
869
|
+
if (options.missing) entries = entries.filter((entry) => !entry.target);
|
|
870
|
+
const path = join(dir, `${locale}.${format === "csv" ? "csv" : "xlf"}`);
|
|
871
|
+
writeFileSync(path, format === "csv" ? toCsv(entries) : toXliff(cfg.sourceLocale, locale, entries));
|
|
872
|
+
files.push({
|
|
873
|
+
locale,
|
|
874
|
+
path,
|
|
875
|
+
total: entries.length,
|
|
876
|
+
untranslated: entries.filter((entry) => !entry.target).length
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
return {
|
|
880
|
+
format,
|
|
881
|
+
dir,
|
|
882
|
+
files
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
function importCatalogs(cfg, catalogs, files, options = {}) {
|
|
886
|
+
const source = catalogs[cfg.sourceLocale] ?? {};
|
|
887
|
+
const result = {
|
|
888
|
+
imported: {},
|
|
889
|
+
rejected: {},
|
|
890
|
+
skipped: {},
|
|
891
|
+
unknown: {}
|
|
892
|
+
};
|
|
893
|
+
for (const file of files) {
|
|
894
|
+
const parsed = parseExchangeFile(file, options.locale);
|
|
895
|
+
const locale = parsed.locale;
|
|
896
|
+
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>.`);
|
|
897
|
+
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.`);
|
|
898
|
+
const catalog = catalogs[locale] ??= {};
|
|
899
|
+
for (const [key, text] of Object.entries(parsed.entries)) {
|
|
900
|
+
if (!text.trim()) continue;
|
|
901
|
+
if (!source[key]) {
|
|
902
|
+
(result.unknown[locale] ??= []).push(key);
|
|
903
|
+
continue;
|
|
904
|
+
}
|
|
905
|
+
if (catalog[key] && !options.overwrite) {
|
|
906
|
+
(result.skipped[locale] ??= []).push(key);
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
if (!structureMatches(source[key], text)) {
|
|
910
|
+
(result.rejected[locale] ??= []).push(key);
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
if (!options.dryRun) catalog[key] = text;
|
|
914
|
+
(result.imported[locale] ??= []).push(key);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
return result;
|
|
918
|
+
}
|
|
919
|
+
function parseExchangeFile(file, localeOverride) {
|
|
920
|
+
const content = readFileSync(file, "utf8");
|
|
921
|
+
if (/\.(xlf|xliff)$/i.test(file)) {
|
|
922
|
+
const parsed = parseXliff(content);
|
|
923
|
+
const locale = localeOverride ?? parsed.locale;
|
|
924
|
+
if (!locale) throw new Error(`[verbaly] ${file} has no trgLang/target-language — pass --locale <id>.`);
|
|
925
|
+
return {
|
|
926
|
+
locale,
|
|
927
|
+
entries: parsed.entries
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
if (/\.csv$/i.test(file)) return {
|
|
931
|
+
locale: localeOverride ?? basename(file).replace(/\.csv$/i, ""),
|
|
932
|
+
entries: parseCsv(content)
|
|
933
|
+
};
|
|
934
|
+
throw new Error(`[verbaly] ${file}: unsupported format — expected .xlf, .xliff or .csv.`);
|
|
935
|
+
}
|
|
936
|
+
function toXliff(sourceLocale, locale, entries) {
|
|
937
|
+
const units = entries.map(({ key, source, target }) => [
|
|
938
|
+
` <unit id="${escapeXml(key)}">`,
|
|
939
|
+
` <segment state="${target ? "translated" : "initial"}">`,
|
|
940
|
+
` <source>${escapeXml(source)}</source>`,
|
|
941
|
+
` <target>${escapeXml(target)}</target>`,
|
|
942
|
+
" </segment>",
|
|
943
|
+
" </unit>"
|
|
944
|
+
].join("\n")).join("\n");
|
|
945
|
+
return [
|
|
946
|
+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
|
|
947
|
+
`<xliff xmlns="urn:oasis:names:tc:xliff:document:2.0" version="2.0" srcLang="${escapeXml(sourceLocale)}" trgLang="${escapeXml(locale)}">`,
|
|
948
|
+
` <file id="verbaly" original="${escapeXml(`${locale}.json`)}">`,
|
|
949
|
+
units,
|
|
950
|
+
" </file>",
|
|
951
|
+
"</xliff>",
|
|
952
|
+
""
|
|
953
|
+
].join("\n");
|
|
954
|
+
}
|
|
955
|
+
function parseXliff(content) {
|
|
956
|
+
const locale = /\btrgLang\s*=\s*"([^"]+)"/.exec(content)?.[1] ?? /\btarget-language\s*=\s*"([^"]+)"/.exec(content)?.[1];
|
|
957
|
+
const entries = {};
|
|
958
|
+
for (const match of content.matchAll(/<(?:trans-)?unit\b([^>]*)>([\s\S]*?)<\/(?:trans-)?unit>/g)) {
|
|
959
|
+
const id = /\bid\s*=\s*"([^"]*)"/.exec(match[1])?.[1];
|
|
960
|
+
if (!id) continue;
|
|
961
|
+
const target = /<target\b[^>]*>([\s\S]*?)<\/target>/.exec(match[2])?.[1];
|
|
962
|
+
entries[unescapeXml(id)] = target === void 0 ? "" : unescapeXml(stripCdata(target));
|
|
963
|
+
}
|
|
964
|
+
return {
|
|
965
|
+
locale,
|
|
966
|
+
entries
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
function escapeXml(text) {
|
|
970
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
971
|
+
}
|
|
972
|
+
function stripCdata(text) {
|
|
973
|
+
return text.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, "$1");
|
|
974
|
+
}
|
|
975
|
+
function unescapeXml(text) {
|
|
976
|
+
return text.replace(/&(lt|gt|amp|quot|apos|#x?[0-9a-fA-F]+);/g, (_, entity) => {
|
|
977
|
+
if (entity === "lt") return "<";
|
|
978
|
+
if (entity === "gt") return ">";
|
|
979
|
+
if (entity === "amp") return "&";
|
|
980
|
+
if (entity === "quot") return "\"";
|
|
981
|
+
if (entity === "apos") return "'";
|
|
982
|
+
const code = entity.startsWith("#x") ? parseInt(entity.slice(2), 16) : parseInt(entity.slice(1), 10);
|
|
983
|
+
return String.fromCodePoint(code);
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
function toCsv(entries) {
|
|
987
|
+
return [
|
|
988
|
+
"key,source,target",
|
|
989
|
+
...entries.map(({ key, source, target }) => `${csvField(key)},${csvField(source)},${csvField(target)}`),
|
|
990
|
+
""
|
|
991
|
+
].join("\r\n");
|
|
992
|
+
}
|
|
993
|
+
function csvField(text) {
|
|
994
|
+
return /[",\r\n]/.test(text) ? `"${text.replace(/"/g, "\"\"")}"` : text;
|
|
995
|
+
}
|
|
996
|
+
function parseCsv(content) {
|
|
997
|
+
const rows = csvRows(content);
|
|
998
|
+
const header = rows[0]?.map((cell) => cell.trim().toLowerCase()) ?? [];
|
|
999
|
+
const keyCol = header.indexOf("key");
|
|
1000
|
+
const targetCol = header.indexOf("target");
|
|
1001
|
+
if (keyCol === -1 || targetCol === -1) throw new Error("[verbaly] CSV needs a header row with \"key\" and \"target\" columns.");
|
|
1002
|
+
const entries = {};
|
|
1003
|
+
for (const row of rows.slice(1)) {
|
|
1004
|
+
const key = row[keyCol];
|
|
1005
|
+
if (key) entries[key] = row[targetCol] ?? "";
|
|
1006
|
+
}
|
|
1007
|
+
return entries;
|
|
1008
|
+
}
|
|
1009
|
+
function csvRows(content) {
|
|
1010
|
+
const rows = [];
|
|
1011
|
+
let row = [];
|
|
1012
|
+
let field = "";
|
|
1013
|
+
let quoted = false;
|
|
1014
|
+
for (let i = 0; i < content.length; i++) {
|
|
1015
|
+
const char = content[i];
|
|
1016
|
+
if (quoted) if (char === "\"") if (content[i + 1] === "\"") {
|
|
1017
|
+
field += "\"";
|
|
1018
|
+
i++;
|
|
1019
|
+
} else quoted = false;
|
|
1020
|
+
else field += char;
|
|
1021
|
+
else if (char === "\"") quoted = true;
|
|
1022
|
+
else if (char === ",") {
|
|
1023
|
+
row.push(field);
|
|
1024
|
+
field = "";
|
|
1025
|
+
} else if (char === "\n" || char === "\r") {
|
|
1026
|
+
if (char === "\r" && content[i + 1] === "\n") i++;
|
|
1027
|
+
row.push(field);
|
|
1028
|
+
field = "";
|
|
1029
|
+
if (row.some((cell) => cell !== "")) rows.push(row);
|
|
1030
|
+
row = [];
|
|
1031
|
+
} else field += char;
|
|
1032
|
+
}
|
|
1033
|
+
row.push(field);
|
|
1034
|
+
if (row.some((cell) => cell !== "")) rows.push(row);
|
|
1035
|
+
return rows;
|
|
1036
|
+
}
|
|
1037
|
+
//#endregion
|
|
797
1038
|
//#region src/providers/claude.ts
|
|
798
1039
|
const DEFAULT_MODEL = "claude-sonnet-5";
|
|
799
1040
|
const SYSTEM = `You translate UI strings for the Verbaly i18n library.
|
|
@@ -993,22 +1234,19 @@ function renderHtml(html, options) {
|
|
|
993
1234
|
const richTags = new Set(options.richTags ?? RICH_TAGS);
|
|
994
1235
|
const globalLinks = options.richLinks;
|
|
995
1236
|
const sourceLocale = options.sourceLocale ?? "en";
|
|
996
|
-
const messages = {};
|
|
997
|
-
for (const [locale, catalog] of Object.entries(options.catalogs)) {
|
|
998
|
-
const clean = {};
|
|
999
|
-
for (const [key, msg] of Object.entries(catalog)) if (msg) clean[key] = msg;
|
|
1000
|
-
messages[locale] = clean;
|
|
1001
|
-
}
|
|
1002
1237
|
const v = createVerbaly({
|
|
1003
1238
|
locale: options.locale,
|
|
1004
1239
|
fallback: sourceLocale,
|
|
1005
|
-
messages
|
|
1240
|
+
messages: options.catalogs
|
|
1006
1241
|
});
|
|
1007
1242
|
const t = v.t;
|
|
1008
1243
|
const ms = new MagicString(html);
|
|
1009
1244
|
const missing = /* @__PURE__ */ new Set();
|
|
1010
1245
|
const skip = protectedRanges(html);
|
|
1011
1246
|
const inSkip = (index) => skip.some(([from, to]) => index >= from && index < to);
|
|
1247
|
+
const selfUrl = options.alternates?.find((a) => a.hreflang === options.locale)?.href;
|
|
1248
|
+
const sourceUrl = options.alternates?.find((a) => a.hreflang === "x-default")?.href;
|
|
1249
|
+
const rewriteUrl = selfUrl !== void 0 && selfUrl !== sourceUrl;
|
|
1012
1250
|
START_TAG.lastIndex = 0;
|
|
1013
1251
|
let m;
|
|
1014
1252
|
while ((m = START_TAG.exec(html)) !== null) {
|
|
@@ -1022,6 +1260,8 @@ function renderHtml(html, options) {
|
|
|
1022
1260
|
continue;
|
|
1023
1261
|
}
|
|
1024
1262
|
const attrs = parseAttrs(attrChunk);
|
|
1263
|
+
if (rewriteUrl && tagName === "link" && attrs.get("rel")?.toLowerCase() === "canonical" && decodeEntities(attrs.get("href") ?? "") === sourceUrl) setAttribute(ms, html, chunkStart, openEnd, attrChunk, "href", selfUrl);
|
|
1264
|
+
if (rewriteUrl && tagName === "meta" && attrs.get("property") === "og:url" && decodeEntities(attrs.get("content") ?? "") === sourceUrl) setAttribute(ms, html, chunkStart, openEnd, attrChunk, "content", selfUrl);
|
|
1025
1265
|
const key = attrs.get(attr);
|
|
1026
1266
|
const attrMapRaw = attrs.get(attrsAttr);
|
|
1027
1267
|
if (key === void 0 && attrMapRaw === void 0) continue;
|
|
@@ -1259,63 +1499,6 @@ function transformCode(code, file, analysis) {
|
|
|
1259
1499
|
};
|
|
1260
1500
|
}
|
|
1261
1501
|
//#endregion
|
|
1262
|
-
|
|
1263
|
-
async function translateCatalogs(cfg, catalogs, provider, options = {}) {
|
|
1264
|
-
const batchSize = options.batchSize ?? 20;
|
|
1265
|
-
const targets = (options.locales ?? cfg.locales).filter((locale) => locale !== cfg.sourceLocale && cfg.locales.includes(locale));
|
|
1266
|
-
const source = catalogs[cfg.sourceLocale] ?? {};
|
|
1267
|
-
const result = {
|
|
1268
|
-
translated: {},
|
|
1269
|
-
invalid: {},
|
|
1270
|
-
pending: {}
|
|
1271
|
-
};
|
|
1272
|
-
for (const locale of targets) {
|
|
1273
|
-
const catalog = catalogs[locale] ??= {};
|
|
1274
|
-
const missing = Object.keys(source).filter((key) => source[key] && !catalog[key]);
|
|
1275
|
-
if (missing.length === 0) continue;
|
|
1276
|
-
if (options.dryRun) {
|
|
1277
|
-
result.pending[locale] = missing;
|
|
1278
|
-
continue;
|
|
1279
|
-
}
|
|
1280
|
-
for (let i = 0; i < missing.length; i += batchSize) {
|
|
1281
|
-
const keys = missing.slice(i, i + batchSize);
|
|
1282
|
-
const messages = Object.fromEntries(keys.map((key) => [key, source[key]]));
|
|
1283
|
-
const out = await provider({
|
|
1284
|
-
sourceLocale: cfg.sourceLocale,
|
|
1285
|
-
targetLocale: locale,
|
|
1286
|
-
messages
|
|
1287
|
-
});
|
|
1288
|
-
for (const key of keys) {
|
|
1289
|
-
const text = out[key];
|
|
1290
|
-
if (typeof text === "string" && text.trim() && structureMatches(source[key], text)) {
|
|
1291
|
-
catalog[key] = text;
|
|
1292
|
-
(result.translated[locale] ??= []).push(key);
|
|
1293
|
-
} else (result.invalid[locale] ??= []).push(key);
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
return result;
|
|
1298
|
-
}
|
|
1299
|
-
function structureMatches(source, translated) {
|
|
1300
|
-
return sameMembers(paramNames(source), paramNames(translated)) && sameMembers(tagTokens(source), tagTokens(translated));
|
|
1301
|
-
}
|
|
1302
|
-
function paramNames(message) {
|
|
1303
|
-
try {
|
|
1304
|
-
return [...collectParams(message).keys()].sort();
|
|
1305
|
-
} catch {
|
|
1306
|
-
return ["\0invalid"];
|
|
1307
|
-
}
|
|
1308
|
-
}
|
|
1309
|
-
const TAG = /<(\/?)([a-zA-Z][\w-]*)(\/?)>/g;
|
|
1310
|
-
function tagTokens(message) {
|
|
1311
|
-
const out = [];
|
|
1312
|
-
for (const match of message.matchAll(TAG)) out.push(`${match[1]}${match[2]}${match[3]}`);
|
|
1313
|
-
return out.sort();
|
|
1314
|
-
}
|
|
1315
|
-
function sameMembers(a, b) {
|
|
1316
|
-
return a.length === b.length && a.every((value, i) => value === b[i]);
|
|
1317
|
-
}
|
|
1318
|
-
//#endregion
|
|
1319
|
-
export { MessageRegistry, PSEUDO_LOCALE, VIRTUAL_ID, analyze, catalogPath, check, claudeProvider, collectParams, detectBundler, doctor, extractProject, findConfigFile, formatCheckResult, generateDts, generateLocaleModule, generateRuntimeModule, init, loadCatalogs, loadConfig, loadConfigFile, pruneCatalogs, pseudoCatalogs, pseudoLocalize, readCatalog, renderHtml, renderParamType, renderSite, resolveConfig, serializeCatalog, stableKey, structureMatches, syncCatalogs, transformCode, translateCatalogs, writeCatalog, writeDts };
|
|
1502
|
+
export { MessageRegistry, PSEUDO_LOCALE, VIRTUAL_ID, analyze, catalogPath, check, claudeProvider, collectParams, detectBundler, doctor, exportCatalogs, extractProject, findConfigFile, formatCheckResult, generateDts, generateLocaleModule, generateRuntimeModule, importCatalogs, init, loadCatalogs, loadConfig, loadConfigFile, parseExchangeFile, pruneCatalogs, pseudoCatalogs, pseudoLocalize, readCatalog, renderHtml, renderParamType, renderSite, resolveConfig, serializeCatalog, stableKey, structureMatches, syncCatalogs, transformCode, translateCatalogs, writeCatalog, writeDts };
|
|
1320
1503
|
|
|
1321
1504
|
//# sourceMappingURL=index.js.map
|