@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.
- package/LICENSE +49 -0
- package/README.md +110 -2
- package/dist/apply-Bre-lY9J.js +228 -0
- package/dist/captured-Bj8bO6vd.js +1034 -0
- package/dist/check-D34P-S--.js +1 -0
- package/dist/check-DyrAbv73.js +201 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +41 -0
- package/dist/codec-DuvU2VQy.js +207 -0
- package/dist/collect-Cx4_SKOe.js +13 -0
- package/dist/collisions-CfpZU8e4.js +217 -0
- package/dist/compile-B5Mz6WJm.js +245 -0
- package/dist/compile-Cc97JaW2.js +2502 -0
- package/dist/concurrency-C9UUWuDC.js +96 -0
- package/dist/concurrency-DONL7t4V.js +1 -0
- package/dist/config.d.ts +21 -0
- package/dist/config.js +39 -0
- package/dist/dir-zw6bGMKQ.js +290 -0
- package/dist/duplicates-yuRljQ6h.js +185 -0
- package/dist/engine-B7p40M__.js +531 -0
- package/dist/engine.d.ts +43 -0
- package/dist/engine.js +1241 -0
- package/dist/exclude-Du-Zd_qI.js +26 -0
- package/dist/export-DGsMbcTZ.js +103 -0
- package/dist/extract-Dujlsgnk.js +298 -0
- package/dist/formats-KPIg-08V.js +781 -0
- package/dist/i18next-CrUnKFfh.js +1596 -0
- package/dist/i18next-Rgo-v9l0.js +1 -0
- package/dist/import-Cwdhcywt.js +225 -0
- package/dist/index.d.ts +156 -0
- package/dist/index.js +239 -0
- package/dist/init-BecCTvKi.js +792 -0
- package/dist/library-D5CWvTQs.js +1882 -0
- package/dist/lingui--PcOOYry.js +833 -0
- package/dist/lint.d.ts +105 -0
- package/dist/lint.js +971 -0
- package/dist/load-CQRz4PpM.js +2950 -0
- package/dist/load-gW-oJt1Q.js +747 -0
- package/dist/locales-DX4MOBFE.js +827 -0
- package/dist/lsp-Cq1Nuu-Q.js +31 -0
- package/dist/match-BGr8EXB3.js +89 -0
- package/dist/mcp-Cf0T7qPW.js +28 -0
- package/dist/merge-driver-CpiiSVIc.js +422 -0
- package/dist/messages-Cxa-5tR0.js +166 -0
- package/dist/migrate-CntqvrW4.js +1 -0
- package/dist/migrate-dPgq9uzy.js +1484 -0
- package/dist/openai-CTaMDcl-.js +346 -0
- package/dist/output-DsaHKHEt.js +164 -0
- package/dist/parse.d.ts +62 -0
- package/dist/parse.js +156 -0
- package/dist/pipeline-C7g11THL.js +2031 -0
- package/dist/pipeline-wxS0SApG.js +1 -0
- package/dist/project-Dwx7iPm6.js +381 -0
- package/dist/project-pXipT8iG.js +1046 -0
- package/dist/pseudo-DRv30iN5.js +145 -0
- package/dist/raw-C4Y6vyGY.js +4708 -0
- package/dist/react-intl-CaBVwJ9u.js +519 -0
- package/dist/restore-Btq66Tid.js +175 -0
- package/dist/rewrite-CiTRAICl.js +803 -0
- package/dist/rich-DCUKl5HM.js +97 -0
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/run-DYD_VXKy.js +694 -0
- package/dist/runtime-DTMmliT1.js +64 -0
- package/dist/scan-worker.d.ts +62 -0
- package/dist/scan-worker.js +1 -0
- package/dist/scoped-BMYpSthj.js +44 -0
- package/dist/server-BDkykMjb.js +11823 -0
- package/dist/server-qv3mz70d.js +10485 -0
- package/dist/server.d.ts +169 -0
- package/dist/server.js +1234 -0
- package/dist/sitemap-DV3o8QD7.js +159 -0
- package/dist/sources-D19czDSA.js +2242 -0
- package/dist/status-DZArW5aE.js +149 -0
- package/dist/stdio-CVaJfxA7.js +29 -0
- package/dist/stdio-FPOKQAgu.js +95 -0
- package/dist/sync-CcVzger_.js +658 -0
- package/dist/t-DC-c9zE5.js +454 -0
- package/dist/test.d.ts +116 -0
- package/dist/test.js +319 -0
- package/dist/translate-DZR3MkSA.js +299 -0
- package/dist/ts-BaU5yOf0.js +4863 -0
- package/dist/types-CgUCkFU1.d.ts +707 -0
- package/dist/types-D3QGM8XO.js +146 -0
- package/dist/types-DsdWBG0b.d.ts +558 -0
- package/dist/types-DtvQ7UdD.js +1948 -0
- package/dist/types-wSh9fCBY.js +11 -0
- package/dist/units-CQ5Q99RC.js +150 -0
- package/dist/url-CRfCOhUI.js +172 -0
- package/dist/url-CsBkRaJy.js +225 -0
- package/dist/validate-BxpupdAf.js +2615 -0
- package/dist/validate-D6DJepZv.js +365 -0
- package/dist/version-BlyLGlOa.js +28 -0
- package/dist/vite-scan-B_YW4X_a.js +242 -0
- package/dist/vite.d.ts +34 -0
- package/dist/vite.js +7558 -0
- package/dist/vue-i18n-CEMnpv7k.js +651 -0
- package/dist/which-C6PFI1Dm.js +116 -0
- package/dist/worker-84U6uYB3.js +1656 -0
- package/package.json +97 -3
- 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";
|
package/dist/config.d.ts
ADDED
|
@@ -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 };
|