@verbaly/compiler 0.9.0 → 0.11.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/LICENSE +21 -201
- package/README.md +64 -43
- package/dist/{claude-RQATA6OI.js → claude-XSRCRBAQ.js} +1 -1
- package/dist/claude-XSRCRBAQ.js.map +1 -0
- package/dist/cli.js +383 -4
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +39 -1
- package/dist/index.js +360 -3
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/claude-RQATA6OI.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -472,7 +472,8 @@ function resolveConfig(config = {}) {
|
|
|
472
472
|
locales: [...locales],
|
|
473
473
|
include: config.include ?? ["src/**/*.{js,jsx,ts,tsx,mjs,mts}"],
|
|
474
474
|
exclude: config.exclude ?? ["**/node_modules/**", "**/dist/**"],
|
|
475
|
-
translate: config.translate ?? {}
|
|
475
|
+
translate: config.translate ?? {},
|
|
476
|
+
render: config.render ?? {}
|
|
476
477
|
};
|
|
477
478
|
}
|
|
478
479
|
async function loadConfigFile(root) {
|
|
@@ -673,12 +674,363 @@ function isModuleNotFound2(error, name) {
|
|
|
673
674
|
return error instanceof Error && error.code === "ERR_MODULE_NOT_FOUND" && error.message.includes(name);
|
|
674
675
|
}
|
|
675
676
|
|
|
676
|
-
// src/
|
|
677
|
+
// src/pseudo.ts
|
|
678
|
+
var PSEUDO_LOCALE = "en-XA";
|
|
679
|
+
var ACCENTS = {
|
|
680
|
+
a: "\xE1",
|
|
681
|
+
b: "\u0180",
|
|
682
|
+
c: "\xE7",
|
|
683
|
+
d: "\u0111",
|
|
684
|
+
e: "\xE9",
|
|
685
|
+
f: "\u0192",
|
|
686
|
+
g: "\u011F",
|
|
687
|
+
h: "\u0125",
|
|
688
|
+
i: "\xED",
|
|
689
|
+
j: "\u0135",
|
|
690
|
+
k: "\u0137",
|
|
691
|
+
l: "\u013A",
|
|
692
|
+
m: "\u0271",
|
|
693
|
+
n: "\xF1",
|
|
694
|
+
o: "\xF3",
|
|
695
|
+
p: "\xFE",
|
|
696
|
+
q: "\u01EB",
|
|
697
|
+
r: "\u0155",
|
|
698
|
+
s: "\u0161",
|
|
699
|
+
t: "\u0163",
|
|
700
|
+
u: "\xFA",
|
|
701
|
+
v: "\u1E7D",
|
|
702
|
+
w: "\u0175",
|
|
703
|
+
x: "\u1E8B",
|
|
704
|
+
y: "\xFD",
|
|
705
|
+
z: "\u017E",
|
|
706
|
+
A: "\xC1",
|
|
707
|
+
B: "\u0181",
|
|
708
|
+
C: "\xC7",
|
|
709
|
+
D: "\u0110",
|
|
710
|
+
E: "\xC9",
|
|
711
|
+
F: "\u0191",
|
|
712
|
+
G: "\u011E",
|
|
713
|
+
H: "\u0124",
|
|
714
|
+
I: "\xCD",
|
|
715
|
+
J: "\u0134",
|
|
716
|
+
K: "\u0136",
|
|
717
|
+
L: "\u0139",
|
|
718
|
+
M: "\u1E3E",
|
|
719
|
+
N: "\xD1",
|
|
720
|
+
O: "\xD3",
|
|
721
|
+
P: "\xDE",
|
|
722
|
+
Q: "\u01EA",
|
|
723
|
+
R: "\u0154",
|
|
724
|
+
S: "\u0160",
|
|
725
|
+
T: "\u0162",
|
|
726
|
+
U: "\xDA",
|
|
727
|
+
V: "\u1E7C",
|
|
728
|
+
W: "\u0174",
|
|
729
|
+
X: "\u1E8C",
|
|
730
|
+
Y: "\xDD",
|
|
731
|
+
Z: "\u017D"
|
|
732
|
+
};
|
|
733
|
+
var TAG_AT = /<\/?[a-zA-Z][\w-]*\/?>/y;
|
|
734
|
+
function pseudoLocalize(message) {
|
|
735
|
+
let out = "";
|
|
736
|
+
let letters = 0;
|
|
737
|
+
let i = 0;
|
|
738
|
+
while (i < message.length) {
|
|
739
|
+
const two = message.slice(i, i + 2);
|
|
740
|
+
if (two === "{{" || two === "}}" || two === "||" || two === "##") {
|
|
741
|
+
out += two;
|
|
742
|
+
i += 2;
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
const ch = message[i];
|
|
746
|
+
if (ch === "{") {
|
|
747
|
+
const end = matchBrace(message, i);
|
|
748
|
+
out += message.slice(i, end);
|
|
749
|
+
i = end;
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
if (ch === "<") {
|
|
753
|
+
TAG_AT.lastIndex = i;
|
|
754
|
+
const m = TAG_AT.exec(message);
|
|
755
|
+
if (m) {
|
|
756
|
+
out += m[0];
|
|
757
|
+
i += m[0].length;
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
const mapped = ACCENTS[ch];
|
|
762
|
+
if (mapped) {
|
|
763
|
+
out += mapped;
|
|
764
|
+
letters += 1;
|
|
765
|
+
} else {
|
|
766
|
+
out += ch;
|
|
767
|
+
}
|
|
768
|
+
i += 1;
|
|
769
|
+
}
|
|
770
|
+
const pad = "~".repeat(Math.ceil(letters / 3));
|
|
771
|
+
return `\u27E6${out}${pad ? " " + pad : ""}\u27E7`;
|
|
772
|
+
}
|
|
773
|
+
function matchBrace(message, start) {
|
|
774
|
+
let depth = 0;
|
|
775
|
+
for (let i = start; i < message.length; i++) {
|
|
776
|
+
const two = message.slice(i, i + 2);
|
|
777
|
+
if (two === "{{" || two === "}}") {
|
|
778
|
+
i += 1;
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
const ch = message[i];
|
|
782
|
+
if (ch === "{") depth += 1;
|
|
783
|
+
else if (ch === "}") {
|
|
784
|
+
depth -= 1;
|
|
785
|
+
if (depth === 0) return i + 1;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return message.length;
|
|
789
|
+
}
|
|
790
|
+
function pseudoCatalogs(cfg, catalogs, locale = PSEUDO_LOCALE) {
|
|
791
|
+
const source = catalogs[cfg.sourceLocale] ?? {};
|
|
792
|
+
const target = {};
|
|
793
|
+
for (const [key, msg] of Object.entries(source)) {
|
|
794
|
+
target[key] = msg ? pseudoLocalize(msg) : "";
|
|
795
|
+
}
|
|
796
|
+
catalogs[locale] = target;
|
|
797
|
+
return Object.keys(target).filter((key) => target[key]);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// src/render.ts
|
|
801
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
802
|
+
import { dirname, join as join4, relative } from "path";
|
|
677
803
|
import MagicString from "magic-string";
|
|
804
|
+
import { glob as glob2 } from "tinyglobby";
|
|
805
|
+
import {
|
|
806
|
+
createVerbaly,
|
|
807
|
+
parseTags,
|
|
808
|
+
RICH_TAGS,
|
|
809
|
+
safeHref
|
|
810
|
+
} from "verbaly";
|
|
811
|
+
var VOID_TAGS = /* @__PURE__ */ new Set([
|
|
812
|
+
"area",
|
|
813
|
+
"base",
|
|
814
|
+
"br",
|
|
815
|
+
"col",
|
|
816
|
+
"embed",
|
|
817
|
+
"hr",
|
|
818
|
+
"img",
|
|
819
|
+
"input",
|
|
820
|
+
"link",
|
|
821
|
+
"meta",
|
|
822
|
+
"param",
|
|
823
|
+
"source",
|
|
824
|
+
"track",
|
|
825
|
+
"wbr"
|
|
826
|
+
]);
|
|
827
|
+
var START_TAG = /<([a-zA-Z][a-zA-Z0-9-]*)((?:"[^"]*"|'[^']*'|[^"'>])*)>/g;
|
|
828
|
+
var ATTR = /([^\s=/"'<>]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+)))?/g;
|
|
829
|
+
function renderHtml(html, options) {
|
|
830
|
+
const attr = options.attribute ?? "data-verbaly";
|
|
831
|
+
const argsAttr = `${attr}-args`;
|
|
832
|
+
const attrsAttr = `${attr}-attr`;
|
|
833
|
+
const richAttr = `${attr}-rich`;
|
|
834
|
+
const linksAttr = `${attr}-links`;
|
|
835
|
+
const richTags = new Set(options.richTags ?? RICH_TAGS);
|
|
836
|
+
const globalLinks = options.richLinks;
|
|
837
|
+
const sourceLocale = options.sourceLocale ?? "en";
|
|
838
|
+
const messages = {};
|
|
839
|
+
for (const [locale, catalog] of Object.entries(options.catalogs)) {
|
|
840
|
+
const clean = {};
|
|
841
|
+
for (const [key, msg] of Object.entries(catalog)) if (msg) clean[key] = msg;
|
|
842
|
+
messages[locale] = clean;
|
|
843
|
+
}
|
|
844
|
+
const v = createVerbaly({ locale: options.locale, fallback: sourceLocale, messages });
|
|
845
|
+
const t = v.t;
|
|
846
|
+
const ms = new MagicString(html);
|
|
847
|
+
const missing = /* @__PURE__ */ new Set();
|
|
848
|
+
const skip = protectedRanges(html);
|
|
849
|
+
const inSkip = (index) => skip.some(([from, to]) => index >= from && index < to);
|
|
850
|
+
START_TAG.lastIndex = 0;
|
|
851
|
+
let m;
|
|
852
|
+
while ((m = START_TAG.exec(html)) !== null) {
|
|
853
|
+
if (inSkip(m.index)) continue;
|
|
854
|
+
const [full, rawName, attrChunk] = m;
|
|
855
|
+
const tagName = rawName.toLowerCase();
|
|
856
|
+
const openEnd = m.index + full.length;
|
|
857
|
+
const chunkStart = m.index + 1 + rawName.length;
|
|
858
|
+
if (tagName === "html" && options.setLang !== false) {
|
|
859
|
+
setAttribute(ms, html, chunkStart, openEnd, attrChunk, "lang", options.locale);
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
const attrs = parseAttrs(attrChunk);
|
|
863
|
+
const key = attrs.get(attr);
|
|
864
|
+
const attrMapRaw = attrs.get(attrsAttr);
|
|
865
|
+
if (key === void 0 && attrMapRaw === void 0) continue;
|
|
866
|
+
const args = parseArgs(attrs.get(argsAttr));
|
|
867
|
+
if (key) {
|
|
868
|
+
if (!v.has(key)) {
|
|
869
|
+
missing.add(key);
|
|
870
|
+
} else if (!VOID_TAGS.has(tagName) && !attrChunk.trimEnd().endsWith("/")) {
|
|
871
|
+
const close = findClose(html, tagName, openEnd, inSkip);
|
|
872
|
+
if (close) {
|
|
873
|
+
const text = t(key, args);
|
|
874
|
+
const own = parseArgs(attrs.get(linksAttr));
|
|
875
|
+
const links = own ? globalLinks ? { ...globalLinks, ...own } : own : globalLinks;
|
|
876
|
+
const content = attrs.has(richAttr) ? richToHtml(parseTags(text), richTags, links) : escapeHtml(text);
|
|
877
|
+
if (html.slice(openEnd, close.contentEnd) !== content) {
|
|
878
|
+
if (openEnd === close.contentEnd) ms.appendLeft(openEnd, content);
|
|
879
|
+
else ms.overwrite(openEnd, close.contentEnd, content);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
if (attrMapRaw !== void 0) {
|
|
885
|
+
const map = parseArgs(attrMapRaw);
|
|
886
|
+
if (map) {
|
|
887
|
+
for (const [name, attrKey] of Object.entries(map)) {
|
|
888
|
+
if (typeof attrKey !== "string" || name.toLowerCase().startsWith("on")) continue;
|
|
889
|
+
if (!v.has(attrKey)) {
|
|
890
|
+
missing.add(attrKey);
|
|
891
|
+
continue;
|
|
892
|
+
}
|
|
893
|
+
setAttribute(ms, html, chunkStart, openEnd, attrChunk, name, t(attrKey, args));
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
return { html: ms.toString(), missing: [...missing] };
|
|
899
|
+
}
|
|
900
|
+
async function renderSite(cfg, options = {}) {
|
|
901
|
+
const site = join4(cfg.root, options.site ?? "dist");
|
|
902
|
+
const locales = options.locales ?? cfg.locales;
|
|
903
|
+
const catalogs = loadCatalogs(cfg);
|
|
904
|
+
const files = await glob2("**/*.html", {
|
|
905
|
+
cwd: site,
|
|
906
|
+
absolute: true,
|
|
907
|
+
ignore: locales.map((locale) => `${locale}/**`)
|
|
908
|
+
});
|
|
909
|
+
const missing = {};
|
|
910
|
+
for (const file of files) {
|
|
911
|
+
const html = readFileSync4(file, "utf8");
|
|
912
|
+
const rel = relative(site, file);
|
|
913
|
+
for (const locale of locales) {
|
|
914
|
+
const result = renderHtml(html, {
|
|
915
|
+
locale,
|
|
916
|
+
catalogs,
|
|
917
|
+
sourceLocale: cfg.sourceLocale,
|
|
918
|
+
attribute: options.attribute,
|
|
919
|
+
richTags: options.richTags,
|
|
920
|
+
richLinks: options.richLinks ?? cfg.render.links
|
|
921
|
+
});
|
|
922
|
+
for (const key of result.missing) {
|
|
923
|
+
const list = missing[locale] ??= [];
|
|
924
|
+
if (!list.includes(key)) list.push(key);
|
|
925
|
+
}
|
|
926
|
+
const out = locale === cfg.sourceLocale ? file : join4(site, locale, rel);
|
|
927
|
+
mkdirSync2(dirname(out), { recursive: true });
|
|
928
|
+
writeFileSync3(out, result.html);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
return { files: files.length, locales: [...locales], missing };
|
|
932
|
+
}
|
|
933
|
+
function parseAttrs(chunk) {
|
|
934
|
+
const attrs = /* @__PURE__ */ new Map();
|
|
935
|
+
ATTR.lastIndex = 0;
|
|
936
|
+
let m;
|
|
937
|
+
while ((m = ATTR.exec(chunk)) !== null) {
|
|
938
|
+
attrs.set(m[1].toLowerCase(), m[2] ?? m[3] ?? m[4] ?? "");
|
|
939
|
+
}
|
|
940
|
+
return attrs;
|
|
941
|
+
}
|
|
942
|
+
function parseArgs(raw) {
|
|
943
|
+
if (!raw) return void 0;
|
|
944
|
+
try {
|
|
945
|
+
return JSON.parse(decodeEntities(raw));
|
|
946
|
+
} catch {
|
|
947
|
+
console.warn(`[verbaly] invalid args JSON: ${raw}`);
|
|
948
|
+
return void 0;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
function protectedRanges(html) {
|
|
952
|
+
const ranges = [];
|
|
953
|
+
const re = /<!--[\s\S]*?-->|<script\b[\s\S]*?<\/script\s*>|<style\b[\s\S]*?<\/style\s*>/gi;
|
|
954
|
+
let m;
|
|
955
|
+
while ((m = re.exec(html)) !== null) {
|
|
956
|
+
const openEnd = m[0].startsWith("<!--") ? m.index : html.indexOf(">", m.index) + 1;
|
|
957
|
+
ranges.push([openEnd, m.index + m[0].length]);
|
|
958
|
+
}
|
|
959
|
+
return ranges;
|
|
960
|
+
}
|
|
961
|
+
function findClose(html, tagName, from, inSkip) {
|
|
962
|
+
const re = new RegExp(
|
|
963
|
+
`<${tagName}(?=[\\s/>])(?:"[^"]*"|'[^']*'|[^"'>])*>|</${tagName}\\s*>`,
|
|
964
|
+
"gi"
|
|
965
|
+
);
|
|
966
|
+
re.lastIndex = from;
|
|
967
|
+
let depth = 1;
|
|
968
|
+
let m;
|
|
969
|
+
while ((m = re.exec(html)) !== null) {
|
|
970
|
+
if (inSkip(m.index)) continue;
|
|
971
|
+
if (m[0][1] === "/") {
|
|
972
|
+
depth -= 1;
|
|
973
|
+
if (depth === 0) return { contentEnd: m.index };
|
|
974
|
+
} else if (!m[0].endsWith("/>")) {
|
|
975
|
+
depth += 1;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
return null;
|
|
979
|
+
}
|
|
980
|
+
function setAttribute(ms, html, chunkStart, openEnd, attrChunk, name, value) {
|
|
981
|
+
const escaped = escapeAttr(value);
|
|
982
|
+
const existing = new RegExp(`(\\s${name}\\s*=\\s*)("[^"]*"|'[^']*'|[^\\s>]+)`, "i").exec(
|
|
983
|
+
attrChunk
|
|
984
|
+
);
|
|
985
|
+
if (existing) {
|
|
986
|
+
const valueStart = chunkStart + existing.index + existing[1].length;
|
|
987
|
+
const valueEnd = valueStart + existing[2].length;
|
|
988
|
+
if (html.slice(valueStart, valueEnd) !== `"${escaped}"`) {
|
|
989
|
+
ms.overwrite(valueStart, valueEnd, `"${escaped}"`);
|
|
990
|
+
}
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
const selfClosing = html.slice(openEnd - 2, openEnd) === "/>";
|
|
994
|
+
const insertAt = openEnd - (selfClosing ? 2 : 1);
|
|
995
|
+
ms.appendLeft(insertAt, ` ${name}="${escaped}"`);
|
|
996
|
+
}
|
|
997
|
+
function richToHtml(nodes, allowed, links) {
|
|
998
|
+
let out = "";
|
|
999
|
+
for (const node of nodes) {
|
|
1000
|
+
if (typeof node === "string") {
|
|
1001
|
+
out += escapeHtml(node);
|
|
1002
|
+
} else if (links?.[node.name] !== void 0) {
|
|
1003
|
+
const link = links[node.name];
|
|
1004
|
+
const { href, target, rel } = typeof link === "string" ? { href: link } : link;
|
|
1005
|
+
const safe = safeHref(href);
|
|
1006
|
+
let attrs = safe !== void 0 ? ` href="${escapeAttr(safe)}"` : "";
|
|
1007
|
+
if (target) attrs += ` target="${escapeAttr(target)}"`;
|
|
1008
|
+
if (rel) attrs += ` rel="${escapeAttr(rel)}"`;
|
|
1009
|
+
out += `<a${attrs}>${richToHtml(node.children, allowed, links)}</a>`;
|
|
1010
|
+
} else if (allowed.has(node.name)) {
|
|
1011
|
+
out += `<${node.name}>${richToHtml(node.children, allowed, links)}</${node.name}>`;
|
|
1012
|
+
} else {
|
|
1013
|
+
out += richToHtml(node.children, allowed, links);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
return out;
|
|
1017
|
+
}
|
|
1018
|
+
function escapeHtml(text) {
|
|
1019
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1020
|
+
}
|
|
1021
|
+
function escapeAttr(text) {
|
|
1022
|
+
return escapeHtml(text).replace(/"/g, """);
|
|
1023
|
+
}
|
|
1024
|
+
function decodeEntities(text) {
|
|
1025
|
+
return text.replace(/"/g, '"').replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// src/transform.ts
|
|
1029
|
+
import MagicString2 from "magic-string";
|
|
678
1030
|
function transformCode(code, file, analysis) {
|
|
679
1031
|
const { tagged } = analysis ?? analyze(code, file);
|
|
680
1032
|
if (tagged.length === 0) return null;
|
|
681
|
-
const s = new
|
|
1033
|
+
const s = new MagicString2(code);
|
|
682
1034
|
for (const msg of tagged) {
|
|
683
1035
|
const seen = /* @__PURE__ */ new Set();
|
|
684
1036
|
const entries = msg.params.filter((p) => !seen.has(p.name) && seen.add(p.name));
|
|
@@ -760,6 +1112,7 @@ function sameMembers(a, b) {
|
|
|
760
1112
|
}
|
|
761
1113
|
export {
|
|
762
1114
|
MessageRegistry,
|
|
1115
|
+
PSEUDO_LOCALE,
|
|
763
1116
|
VIRTUAL_ID,
|
|
764
1117
|
analyze,
|
|
765
1118
|
catalogPath,
|
|
@@ -775,8 +1128,12 @@ export {
|
|
|
775
1128
|
loadConfig,
|
|
776
1129
|
loadConfigFile,
|
|
777
1130
|
pruneCatalogs,
|
|
1131
|
+
pseudoCatalogs,
|
|
1132
|
+
pseudoLocalize,
|
|
778
1133
|
readCatalog,
|
|
1134
|
+
renderHtml,
|
|
779
1135
|
renderParamType,
|
|
1136
|
+
renderSite,
|
|
780
1137
|
resolveConfig,
|
|
781
1138
|
serializeCatalog,
|
|
782
1139
|
stableKey,
|