@menapo/vi18n 0.0.0-stage → 0.1.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.
Files changed (100) hide show
  1. package/LICENSE +49 -0
  2. package/README.md +110 -2
  3. package/dist/apply-Bre-lY9J.js +228 -0
  4. package/dist/captured-Bj8bO6vd.js +1034 -0
  5. package/dist/check-D34P-S--.js +1 -0
  6. package/dist/check-DyrAbv73.js +201 -0
  7. package/dist/cli.d.ts +1 -0
  8. package/dist/cli.js +41 -0
  9. package/dist/codec-DuvU2VQy.js +207 -0
  10. package/dist/collect-Cx4_SKOe.js +13 -0
  11. package/dist/collisions-CfpZU8e4.js +217 -0
  12. package/dist/compile-B5Mz6WJm.js +245 -0
  13. package/dist/compile-Cc97JaW2.js +2502 -0
  14. package/dist/concurrency-C9UUWuDC.js +96 -0
  15. package/dist/concurrency-DONL7t4V.js +1 -0
  16. package/dist/config.d.ts +21 -0
  17. package/dist/config.js +39 -0
  18. package/dist/dir-zw6bGMKQ.js +290 -0
  19. package/dist/duplicates-yuRljQ6h.js +185 -0
  20. package/dist/engine-B7p40M__.js +531 -0
  21. package/dist/engine.d.ts +43 -0
  22. package/dist/engine.js +1241 -0
  23. package/dist/exclude-Du-Zd_qI.js +26 -0
  24. package/dist/export-DGsMbcTZ.js +103 -0
  25. package/dist/extract-Dujlsgnk.js +298 -0
  26. package/dist/formats-KPIg-08V.js +781 -0
  27. package/dist/i18next-CrUnKFfh.js +1596 -0
  28. package/dist/i18next-Rgo-v9l0.js +1 -0
  29. package/dist/import-Cwdhcywt.js +225 -0
  30. package/dist/index.d.ts +156 -0
  31. package/dist/index.js +239 -0
  32. package/dist/init-BecCTvKi.js +792 -0
  33. package/dist/library-D5CWvTQs.js +1882 -0
  34. package/dist/lingui--PcOOYry.js +833 -0
  35. package/dist/lint.d.ts +105 -0
  36. package/dist/lint.js +971 -0
  37. package/dist/load-CQRz4PpM.js +2950 -0
  38. package/dist/load-gW-oJt1Q.js +747 -0
  39. package/dist/locales-DX4MOBFE.js +827 -0
  40. package/dist/lsp-Cq1Nuu-Q.js +31 -0
  41. package/dist/match-BGr8EXB3.js +89 -0
  42. package/dist/mcp-Cf0T7qPW.js +28 -0
  43. package/dist/merge-driver-CpiiSVIc.js +422 -0
  44. package/dist/messages-Cxa-5tR0.js +166 -0
  45. package/dist/migrate-CntqvrW4.js +1 -0
  46. package/dist/migrate-dPgq9uzy.js +1484 -0
  47. package/dist/openai-CTaMDcl-.js +346 -0
  48. package/dist/output-DsaHKHEt.js +164 -0
  49. package/dist/parse.d.ts +62 -0
  50. package/dist/parse.js +156 -0
  51. package/dist/pipeline-C7g11THL.js +2031 -0
  52. package/dist/pipeline-wxS0SApG.js +1 -0
  53. package/dist/project-Dwx7iPm6.js +381 -0
  54. package/dist/project-pXipT8iG.js +1046 -0
  55. package/dist/pseudo-DRv30iN5.js +145 -0
  56. package/dist/raw-C4Y6vyGY.js +4708 -0
  57. package/dist/react-intl-CaBVwJ9u.js +519 -0
  58. package/dist/restore-Btq66Tid.js +175 -0
  59. package/dist/rewrite-CiTRAICl.js +803 -0
  60. package/dist/rich-DCUKl5HM.js +97 -0
  61. package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
  62. package/dist/run-DYD_VXKy.js +694 -0
  63. package/dist/runtime-DTMmliT1.js +64 -0
  64. package/dist/scan-worker.d.ts +62 -0
  65. package/dist/scan-worker.js +1 -0
  66. package/dist/scoped-BMYpSthj.js +44 -0
  67. package/dist/server-BDkykMjb.js +11823 -0
  68. package/dist/server-qv3mz70d.js +10485 -0
  69. package/dist/server.d.ts +169 -0
  70. package/dist/server.js +1234 -0
  71. package/dist/sitemap-DV3o8QD7.js +159 -0
  72. package/dist/sources-D19czDSA.js +2242 -0
  73. package/dist/status-DZArW5aE.js +149 -0
  74. package/dist/stdio-CVaJfxA7.js +29 -0
  75. package/dist/stdio-FPOKQAgu.js +95 -0
  76. package/dist/sync-CcVzger_.js +658 -0
  77. package/dist/t-DC-c9zE5.js +454 -0
  78. package/dist/test.d.ts +116 -0
  79. package/dist/test.js +319 -0
  80. package/dist/translate-DZR3MkSA.js +299 -0
  81. package/dist/ts-BaU5yOf0.js +4863 -0
  82. package/dist/types-CgUCkFU1.d.ts +707 -0
  83. package/dist/types-D3QGM8XO.js +146 -0
  84. package/dist/types-DsdWBG0b.d.ts +558 -0
  85. package/dist/types-DtvQ7UdD.js +1948 -0
  86. package/dist/types-wSh9fCBY.js +11 -0
  87. package/dist/units-CQ5Q99RC.js +150 -0
  88. package/dist/url-CRfCOhUI.js +172 -0
  89. package/dist/url-CsBkRaJy.js +225 -0
  90. package/dist/validate-BxpupdAf.js +2615 -0
  91. package/dist/validate-D6DJepZv.js +365 -0
  92. package/dist/version-BlyLGlOa.js +28 -0
  93. package/dist/vite-scan-B_YW4X_a.js +242 -0
  94. package/dist/vite.d.ts +34 -0
  95. package/dist/vite.js +7558 -0
  96. package/dist/vue-i18n-CEMnpv7k.js +651 -0
  97. package/dist/which-C6PFI1Dm.js +116 -0
  98. package/dist/worker-84U6uYB3.js +1656 -0
  99. package/package.json +97 -3
  100. package/schemas/catalog.json +57 -0
