@echojs-ecosystem/i18n 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/README.md ADDED
@@ -0,0 +1,69 @@
1
+ <div align="center">
2
+
3
+ # @echojs-ecosystem/i18n
4
+
5
+ **Signal-native internationalization with typed message keys and Intl helpers.**
6
+
7
+ [![npm](https://img.shields.io/npm/v/@echojs-ecosystem/i18n)](https://www.npmjs.com/package/@echojs-ecosystem/i18n)
8
+ [![docs](https://img.shields.io/badge/docs-echojs.dev-blue)](https://echojs.dev/docs/packages/i18n)
9
+
10
+ </div>
11
+
12
+ ---
13
+
14
+ Lightweight i18n for EchoJS. **Locale is a signal** — `t()` inside `computed` / `effect` re-runs when the locale changes. Works in browser, SSR, and Node.js.
15
+
16
+ ## Features
17
+
18
+ - **Typed keys** — `TranslationKey` inferred from your JSON locale files
19
+ - **Eager + lazy locales** — inline object or dynamic `import()`
20
+ - **Interpolation & plural** — `{name}`, `{count}` in messages
21
+ - **Intl helpers** — `number`, `currency`, `date`, `relativeTime`
22
+ - **Framework-agnostic** — no hooks, no VDOM
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ npm install @echojs-ecosystem/i18n @echojs-ecosystem/reactivity
28
+ ```
29
+
30
+ ## Quick start
31
+
32
+ ```ts
33
+ import { createI18n } from "@echojs-ecosystem/i18n";
34
+ import ru from "./locales/ru.json";
35
+
36
+ const i18n = createI18n({
37
+ defaultLocale: "ru",
38
+ fallbackLocale: "en",
39
+ locales: {
40
+ ru,
41
+ en: () => import("./locales/en.json"),
42
+ },
43
+ });
44
+
45
+ i18n.t("common.save"); // typed key
46
+ i18n.t("greeting", { name: "Vova" });
47
+ i18n.t("items", { count: 5 }); // plural
48
+
49
+ await i18n.setLocale("en");
50
+ i18n.$locale.value(); // reactive
51
+ ```
52
+
53
+ ## API
54
+
55
+ | Export | Description |
56
+ |--------|-------------|
57
+ | `createI18n` | Factory |
58
+ | `i18n.t(key, params?)` | Translate |
59
+ | `i18n.setLocale` / `i18n.loadLocale` | Switch & lazy-load |
60
+ | `i18n.number` / `currency` / `date` / `relativeTime` | Intl formatting |
61
+ | `TranslationKey` | Type helper for nested keys |
62
+
63
+ ## Provider (apps)
64
+
65
+ Use `createI18nProvider` with [`@echojs-ecosystem/framework`](https://www.npmjs.com/package/@echojs-ecosystem/framework) to inject `i18n` into views.
66
+
67
+ ## Documentation
68
+
69
+ [echojs.dev/docs/packages/i18n](https://echojs.dev/docs/packages/i18n)
@@ -0,0 +1,127 @@
1
+ import { Signal } from '@echojs-ecosystem/reactivity';
2
+
3
+ type PluralShape = Partial<Record<"one" | "few" | "many" | "other", string>>;
4
+ type IsPluralLeaf<T> = T extends PluralShape ? keyof T extends keyof PluralMessages | undefined ? true : false : false;
5
+ /** Leaf values addressable by `t("a.b.c")`. */
6
+ type IsMessageLeaf<T> = T extends string ? true : IsPluralLeaf<T>;
7
+ /**
8
+ * Dot-separated paths to translatable leaves in a nested message tree.
9
+ *
10
+ * @example
11
+ * type Keys = TranslationKey<{ common: { save: string } }>; // "common.save"
12
+ */
13
+ type TranslationKey<T, Prefix extends string = ""> = IsMessageLeaf<T> extends true ? Prefix extends "" ? never : Prefix : T extends Record<string, unknown> ? {
14
+ [K in keyof T & string]: TranslationKey<T[K], Prefix extends "" ? K : `${Prefix}.${K}`>;
15
+ }[keyof T & string] : never;
16
+
17
+ /** Nested message tree (string leaves and optional plural buckets). */
18
+ type MessageSchema = Record<string, unknown>;
19
+ type Messages = MessageSchema;
20
+ type LocaleMessages = Record<string, Messages>;
21
+ type MissingKeyStrategy = "key" | "empty";
22
+ type LocaleModuleResult<T extends MessageSchema = MessageSchema> = T | {
23
+ default: T;
24
+ };
25
+ /** Dynamic import of a locale module (e.g. `() => import("./en.json")`). */
26
+ type LocaleImporter<T extends MessageSchema = MessageSchema> = () => Promise<LocaleModuleResult<T>>;
27
+ /** Eager messages object or lazy module import. */
28
+ type LocaleSource<T extends MessageSchema = MessageSchema> = T | LocaleImporter<T>;
29
+ type LocalesMap<TLocale extends string, TMessages extends MessageSchema> = Record<TLocale, LocaleSource<TMessages>>;
30
+ /** Any `locales` map accepted by `createI18n` (used for inference). */
31
+ type AnyLocalesMap = Record<string, LocaleSource<MessageSchema>>;
32
+ /** Message schema from eager entries in `locales` (e.g. `typeof ru`). */
33
+ type InferMessagesFromLocalesMap<TLocales extends AnyLocalesMap> = Extract<TLocales[keyof TLocales], MessageSchema> extends infer E ? E extends MessageSchema ? E : MessageSchema : MessageSchema;
34
+ type InferLocaleFromLocalesMap<TLocales extends AnyLocalesMap> = keyof TLocales & string;
35
+ type CreateI18nOptions<TLocales extends AnyLocalesMap> = {
36
+ /**
37
+ * Required unless browser options are set (`storageKey`, `navigatorRules`, or `documentTitleKey`).
38
+ * Then the initial locale is detected automatically.
39
+ */
40
+ defaultLocale?: InferLocaleFromLocalesMap<TLocales>;
41
+ fallbackLocale: InferLocaleFromLocalesMap<TLocales>;
42
+ locales: TLocales;
43
+ missingKeyStrategy?: MissingKeyStrategy;
44
+ /** Persist / restore locale in `localStorage`. Enables auto-detect when set. */
45
+ storageKey?: string;
46
+ navigatorRules?: ReadonlyArray<{
47
+ prefix: string;
48
+ locale: InferLocaleFromLocalesMap<TLocales>;
49
+ }>;
50
+ /** Sync `document.documentElement.lang` (default `true` when browser options are used). */
51
+ syncDocument?: boolean;
52
+ documentTitleKey?: TranslationKey<InferMessagesFromLocalesMap<TLocales>>;
53
+ };
54
+ /** @deprecated Prefer `CreateI18nOptions` with inferred `locales` */
55
+ type CreateI18nOptionsLegacy<TLocale extends string, TMessages extends MessageSchema> = {
56
+ defaultLocale: TLocale;
57
+ fallbackLocale: TLocale;
58
+ locales: LocalesMap<TLocale, TMessages>;
59
+ missingKeyStrategy?: MissingKeyStrategy;
60
+ };
61
+ type I18n<TLocale extends string = string, TMessages extends MessageSchema = MessageSchema> = {
62
+ /** Locale ids declared in `createI18n({ locales })`. */
63
+ readonly supportedLocales: readonly TLocale[];
64
+ locale(): TLocale;
65
+ setLocale(locale: TLocale): Promise<void>;
66
+ fallbackLocale(): TLocale;
67
+ t(key: TranslationKey<TMessages>, params?: Record<string, unknown>): string;
68
+ exists(key: TranslationKey<TMessages>): boolean;
69
+ addMessages(locale: TLocale, messages: Partial<TMessages> | Messages): void;
70
+ removeLocale(locale: TLocale): void;
71
+ loadLocale(locale: TLocale): Promise<void>;
72
+ number(value: number, options?: Intl.NumberFormatOptions): string;
73
+ currency(value: number, currency: string, options?: Intl.NumberFormatOptions): string;
74
+ date(value: Date, options?: Intl.DateTimeFormatOptions): string;
75
+ relativeTime(value: number, unit: Intl.RelativeTimeFormatUnit): string;
76
+ readonly $locale: Signal<TLocale>;
77
+ readonly $pending: Signal<boolean>;
78
+ readonly $error: Signal<unknown | null>;
79
+ /**
80
+ * Detect locale, sync document/storage, register effects.
81
+ * Lazy locale chunks load in the background — does not block app mount.
82
+ */
83
+ start(): Promise<void>;
84
+ };
85
+ /** @deprecated Use `CreateI18nOptions` */
86
+ type I18nOptions<TLocale extends string = string, TMessages extends MessageSchema = MessageSchema> = CreateI18nOptionsLegacy<TLocale, TMessages>;
87
+ /** Plural message buckets keyed by Intl plural category. */
88
+ type PluralMessages = Partial<Record<"one" | "few" | "many" | "other", string>>;
89
+ /** @deprecated Use `LocaleImporter` */
90
+ type LocaleLoader<T extends MessageSchema = MessageSchema> = LocaleImporter<T>;
91
+ /** @deprecated Use `LocaleModuleResult` */
92
+ type LocaleLoaderResult<T extends MessageSchema = MessageSchema> = LocaleModuleResult<T>;
93
+ /** @deprecated Use `LocalesMap` */
94
+ type LocaleLoaders<TLocale extends string = string, TMessages extends MessageSchema = MessageSchema> = LocalesMap<TLocale, TMessages>;
95
+
96
+ declare function createI18n<const TLocales extends AnyLocalesMap>(options: CreateI18nOptions<TLocales>): I18n<InferLocaleFromLocalesMap<TLocales>, InferMessagesFromLocalesMap<TLocales>>;
97
+
98
+ /** Echo app provider with the active {@link I18n} instance. */
99
+ type EchoI18nProvider<TLocale extends string = string, TMessages extends Messages = Messages> = {
100
+ name: "i18n";
101
+ readonly i18n: I18n<TLocale, TMessages>;
102
+ setup: () => void | Promise<void>;
103
+ };
104
+ /**
105
+ * Creates i18n and an Echo app provider (`setup` runs {@link I18n.start}).
106
+ *
107
+ * ```ts
108
+ * export const i18nProvider = createI18nProvider({ locales: { en, ru } });
109
+ * app.use(i18nProvider);
110
+ * i18nProvider.i18n.t("key");
111
+ * ```
112
+ */
113
+ declare function createI18nProvider<const TLocales extends AnyLocalesMap>(options: CreateI18nOptions<TLocales>): EchoI18nProvider<InferLocaleFromLocalesMap<TLocales>, InferMessagesFromLocalesMap<TLocales>>;
114
+
115
+ type DetectLocaleOptions<TLocale extends string> = {
116
+ supported: readonly TLocale[];
117
+ fallback: TLocale;
118
+ storageKey?: string;
119
+ /** e.g. `{ prefix: "ru", locale: "ru" }` when `navigator.language` matches. */
120
+ navigatorRules?: ReadonlyArray<{
121
+ prefix: string;
122
+ locale: TLocale;
123
+ }>;
124
+ };
125
+ declare const detectLocale: <TLocale extends string>(options: DetectLocaleOptions<TLocale>) => TLocale;
126
+
127
+ export { type AnyLocalesMap, type CreateI18nOptions, type CreateI18nOptionsLegacy, type EchoI18nProvider, type I18n, type I18nOptions, type InferLocaleFromLocalesMap, type InferMessagesFromLocalesMap, type LocaleImporter, type LocaleLoader, type LocaleLoaderResult, type LocaleLoaders, type LocaleMessages, type LocaleModuleResult, type LocaleSource, type LocalesMap, type MessageSchema, type Messages, type MissingKeyStrategy, type PluralMessages, type TranslationKey, createI18n, createI18nProvider, detectLocale };
package/dist/index.js ADDED
@@ -0,0 +1,363 @@
1
+ import { effect, signal } from '@echojs-ecosystem/reactivity';
2
+
3
+ // src/core/messages.ts
4
+ var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5
+ var deepMerge = (target, source) => {
6
+ const result = { ...target };
7
+ for (const key of Object.keys(source)) {
8
+ const sourceValue = source[key];
9
+ const targetValue = result[key];
10
+ if (isPlainObject(sourceValue) && isPlainObject(targetValue)) {
11
+ result[key] = deepMerge(targetValue, sourceValue);
12
+ } else {
13
+ result[key] = sourceValue;
14
+ }
15
+ }
16
+ return result;
17
+ };
18
+ var cloneLocaleMessages = (messages) => {
19
+ const clone = {};
20
+ for (const locale of Object.keys(messages)) {
21
+ const localeMessages = messages[locale];
22
+ if (localeMessages !== void 0) {
23
+ clone[locale] = deepMerge({}, localeMessages);
24
+ }
25
+ }
26
+ return clone;
27
+ };
28
+ var ensureLocaleBucket = (store, locale) => {
29
+ const existing = store[locale];
30
+ if (existing !== void 0) {
31
+ return existing;
32
+ }
33
+ const bucket = {};
34
+ store[locale] = bucket;
35
+ return bucket;
36
+ };
37
+
38
+ // src/core/path.ts
39
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
40
+ var getMessageByPath = (messages, path) => {
41
+ if (!messages || path.length === 0) {
42
+ return void 0;
43
+ }
44
+ const segments = path.split(".");
45
+ let current = messages;
46
+ for (const segment of segments) {
47
+ if (!isRecord(current) || !(segment in current)) {
48
+ return void 0;
49
+ }
50
+ current = current[segment];
51
+ }
52
+ return current;
53
+ };
54
+
55
+ // src/core/fallback.ts
56
+ var resolveMessageValue = (store, locale, fallbackLocale, key) => {
57
+ const primary = getMessageByPath(store[locale], key);
58
+ if (primary !== void 0) {
59
+ return primary;
60
+ }
61
+ if (locale === fallbackLocale) {
62
+ return void 0;
63
+ }
64
+ return getMessageByPath(store[fallbackLocale], key);
65
+ };
66
+ var hasMessage = (store, locale, fallbackLocale, key) => resolveMessageValue(store, locale, fallbackLocale, key) !== void 0;
67
+
68
+ // src/core/formatter.ts
69
+ var formatNumber = (locale, value, options) => new Intl.NumberFormat(locale, options).format(value);
70
+ var formatCurrency = (locale, value, currency, options) => new Intl.NumberFormat(locale, {
71
+ ...options,
72
+ style: "currency",
73
+ currency
74
+ }).format(value);
75
+ var formatDate = (locale, value, options) => new Intl.DateTimeFormat(locale, options).format(value);
76
+ var formatRelativeTime = (locale, value, unit) => new Intl.RelativeTimeFormat(locale).format(value, unit);
77
+
78
+ // src/core/locale-source.ts
79
+ var isLocaleImporter = (source) => typeof source === "function";
80
+ var hasDefaultExport = (result) => typeof result === "object" && result !== null && "default" in result && result.default !== void 0 && typeof result.default === "object" && result.default !== null && !Array.isArray(result.default);
81
+ var resolveLocaleModule = async (source) => {
82
+ if (!isLocaleImporter(source)) {
83
+ return source;
84
+ }
85
+ const result = await source();
86
+ if (hasDefaultExport(result)) {
87
+ return result.default;
88
+ }
89
+ return result;
90
+ };
91
+ var resolveLocaleModuleSync = (source) => isLocaleImporter(source) ? void 0 : source;
92
+
93
+ // src/plugin/detect-locale.ts
94
+ var detectLocale = (options) => {
95
+ const { supported, fallback, storageKey, navigatorRules } = options;
96
+ const isSupported = (value) => supported.includes(value);
97
+ if (storageKey && typeof localStorage !== "undefined") {
98
+ const stored = localStorage.getItem(storageKey);
99
+ if (stored && isSupported(stored)) {
100
+ return stored;
101
+ }
102
+ }
103
+ if (navigatorRules?.length && typeof navigator !== "undefined") {
104
+ const lang = navigator.language.toLowerCase();
105
+ for (const rule of navigatorRules) {
106
+ if (lang.startsWith(rule.prefix)) {
107
+ return rule.locale;
108
+ }
109
+ }
110
+ }
111
+ return fallback;
112
+ };
113
+ var createLocaleState = (initialLocale, fallbackLocale) => ({
114
+ $locale: signal(initialLocale),
115
+ $pending: signal(false),
116
+ $error: signal(null),
117
+ fallbackLocale
118
+ });
119
+
120
+ // src/core/interpolation.ts
121
+ var PLACEHOLDER_PATTERN = /\{([^{}]+)\}/g;
122
+ var interpolate = (template, params) => {
123
+ if (!params) {
124
+ return template;
125
+ }
126
+ return template.replace(PLACEHOLDER_PATTERN, (match, rawKey) => {
127
+ const key = rawKey.trim();
128
+ if (!(key in params)) {
129
+ return match;
130
+ }
131
+ const value = params[key];
132
+ if (value === null || value === void 0) {
133
+ return match;
134
+ }
135
+ return String(value);
136
+ });
137
+ };
138
+
139
+ // src/core/plural.ts
140
+ var PLURAL_CATEGORIES = ["one", "few", "many", "other"];
141
+ var isPluralMessages = (value) => {
142
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
143
+ return false;
144
+ }
145
+ const record = value;
146
+ return PLURAL_CATEGORIES.some((category) => typeof record[category] === "string");
147
+ };
148
+ var pickPluralTemplate = (messages, category) => {
149
+ const direct = category in messages ? messages[category] : void 0;
150
+ if (typeof direct === "string") {
151
+ return direct;
152
+ }
153
+ if (category !== "other" && typeof messages.other === "string") {
154
+ return messages.other;
155
+ }
156
+ for (const fallbackCategory of PLURAL_CATEGORIES) {
157
+ const template = messages[fallbackCategory];
158
+ if (typeof template === "string") {
159
+ return template;
160
+ }
161
+ }
162
+ return void 0;
163
+ };
164
+ var resolvePluralMessage = (value, locale, params) => {
165
+ if (!isPluralMessages(value)) {
166
+ return void 0;
167
+ }
168
+ const count = params?.count;
169
+ if (typeof count !== "number" || !Number.isFinite(count)) {
170
+ return void 0;
171
+ }
172
+ const rules = new Intl.PluralRules(locale);
173
+ const category = rules.select(count);
174
+ const template = pickPluralTemplate(value, category);
175
+ if (template === void 0) {
176
+ return void 0;
177
+ }
178
+ return interpolate(template, { ...params, count });
179
+ };
180
+
181
+ // src/core/translator.ts
182
+ var formatMissingKey = (key, strategy) => strategy === "empty" ? "" : key;
183
+ var createTranslator = (context) => {
184
+ const translate = (key, params) => {
185
+ const locale = context.getLocale();
186
+ const resolved = resolveMessageValue(
187
+ context.messages,
188
+ locale,
189
+ context.fallbackLocale,
190
+ key
191
+ );
192
+ if (resolved === void 0) {
193
+ return formatMissingKey(key, context.missingKeyStrategy);
194
+ }
195
+ const plural = resolvePluralMessage(resolved, locale, params);
196
+ if (plural !== void 0) {
197
+ return plural;
198
+ }
199
+ if (typeof resolved === "string") {
200
+ return interpolate(resolved, params);
201
+ }
202
+ return formatMissingKey(key, context.missingKeyStrategy);
203
+ };
204
+ return { translate };
205
+ };
206
+
207
+ // src/create-i18n.ts
208
+ var registerEagerLocales = (locales, messageStore, loadedLocales) => {
209
+ for (const locale of Object.keys(locales)) {
210
+ const source = locales[locale];
211
+ if (source === void 0) {
212
+ continue;
213
+ }
214
+ const eager = resolveLocaleModuleSync(source);
215
+ if (eager !== void 0) {
216
+ messageStore[locale] = deepMerge({}, eager);
217
+ loadedLocales.add(locale);
218
+ }
219
+ }
220
+ };
221
+ var hasBrowserOptions = (options) => options.storageKey !== void 0 || options.navigatorRules !== void 0 || options.documentTitleKey !== void 0;
222
+ function createI18n(options) {
223
+ const {
224
+ defaultLocale: defaultLocaleOption,
225
+ fallbackLocale,
226
+ locales,
227
+ missingKeyStrategy = "key",
228
+ storageKey,
229
+ navigatorRules,
230
+ syncDocument = true,
231
+ documentTitleKey
232
+ } = options;
233
+ const browser = hasBrowserOptions(options);
234
+ const supportedLocales = Object.keys(locales);
235
+ const resolveDetectedLocale = () => detectLocale({
236
+ supported: supportedLocales,
237
+ fallback: fallbackLocale,
238
+ storageKey,
239
+ navigatorRules
240
+ });
241
+ const defaultLocale = defaultLocaleOption ?? (browser ? resolveDetectedLocale() : void 0);
242
+ if (defaultLocale === void 0) {
243
+ throw new Error(
244
+ "createI18n: `defaultLocale` is required when browser options are not set"
245
+ );
246
+ }
247
+ if (!supportedLocales.includes(defaultLocale)) {
248
+ throw new RangeError(
249
+ `createI18n: defaultLocale "${String(defaultLocale)}" is not listed in locales`
250
+ );
251
+ }
252
+ if (!supportedLocales.includes(fallbackLocale)) {
253
+ throw new RangeError(
254
+ `createI18n: fallbackLocale "${String(fallbackLocale)}" is not listed in locales`
255
+ );
256
+ }
257
+ const state = createLocaleState(defaultLocale, fallbackLocale);
258
+ const messageStore = cloneLocaleMessages({});
259
+ const loadedLocales = /* @__PURE__ */ new Set();
260
+ registerEagerLocales(locales, messageStore, loadedLocales);
261
+ const { translate } = createTranslator({
262
+ getLocale: () => state.$locale.value(),
263
+ fallbackLocale,
264
+ messages: messageStore,
265
+ missingKeyStrategy
266
+ });
267
+ const addMessages = (locale, messages) => {
268
+ const bucket = ensureLocaleBucket(messageStore, locale);
269
+ messageStore[locale] = deepMerge(bucket, messages);
270
+ loadedLocales.add(locale);
271
+ };
272
+ const loadLocale = async (locale) => {
273
+ if (loadedLocales.has(locale)) {
274
+ return;
275
+ }
276
+ const source = locales[locale];
277
+ if (!source) {
278
+ return;
279
+ }
280
+ if (!isLocaleImporter(source)) {
281
+ addMessages(locale, resolveLocaleModuleSync(source));
282
+ return;
283
+ }
284
+ state.$pending.set(true);
285
+ state.$error.set(null);
286
+ try {
287
+ const messages = await resolveLocaleModule(source);
288
+ addMessages(locale, messages);
289
+ } catch (error) {
290
+ state.$error.set(error);
291
+ throw error;
292
+ } finally {
293
+ state.$pending.set(false);
294
+ }
295
+ };
296
+ const setLocale = async (locale) => {
297
+ const source = locales[locale];
298
+ if (!loadedLocales.has(locale) && source !== void 0 && isLocaleImporter(source)) {
299
+ await loadLocale(locale);
300
+ }
301
+ state.$locale.set(locale);
302
+ };
303
+ const start = () => {
304
+ if (!browser) {
305
+ return Promise.resolve();
306
+ }
307
+ const locale = resolveDetectedLocale();
308
+ state.$locale.set(locale);
309
+ effect(() => {
310
+ const current = state.$locale.value();
311
+ if (typeof document !== "undefined" && syncDocument) {
312
+ document.documentElement.lang = current;
313
+ if (documentTitleKey) {
314
+ document.title = translate(documentTitleKey);
315
+ }
316
+ }
317
+ if (storageKey && typeof localStorage !== "undefined") {
318
+ localStorage.setItem(storageKey, current);
319
+ }
320
+ });
321
+ const source = locales[locale];
322
+ if (!loadedLocales.has(locale) && source !== void 0 && isLocaleImporter(source)) {
323
+ void loadLocale(locale);
324
+ }
325
+ return Promise.resolve();
326
+ };
327
+ return {
328
+ supportedLocales,
329
+ locale: () => state.$locale.peek(),
330
+ setLocale,
331
+ start,
332
+ fallbackLocale: () => fallbackLocale,
333
+ t: translate,
334
+ exists: (key) => hasMessage(messageStore, state.$locale.peek(), fallbackLocale, key),
335
+ addMessages,
336
+ removeLocale: (locale) => {
337
+ delete messageStore[locale];
338
+ loadedLocales.delete(locale);
339
+ },
340
+ loadLocale,
341
+ number: (value, formatOptions) => formatNumber(state.$locale.value(), value, formatOptions),
342
+ currency: (value, currencyCode, formatOptions) => formatCurrency(state.$locale.value(), value, currencyCode, formatOptions),
343
+ date: (value, formatOptions) => formatDate(state.$locale.value(), value, formatOptions),
344
+ relativeTime: (value, unit) => formatRelativeTime(state.$locale.value(), value, unit),
345
+ $locale: state.$locale,
346
+ $pending: state.$pending,
347
+ $error: state.$error
348
+ };
349
+ }
350
+
351
+ // src/plugin/i18n-provider.ts
352
+ function createI18nProvider(options) {
353
+ const i18n = createI18n(options);
354
+ return {
355
+ name: "i18n",
356
+ i18n,
357
+ setup: () => i18n.start()
358
+ };
359
+ }
360
+
361
+ export { createI18n, createI18nProvider, detectLocale };
362
+ //# sourceMappingURL=index.js.map
363
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/messages.ts","../src/core/path.ts","../src/core/fallback.ts","../src/core/formatter.ts","../src/core/locale-source.ts","../src/plugin/detect-locale.ts","../src/core/locale.ts","../src/core/interpolation.ts","../src/core/plural.ts","../src/core/translator.ts","../src/create-i18n.ts","../src/plugin/i18n-provider.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { LocaleMessages, Messages } from \"../types\";\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nexport const deepMerge = (\n target: Messages,\n source: Messages,\n): Messages => {\n const result: Messages = { ...target };\n\n for (const key of Object.keys(source)) {\n const sourceValue = source[key];\n const targetValue = result[key];\n\n if (isPlainObject(sourceValue) && isPlainObject(targetValue)) {\n result[key] = deepMerge(targetValue, sourceValue);\n } else {\n result[key] = sourceValue;\n }\n }\n\n return result;\n};\n\nexport const cloneLocaleMessages = (\n messages: LocaleMessages,\n): LocaleMessages => {\n const clone: LocaleMessages = {};\n\n for (const locale of Object.keys(messages)) {\n const localeMessages = messages[locale];\n if (localeMessages !== undefined) {\n clone[locale] = deepMerge({}, localeMessages);\n }\n }\n\n return clone;\n};\n\nexport const ensureLocaleBucket = (\n store: LocaleMessages,\n locale: string,\n): Messages => {\n const existing = store[locale];\n if (existing !== undefined) {\n return existing;\n }\n\n const bucket: Messages = {};\n store[locale] = bucket;\n return bucket;\n};\n","import type { Messages } from \"../types\";\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\n/**\n * Resolves a dot-separated path against nested message objects.\n */\nexport const getMessageByPath = (\n messages: Messages | undefined,\n path: string,\n): unknown => {\n if (!messages || path.length === 0) {\n return undefined;\n }\n\n const segments = path.split(\".\");\n let current: unknown = messages;\n\n for (const segment of segments) {\n if (!isRecord(current) || !(segment in current)) {\n return undefined;\n }\n current = current[segment];\n }\n\n return current;\n};\n","import { getMessageByPath } from \"./path\";\nimport type { LocaleMessages } from \"../types\";\n\nexport const resolveMessageValue = (\n store: LocaleMessages,\n locale: string,\n fallbackLocale: string,\n key: string,\n): unknown => {\n const primary = getMessageByPath(store[locale], key);\n if (primary !== undefined) {\n return primary;\n }\n\n if (locale === fallbackLocale) {\n return undefined;\n }\n\n return getMessageByPath(store[fallbackLocale], key);\n};\n\nexport const hasMessage = (\n store: LocaleMessages,\n locale: string,\n fallbackLocale: string,\n key: string,\n): boolean => resolveMessageValue(store, locale, fallbackLocale, key) !== undefined;\n","export const formatNumber = (\n locale: string,\n value: number,\n options?: Intl.NumberFormatOptions,\n): string => new Intl.NumberFormat(locale, options).format(value);\n\nexport const formatCurrency = (\n locale: string,\n value: number,\n currency: string,\n options?: Intl.NumberFormatOptions,\n): string =>\n new Intl.NumberFormat(locale, {\n ...options,\n style: \"currency\",\n currency,\n }).format(value);\n\nexport const formatDate = (\n locale: string,\n value: Date,\n options?: Intl.DateTimeFormatOptions,\n): string => new Intl.DateTimeFormat(locale, options).format(value);\n\nexport const formatRelativeTime = (\n locale: string,\n value: number,\n unit: Intl.RelativeTimeFormatUnit,\n): string => new Intl.RelativeTimeFormat(locale).format(value, unit);\n","import type { LocaleImporter, LocaleModuleResult, LocaleSource, Messages } from \"../types\";\n\nexport const isLocaleImporter = <T extends Messages>(\n source: LocaleSource<T>,\n): source is LocaleImporter<T> => typeof source === \"function\";\n\nconst hasDefaultExport = <T extends Messages>(\n result: LocaleModuleResult<T>,\n): result is { default: T } =>\n typeof result === \"object\" &&\n result !== null &&\n \"default\" in result &&\n result.default !== undefined &&\n typeof result.default === \"object\" &&\n result.default !== null &&\n !Array.isArray(result.default);\n\nexport const resolveLocaleModule = async <T extends Messages>(\n source: LocaleSource<T>,\n): Promise<T> => {\n if (!isLocaleImporter(source)) {\n return source;\n }\n\n const result = await source();\n if (hasDefaultExport(result)) {\n return result.default;\n }\n\n return result;\n};\n\nexport const resolveLocaleModuleSync = <T extends Messages>(\n source: LocaleSource<T>,\n): T | undefined => (isLocaleImporter(source) ? undefined : source);\n","export type DetectLocaleOptions<TLocale extends string> = {\n supported: readonly TLocale[];\n fallback: TLocale;\n storageKey?: string;\n /** e.g. `{ prefix: \"ru\", locale: \"ru\" }` when `navigator.language` matches. */\n navigatorRules?: ReadonlyArray<{ prefix: string; locale: TLocale }>;\n};\n\nexport const detectLocale = <TLocale extends string>(\n options: DetectLocaleOptions<TLocale>,\n): TLocale => {\n const { supported, fallback, storageKey, navigatorRules } = options;\n const isSupported = (value: string): value is TLocale =>\n (supported as readonly string[]).includes(value);\n\n if (storageKey && typeof localStorage !== \"undefined\") {\n const stored = localStorage.getItem(storageKey);\n if (stored && isSupported(stored)) {\n return stored;\n }\n }\n\n if (navigatorRules?.length && typeof navigator !== \"undefined\") {\n const lang = navigator.language.toLowerCase();\n for (const rule of navigatorRules) {\n if (lang.startsWith(rule.prefix)) {\n return rule.locale;\n }\n }\n }\n\n return fallback;\n};\n","import { signal } from \"@echojs-ecosystem/reactivity\";\nimport type { Signal } from \"@echojs-ecosystem/reactivity\";\n\nexport type LocaleState<TLocale extends string = string> = {\n readonly $locale: Signal<TLocale>;\n readonly $pending: Signal<boolean>;\n readonly $error: Signal<unknown | null>;\n readonly fallbackLocale: TLocale;\n};\n\nexport const createLocaleState = <TLocale extends string>(\n initialLocale: TLocale,\n fallbackLocale: TLocale,\n): LocaleState<TLocale> => ({\n $locale: signal(initialLocale),\n $pending: signal(false),\n $error: signal<unknown | null>(null),\n fallbackLocale,\n});\n","const PLACEHOLDER_PATTERN = /\\{([^{}]+)\\}/g;\n\nexport const interpolate = (\n template: string,\n params: Record<string, unknown> | undefined,\n): string => {\n if (!params) {\n return template;\n }\n\n return template.replace(PLACEHOLDER_PATTERN, (match, rawKey: string) => {\n const key = rawKey.trim();\n if (!(key in params)) {\n return match;\n }\n\n const value = params[key];\n if (value === null || value === undefined) {\n return match;\n }\n\n return String(value);\n });\n};\n","import { interpolate } from \"./interpolation\";\nimport type { PluralMessages } from \"../types\";\n\nconst PLURAL_CATEGORIES = [\"one\", \"few\", \"many\", \"other\"] as const;\n\nconst isPluralMessages = (value: unknown): value is PluralMessages => {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return false;\n }\n\n const record = value as Record<string, unknown>;\n return PLURAL_CATEGORIES.some((category) => typeof record[category] === \"string\");\n};\n\nconst pickPluralTemplate = (\n messages: PluralMessages,\n category: Intl.LDMLPluralRule,\n): string | undefined => {\n const direct =\n category in messages\n ? messages[category as keyof PluralMessages]\n : undefined;\n if (typeof direct === \"string\") {\n return direct;\n }\n\n if (category !== \"other\" && typeof messages.other === \"string\") {\n return messages.other;\n }\n\n for (const fallbackCategory of PLURAL_CATEGORIES) {\n const template = messages[fallbackCategory];\n if (typeof template === \"string\") {\n return template;\n }\n }\n\n return undefined;\n};\n\nexport const resolvePluralMessage = (\n value: unknown,\n locale: string,\n params: Record<string, unknown> | undefined,\n): string | undefined => {\n if (!isPluralMessages(value)) {\n return undefined;\n }\n\n const count = params?.count;\n if (typeof count !== \"number\" || !Number.isFinite(count)) {\n return undefined;\n }\n\n const rules = new Intl.PluralRules(locale);\n const category = rules.select(count);\n const template = pickPluralTemplate(value, category);\n\n if (template === undefined) {\n return undefined;\n }\n\n return interpolate(template, { ...params, count });\n};\n","import { resolveMessageValue } from \"./fallback\";\nimport { interpolate } from \"./interpolation\";\nimport { resolvePluralMessage } from \"./plural\";\nimport type { LocaleMessages, MissingKeyStrategy } from \"../types\";\n\nexport type TranslateContext = {\n getLocale: () => string;\n fallbackLocale: string;\n messages: LocaleMessages;\n missingKeyStrategy: MissingKeyStrategy;\n};\n\nconst formatMissingKey = (\n key: string,\n strategy: MissingKeyStrategy,\n): string => (strategy === \"empty\" ? \"\" : key);\n\nexport const createTranslator = (context: TranslateContext) => {\n const translate = (\n key: string,\n params?: Record<string, unknown>,\n ): string => {\n const locale = context.getLocale();\n const resolved = resolveMessageValue(\n context.messages,\n locale,\n context.fallbackLocale,\n key,\n );\n\n if (resolved === undefined) {\n return formatMissingKey(key, context.missingKeyStrategy);\n }\n\n const plural = resolvePluralMessage(resolved, locale, params);\n if (plural !== undefined) {\n return plural;\n }\n\n if (typeof resolved === \"string\") {\n return interpolate(resolved, params);\n }\n\n return formatMissingKey(key, context.missingKeyStrategy);\n };\n\n return { translate };\n};\n","import {\n cloneLocaleMessages,\n deepMerge,\n ensureLocaleBucket,\n} from \"./core/messages\";\nimport { hasMessage } from \"./core/fallback\";\nimport {\n formatCurrency,\n formatDate,\n formatNumber,\n formatRelativeTime,\n} from \"./core/formatter\";\nimport {\n isLocaleImporter,\n resolveLocaleModule,\n resolveLocaleModuleSync,\n} from \"./core/locale-source\";\nimport { detectLocale } from \"./plugin/detect-locale\";\nimport { effect } from \"@echojs-ecosystem/reactivity\";\nimport { createLocaleState } from \"./core/locale\";\nimport { createTranslator } from \"./core/translator\";\nimport type {\n AnyLocalesMap,\n CreateI18nOptions,\n I18n,\n InferLocaleFromLocalesMap,\n InferMessagesFromLocalesMap,\n Messages,\n} from \"./types\";\n\nconst registerEagerLocales = (\n locales: AnyLocalesMap,\n messageStore: Record<string, Messages>,\n loadedLocales: Set<string>,\n): void => {\n for (const locale of Object.keys(locales)) {\n const source = locales[locale];\n if (source === undefined) {\n continue;\n }\n const eager = resolveLocaleModuleSync(source);\n if (eager !== undefined) {\n messageStore[locale] = deepMerge({}, eager);\n loadedLocales.add(locale);\n }\n }\n};\n\nconst hasBrowserOptions = <TLocales extends AnyLocalesMap>(\n options: CreateI18nOptions<TLocales>,\n): boolean =>\n options.storageKey !== undefined ||\n options.navigatorRules !== undefined ||\n options.documentTitleKey !== undefined;\n\nexport function createI18n<const TLocales extends AnyLocalesMap>(\n options: CreateI18nOptions<TLocales>,\n): I18n<InferLocaleFromLocalesMap<TLocales>, InferMessagesFromLocalesMap<TLocales>> {\n type TLocale = InferLocaleFromLocalesMap<TLocales>;\n type TMessages = InferMessagesFromLocalesMap<TLocales>;\n\n const {\n defaultLocale: defaultLocaleOption,\n fallbackLocale,\n locales,\n missingKeyStrategy = \"key\",\n storageKey,\n navigatorRules,\n syncDocument = true,\n documentTitleKey,\n } = options;\n\n const browser = hasBrowserOptions(options);\n const supportedLocales = Object.keys(locales) as TLocale[];\n\n const resolveDetectedLocale = (): TLocale =>\n detectLocale({\n supported: supportedLocales,\n fallback: fallbackLocale as TLocale,\n storageKey,\n navigatorRules,\n });\n\n const defaultLocale = (defaultLocaleOption ?? (browser ? resolveDetectedLocale() : undefined)) as\n | TLocale\n | undefined;\n\n if (defaultLocale === undefined) {\n throw new Error(\n \"createI18n: `defaultLocale` is required when browser options are not set\",\n );\n }\n\n if (!supportedLocales.includes(defaultLocale as TLocale)) {\n throw new RangeError(\n `createI18n: defaultLocale \"${String(defaultLocale)}\" is not listed in locales`,\n );\n }\n\n if (!supportedLocales.includes(fallbackLocale as TLocale)) {\n throw new RangeError(\n `createI18n: fallbackLocale \"${String(fallbackLocale)}\" is not listed in locales`,\n );\n }\n\n const state = createLocaleState(defaultLocale as TLocale, fallbackLocale as TLocale);\n const messageStore = cloneLocaleMessages({});\n const loadedLocales = new Set<string>();\n\n registerEagerLocales(locales, messageStore, loadedLocales);\n\n const { translate } = createTranslator({\n getLocale: () => state.$locale.value(),\n fallbackLocale: fallbackLocale as TLocale,\n messages: messageStore,\n missingKeyStrategy,\n });\n\n const addMessages = (locale: TLocale, messages: Messages): void => {\n const bucket = ensureLocaleBucket(messageStore, locale);\n messageStore[locale] = deepMerge(bucket, messages);\n loadedLocales.add(locale);\n };\n\n const loadLocale = async (locale: TLocale): Promise<void> => {\n if (loadedLocales.has(locale)) {\n return;\n }\n\n const source = locales[locale];\n if (!source) {\n return;\n }\n\n if (!isLocaleImporter(source)) {\n addMessages(locale, resolveLocaleModuleSync(source)!);\n return;\n }\n\n state.$pending.set(true);\n state.$error.set(null);\n\n try {\n const messages = await resolveLocaleModule(source);\n addMessages(locale, messages);\n } catch (error: unknown) {\n state.$error.set(error);\n throw error;\n } finally {\n state.$pending.set(false);\n }\n };\n\n const setLocale = async (locale: TLocale): Promise<void> => {\n const source = locales[locale];\n if (!loadedLocales.has(locale) && source !== undefined && isLocaleImporter(source)) {\n await loadLocale(locale);\n }\n\n state.$locale.set(locale);\n };\n\n const start = (): Promise<void> => {\n if (!browser) {\n return Promise.resolve();\n }\n\n const locale = resolveDetectedLocale();\n state.$locale.set(locale);\n\n effect(() => {\n const current = state.$locale.value();\n if (typeof document !== \"undefined\" && syncDocument) {\n document.documentElement.lang = current;\n if (documentTitleKey) {\n document.title = translate(documentTitleKey);\n }\n }\n if (storageKey && typeof localStorage !== \"undefined\") {\n localStorage.setItem(storageKey, current);\n }\n });\n\n const source = locales[locale];\n if (\n !loadedLocales.has(locale) &&\n source !== undefined &&\n isLocaleImporter(source)\n ) {\n void loadLocale(locale);\n }\n\n return Promise.resolve();\n };\n\n return {\n supportedLocales,\n\n locale: (): TLocale => state.$locale.peek() as TLocale,\n setLocale,\n start,\n\n fallbackLocale: (): TLocale => fallbackLocale as TLocale,\n\n t: translate,\n\n exists: (key) =>\n hasMessage(messageStore, state.$locale.peek(), fallbackLocale as TLocale, key),\n\n addMessages,\n\n removeLocale: (locale: TLocale) => {\n delete messageStore[locale];\n loadedLocales.delete(locale);\n },\n\n loadLocale,\n\n number: (value, formatOptions) =>\n formatNumber(state.$locale.value(), value, formatOptions),\n\n currency: (value, currencyCode, formatOptions) =>\n formatCurrency(state.$locale.value(), value, currencyCode, formatOptions),\n\n date: (value, formatOptions) =>\n formatDate(state.$locale.value(), value, formatOptions),\n\n relativeTime: (value, unit) =>\n formatRelativeTime(state.$locale.value(), value, unit),\n\n $locale: state.$locale,\n $pending: state.$pending,\n $error: state.$error,\n };\n}\n","import { createI18n } from \"../create-i18n\";\nimport type {\n AnyLocalesMap,\n CreateI18nOptions,\n I18n,\n InferLocaleFromLocalesMap,\n InferMessagesFromLocalesMap,\n Messages,\n} from \"../types\";\n\n/** Echo app provider with the active {@link I18n} instance. */\nexport type EchoI18nProvider<\n TLocale extends string = string,\n TMessages extends Messages = Messages,\n> = {\n name: \"i18n\";\n readonly i18n: I18n<TLocale, TMessages>;\n setup: () => void | Promise<void>;\n};\n\n/**\n * Creates i18n and an Echo app provider (`setup` runs {@link I18n.start}).\n *\n * ```ts\n * export const i18nProvider = createI18nProvider({ locales: { en, ru } });\n * app.use(i18nProvider);\n * i18nProvider.i18n.t(\"key\");\n * ```\n */\nexport function createI18nProvider<const TLocales extends AnyLocalesMap>(\n options: CreateI18nOptions<TLocales>,\n): EchoI18nProvider<\n InferLocaleFromLocalesMap<TLocales>,\n InferMessagesFromLocalesMap<TLocales>\n> {\n const i18n = createI18n(options);\n return {\n name: \"i18n\",\n i18n,\n setup: () => i18n.start(),\n };\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@echojs-ecosystem/i18n",
3
+ "version": "0.1.0",
4
+ "files": [
5
+ "dist"
6
+ ],
7
+ "type": "module",
8
+ "sideEffects": false,
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ },
14
+ "./package.json": "./package.json"
15
+ },
16
+ "scripts": {
17
+ "build": "tsup",
18
+ "check-types": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.typing.json --noEmit",
19
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.typing.json --noEmit",
20
+ "test:types": "tsc -p tsconfig.typing.json --noEmit",
21
+ "lint": "oxlint .",
22
+ "lint:fix": "oxlint . --fix",
23
+ "format": "oxfmt --check .",
24
+ "format:fix": "oxfmt .",
25
+ "test": "vitest run",
26
+ "test:watch": "vitest",
27
+ "test:coverage": "vitest run --coverage"
28
+ },
29
+ "dependencies": {
30
+ "@echojs-ecosystem/reactivity": "workspace:*"
31
+ },
32
+ "devDependencies": {
33
+ "@echojs-ecosystem/oxc-config": "workspace:*",
34
+ "typescript": "5.9.2",
35
+ "vitest": "^4.1.4"
36
+ },
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/echojs-ecosystem/echojs-core.git",
43
+ "directory": "packages/i18n"
44
+ }
45
+ }