@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,96 @@
1
+ //#region src/ai/concurrency.ts
2
+ /**
3
+ * Adaptive concurrency (PLAN §5.20, INTERNALS §2.15): requests start at
4
+ * `ai.concurrency` in flight; a rate limit halves that and holds every new
5
+ * request for the delay the server asked (`RetryInfo`, `Retry-After`), and
6
+ * each full window of requests without one gives a slot back (AIMD). The
7
+ * provider retries the limited request itself; this only decides how many
8
+ * others may start. One instance spans a whole `vi18n translate` run, so a
9
+ * quota learnt on one locale is not rediscovered on the next.
10
+ */
11
+ /** Hold after a rate limit whose answer named no delay. */
12
+ const DEFAULT_HOLD_MS = 1e3;
13
+ function adaptiveConcurrency(max, now = Date.now) {
14
+ const ceiling = Math.max(1, Math.floor(max));
15
+ let limit = ceiling;
16
+ let inFlight = 0;
17
+ let holdUntil = 0;
18
+ let successes = 0;
19
+ let rateLimits = 0;
20
+ let epoch = 0;
21
+ const waiters = [];
22
+ let timer = null;
23
+ const pump = () => {
24
+ if (timer !== null) {
25
+ clearTimeout(timer);
26
+ timer = null;
27
+ }
28
+ while (waiters.length > 0 && inFlight < limit) {
29
+ const wait = holdUntil - now();
30
+ if (wait > 0) {
31
+ timer = setTimeout(pump, wait);
32
+ return;
33
+ }
34
+ waiters.shift().grant();
35
+ }
36
+ };
37
+ const slot = () => {
38
+ const started = epoch;
39
+ let limited = false;
40
+ let released = false;
41
+ inFlight++;
42
+ return {
43
+ rateLimited(retryAfterMs) {
44
+ rateLimits++;
45
+ limited = true;
46
+ if (started === epoch) {
47
+ limit = Math.max(1, Math.floor(limit / 2));
48
+ successes = 0;
49
+ epoch++;
50
+ }
51
+ holdUntil = Math.max(holdUntil, now() + (retryAfterMs ?? DEFAULT_HOLD_MS));
52
+ },
53
+ release() {
54
+ if (released) return;
55
+ released = true;
56
+ inFlight--;
57
+ if (!limited && started === epoch && ++successes >= limit && limit < ceiling) {
58
+ limit++;
59
+ successes = 0;
60
+ }
61
+ pump();
62
+ }
63
+ };
64
+ };
65
+ return {
66
+ max: ceiling,
67
+ get limit() {
68
+ return limit;
69
+ },
70
+ get rateLimits() {
71
+ return rateLimits;
72
+ },
73
+ acquire(signal) {
74
+ return new Promise((resolve, reject) => {
75
+ if (signal?.aborted === true) {
76
+ reject(signal.reason);
77
+ return;
78
+ }
79
+ const onAbort = () => {
80
+ const index = waiters.indexOf(waiter);
81
+ if (index !== -1) waiters.splice(index, 1);
82
+ reject(signal?.reason);
83
+ };
84
+ const waiter = { grant: () => {
85
+ signal?.removeEventListener("abort", onAbort);
86
+ resolve(slot());
87
+ } };
88
+ signal?.addEventListener("abort", onAbort, { once: true });
89
+ waiters.push(waiter);
90
+ pump();
91
+ });
92
+ }
93
+ };
94
+ }
95
+ //#endregion
96
+ export { adaptiveConcurrency as t };
@@ -0,0 +1 @@
1
+ export { t as adaptiveConcurrency } from "./concurrency-C9UUWuDC.js";
@@ -0,0 +1,21 @@
1
+ import { a as Vi18nConfig, c as OpenAICompatibleOptions, d as ProviderResponse, f as Tier, l as ProviderOptions, m as TranslationProvider, n as OutputOptions, o as AiItem, p as TranslateOptions, r as PseudoLocale, s as BatchOptions, t as Level, u as ProviderRequest, v as LocaleStrategy, y as UrlStrategy } from "./types-CgUCkFU1.js";
2
+ //#region src/config/index.d.ts
3
+ /**
4
+ * Types a `vi18n.config.ts`. The config is validated when it is loaded
5
+ * (`vi18n` plugin, CLI), where errors can name the file.
6
+ */
7
+ export declare function defineConfig(config: Vi18nConfig): Vi18nConfig;
8
+ /**
9
+ * Every path in every configured locale, for prerendering and sitemaps at
10
+ * build time (the request-time `localizedPaths` of `/server` reads the
11
+ * registered config instead), each locale's localized path where
12
+ * `routing.paths` has one. Defaults are applied to the routing keys by
13
+ * plain merging; the app base is not part of a route path, so it is `/`.
14
+ * Domains play no part: the list is the paths any unlisted host serves.
15
+ */
16
+ export declare function localizedPaths(paths: readonly string[], config: Vi18nConfig): string[];
17
+ //#endregion
18
+ //#region src/ai/openai.d.ts
19
+ export declare function openaiCompatible(options: OpenAICompatibleOptions): TranslationProvider;
20
+ //#endregion
21
+ export type { AiItem, BatchOptions, Level, LocaleStrategy, OpenAICompatibleOptions, OutputOptions, ProviderOptions, ProviderRequest, ProviderResponse, PseudoLocale, Tier, TranslateOptions, TranslationProvider, UrlStrategy, Vi18nConfig };
package/dist/config.js ADDED
@@ -0,0 +1,39 @@
1
+ import { n as DEFAULT_CONFIG } from "./types-D3QGM8XO.js";
2
+ import { i as localizedPathsFor } from "./url-CRfCOhUI.js";
3
+ import { n as openaiCompatible } from "./openai-CTaMDcl-.js";
4
+ //#region src/config/index.ts
5
+ /**
6
+ * `@menapo/vi18n/config` implementation (docs/INTERNALS.md §3.4). Loaded by
7
+ * every `vi18n.config.ts` under Node, so it stays a few lines: no zod, no file
8
+ * system, and only the pure URL codec from the runtime.
9
+ */
10
+ /**
11
+ * Types a `vi18n.config.ts`. The config is validated when it is loaded
12
+ * (`vi18n` plugin, CLI), where errors can name the file.
13
+ */
14
+ function defineConfig(config) {
15
+ return config;
16
+ }
17
+ /**
18
+ * Every path in every configured locale, for prerendering and sitemaps at
19
+ * build time (the request-time `localizedPaths` of `/server` reads the
20
+ * registered config instead), each locale's localized path where
21
+ * `routing.paths` has one. Defaults are applied to the routing keys by
22
+ * plain merging; the app base is not part of a route path, so it is `/`.
23
+ * Domains play no part: the list is the paths any unlisted host serves.
24
+ */
25
+ function localizedPaths(paths, config) {
26
+ const sourceLocale = config.sourceLocale ?? DEFAULT_CONFIG.sourceLocale;
27
+ return localizedPathsFor(paths, {
28
+ sourceLocale,
29
+ locales: config.locales ?? [sourceLocale],
30
+ pseudoLocales: config.pseudoLocales ?? DEFAULT_CONFIG.pseudoLocales,
31
+ routing: {
32
+ url: config.routing?.url ?? DEFAULT_CONFIG.routing.url,
33
+ base: "/",
34
+ paths: config.routing?.paths
35
+ }
36
+ });
37
+ }
38
+ //#endregion
39
+ export { defineConfig, localizedPaths, openaiCompatible };
@@ -0,0 +1,290 @@
1
+ //#region src/runtime/ctx.ts
2
+ /** Dev helpers shared by the runtime modules. */
3
+ /**
4
+ * Dev mode, from the plugin's `define` (INTERNALS §6 D44): code bundled
5
+ * without the plugin behaves as production. A module constant, so the define
6
+ * folds every `if (DEV)` branch out of production bundles.
7
+ */
8
+ const DEV = typeof __VI18N_DEV__ !== "undefined" && !!__VI18N_DEV__;
9
+ /**
10
+ * A production build the plugin made (`__VI18N_DEV__` defined false). Its
11
+ * client registrations are always the build boot's (`mode: "build"`, INTERNALS
12
+ * §2.11.4), never a dev or Vitest one, so the dev catalog, test and fetch
13
+ * restoring branches fold out of it (PLAN §6). Code bundled without the
14
+ * plugin keeps them.
15
+ */
16
+ const BUILD = typeof __VI18N_DEV__ !== "undefined" && !__VI18N_DEV__;
17
+ /** True in a document (browser main thread). */
18
+ const browser = () => typeof document === "object";
19
+ /**
20
+ * One binding the core's modules share: fewer bytes than `Object.hasOwn` at
21
+ * each use. The engine keeps its own, which measured smaller in its bundle.
22
+ */
23
+ const own = Object.hasOwn;
24
+ /**
25
+ * Bounds a cache before an insertion: past `max` entries the oldest goes
26
+ * (insertion order), so keys from requests or apps cycling through values
27
+ * evict their own stale entries instead of growing without bound.
28
+ */
29
+ const trim = (cache, max) => cache.size > max && cache.delete(cache.keys().next().value);
30
+ const warned = /* @__PURE__ */ new Set();
31
+ /** Dev only: warn once per distinct text. */
32
+ const warnOnce = (text) => {
33
+ const seen = warned.delete(text);
34
+ if (!seen) trim(warned, 499);
35
+ warned.add(text);
36
+ if (!seen) console.warn("vi18n: " + text);
37
+ };
38
+ //#endregion
39
+ //#region src/runtime/scope.ts
40
+ function createScope(kind, locale, messages, options = {}) {
41
+ return {
42
+ kind,
43
+ locale,
44
+ messages,
45
+ timeZone: options.timeZone,
46
+ now: options.now,
47
+ timeZoneSource: options.timeZoneSource,
48
+ formattedTime: false,
49
+ used: options.trackUsed ? /* @__PURE__ */ new Set() : void 0,
50
+ sources: options.sources,
51
+ labels: options.labels
52
+ };
53
+ }
54
+ //#endregion
55
+ //#region src/runtime/types.ts
56
+ /**
57
+ * Opcodes of the IR. The runtime may inline the literal numbers to save
58
+ * bytes; 9 (`list`) and 10 (MF2 `.match`) are reserved and never emitted in 1.0.
59
+ */
60
+ const OP = {
61
+ select: 1,
62
+ plural: 2,
63
+ selectordinal: 3,
64
+ number: 4,
65
+ date: 5,
66
+ time: 6,
67
+ tag: 7,
68
+ tagStandalone: 8,
69
+ list: 9,
70
+ match: 10
71
+ };
72
+ /** The `globalThis[Symbol.for("menapo.vi18n")]` key. */
73
+ const STATE_KEY = "menapo.vi18n";
74
+ //#endregion
75
+ //#region src/runtime/state.ts
76
+ /**
77
+ * The page-wide state on `globalThis[Symbol.for("menapo.vi18n")]`
78
+ * (INTERNALS §2.11.1). The first runtime copy to evaluate creates it, or
79
+ * upgrades the SPA boot script's stub in place, and installs methods that
80
+ * close over its private data; later copies only count themselves. Methods
81
+ * never use `this`, so glue may pass them unbound.
82
+ *
83
+ * This module holds only what `t()`, `fmt` and `/server` need: scope
84
+ * resolution, trackers, `untracked` and missing reports. The client engine
85
+ * (loading, skew, switching, navigation, `fetch` wrapper, persistence) is
86
+ * `loader.ts`, behind the internal `@menapo/vi18n/engine` entry that the
87
+ * generated client boot module imports first: it hooks into `register`
88
+ * through `setEngine` and replaces the inert client methods installed here.
89
+ * Neither the `.` entry nor `/server` carries it (PLAN §6, revision 4.3).
90
+ */
91
+ /** This copy created the state (or upgraded the boot stub), so its engine may install. */
92
+ let created = false;
93
+ const resolved = Promise.resolve();
94
+ const noop = () => {};
95
+ /**
96
+ * The browser's one scope; the engine flips its locale and messages. "und"
97
+ * (the root locale) until registration, so early formatting cannot throw on
98
+ * an empty tag.
99
+ */
100
+ const page = createScope("page", "und", /* @__PURE__ */ new Map());
101
+ const trackers = /* @__PURE__ */ new Set();
102
+ let epoch = 0;
103
+ const handlers = /* @__PURE__ */ new Set();
104
+ const reported = /* @__PURE__ */ new Set();
105
+ let fallback;
106
+ /** `untracked()` depth: `t()` records nothing into `scope.used` while above 0. */
107
+ let quiet = 0;
108
+ /**
109
+ * `subscribe()` listeners and the `setNavigator()` function. The core keeps
110
+ * them and the engine only reads them, so app code that ran between the `.`
111
+ * entry and the engine (a library imported ahead of the boot module) is not
112
+ * silently dropped when the engine installs.
113
+ */
114
+ const subs = /* @__PURE__ */ new Set();
115
+ let nav;
116
+ let engine;
117
+ /**
118
+ * Installs the engine's half of `register`. The core half runs first, so
119
+ * `/server`'s wrapper sees `S.registration` set as soon as `register`
120
+ * returns, and a server that registered before the `.` entry evaluated still
121
+ * has a registration for the engine to catch up on.
122
+ */
123
+ const setEngine = (e) => {
124
+ engine = e;
125
+ };
126
+ /** Bumps the epoch; with `trigger`, re-renders every tracked computation. */
127
+ function changed(trigger) {
128
+ epoch++;
129
+ if (trigger) for (const t of [...trackers]) t.trigger();
130
+ }
131
+ /**
132
+ * `onMissing` handlers hear of each (key, locale) once. Keys can come from
133
+ * requests (`t(props.label)` on a server), so the memory is bounded, but
134
+ * generously and oldest first: never the whole set at once.
135
+ */
136
+ function report(key, locale, reason) {
137
+ const k = key + "\0" + locale;
138
+ if (reported.has(k) || !handlers.size) return;
139
+ trim(reported, 9999);
140
+ reported.add(k);
141
+ for (const h of handlers) try {
142
+ h({
143
+ key,
144
+ locale,
145
+ reason
146
+ });
147
+ } catch (e) {
148
+ if (DEV) console.error(e);
149
+ }
150
+ }
151
+ function createState(stub) {
152
+ /** A request-less `t()` on the server (§2.11.1 `resolve`). */
153
+ const noScope = () => {
154
+ const reg = S.registration;
155
+ if (!BUILD && reg?.test) return page;
156
+ if (DEV) throw new Error("vi18n: t() called outside a request scope");
157
+ const locale = reg?.config.sourceLocale ?? "und";
158
+ report("", locale, "no-scope");
159
+ return S.serverFallback ?? (fallback ??= createScope("request", locale, /* @__PURE__ */ new Map()));
160
+ };
161
+ /**
162
+ * Dev: a client page that should have the engine. Servers run without it by
163
+ * design, and so does everything outside a document.
164
+ */
165
+ const clientPage = () => browser() && !S.serverResolver && S.registration?.consumer !== "server";
166
+ /**
167
+ * Dev: says why a client page renders no translations when the engine never
168
+ * evaluated (code the plugin did not process, a suite without the boot
169
+ * module).
170
+ */
171
+ const noEngine = () => {
172
+ if (clientPage()) warnOnce("the client engine is not loaded, so nothing is fetched or switched and t() renders no translations; the plugin's boot module imports \"@menapo/vi18n/engine\" first. Is this code processed by vi18n()?");
173
+ };
174
+ /**
175
+ * The engine's client methods where it never evaluated: on servers and on
176
+ * pages the plugin did not process. One function for all that return
177
+ * nothing or a settled promise (a promise where `void` is declared is
178
+ * harmless), so the core pays a few bytes, not five stubs (PLAN §6). The
179
+ * engine replaces `need` and `ready` with two different functions, so
180
+ * `need === ready` means no engine is installed.
181
+ */
182
+ const idle = () => {
183
+ if (DEV) noEngine();
184
+ return resolved;
185
+ };
186
+ const core = {
187
+ protocol: 1,
188
+ registration: null,
189
+ copies: 1,
190
+ serverResolver: null,
191
+ serverFallback: null,
192
+ richKey: null,
193
+ locales: [],
194
+ need: idle,
195
+ lazy(load) {
196
+ if (DEV) noEngine();
197
+ return load();
198
+ },
199
+ prefetch: idle,
200
+ ready: idle,
201
+ replace: idle,
202
+ subscribe(listener) {
203
+ subs.add(listener);
204
+ return () => void subs.delete(listener);
205
+ },
206
+ setNavigator(navigate) {
207
+ nav = navigate;
208
+ },
209
+ register(r) {
210
+ S.registration = r;
211
+ S.locales.length = 0;
212
+ S.locales.push(...r.config.locales);
213
+ page.locale = r.config.sourceLocale;
214
+ return engine ? engine(r) : resolved;
215
+ },
216
+ setLocale() {
217
+ throw new Error("vi18n: setLocale() is client-only" + (DEV ? clientPage() ? " and needs the client engine (\"@menapo/vi18n/engine\", imported by the boot module)" : "; use withLocale() or scopeLocale() on the server" : ""));
218
+ },
219
+ resolve: () => {
220
+ const r = S.serverResolver;
221
+ return r ? r() ?? noScope() : page;
222
+ },
223
+ untracked(fn) {
224
+ quiet++;
225
+ try {
226
+ return fn();
227
+ } finally {
228
+ quiet--;
229
+ }
230
+ },
231
+ track() {
232
+ for (const t of trackers) t.track();
233
+ },
234
+ addTracker(tracker) {
235
+ trackers.add(tracker);
236
+ return () => void trackers.delete(tracker);
237
+ },
238
+ epoch: () => epoch,
239
+ onMissing(handler) {
240
+ handlers.add(handler);
241
+ return () => void handlers.delete(handler);
242
+ },
243
+ missing(scope, key, reason) {
244
+ if (DEV && scope === page) noEngine();
245
+ report(key, scope.locale, reason);
246
+ }
247
+ };
248
+ const S = stub ? Object.assign(stub, core) : core;
249
+ return S;
250
+ }
251
+ /** Creates the global state, or upgrades the boot stub, or joins another copy's state. */
252
+ function init() {
253
+ const g = globalThis;
254
+ const key = Symbol.for(STATE_KEY);
255
+ const existing = g[key];
256
+ if (existing?.register) {
257
+ existing.copies++;
258
+ if (DEV) console.warn(`vi18n: ${existing.copies} copies of @menapo/vi18n are loaded; they share one state, but dedupe the package`);
259
+ return existing;
260
+ }
261
+ created = true;
262
+ return g[key] = createState(existing);
263
+ }
264
+ /** The state, for runtime modules. Evaluating the runtime is what creates (or joins) it. */
265
+ const S = init();
266
+ /** The global state this copy created or joined. Pinned for `/server` and generated glue. */
267
+ function getState() {
268
+ return S;
269
+ }
270
+ //#endregion
271
+ //#region src/runtime/dir.ts
272
+ /** Text direction of a locale (PLAN §4.16). */
273
+ const RTL_SCRIPTS = "AdlmArabHebrMandMendNkooRohgSamrSyrcThaaYezi";
274
+ const cache = /* @__PURE__ */ new Map();
275
+ function textDirection(locale) {
276
+ let d = cache.get(locale);
277
+ if (!d) {
278
+ d = "ltr";
279
+ try {
280
+ const l = new Intl.Locale(locale);
281
+ const info = l.getTextInfo?.() ?? l.textInfo;
282
+ if (info ? info.direction === "rtl" : RTL_SCRIPTS.includes(l.maximize().script)) d = "rtl";
283
+ } catch {}
284
+ trim(cache, 127);
285
+ cache.set(locale, d);
286
+ }
287
+ return d;
288
+ }
289
+ //#endregion
290
+ export { DEV as _, getState as a, trim as b, page as c, resolved as d, setEngine as f, BUILD as g, createScope as h, created as i, quiet as l, OP as m, S as n, nav as o, subs as p, changed as r, noop as s, textDirection as t, report as u, browser as v, warnOnce as x, own as y };
@@ -0,0 +1,185 @@
1
+ import { S as entryKey } from "./library-D5CWvTQs.js";
2
+ //#region src/core/catalog/duplicates.ts
3
+ const RANK = {
4
+ translated: 2,
5
+ ai: 1,
6
+ untranslated: 0
7
+ };
8
+ /** Entries grouped by key; only keys seen more than once. Null when there are none. */
9
+ function duplicateGroups(catalog, codec) {
10
+ const first = /* @__PURE__ */ new Map();
11
+ let groups = null;
12
+ const entries = catalog.entries;
13
+ for (let i = 0; i < entries.length; i++) {
14
+ const key = entryKey(entries[i], catalog.layout, codec);
15
+ if (key === null) continue;
16
+ const seen = first.get(key);
17
+ if (seen === void 0) {
18
+ first.set(key, i);
19
+ continue;
20
+ }
21
+ groups ??= /* @__PURE__ */ new Map();
22
+ const group = groups.get(key);
23
+ if (group === void 0) groups.set(key, [seen, i]);
24
+ else group.push(i);
25
+ }
26
+ return groups;
27
+ }
28
+ /** `translated` over `ai` over `untranslated`, then the first in the file. */
29
+ function winnerOf(entries, indexes) {
30
+ let best = indexes[0];
31
+ for (const index of indexes) if (RANK[entries[index].state] > RANK[entries[best].state]) best = index;
32
+ return best;
33
+ }
34
+ /**
35
+ * The read-side pick: one entry per key, the losers dropped. Returns the
36
+ * input catalog itself when it has no duplicates, so the common case
37
+ * allocates nothing.
38
+ */
39
+ function resolveDuplicates(catalog, codec) {
40
+ const groups = duplicateGroups(catalog, codec);
41
+ if (groups === null) return {
42
+ catalog,
43
+ duplicates: []
44
+ };
45
+ const entries = catalog.entries;
46
+ const dropped = /* @__PURE__ */ new Set();
47
+ const duplicates = [];
48
+ for (const [key, indexes] of groups) {
49
+ const winner = winnerOf(entries, indexes);
50
+ const lines = [entries[winner].line];
51
+ for (const index of indexes) {
52
+ if (index === winner) continue;
53
+ dropped.add(index);
54
+ lines.push(entries[index].line);
55
+ }
56
+ duplicates.push({
57
+ locale: catalog.locale,
58
+ key,
59
+ lines
60
+ });
61
+ }
62
+ return {
63
+ catalog: {
64
+ ...catalog,
65
+ entries: entries.filter((_, index) => !dropped.has(index))
66
+ },
67
+ duplicates
68
+ };
69
+ }
70
+ /**
71
+ * The `catalog-duplicate` warnings for {@link resolveDuplicates}' reports,
72
+ * for callers that surface them (`compileLocales`, `check`, `status`).
73
+ */
74
+ function duplicateDiagnostics(duplicates, file) {
75
+ return duplicates.map((duplicate) => {
76
+ const [winner, ...others] = duplicate.lines;
77
+ const diagnostic = {
78
+ code: "catalog-duplicate",
79
+ severity: "warning",
80
+ message: `${duplicate.key} appears ${duplicate.lines.length} times; the entry on line ${winner} is used and vi18n extract merges the copies (other lines: ${others.join(", ")})`,
81
+ locale: duplicate.locale,
82
+ id: duplicate.key
83
+ };
84
+ if (file !== void 0) diagnostic.file = file;
85
+ if (winner !== void 0 && winner > 0) diagnostic.line = winner;
86
+ return diagnostic;
87
+ });
88
+ }
89
+ /**
90
+ * Appends the values of `from` that `seen` lacks, in order. `seen` holds
91
+ * what `into` has, so a merge costs one lookup per value, not a search of
92
+ * everything kept so far: a file is input, and may repeat one key with any
93
+ * number of notes.
94
+ */
95
+ function appendNew(into, seen, from) {
96
+ for (const value of from) {
97
+ if (seen.has(value)) continue;
98
+ seen.add(value);
99
+ into.push(value);
100
+ }
101
+ }
102
+ /**
103
+ * Copies `from`'s fields onto `into` in place, not by a spread per copy
104
+ * (which would copy everything kept so far each time); defineProperty keeps
105
+ * a `__proto__` field data, as the JSON reader does.
106
+ */
107
+ function defineFields(into, from) {
108
+ for (const field of Object.keys(from)) Object.defineProperty(into, field, {
109
+ value: from[field],
110
+ enumerable: true,
111
+ writable: true,
112
+ configurable: true
113
+ });
114
+ return into;
115
+ }
116
+ /**
117
+ * The winner with every other copy merged in, in file order: as merging the
118
+ * copies one by one would, but with one pass over each copy, so the cost
119
+ * stays linear in the group's size however many copies there are.
120
+ */
121
+ function mergeGroup(winner, others) {
122
+ const contexts = winner.contexts.slice();
123
+ const seenContexts = new Set(contexts);
124
+ const comments = winner.comments.slice();
125
+ const seenComments = new Set(comments);
126
+ const flags = winner.flags.slice();
127
+ const seenFlags = new Set(flags.map((flag) => flag.line + flag.name));
128
+ const refs = new Set(winner.refs);
129
+ let maxLength = winner.maxLength;
130
+ let extra;
131
+ for (let i = others.length - 1; i >= 0; i--) {
132
+ const other = others[i].extra;
133
+ if (other !== void 0) extra = defineFields(extra ?? {}, other);
134
+ }
135
+ if (extra !== void 0 && winner.extra !== void 0) defineFields(extra, winner.extra);
136
+ for (const other of others) {
137
+ appendNew(contexts, seenContexts, other.contexts);
138
+ appendNew(comments, seenComments, other.comments);
139
+ for (const flag of other.flags) {
140
+ const key = flag.line + flag.name;
141
+ if (seenFlags.has(key)) continue;
142
+ seenFlags.add(key);
143
+ flags.push(flag);
144
+ }
145
+ for (const ref of other.refs) refs.add(ref);
146
+ if (other.maxLength !== null) maxLength = maxLength === null ? other.maxLength : Math.min(maxLength, other.maxLength);
147
+ }
148
+ const merged = {
149
+ ...winner,
150
+ contexts,
151
+ comments,
152
+ flags,
153
+ refs: [...refs].sort((x, y) => x < y ? -1 : x > y ? 1 : 0),
154
+ maxLength
155
+ };
156
+ if (extra !== void 0) merged.extra = extra;
157
+ return merged;
158
+ }
159
+ /**
160
+ * What a full extract does with duplicates: one entry per key at the
161
+ * winner's position, keeping its translation, state and fuzzy flag and the
162
+ * union of every copy's references, notes, comments and flags.
163
+ */
164
+ function mergeDuplicates(catalog, codec) {
165
+ const groups = duplicateGroups(catalog, codec);
166
+ if (groups === null) return catalog;
167
+ const entries = catalog.entries.slice();
168
+ const dropped = /* @__PURE__ */ new Set();
169
+ for (const indexes of groups.values()) {
170
+ const winner = winnerOf(entries, indexes);
171
+ const others = [];
172
+ for (const index of indexes) {
173
+ if (index === winner) continue;
174
+ others.push(entries[index]);
175
+ dropped.add(index);
176
+ }
177
+ entries[winner] = mergeGroup(entries[winner], others);
178
+ }
179
+ return {
180
+ ...catalog,
181
+ entries: entries.filter((_, index) => !dropped.has(index))
182
+ };
183
+ }
184
+ //#endregion
185
+ export { mergeDuplicates as n, resolveDuplicates as r, duplicateDiagnostics as t };