@zerolith/i18n 0.1.0-alpha.1 → 0.1.0-alpha.10
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/README.md +8 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +66 -8
- package/dist/cli.js.map +1 -1
- package/dist/compile.d.ts +6 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +179 -85
- package/dist/compile.js.map +1 -1
- package/dist/index.d.ts +14 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -14
- package/dist/index.js.map +1 -1
- package/dist/production/compile.js +167 -0
- package/dist/production/compile.js.map +1 -0
- package/dist/production/index.js +2 -0
- package/dist/production/index.js.map +1 -0
- package/package.json +9 -7
- package/src/cli.ts +87 -8
- package/src/compile.ts +203 -86
- package/src/index.ts +43 -16
package/src/index.ts
CHANGED
|
@@ -12,7 +12,9 @@ import { type ReadableSignal, type Signal, computed, effect, signal } from "zero
|
|
|
12
12
|
/** One entry of an `Accept-Language` header or of `navigator.languages`. */
|
|
13
13
|
export interface LanguageRange
|
|
14
14
|
{
|
|
15
|
+
/** The language tag, or `*` for any language, as written before any `;` parameters. */
|
|
15
16
|
readonly range: string;
|
|
17
|
+
/** The entry's `q` weight between 0 and 1, rounded to three decimals; 1 when the entry gives none. */
|
|
16
18
|
readonly quality: number;
|
|
17
19
|
}
|
|
18
20
|
|
|
@@ -67,7 +69,9 @@ function canonical(tag: string): string
|
|
|
67
69
|
* `Accept-Language` header, `navigator.languages`, or one tag. Each range
|
|
68
70
|
* is tried in order of quality, first as an exact match, then by dropping
|
|
69
71
|
* subtags from the end as RFC 4647 lookup does, then as the language of a
|
|
70
|
-
* more specific supported locale
|
|
72
|
+
* more specific supported locale, preferring one written in the same
|
|
73
|
+
* script, so `zh-HK` finds `zh-TW` before `zh-CN`. `*` and no match at all
|
|
74
|
+
* give `fallback`.
|
|
71
75
|
*/
|
|
72
76
|
export function negotiateLocale(requested: string | readonly string[] | null | undefined, supported: readonly string[], fallback: string): string
|
|
73
77
|
{
|
|
@@ -89,18 +93,33 @@ export function negotiateLocale(requested: string | readonly string[] | null | u
|
|
|
89
93
|
}
|
|
90
94
|
}
|
|
91
95
|
const language = wanted.split("-")[0] ?? wanted;
|
|
92
|
-
const related = candidates.
|
|
93
|
-
if (related
|
|
96
|
+
const related = candidates.filter((candidate) => candidate.lower.startsWith(`${language}-`));
|
|
97
|
+
if (related.length > 0)
|
|
94
98
|
{
|
|
95
|
-
|
|
99
|
+
const script = scriptOf(range);
|
|
100
|
+
return (related.find((candidate) => scriptOf(candidate.tag) === script) ?? related[0])?.tag ?? fallback;
|
|
96
101
|
}
|
|
97
102
|
}
|
|
98
103
|
return fallback;
|
|
99
104
|
}
|
|
100
105
|
|
|
101
|
-
|
|
106
|
+
/** The script a locale is written in, with the likely script filled in, as `Hant` for `zh-TW`; undefined for a tag that does not parse. */
|
|
107
|
+
function scriptOf(tag: string): string | undefined
|
|
108
|
+
{
|
|
109
|
+
try
|
|
110
|
+
{
|
|
111
|
+
return new Intl.Locale(tag).maximize().script;
|
|
112
|
+
}
|
|
113
|
+
catch
|
|
114
|
+
{
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
102
118
|
|
|
103
|
-
/** The
|
|
119
|
+
/** The scripts written right to left, by their ISO 15924 codes in lowercase. */
|
|
120
|
+
const RTL_SCRIPTS: ReadonlySet<string> = new Set(["adlm", "arab", "hebr", "mand", "mend", "nkoo", "rohg", "samr", "syrc", "thaa", "yezi"]);
|
|
121
|
+
|
|
122
|
+
/** The writing direction of a locale, from `Intl.Locale` where the engine provides it and otherwise from its script, the likely one when the tag names none, as engines decide. */
|
|
104
123
|
export function localeDirection(locale: string): "ltr" | "rtl"
|
|
105
124
|
{
|
|
106
125
|
try
|
|
@@ -112,13 +131,7 @@ export function localeDirection(locale: string): "ltr" | "rtl"
|
|
|
112
131
|
{
|
|
113
132
|
return info.direction === "rtl" ? "rtl" : "ltr";
|
|
114
133
|
}
|
|
115
|
-
|
|
116
|
-
const script = parsed.script?.toLowerCase();
|
|
117
|
-
if (script === "arab" || script === "hebr" || script === "thaa" || script === "syrc" || script === "nkoo")
|
|
118
|
-
{
|
|
119
|
-
return "rtl";
|
|
120
|
-
}
|
|
121
|
-
return RTL_LANGUAGES.has(language) || RTL_LANGUAGES.has(`${language}-${script ?? ""}`) ? "rtl" : "ltr";
|
|
134
|
+
return RTL_SCRIPTS.has((parsed.maximize().script ?? "").toLowerCase()) ? "rtl" : "ltr";
|
|
122
135
|
}
|
|
123
136
|
catch
|
|
124
137
|
{
|
|
@@ -131,7 +144,7 @@ export interface LocaleOptions
|
|
|
131
144
|
{
|
|
132
145
|
/** The locales the application ships, most preferred first. */
|
|
133
146
|
supported: readonly string[];
|
|
134
|
-
/** The locale used when nothing matches
|
|
147
|
+
/** The locale used when nothing matches, one of `supported`. Defaults to the first supported one. */
|
|
135
148
|
fallback?: string;
|
|
136
149
|
/** What to negotiate from at first: an `Accept-Language` header, `navigator.languages`, or a tag. Defaults to the fallback. */
|
|
137
150
|
initial?: string | readonly string[] | null;
|
|
@@ -144,16 +157,25 @@ export interface LocaleState
|
|
|
144
157
|
readonly locale: ReadableSignal<string>;
|
|
145
158
|
/** The writing direction of the current locale. */
|
|
146
159
|
readonly direction: ReadableSignal<"ltr" | "rtl">;
|
|
160
|
+
/** A copy of the `supported` option, in the same order; `set` negotiates against it. */
|
|
147
161
|
readonly supported: readonly string[];
|
|
148
162
|
/** Switches locales after negotiating the request against `supported`. */
|
|
149
163
|
set(requested: string | readonly string[]): void;
|
|
150
164
|
}
|
|
151
165
|
|
|
152
|
-
/** Creates the locale state for an application. */
|
|
166
|
+
/** Creates the locale state for an application. Throws a `TypeError` when `supported` is empty or `fallback` is not one of its locales, since the locale must always be one of them. */
|
|
153
167
|
export function createLocale(options: LocaleOptions): LocaleState
|
|
154
168
|
{
|
|
155
169
|
const supported = [...options.supported];
|
|
156
|
-
|
|
170
|
+
if (supported.length === 0)
|
|
171
|
+
{
|
|
172
|
+
throw new TypeError("zerolith/i18n: createLocale needs at least one supported locale");
|
|
173
|
+
}
|
|
174
|
+
const fallback = options.fallback === undefined ? (supported[0] ?? "") : supported.find((tag) => canonical(tag).toLowerCase() === canonical(options.fallback ?? "").toLowerCase());
|
|
175
|
+
if (fallback === undefined)
|
|
176
|
+
{
|
|
177
|
+
throw new TypeError(`zerolith/i18n: the fallback ${options.fallback ?? ""} is not one of the supported locales, ${supported.join(", ")}`);
|
|
178
|
+
}
|
|
157
179
|
const locale: Signal<string> = signal(negotiateLocale(options.initial ?? fallback, supported, fallback), { name: "locale" });
|
|
158
180
|
return {
|
|
159
181
|
locale,
|
|
@@ -179,10 +201,15 @@ export function applyLocale(state: LocaleState, doc: Document = document): () =>
|
|
|
179
201
|
/** Formatters for the current locale; each reads the locale signal when called, so a computed value or template re-renders on a locale change. */
|
|
180
202
|
export interface Formatters
|
|
181
203
|
{
|
|
204
|
+
/** Formats a number with `Intl.NumberFormat`. */
|
|
182
205
|
number(value: number, options?: Intl.NumberFormatOptions): string;
|
|
206
|
+
/** Formats a `Date`, or a timestamp in milliseconds since the epoch, with `Intl.DateTimeFormat`. */
|
|
183
207
|
date(value: Date | number, options?: Intl.DateTimeFormatOptions): string;
|
|
208
|
+
/** Formats a time `value` units of `unit` away from now with `Intl.RelativeTimeFormat`, such as `in 2 hours`; a negative value lies in the past. */
|
|
184
209
|
relative(value: number, unit: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions): string;
|
|
210
|
+
/** Joins strings into a list with `Intl.ListFormat`, such as `a, b, and c` in English. */
|
|
185
211
|
list(values: Iterable<string>, options?: Intl.ListFormatOptions): string;
|
|
212
|
+
/** Selects the plural category of a number with `Intl.PluralRules`, such as `one` or `other`, for choosing a message variant. */
|
|
186
213
|
plural(value: number, options?: Intl.PluralRulesOptions): Intl.LDMLPluralRule;
|
|
187
214
|
}
|
|
188
215
|
|