@@ -0,0 +1,11 @@
1
+ //#region src/cli/types.ts
2
+ /** Exit codes of every command. */
3
+ const EXIT = {
4
+ ok: 0,
5
+ /** Errors found, drift, a safety refusal, failed translations. */
6
+ failure: 1,
7
+ /** Bad flags, no config, invalid config. */
8
+ usage: 2
9
+ };
10
+ //#endregion
11
+ export { EXIT as t };
@@ -0,0 +1,150 @@
1
+ import { o as isDiagnosticError } from "./types-D3QGM8XO.js";
2
+ import { C as usableTranslation, S as entryKey, x as entryCanonical } from "./library-D5CWvTQs.js";
3
+ import { r as resolveDuplicates } from "./duplicates-yuRljQ6h.js";
4
+ //#region src/core/exchange/units.ts
5
+ /**
6
+ * Catalog exchange (PLAN §4.4, §5.19, INTERNALS §2.6.9): one format-neutral
7
+ * view of a locale's entries that every export format writes, and the
8
+ * source-identity rules that export, import and restore share. A unit's
9
+ * source is always the text its translation was made for: for an `"id"`-layout
10
+ * target that is the source locale's text when its label still fingerprints
11
+ * it, else the entry's `#. source:` note, else nothing.
12
+ */
13
+ const EXCHANGE_FORMATS = [
14
+ "po",
15
+ "json",
16
+ "flat",
17
+ "xliff2",
18
+ "arb",
19
+ "csv"
20
+ ];
21
+ /** File extension per format; `vi18n import` infers the format from it. */
22
+ const EXTENSIONS = {
23
+ po: ".po",
24
+ json: ".json",
25
+ flat: ".json",
26
+ xliff2: ".xliff",
27
+ arb: ".arb",
28
+ csv: ".csv"
29
+ };
30
+ /** `"id"`-layout targets join the source locale's entries by key (INTERNALS §2.6.1). */
31
+ function sourceIndexOf(sourceCatalog, layout, codec) {
32
+ if (layout !== "id" || sourceCatalog === null) return null;
33
+ const index = /* @__PURE__ */ new Map();
34
+ for (const entry of sourceCatalog.entries) {
35
+ const key = entryKey(entry, layout, codec);
36
+ if (key !== null && entry.source !== null && !index.has(key)) index.set(key, entry.source);
37
+ }
38
+ return index;
39
+ }
40
+ function canonicalOrNull(text, codec) {
41
+ try {
42
+ return codec.canonicalize(text);
43
+ } catch (error) {
44
+ if (isDiagnosticError(error)) return null;
45
+ throw error;
46
+ }
47
+ }
48
+ /**
49
+ * The canonical source and label an entry's translation belongs to, or null
50
+ * when the catalog cannot say (an `"id"`-layout entry whose label no known
51
+ * text fingerprints, or a source that does not parse).
52
+ */
53
+ function entrySource(entry, key, context) {
54
+ const { codec, layout } = context;
55
+ if (layout === "id") {
56
+ if (entry.label === null) return null;
57
+ for (const text of [context.sourceIndex?.get(key) ?? null, entry.source]) {
58
+ if (text === null) continue;
59
+ const canonical = canonicalOrNull(text, codec);
60
+ if (canonical !== null && codec.label(canonical) === entry.label) return {
61
+ canonical,
62
+ label: entry.label
63
+ };
64
+ }
65
+ return null;
66
+ }
67
+ const canonical = entryCanonical(entry, codec);
68
+ return canonical === null ? null : {
69
+ canonical,
70
+ label: codec.label(canonical)
71
+ };
72
+ }
73
+ /**
74
+ * The units of one locale's catalog, in catalog order, one per key (the
75
+ * read-side duplicate pick). Foreign contexts, and entries whose source is
76
+ * unknown, are counted in `unsourced` and left out.
77
+ */
78
+ function exchangeUnits(input) {
79
+ const { catalog, context, locale, isSourceLocale } = input;
80
+ if (catalog === null) return {
81
+ units: [],
82
+ unsourced: 0
83
+ };
84
+ const { codec, layout } = context;
85
+ const resolved = resolveDuplicates(catalog, codec).catalog;
86
+ const units = [];
87
+ const seen = /* @__PURE__ */ new Set();
88
+ let unsourced = 0;
89
+ for (const entry of resolved.entries) {
90
+ const key = entryKey(entry, layout, codec);
91
+ if (key === null) {
92
+ unsourced++;
93
+ continue;
94
+ }
95
+ if (seen.has(key)) continue;
96
+ seen.add(key);
97
+ const source = entrySource(entry, key, context);
98
+ if (source === null) {
99
+ unsourced++;
100
+ continue;
101
+ }
102
+ const manual = entry.manualId !== null;
103
+ const translation = isSourceLocale ? "" : entry.translation;
104
+ const usable = !isSourceLocale && usableTranslation(entry, layout, {
105
+ id: key,
106
+ manual,
107
+ canonical: source.canonical,
108
+ sourceHash: codec.sourceHash(source.canonical),
109
+ label: layout === "id" ? entry.label : source.label
110
+ }, codec, locale).ok;
111
+ units.push({
112
+ id: key,
113
+ manual,
114
+ source: source.canonical,
115
+ label: source.label,
116
+ translation,
117
+ state: isSourceLocale ? "untranslated" : entry.state,
118
+ fuzzy: !isSourceLocale && entry.fuzzy,
119
+ usable,
120
+ entry
121
+ });
122
+ }
123
+ return {
124
+ units,
125
+ unsourced
126
+ };
127
+ }
128
+ /**
129
+ * Key-only formats (flat JSON, ARB, CSV) carry no source, so a manual id is
130
+ * keyed with its source's label (PLAN §5.19): a row translated against older
131
+ * text no longer matches and is reported instead of imported. A generated
132
+ * id is already its source's label.
133
+ */
134
+ function exchangeKey(unit) {
135
+ return unit.manual ? `${unit.id}~${unit.label}` : unit.id;
136
+ }
137
+ /** `hero.cta~Hpi4JmqaQa60` → the manual id and the label; a plain key has no label. */
138
+ function parseExchangeKey(raw) {
139
+ const at = raw.lastIndexOf("~");
140
+ if (at === -1) return {
141
+ key: raw,
142
+ label: null
143
+ };
144
+ return {
145
+ key: raw.slice(0, at),
146
+ label: raw.slice(at + 1)
147
+ };
148
+ }
149
+ //#endregion
150
+ export { exchangeKey as a, sourceIndexOf as c, entrySource as i, EXTENSIONS as n, exchangeUnits as o, canonicalOrNull as r, parseExchangeKey as s, EXCHANGE_FORMATS as t };
@@ -0,0 +1,172 @@
1
+ //#region src/runtime/url.ts
2
+ /**
3
+ * Whether localized paths and domains are compiled in (§6 D134). The plugin
4
+ * defines `__VI18N_ROUTES__` false for a config with neither, which folds
5
+ * every `ROUTES` branch out of the app, so a default app ships the codec it
6
+ * shipped before; code bundled without the plugin (servers registered with
7
+ * `registerCatalogs()`, `/config` under Node) keeps them.
8
+ */
9
+ const ROUTES = typeof __VI18N_ROUTES__ === "undefined" || !!__VI18N_ROUTES__;
10
+ /** The base as a path starting and ending with `/`; a relative Vite base has no path to strip. */
11
+ const baseOf = (config) => {
12
+ const b = config.routing.base;
13
+ return b[0] === "/" ? b.replace(/\/?$/, "/") : "/";
14
+ };
15
+ /** The part of `pathname` after the base, or null when the pathname is outside it. */
16
+ const rest = (pathname, base) => pathname.startsWith(base) ? pathname.slice(base.length) : pathname + "/" === base ? "" : null;
17
+ /**
18
+ * Collapses leading slashes and backslashes to one `/`. Browsers read `//host`
19
+ * and `/\host` as another origin, so a same-origin path such as
20
+ * `/de//evil.example/` must never come out of the codec in that form: its
21
+ * output becomes page loads, router navigations and the server's
22
+ * canonical redirect.
23
+ */
24
+ const local = (path) => path.replace(/^[/\\]{2,}/, "/");
25
+ /**
26
+ * Splits the locale segment off a pathname. Only a segment equal to a
27
+ * configured locale (case-sensitive) is a locale; `path` keeps the base and
28
+ * any trailing slash, and a localized root (`/de`, `/de/`) becomes the base.
29
+ */
30
+ function splitLocalePath(pathname, config) {
31
+ const base = baseOf(config);
32
+ const p = pathname + "/";
33
+ if (config.routing.url !== "no_prefix" && p.startsWith(base)) {
34
+ const r = p.slice(base.length);
35
+ const i = r.indexOf("/");
36
+ const seg = r.slice(0, i);
37
+ if (config.locales.includes(seg)) {
38
+ if (ROUTES) return {
39
+ locale: seg,
40
+ path: translate(local(base + r.slice(i + 1, -1)), seg, config, 1)
41
+ };
42
+ return {
43
+ locale: seg,
44
+ path: local(base + r.slice(i + 1, -1))
45
+ };
46
+ }
47
+ }
48
+ if (ROUTES) return {
49
+ locale: null,
50
+ path: translate(pathname, unprefixed(config), config, 1)
51
+ };
52
+ return {
53
+ locale: null,
54
+ path: pathname
55
+ };
56
+ }
57
+ /**
58
+ * The pathname of `path` in `locale`. Any locale segment already in `path` is
59
+ * replaced, so localizing is idempotent. An unconfigured locale, `no_prefix`
60
+ * and pathnames outside the base leave `path` unchanged.
61
+ */
62
+ function localizePath(path, locale, config) {
63
+ const url = config.routing.url;
64
+ let p = path;
65
+ if (url !== "no_prefix" && config.locales.includes(locale)) {
66
+ p = splitLocalePath(path, config).path;
67
+ if (ROUTES) p = translate(p, locale, config, 0);
68
+ if (locale !== (ROUTES && config.routing.home || config.sourceLocale) || url === "prefix") {
69
+ const base = baseOf(config);
70
+ const q = p + "/";
71
+ if (q.startsWith(base)) {
72
+ const r = q.slice(base.length, -1);
73
+ p = base + locale + (r && "/" + r);
74
+ }
75
+ }
76
+ }
77
+ return local(p);
78
+ }
79
+ /**
80
+ * Every path in every configured locale: per path in order, one entry per
81
+ * locale in `locales` order, duplicates dropped. Pseudo-locales are never
82
+ * prerendered or advertised, so they are left out.
83
+ */
84
+ function localizedPathsFor(paths, config) {
85
+ const out = /* @__PURE__ */ new Set();
86
+ const pseudo = config.pseudoLocales ?? [];
87
+ for (const path of paths) for (const l of config.locales) if (!pseudo.includes(l)) out.add(localizePath(path, l, config));
88
+ return [...out];
89
+ }
90
+ /** Segments of a path as a pathname spells them: `/über-uns` → `%C3%BCber-uns`. */
91
+ const segmentsOf = (path) => new URL(path, "http://h").pathname.split("/").slice(1);
92
+ /**
93
+ * `from` as a pattern and `to` as its template: a `*` segment matches one
94
+ * segment and a final `**` any number (with their slashes), and the
95
+ * template puts them back in order (`$1`, `$2`). A template never has a
96
+ * digit right after `$n`: a slash or its end follows.
97
+ */
98
+ function pathRule(from, to) {
99
+ let source = "";
100
+ let template = "";
101
+ let n = 0;
102
+ for (const s of segmentsOf(from)) source += s === "**" ? "((?:/[^/]+)*)" : s === "*" ? "/([^/]+)" : "/" + s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
103
+ for (const s of segmentsOf(to)) template += s === "**" ? "$" + ++n : s === "*" ? "/$" + ++n : "/" + s.replace(/\$/g, "$$$$");
104
+ return [new RegExp("^" + source + "$"), template];
105
+ }
106
+ /**
107
+ * `routing.paths` as rules, for the codec, the SPA boot script and the CLI.
108
+ * Exact paths come before patterns (each group in config order), so
109
+ * `/blog/new` wins over `/blog/*` in both directions, as routers rank them.
110
+ */
111
+ function pathTable(paths) {
112
+ const table = {
113
+ to: {},
114
+ from: {}
115
+ };
116
+ const wild = (p) => +p.includes("*");
117
+ for (const key of Object.keys(paths).sort((a, b) => wild(a) - wild(b))) {
118
+ const entry = paths[key];
119
+ for (const l of Object.keys(entry)) {
120
+ (table.to[l] ??= []).push(pathRule(key, entry[l]));
121
+ (table.from[l] ??= []).push(pathRule(entry[l], key));
122
+ }
123
+ }
124
+ return table;
125
+ }
126
+ const compiled = /* @__PURE__ */ new WeakMap();
127
+ /** The locale of an unprefixed path: the host's, else the source locale; none under `prefix`. */
128
+ const unprefixed = (config) => {
129
+ const url = config.routing.url;
130
+ return url === "prefix" || url === "no_prefix" ? void 0 : config.routing.home ?? config.sourceLocale;
131
+ };
132
+ /**
133
+ * `path` (a pathname with base) in locale `l` (`back` 0: from a route path)
134
+ * or back to its route path (`back` 1). A path outside the base, the root
135
+ * and paths no rule matches are left alone; a trailing slash is kept.
136
+ */
137
+ function translate(path, l, config, back) {
138
+ const paths = config.routing.paths;
139
+ if (!paths || l === void 0) return path;
140
+ let table = compiled.get(paths);
141
+ if (!table) compiled.set(paths, table = pathTable(paths));
142
+ const rules = back ? table.from : table.to;
143
+ const list = Object.hasOwn(rules, l) ? rules[l] : void 0;
144
+ const base = baseOf(config);
145
+ const r = list ? rest(path, base) : null;
146
+ if (!list || !r) return path;
147
+ const end = r.endsWith("/") ? "/" : "";
148
+ const p = "/" + (end ? r.slice(0, -1) : r);
149
+ for (const [re, template] of list) if (re.test(p)) return local(base + p.replace(re, template).slice(1) + end);
150
+ return path;
151
+ }
152
+ /** Where links to `locale` go: its first listed origin. */
153
+ function localeOrigin(locale, config) {
154
+ const domains = config.routing.domains;
155
+ if (domains) {
156
+ for (const o in domains) if (domains[o] === locale) return o;
157
+ }
158
+ }
159
+ /**
160
+ * `config` for a page whose host serves `home` (undefined: an unlisted
161
+ * host). The copy shares every table with `config`, so compiled path rules
162
+ * and route-glob keys stay cached across copies.
163
+ */
164
+ const atHome = (config, home) => home === config.routing.home ? config : {
165
+ ...config,
166
+ routing: {
167
+ ...config.routing,
168
+ home
169
+ }
170
+ };
171
+ //#endregion
172
+ export { pathTable as a, localizedPathsFor as i, localeOrigin as n, localizePath as r, atHome as t };
@@ -0,0 +1,225 @@
1
+ //#region src/runtime/url.ts
2
+ /**
3
+ * Whether localized paths and domains are compiled in (§6 D134). The plugin
4
+ * defines `__VI18N_ROUTES__` false for a config with neither, which folds
5
+ * every `ROUTES` branch out of the app, so a default app ships the codec it
6
+ * shipped before; code bundled without the plugin (servers registered with
7
+ * `registerCatalogs()`, `/config` under Node) keeps them.
8
+ */
9
+ const ROUTES = typeof __VI18N_ROUTES__ === "undefined" || !!__VI18N_ROUTES__;
10
+ /** The base as a path starting and ending with `/`; a relative Vite base has no path to strip. */
11
+ const baseOf = (config) => {
12
+ const b = config.routing.base;
13
+ return b[0] === "/" ? b.replace(/\/?$/, "/") : "/";
14
+ };
15
+ /** The part of `pathname` after the base, or null when the pathname is outside it. */
16
+ const rest = (pathname, base) => pathname.startsWith(base) ? pathname.slice(base.length) : pathname + "/" === base ? "" : null;
17
+ /**
18
+ * Collapses leading slashes and backslashes to one `/`. Browsers read `//host`
19
+ * and `/\host` as another origin, so a same-origin path such as
20
+ * `/de//evil.example/` must never come out of the codec in that form: its
21
+ * output becomes page loads, router navigations and the server's
22
+ * canonical redirect.
23
+ */
24
+ const local = (path) => path.replace(/^[/\\]{2,}/, "/");
25
+ /**
26
+ * Splits the locale segment off a pathname. Only a segment equal to a
27
+ * configured locale (case-sensitive) is a locale; `path` keeps the base and
28
+ * any trailing slash, and a localized root (`/de`, `/de/`) becomes the base.
29
+ */
30
+ function splitLocalePath(pathname, config) {
31
+ const base = baseOf(config);
32
+ const p = pathname + "/";
33
+ if (config.routing.url !== "no_prefix" && p.startsWith(base)) {
34
+ const r = p.slice(base.length);
35
+ const i = r.indexOf("/");
36
+ const seg = r.slice(0, i);
37
+ if (config.locales.includes(seg)) {
38
+ if (ROUTES) return {
39
+ locale: seg,
40
+ path: translate(local(base + r.slice(i + 1, -1)), seg, config, 1)
41
+ };
42
+ return {
43
+ locale: seg,
44
+ path: local(base + r.slice(i + 1, -1))
45
+ };
46
+ }
47
+ }
48
+ if (ROUTES) return {
49
+ locale: null,
50
+ path: translate(pathname, unprefixed(config), config, 1)
51
+ };
52
+ return {
53
+ locale: null,
54
+ path: pathname
55
+ };
56
+ }
57
+ /**
58
+ * The pathname of `path` in `locale`. Any locale segment already in `path` is
59
+ * replaced, so localizing is idempotent. An unconfigured locale, `no_prefix`
60
+ * and pathnames outside the base leave `path` unchanged.
61
+ */
62
+ function localizePath(path, locale, config) {
63
+ const url = config.routing.url;
64
+ let p = path;
65
+ if (url !== "no_prefix" && config.locales.includes(locale)) {
66
+ p = splitLocalePath(path, config).path;
67
+ if (ROUTES) p = translate(p, locale, config, 0);
68
+ if (locale !== (ROUTES && config.routing.home || config.sourceLocale) || url === "prefix") {
69
+ const base = baseOf(config);
70
+ const q = p + "/";
71
+ if (q.startsWith(base)) {
72
+ const r = q.slice(base.length, -1);
73
+ p = base + locale + (r && "/" + r);
74
+ }
75
+ }
76
+ }
77
+ return local(p);
78
+ }
79
+ /**
80
+ * Every path in every configured locale: per path in order, one entry per
81
+ * locale in `locales` order, duplicates dropped. Pseudo-locales are never
82
+ * prerendered or advertised, so they are left out.
83
+ */
84
+ function localizedPathsFor(paths, config) {
85
+ const out = /* @__PURE__ */ new Set();
86
+ const pseudo = config.pseudoLocales ?? [];
87
+ for (const path of paths) for (const l of config.locales) if (!pseudo.includes(l)) out.add(localizePath(path, l, config));
88
+ return [...out];
89
+ }
90
+ /** Segments of a path as a pathname spells them: `/über-uns` → `%C3%BCber-uns`. */
91
+ const segmentsOf = (path) => new URL(path, "http://h").pathname.split("/").slice(1);
92
+ /**
93
+ * `from` as a pattern and `to` as its template: a `*` segment matches one
94
+ * segment and a final `**` any number (with their slashes), and the
95
+ * template puts them back in order (`$1`, `$2`). A template never has a
96
+ * digit right after `$n`: a slash or its end follows.
97
+ */
98
+ function pathRule(from, to) {
99
+ let source = "";
100
+ let template = "";
101
+ let n = 0;
102
+ for (const s of segmentsOf(from)) source += s === "**" ? "((?:/[^/]+)*)" : s === "*" ? "/([^/]+)" : "/" + s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
103
+ for (const s of segmentsOf(to)) template += s === "**" ? "$" + ++n : s === "*" ? "/$" + ++n : "/" + s.replace(/\$/g, "$$$$");
104
+ return [new RegExp("^" + source + "$"), template];
105
+ }
106
+ /**
107
+ * `routing.paths` as rules, for the codec, the SPA boot script and the CLI.
108
+ * Exact paths come before patterns (each group in config order), so
109
+ * `/blog/new` wins over `/blog/*` in both directions, as routers rank them.
110
+ */
111
+ function pathTable(paths) {
112
+ const table = {
113
+ to: {},
114
+ from: {}
115
+ };
116
+ const wild = (p) => +p.includes("*");
117
+ for (const key of Object.keys(paths).sort((a, b) => wild(a) - wild(b))) {
118
+ const entry = paths[key];
119
+ for (const l of Object.keys(entry)) {
120
+ (table.to[l] ??= []).push(pathRule(key, entry[l]));
121
+ (table.from[l] ??= []).push(pathRule(entry[l], key));
122
+ }
123
+ }
124
+ return table;
125
+ }
126
+ const compiled = /* @__PURE__ */ new WeakMap();
127
+ /** The locale of an unprefixed path: the host's, else the source locale; none under `prefix`. */
128
+ const unprefixed = (config) => {
129
+ const url = config.routing.url;
130
+ return url === "prefix" || url === "no_prefix" ? void 0 : config.routing.home ?? config.sourceLocale;
131
+ };
132
+ /**
133
+ * `path` (a pathname with base) in locale `l` (`back` 0: from a route path)
134
+ * or back to its route path (`back` 1). A path outside the base, the root
135
+ * and paths no rule matches are left alone; a trailing slash is kept.
136
+ */
137
+ function translate(path, l, config, back) {
138
+ const paths = config.routing.paths;
139
+ if (!paths || l === void 0) return path;
140
+ let table = compiled.get(paths);
141
+ if (!table) compiled.set(paths, table = pathTable(paths));
142
+ const rules = back ? table.from : table.to;
143
+ const list = Object.hasOwn(rules, l) ? rules[l] : void 0;
144
+ const base = baseOf(config);
145
+ const r = list ? rest(path, base) : null;
146
+ if (!list || !r) return path;
147
+ const end = r.endsWith("/") ? "/" : "";
148
+ const p = "/" + (end ? r.slice(0, -1) : r);
149
+ for (const [re, template] of list) if (re.test(p)) return local(base + p.replace(re, template).slice(1) + end);
150
+ return path;
151
+ }
152
+ /** The locale a listed host (`example.de`, `localhost:5173`) serves; undefined for any other host. */
153
+ function hostLocale(host, config) {
154
+ const domains = config.routing.domains;
155
+ if (domains) {
156
+ const h = host.toLowerCase();
157
+ for (const o in domains) if (o.slice(o.indexOf("//") + 2) === h) return domains[o];
158
+ }
159
+ }
160
+ /** Where links to `locale` go: its first listed origin. */
161
+ function localeOrigin(locale, config) {
162
+ const domains = config.routing.domains;
163
+ if (domains) {
164
+ for (const o in domains) if (domains[o] === locale) return o;
165
+ }
166
+ }
167
+ /**
168
+ * `config` for a page whose host serves `home` (undefined: an unlisted
169
+ * host). The copy shares every table with `config`, so compiled path rules
170
+ * and route-glob keys stay cached across copies.
171
+ */
172
+ const atHome = (config, home) => home === config.routing.home ? config : {
173
+ ...config,
174
+ routing: {
175
+ ...config.routing,
176
+ home
177
+ }
178
+ };
179
+ /** `config` for a page on `host`: a listed host's locale is its unprefixed one. */
180
+ const atHost = (config, host) => config.routing.domains ? atHome(config, hostLocale(host, config)) : config;
181
+ /**
182
+ * The absolute URL of `route` (a route path with base) in `locale`, when
183
+ * the page is on a listed host and `locale` lives on another: that host's
184
+ * origin and the locale's unprefixed path. Null on the same host, on an
185
+ * unlisted host (dev servers keep their prefixes) and for a locale without
186
+ * a host (pseudo-locales). Only configured origins ever come out.
187
+ */
188
+ function awayHref(route, locale, config) {
189
+ const home = config.routing.home;
190
+ const o = home !== void 0 && home !== locale ? localeOrigin(locale, config) : void 0;
191
+ return o ? o + localizePath(route, locale, atHome(config, locale)) : null;
192
+ }
193
+ /** `p` without a trailing slash; the root stays `/`. */
194
+ const trail = (p) => p.length > 1 && p.endsWith("/") ? p.slice(0, -1) : p;
195
+ /**
196
+ * The pathname as route globs see it (INTERNALS §2.7.3): delocalized, without
197
+ * the base and without a trailing slash; the root is `/`.
198
+ */
199
+ function routePath(pathname, config) {
200
+ const p = splitLocalePath(pathname, config).path;
201
+ const r = rest(p, baseOf(config));
202
+ return trail(r === null ? p : "/" + r);
203
+ }
204
+ const globs = /* @__PURE__ */ new Map();
205
+ const globToRegExp = (pattern) => {
206
+ let src = "";
207
+ for (const seg of (pattern[0] === "/" ? pattern : "/" + pattern).split("/").slice(1)) src += seg === "**" ? "(?:/.*)?" : "/" + seg.split("*").map((s) => s.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]+");
208
+ return new RegExp("^" + (src || "/") + "$");
209
+ };
210
+ /**
211
+ * The first pattern (in the given order) matching `path`: `*` is one or more
212
+ * characters other than `/`, a `**` segment is any number of whole segments.
213
+ * Case-sensitive. A trailing slash on `path` is ignored.
214
+ */
215
+ function matchRoute(patterns, path) {
216
+ const p = trail(path);
217
+ for (const pattern of patterns) {
218
+ let re = globs.get(pattern);
219
+ if (!re) globs.set(pattern, re = globToRegExp(pattern));
220
+ if (re.test(p)) return pattern;
221
+ }
222
+ return null;
223
+ }
224
+ //#endregion
225
+ export { hostLocale as a, localizedPathsFor as c, splitLocalePath as d, awayHref as i, matchRoute as l, atHome as n, localeOrigin as o, atHost as r, localizePath as s, ROUTES as t, routePath as u };