@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,454 @@
|
|
|
1
|
+
import { _ as DEV, b as trim, g as BUILD, l as quiet, n as S, t as textDirection, x as warnOnce, y as own } from "./dir-zw6bGMKQ.js";
|
|
2
|
+
//#region src/runtime/bidi.ts
|
|
3
|
+
/**
|
|
4
|
+
* Automatic bidi isolation of interpolated strings (INTERNALS §2.3, PLAN
|
|
5
|
+
* §4.2): a user-supplied name in Arabic inside an English sentence, or a Latin
|
|
6
|
+
* name in Hebrew UI, must not reorder the text around it.
|
|
7
|
+
*/
|
|
8
|
+
const RTL_OR_CONTROL = /[\u0590-\u08FF\u200E\u200F\u202A-\u202E\u2066-\u2069\uFB1D-\uFDFF\uFE70-\uFEFE\u{10D00}-\u{10D3F}\u{1E900}-\u{1E95F}]/u;
|
|
9
|
+
/**
|
|
10
|
+
* Wraps `value` in FSI … PDI when the locale is RTL or the value carries RTL
|
|
11
|
+
* text or bidi controls. Isolates inside the value are balanced first (UAX #9
|
|
12
|
+
* X6a): a PDI without its initiator would close our FSI early and leave the
|
|
13
|
+
* rest of the sentence to an override in the value, and an initiator without
|
|
14
|
+
* its PDI would take our PDI and the rest of the sentence with it. `open`
|
|
15
|
+
* counts the value's open isolates (a parameter, not a local: no block body).
|
|
16
|
+
*/
|
|
17
|
+
const isolate = (value, locale, open = 0) => value && (textDirection(locale) === "rtl" || RTL_OR_CONTROL.test(value)) ? "" + value.replace(/[\u2066-\u2069]/g, (c) => (c < "" ? ++open : open && open--) ? c : "") + "".repeat(open + 1) : value;
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/runtime/intl.ts
|
|
20
|
+
/**
|
|
21
|
+
* Intl formatter caches and the scope-taking implementations behind `fmt`
|
|
22
|
+
* (PLAN §5.12, §5.17). Constructing an Intl formatter costs 23–228 µs and a
|
|
23
|
+
* cached lookup under 1.5 µs (report 06), so no call path constructs one per
|
|
24
|
+
* call: IR options are stable objects and get an identity-keyed cache, while
|
|
25
|
+
* caller-supplied `fmt` options go through a bounded map keyed by value.
|
|
26
|
+
*/
|
|
27
|
+
/** Whether date options render a time of day. */
|
|
28
|
+
const hasTime = (options) => [
|
|
29
|
+
"hour",
|
|
30
|
+
"minute",
|
|
31
|
+
"second",
|
|
32
|
+
"timeStyle",
|
|
33
|
+
"dayPeriod"
|
|
34
|
+
].some((k) => own(options, k));
|
|
35
|
+
/**
|
|
36
|
+
* Maps U+202F to a plain space when `ssr.normalizeSpaces` is on, so server and
|
|
37
|
+
* browser ICU versions hydrate to the same text. Numbers never come here: the
|
|
38
|
+
* narrow no-break space is a real group separator there (`1 234,56`).
|
|
39
|
+
*/
|
|
40
|
+
const normalize = (text) => S.registration?.config.ssr.normalizeSpaces === false ? text : text.replace(/\u202F/g, " ");
|
|
41
|
+
/** Time-of-day output: also marks the scope, so `/server` keeps per-user-zone responses private. */
|
|
42
|
+
const timeText = (text, scope) => {
|
|
43
|
+
scope.formattedTime = true;
|
|
44
|
+
return normalize(text);
|
|
45
|
+
};
|
|
46
|
+
const NO_OPTIONS = [
|
|
47
|
+
{},
|
|
48
|
+
{},
|
|
49
|
+
{
|
|
50
|
+
hour: "numeric",
|
|
51
|
+
minute: "numeric",
|
|
52
|
+
second: "numeric"
|
|
53
|
+
}
|
|
54
|
+
];
|
|
55
|
+
const irCache = /* @__PURE__ */ new WeakMap();
|
|
56
|
+
const interned = /* @__PURE__ */ new Map();
|
|
57
|
+
const intern = (o) => {
|
|
58
|
+
const k = JSON.stringify(o);
|
|
59
|
+
let i = interned.get(k);
|
|
60
|
+
if (!i) {
|
|
61
|
+
trim(interned, 499);
|
|
62
|
+
interned.set(k, i = o);
|
|
63
|
+
}
|
|
64
|
+
return i;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* `{x, number|date|time, …}` (opcodes 4, 5, 6) and `#` (4 without options).
|
|
68
|
+
* A missing value renders nothing, like a plain `{x}`: never the current
|
|
69
|
+
* time, 1970 or "NaN" (`#` gets null when its plural has no value).
|
|
70
|
+
*/
|
|
71
|
+
function formatPart(op, options, value, scope) {
|
|
72
|
+
if (value == null) return "";
|
|
73
|
+
const o = options ? DEV ? intern(options) : options : NO_OPTIONS[op - 4];
|
|
74
|
+
let formatters = irCache.get(o);
|
|
75
|
+
if (!formatters) irCache.set(o, formatters = /* @__PURE__ */ new Map());
|
|
76
|
+
const { locale, timeZone: zone } = scope;
|
|
77
|
+
const k = locale + (op === 4 || !zone ? "" : "\0" + zone.toLowerCase());
|
|
78
|
+
let e = formatters.get(k);
|
|
79
|
+
if (!e) {
|
|
80
|
+
trim(formatters, 63);
|
|
81
|
+
formatters.set(k, e = op === 4 ? [new Intl.NumberFormat(locale, o)] : [new Intl.DateTimeFormat(locale, {
|
|
82
|
+
timeZone: zone,
|
|
83
|
+
...o
|
|
84
|
+
}), op === 6 || hasTime(o)]);
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const s = e[0].format(value);
|
|
88
|
+
return e[1] ? timeText(s, scope) : s;
|
|
89
|
+
} catch {
|
|
90
|
+
return String(value);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const plurals = /* @__PURE__ */ new Map();
|
|
94
|
+
/** PluralRules in a fixed (locale, type) slot. */
|
|
95
|
+
const pluralRules = (locale, ordinal) => {
|
|
96
|
+
let slot = plurals.get(locale);
|
|
97
|
+
if (!slot) plurals.set(locale, slot = []);
|
|
98
|
+
return slot[+ordinal] ??= new Intl.PluralRules(locale, { type: ordinal ? "ordinal" : "cardinal" });
|
|
99
|
+
};
|
|
100
|
+
const memo = /* @__PURE__ */ new Map();
|
|
101
|
+
const stable = (o) => o ? Object.keys(o).sort().map((k) => k + ":" + JSON.stringify(o[k])).join() : "";
|
|
102
|
+
/**
|
|
103
|
+
* The formatter of `kind` for the scope's locale. Only date kinds depend on
|
|
104
|
+
* the zone, and `df` puts it into their `kind`: with per-user zones, a key
|
|
105
|
+
* holding it for every kind would churn the map and construct number
|
|
106
|
+
* formatters per call.
|
|
107
|
+
*/
|
|
108
|
+
function cached(kind, scope, options, make) {
|
|
109
|
+
const key = kind + "\0" + scope.locale + "\0" + stable(options);
|
|
110
|
+
let f = memo.get(key);
|
|
111
|
+
if (f === void 0) {
|
|
112
|
+
trim(memo, 499);
|
|
113
|
+
memo.set(key, f = make(scope.locale));
|
|
114
|
+
}
|
|
115
|
+
return f;
|
|
116
|
+
}
|
|
117
|
+
const nf = (scope, kind, options, extra) => cached(kind, scope, options, (l) => new Intl.NumberFormat(l, {
|
|
118
|
+
...options,
|
|
119
|
+
...extra
|
|
120
|
+
}));
|
|
121
|
+
const df = (scope, kind, options) => cached(kind + (scope.timeZone ?? ""), scope, options, (l) => new Intl.DateTimeFormat(l, {
|
|
122
|
+
timeZone: scope.timeZone,
|
|
123
|
+
...options
|
|
124
|
+
}));
|
|
125
|
+
/**
|
|
126
|
+
* A date input as every engine's Intl takes it, and the zone it is read in
|
|
127
|
+
* (PLAN §4.2, INTERNALS §6 D208). Temporal values are recognised by their
|
|
128
|
+
* tag, so native objects and a polyfill's alike, and converted everywhere:
|
|
129
|
+
* Intl takes them only where the engine has Temporal (not Node 22), never a
|
|
130
|
+
* ZonedDateTime, and only in the formatter's calendar, while a server and a
|
|
131
|
+
* browser must render the same text for hydration. Exact times (Instant,
|
|
132
|
+
* ZonedDateTime) become epoch ms, a ZonedDateTime read in its own zone;
|
|
133
|
+
* wall-clock values (PlainDate, PlainDateTime, PlainTime, PlainYearMonth,
|
|
134
|
+
* PlainMonthDay) become their ISO fields read in UTC, so no zone moves them.
|
|
135
|
+
*/
|
|
136
|
+
const instant = (value) => {
|
|
137
|
+
let v = value;
|
|
138
|
+
if (!String(v[Symbol.toStringTag]).startsWith("Temporal.")) return [value];
|
|
139
|
+
if (v.epochMilliseconds != null) return [v.epochMilliseconds, v.timeZoneId];
|
|
140
|
+
v = v.withCalendar?.("iso8601") ?? v;
|
|
141
|
+
return [Date.UTC(v.year ?? 1972, (v.monthCode?.slice(1) ?? 1) - 1, v.day ?? 1, v.hour ?? 0, v.minute ?? 0, v.second ?? 0, v.millisecond ?? 0), "UTC"];
|
|
142
|
+
};
|
|
143
|
+
/** The cached formatter for `value` (its own zone wins) and the value it formats. */
|
|
144
|
+
const dt = (scope, kind, options, value) => {
|
|
145
|
+
const [v, zone] = instant(value);
|
|
146
|
+
return [df(scope, kind, zone ? {
|
|
147
|
+
...options,
|
|
148
|
+
timeZone: zone
|
|
149
|
+
} : options), v];
|
|
150
|
+
};
|
|
151
|
+
const fNumber = (scope, value, options) => nf(scope, "n", options).format(value);
|
|
152
|
+
const fCurrency = (scope, value, currency, options) => nf(scope, "c" + currency, options, {
|
|
153
|
+
style: "currency",
|
|
154
|
+
currency
|
|
155
|
+
}).format(value);
|
|
156
|
+
/**
|
|
157
|
+
* ISO 4217 list one: the minor unit of every code whose minor unit is not 2.
|
|
158
|
+
* ISO gives the last group none (N.A.: precious metals, bond-market units,
|
|
159
|
+
* XDR, XSU, XUA, XTS for testing, XXX for no currency), so an amount of them
|
|
160
|
+
* counts whole units. Intl reports CLDR's display digits instead, which are
|
|
161
|
+
* not ISO's and change with the ICU version (CLDR 48 shows COP, HUF, IDR and
|
|
162
|
+
* PKR without decimals, CLDR 47 RSD), and browsers ship different ones: Intl
|
|
163
|
+
* only formats.
|
|
164
|
+
*/
|
|
165
|
+
const MINOR_UNITS = {
|
|
166
|
+
BIF: 0,
|
|
167
|
+
CLP: 0,
|
|
168
|
+
DJF: 0,
|
|
169
|
+
GNF: 0,
|
|
170
|
+
ISK: 0,
|
|
171
|
+
JPY: 0,
|
|
172
|
+
KMF: 0,
|
|
173
|
+
KRW: 0,
|
|
174
|
+
PYG: 0,
|
|
175
|
+
RWF: 0,
|
|
176
|
+
UGX: 0,
|
|
177
|
+
UYI: 0,
|
|
178
|
+
VND: 0,
|
|
179
|
+
VUV: 0,
|
|
180
|
+
XAF: 0,
|
|
181
|
+
XOF: 0,
|
|
182
|
+
XPF: 0,
|
|
183
|
+
BHD: 3,
|
|
184
|
+
IQD: 3,
|
|
185
|
+
JOD: 3,
|
|
186
|
+
KWD: 3,
|
|
187
|
+
LYD: 3,
|
|
188
|
+
OMR: 3,
|
|
189
|
+
TND: 3,
|
|
190
|
+
CLF: 4,
|
|
191
|
+
UYW: 4,
|
|
192
|
+
XAG: 0,
|
|
193
|
+
XAU: 0,
|
|
194
|
+
XBA: 0,
|
|
195
|
+
XBB: 0,
|
|
196
|
+
XBC: 0,
|
|
197
|
+
XBD: 0,
|
|
198
|
+
XDR: 0,
|
|
199
|
+
XPD: 0,
|
|
200
|
+
XPT: 0,
|
|
201
|
+
XSU: 0,
|
|
202
|
+
XTS: 0,
|
|
203
|
+
XUA: 0,
|
|
204
|
+
XXX: 0
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Minor units: divided by the currency's ISO 4217 minor unit (JPY 0, USD 2,
|
|
208
|
+
* BHD 3, `MINOR_UNITS`), never a fixed 100 nor CLDR's display digits, which
|
|
209
|
+
* only decide what the output shows. Bigints become an exact decimal string,
|
|
210
|
+
* which Intl formats without going through a double.
|
|
211
|
+
*/
|
|
212
|
+
const fCurrencyMinor = (scope, minor, currency, options) => {
|
|
213
|
+
const d = MINOR_UNITS[currency.toUpperCase()] ?? 2;
|
|
214
|
+
let value;
|
|
215
|
+
if (typeof minor === "bigint") {
|
|
216
|
+
const s = (minor < 0n ? -minor : minor).toString().padStart(d + 1, "0");
|
|
217
|
+
value = (minor < 0n ? "-" : "") + s.slice(0, s.length - d) + (d ? "." + s.slice(-d) : "");
|
|
218
|
+
} else value = minor / 10 ** d;
|
|
219
|
+
return fCurrency(scope, value, currency, options);
|
|
220
|
+
};
|
|
221
|
+
const fUnit = (scope, value, unit, options) => nf(scope, "u" + unit, options, {
|
|
222
|
+
style: "unit",
|
|
223
|
+
unit
|
|
224
|
+
}).format(value);
|
|
225
|
+
const fCompact = (scope, value, options) => nf(scope, "k", options, { notation: "compact" }).format(value);
|
|
226
|
+
const fPercent = (scope, value, options) => nf(scope, "p", options, { style: "percent" }).format(value);
|
|
227
|
+
const fDate = (scope, value, options) => {
|
|
228
|
+
const [f, v] = dt(scope, "d", options, value);
|
|
229
|
+
const s = f.format(v);
|
|
230
|
+
return options && hasTime(options) ? timeText(s, scope) : s;
|
|
231
|
+
};
|
|
232
|
+
const SHORT_TIME = { timeStyle: "short" };
|
|
233
|
+
const fTime = (scope, value, options) => {
|
|
234
|
+
const [f, v] = dt(scope, "t", options ?? SHORT_TIME, value);
|
|
235
|
+
return timeText(f.format(v), scope);
|
|
236
|
+
};
|
|
237
|
+
const fDateRange = (scope, start, end, options) => {
|
|
238
|
+
const [f, a] = dt(scope, "d", options, start);
|
|
239
|
+
const s = f.formatRange(a, instant(end)[0]);
|
|
240
|
+
return options && hasTime(options) ? timeText(s, scope) : normalize(s);
|
|
241
|
+
};
|
|
242
|
+
const SECONDS = {
|
|
243
|
+
second: 1,
|
|
244
|
+
minute: 60,
|
|
245
|
+
hour: 3600,
|
|
246
|
+
day: 86400,
|
|
247
|
+
week: 604800,
|
|
248
|
+
month: 2629746,
|
|
249
|
+
quarter: 7889238,
|
|
250
|
+
year: 31556952
|
|
251
|
+
};
|
|
252
|
+
const AUTO_UNITS = [
|
|
253
|
+
"second",
|
|
254
|
+
"minute",
|
|
255
|
+
"hour",
|
|
256
|
+
"day",
|
|
257
|
+
"week",
|
|
258
|
+
"month",
|
|
259
|
+
"year"
|
|
260
|
+
];
|
|
261
|
+
const fRelative = (scope, value, options) => {
|
|
262
|
+
const { now, unit, ...rest } = options ?? {};
|
|
263
|
+
const seconds = (+(value.epochMilliseconds ?? value) - (now ?? scope.now ?? Date.now())) / 1e3;
|
|
264
|
+
let u = unit ? unit.replace(/s$/, "") : "";
|
|
265
|
+
if (!own(SECONDS, u)) {
|
|
266
|
+
const a = Math.abs(seconds);
|
|
267
|
+
let i = 0;
|
|
268
|
+
for (; i < 6; i++) {
|
|
269
|
+
const s = SECONDS[AUTO_UNITS[i]];
|
|
270
|
+
if (Math.max(a, Math.round(a / s) * s) < SECONDS[AUTO_UNITS[i + 1]]) break;
|
|
271
|
+
}
|
|
272
|
+
u = AUTO_UNITS[i];
|
|
273
|
+
}
|
|
274
|
+
return cached("r", scope, rest, (l) => new Intl.RelativeTimeFormat(l, rest)).format(Math.round(seconds / SECONDS[u]) + 0, u);
|
|
275
|
+
};
|
|
276
|
+
const fList = (scope, items, options) => cached("l", scope, options, (l) => new Intl.ListFormat(l, options)).format(items);
|
|
277
|
+
const fCollator = (scope, options) => cached("o", scope, options, (l) => new Intl.Collator(l, options));
|
|
278
|
+
const fCompare = (scope, a, b, options) => fCollator(scope, options).compare(a, b);
|
|
279
|
+
const fSegment = (scope, text, options) => cached("g", scope, options, (l) => new Intl.Segmenter(l, options)).segment(text);
|
|
280
|
+
const fDisplayName = (scope, code, options) => {
|
|
281
|
+
try {
|
|
282
|
+
return cached("y", scope, options, (l) => new Intl.DisplayNames(l, options)).of(code);
|
|
283
|
+
} catch {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
const DURATION_UNITS = [
|
|
288
|
+
"year",
|
|
289
|
+
"month",
|
|
290
|
+
"week",
|
|
291
|
+
"day",
|
|
292
|
+
"hour",
|
|
293
|
+
"minute",
|
|
294
|
+
"second",
|
|
295
|
+
"millisecond"
|
|
296
|
+
];
|
|
297
|
+
/**
|
|
298
|
+
* `Intl.DurationFormat` where the engine has it; elsewhere (Node 22) unit
|
|
299
|
+
* numbers joined by a unit list, which renders the same text for the common
|
|
300
|
+
* styles (`1 hr, 30 min`), and `h:mm:ss` for `digital`.
|
|
301
|
+
*/
|
|
302
|
+
const fDuration = (scope, duration, options) => {
|
|
303
|
+
const style = options?.style ?? "short";
|
|
304
|
+
const DF = Intl.DurationFormat;
|
|
305
|
+
if (DF) return cached("D", scope, options, (l) => new DF(l, { style })).format(duration);
|
|
306
|
+
const parts = [];
|
|
307
|
+
const display = style === "digital" ? "short" : style;
|
|
308
|
+
const v = (u) => duration[u + "s"] ?? 0;
|
|
309
|
+
for (const u of DURATION_UNITS) {
|
|
310
|
+
if (style === "digital" && u === "hour") {
|
|
311
|
+
const two = nf(scope, "n", { minimumIntegerDigits: 2 });
|
|
312
|
+
parts.push(fNumber(scope, v("hour")) + ":" + two.format(v("minute")) + ":" + two.format(v("second") + v("millisecond") / 1e3));
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
if (v(u)) parts.push(nf(scope, "U" + u + display, { unitDisplay: display }, {
|
|
316
|
+
style: "unit",
|
|
317
|
+
unit: u
|
|
318
|
+
}).format(v(u)));
|
|
319
|
+
}
|
|
320
|
+
if (!parts.length) parts.push(nf(scope, "Usecond" + display, { unitDisplay: display }, {
|
|
321
|
+
style: "unit",
|
|
322
|
+
unit: "second"
|
|
323
|
+
}).format(0));
|
|
324
|
+
return fList(scope, parts, {
|
|
325
|
+
type: "unit",
|
|
326
|
+
style: display === "long" ? "long" : display
|
|
327
|
+
});
|
|
328
|
+
};
|
|
329
|
+
//#endregion
|
|
330
|
+
//#region src/runtime/format.ts
|
|
331
|
+
/**
|
|
332
|
+
* The IR interpreter (INTERNALS §2.3 "Interpretation"). Normative: the server
|
|
333
|
+
* and the client must render byte-identical text, so every rule lives here
|
|
334
|
+
* once and `rich.ts` reuses the helpers. Only plain text is rendered here:
|
|
335
|
+
* `t()` ships this file, and the rich-text outputs (handlers, nodes, HTML
|
|
336
|
+
* escaping) are `rich.ts`'s.
|
|
337
|
+
*/
|
|
338
|
+
/**
|
|
339
|
+
* A value by argument name. Own properties only: a message argument named
|
|
340
|
+
* `constructor` must not read `Object.prototype.constructor` off a values object.
|
|
341
|
+
*/
|
|
342
|
+
const valueOf = (values, name) => values && own(values, name) ? values[name] : void 0;
|
|
343
|
+
/**
|
|
344
|
+
* The case of a select (`String(value)`) or plural (`=N`, then the category)
|
|
345
|
+
* part. `n` receives the `#` value of the case: the plural value minus the
|
|
346
|
+
* offset, or the enclosing one for a select.
|
|
347
|
+
*/
|
|
348
|
+
function caseOf(part, value, locale) {
|
|
349
|
+
const cases = part[2];
|
|
350
|
+
let key = String(value);
|
|
351
|
+
if (part[1] > 1) {
|
|
352
|
+
const n = Number(value ?? NaN);
|
|
353
|
+
if (!own(cases, key = "=" + n)) key = pluralRules(locale, part[1] > 2).select(n - (part[3] ?? 0));
|
|
354
|
+
}
|
|
355
|
+
return own(cases, key) ? cases[key] : cases.other;
|
|
356
|
+
}
|
|
357
|
+
/** `#` value inside a plural case: the raw number minus the offset; null (nothing) without a value. */
|
|
358
|
+
const poundOf = (part, value) => value == null ? null : Number(value) - (part[3] ?? 0);
|
|
359
|
+
/** `{x, date, medium}`'s options (FormatJS's default `formats`), one object so its formatters are cached. */
|
|
360
|
+
const MEDIUM = {
|
|
361
|
+
month: "short",
|
|
362
|
+
day: "numeric",
|
|
363
|
+
year: "numeric"
|
|
364
|
+
};
|
|
365
|
+
/**
|
|
366
|
+
* Text of a plain `{arg}`: strings are bidi-isolated, numbers and bigints use
|
|
367
|
+
* `String()` (FormatJS parity; `{n, number}` localises), a `Date` renders as
|
|
368
|
+
* `{x, date, medium}` in the scope's locale and zone (`String(date)` is
|
|
369
|
+
* English in the host's zone, so server and browser would disagree), null
|
|
370
|
+
* and undefined render nothing.
|
|
371
|
+
*/
|
|
372
|
+
function argText(value, scope) {
|
|
373
|
+
if (typeof value === "string") return isolate(value, scope.locale);
|
|
374
|
+
if (value instanceof Date) return formatPart(5, MEDIUM, value, scope);
|
|
375
|
+
return String(value ?? "");
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Renders a compiled message as plain text in `scope` (tags render their
|
|
379
|
+
* children). Recursion passes `n`, the `#` value of the innermost plural, and
|
|
380
|
+
* `from`: tags reuse their own array from index 2, so no child list is ever
|
|
381
|
+
* sliced.
|
|
382
|
+
*/
|
|
383
|
+
function formatMessage(message, values, scope, n = 0, from = 0) {
|
|
384
|
+
if (typeof message === "string") return message;
|
|
385
|
+
let out = "";
|
|
386
|
+
for (let i = from; i < message.length; i++) {
|
|
387
|
+
const p = message[i];
|
|
388
|
+
if (typeof p === "string") out += p;
|
|
389
|
+
else if (p === 0) out += formatPart(4, void 0, n, scope);
|
|
390
|
+
else {
|
|
391
|
+
const op = p[1];
|
|
392
|
+
if (op === 7) out += formatMessage(p, values, scope, n, 2);
|
|
393
|
+
else if (op !== 8) {
|
|
394
|
+
const value = valueOf(values, p[0]);
|
|
395
|
+
if (DEV && value == null && op !== 1) warnOnce(`no value for {${p[0]}} in ${JSON.stringify(message)}`);
|
|
396
|
+
if (op === void 0) out += argText(value, scope);
|
|
397
|
+
else if (op < 4) {
|
|
398
|
+
const c = caseOf(p, value, scope.locale);
|
|
399
|
+
if (c !== void 0) out += formatMessage(c, values, scope, op === 1 ? n : poundOf(p, value));
|
|
400
|
+
} else out += formatPart(op, p[2], value, scope);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return out;
|
|
405
|
+
}
|
|
406
|
+
//#endregion
|
|
407
|
+
//#region src/runtime/t.ts
|
|
408
|
+
/**
|
|
409
|
+
* `t` and `msg` (PLAN §5.3, §5.12; INTERNALS §2.11.2). The hot path for a
|
|
410
|
+
* plain string is: resolve the scope, one `Map.get`, one `typeof`.
|
|
411
|
+
*/
|
|
412
|
+
const { resolve, track: notify } = S;
|
|
413
|
+
/**
|
|
414
|
+
* The message to render for `key` in `scope`. `track` notifies the framework
|
|
415
|
+
* trackers; translators bound to a fixed scope pass false.
|
|
416
|
+
*/
|
|
417
|
+
function lookup(scope, key, track) {
|
|
418
|
+
let text;
|
|
419
|
+
let d;
|
|
420
|
+
if (key && typeof key === "object") {
|
|
421
|
+
d = key;
|
|
422
|
+
text = d.message ?? d.id ?? "";
|
|
423
|
+
} else text = String(key);
|
|
424
|
+
const id = d?.id ?? scope.sources?.get(text) ?? text;
|
|
425
|
+
if (!quiet) scope.used?.add(id);
|
|
426
|
+
if (track) notify();
|
|
427
|
+
if (DEV) scope.seen?.(id);
|
|
428
|
+
if (!BUILD && scope.test) return scope.test(id, d, text);
|
|
429
|
+
if (DEV && d && d.h !== void 0 && d.ir !== void 0) {
|
|
430
|
+
const label = scope.labels?.get(id);
|
|
431
|
+
if (label !== void 0 && label !== d.h) {
|
|
432
|
+
warnOnce(`${id}: translation is for other text than ${JSON.stringify(d.message)}; run vi18n extract`);
|
|
433
|
+
S.missing(scope, id, "source-mismatch");
|
|
434
|
+
return d.ir;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const m = scope.messages.get(id) ?? d?.ir;
|
|
438
|
+
if (m !== void 0) return m;
|
|
439
|
+
S.missing(scope, id, "unknown-id");
|
|
440
|
+
if (DEV) {
|
|
441
|
+
warnOnce(`no message ${JSON.stringify(text)} in ${scope.locale}: unknown id, or a call the plugin did not process`);
|
|
442
|
+
return text;
|
|
443
|
+
}
|
|
444
|
+
return scope.kind === "page" && !S.serverResolver && S.registration?.consumer !== "server" ? "" : text;
|
|
445
|
+
}
|
|
446
|
+
const t = ((key, values) => {
|
|
447
|
+
const scope = resolve();
|
|
448
|
+
return formatMessage(lookup(scope, key, true), values, scope);
|
|
449
|
+
});
|
|
450
|
+
function msg(message) {
|
|
451
|
+
return message;
|
|
452
|
+
}
|
|
453
|
+
//#endregion
|
|
454
|
+
export { fTime as C, fSegment as S, formatPart as T, fDuration as _, caseOf as a, fPercent as b, valueOf as c, fCompare as d, fCurrency as f, fDisplayName as g, fDateRange as h, argText as i, fCollator as l, fDate as m, msg as n, formatMessage as o, fCurrencyMinor as p, t as r, poundOf as s, lookup as t, fCompact as u, fList as v, fUnit as w, fRelative as x, fNumber as y };
|
package/dist/test.d.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { a as Message, y as Translator } from "./types-DsdWBG0b.js";
|
|
2
|
+
//#region src/test/i18n.d.ts
|
|
3
|
+
/** What every message renders as: its translation or source, its id, or pseudo text. */
|
|
4
|
+
type TestMode = "source" | "ids" | "pseudo";
|
|
5
|
+
/** One locale's translations from the project's catalogs (`loadCatalog`). */
|
|
6
|
+
interface TestCatalog {
|
|
7
|
+
readonly locale: string;
|
|
8
|
+
/** Id → compiled message, fallbacks applied; untranslated ids are absent and render the source. */
|
|
9
|
+
readonly messages: ReadonlyMap<string, Message>;
|
|
10
|
+
}
|
|
11
|
+
interface TestI18nOptions {
|
|
12
|
+
/**
|
|
13
|
+
* The locale to render in (formatting, plural rules, `getLocale()`,
|
|
14
|
+
* `<html lang dir>`). Default: the catalog's, `en-XA` in pseudo mode, else
|
|
15
|
+
* the source locale.
|
|
16
|
+
*/
|
|
17
|
+
locale?: string;
|
|
18
|
+
/** Default `"source"`: translations where given, the source text elsewhere. */
|
|
19
|
+
mode?: TestMode;
|
|
20
|
+
/** Real translations, from `await loadCatalog(locale)`. */
|
|
21
|
+
catalog?: TestCatalog;
|
|
22
|
+
/** Hand-written translations (ICU), keyed by id or by source text; they win over `catalog`. */
|
|
23
|
+
messages?: {
|
|
24
|
+
readonly [idOrSource: string]: string;
|
|
25
|
+
};
|
|
26
|
+
/** IANA zone for dates and times. Default `"UTC"`. */
|
|
27
|
+
timeZone?: string;
|
|
28
|
+
/** `fmt.relative`'s now. Default 2026-01-01T00:00:00Z. */
|
|
29
|
+
now?: number | Date;
|
|
30
|
+
/** Ids mode for calls the plugin did not rewrite: the generated id length (`ids.length`). Default 12. */
|
|
31
|
+
idLength?: number;
|
|
32
|
+
}
|
|
33
|
+
/** The API bound to the test scope, and the way back. */
|
|
34
|
+
interface TestI18n extends Translator {
|
|
35
|
+
readonly mode: TestMode;
|
|
36
|
+
/** Restores what the first `createTestI18n` found (the plugin's source-locale default). */
|
|
37
|
+
reset(): void;
|
|
38
|
+
}
|
|
39
|
+
/** Puts the page back as the first `createTestI18n` found it. */
|
|
40
|
+
export declare function resetTestI18n(): void;
|
|
41
|
+
/**
|
|
42
|
+
* Renders every `t()` of the test in `options.locale` and `options.mode`
|
|
43
|
+
* until `reset()` (or the next `createTestI18n`, which replaces it). Without
|
|
44
|
+
* a call, tests under the plugin render the source locale as before.
|
|
45
|
+
*/
|
|
46
|
+
export declare function createTestI18n(options?: TestI18nOptions): TestI18n;
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/test/catalog.d.ts
|
|
49
|
+
interface LoadCatalogOptions {
|
|
50
|
+
/**
|
|
51
|
+
* Where to look for `vi18n.config.ts` (walking up). Default: the project
|
|
52
|
+
* the plugin resolved under Vitest, else the working directory.
|
|
53
|
+
*/
|
|
54
|
+
root?: string;
|
|
55
|
+
/** The config file, relative to `root`. */
|
|
56
|
+
configFile?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The locale's translations, fallbacks applied; untranslated and unusable
|
|
60
|
+
* entries are left out, so those messages render their source text, as in
|
|
61
|
+
* `vite dev`. Memoised per project and locale for the test file.
|
|
62
|
+
*/
|
|
63
|
+
export declare function loadCatalog(locale: string, options?: LoadCatalogOptions): Promise<TestCatalog>;
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/test/pseudo.d.ts
|
|
66
|
+
/**
|
|
67
|
+
* Pseudo-locale checks for browser tests (PLAN §4.14, §5.21; INTERNALS §6
|
|
68
|
+
* D193): `en-XA` and `ar-XB` bracket every rendered message, so on a page
|
|
69
|
+
* switched to one, letters outside any bracket pair are text that never went
|
|
70
|
+
* through `t()`. The page is typed by the two methods used, so Playwright's
|
|
71
|
+
* `Page` fits and nothing here depends on Playwright. This module imports no
|
|
72
|
+
* runtime: the scanners run in the test runner, the page has its own copy.
|
|
73
|
+
*/
|
|
74
|
+
/** What the helpers call on a Playwright `Page` (or anything with the same methods). */
|
|
75
|
+
interface PseudoPage {
|
|
76
|
+
evaluate(fn: (arg: unknown) => unknown, arg?: unknown): Promise<unknown>;
|
|
77
|
+
waitForFunction(fn: (arg: unknown) => unknown, arg?: unknown, options?: {
|
|
78
|
+
timeout?: number;
|
|
79
|
+
}): Promise<unknown>;
|
|
80
|
+
}
|
|
81
|
+
interface PseudoLeak {
|
|
82
|
+
/** The run of text found outside brackets, allowed parts included. */
|
|
83
|
+
text: string;
|
|
84
|
+
/** Up to 10 characters on either side of it, for the failure message. */
|
|
85
|
+
context: string;
|
|
86
|
+
}
|
|
87
|
+
interface PseudoScanOptions {
|
|
88
|
+
/**
|
|
89
|
+
* Text that may legitimately appear outside brackets: user data, brand
|
|
90
|
+
* names. Each occurrence is cut out of a run and the rest is still checked,
|
|
91
|
+
* so allowing `Ann` does not hide the hard-coded `Welcome back,` next to it.
|
|
92
|
+
*/
|
|
93
|
+
allow?: readonly (string | RegExp)[];
|
|
94
|
+
/** `scanPseudo` only: CSS selectors whose text is not checked, beside `[data-pseudo-ignore]`. */
|
|
95
|
+
ignore?: readonly string[];
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Runs of letters at bracket depth 0 in `text`. The depth is per line: page
|
|
99
|
+
* text joins blocks with newlines, and a stray `]` in one block must not hide
|
|
100
|
+
* a leak in the next.
|
|
101
|
+
*/
|
|
102
|
+
export declare function findPseudoLeaks(text: string, options?: PseudoScanOptions): PseudoLeak[];
|
|
103
|
+
/** Text outside pseudo brackets on the page as it is now; empty means clean. */
|
|
104
|
+
export declare function scanPseudo(page: PseudoPage, options?: PseudoScanOptions): Promise<PseudoLeak[]>;
|
|
105
|
+
/**
|
|
106
|
+
* Switches the page to a pseudo-locale through its own vi18n runtime
|
|
107
|
+
* (`setLocale`, in place or by navigation, as the app's routing does it) and
|
|
108
|
+
* waits until `<html lang>` names it and its strings are loaded. Pseudo-locales
|
|
109
|
+
* exist in `vite dev` and non-production builds that list them in
|
|
110
|
+
* `pseudoLocales`.
|
|
111
|
+
*/
|
|
112
|
+
export declare function setPseudoLocale(page: PseudoPage, locale?: "en-XA" | "ar-XB", options?: {
|
|
113
|
+
timeout?: number;
|
|
114
|
+
}): Promise<void>;
|
|
115
|
+
//#endregion
|
|
116
|
+
export type { LoadCatalogOptions, PseudoLeak, PseudoPage, PseudoScanOptions, TestCatalog, TestI18n, TestI18nOptions, TestMode };
|