@fanee/core 0.4.0 → 0.6.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 +201 -0
- package/dist/index.d.ts +297 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/package.json +4 -4
- package/dist/index.d.mts +0 -71
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs +0 -3
- package/dist/index.mjs.map +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# @fanee/core
|
|
2
|
+
|
|
3
|
+
Core runtime for the OTB (Open Translation Bundle) format.
|
|
4
|
+
|
|
5
|
+
Provides `FaneeRuntime`, a plugin-based i18n runtime with MF2 MessageFormat support, and `i18n`, a pre-instantiated singleton.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @fanee/core
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { FaneeRuntime } from "@fanee/core";
|
|
17
|
+
|
|
18
|
+
const runtime = new FaneeRuntime().config({
|
|
19
|
+
defaultLocale: "en",
|
|
20
|
+
currentLocale: "en",
|
|
21
|
+
resources: {
|
|
22
|
+
"": {
|
|
23
|
+
en: { greeting: "Hello, {name}!" },
|
|
24
|
+
fr: { greeting: "Bonjour, {name}!" },
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
runtime.t("greeting"); // "Hello, {name}!"
|
|
30
|
+
runtime.t("greeting", { name: "World" }); // "Hello, World!"
|
|
31
|
+
|
|
32
|
+
runtime.setLocale("fr");
|
|
33
|
+
runtime.t("greeting", { name: "World" }); // "Bonjour, World!"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or use the global singleton:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { i18n } from "@fanee/core";
|
|
40
|
+
|
|
41
|
+
i18n.config({
|
|
42
|
+
defaultLocale: "en",
|
|
43
|
+
currentLocale: "en",
|
|
44
|
+
resources: {
|
|
45
|
+
"": { en: { hello: "Hello, World!" } },
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
i18n.t("hello"); // "Hello, World!"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## API
|
|
53
|
+
|
|
54
|
+
### `FaneeRuntime`
|
|
55
|
+
|
|
56
|
+
#### `config(patch)`
|
|
57
|
+
|
|
58
|
+
Shallow-merges a partial state patch into the runtime state. Returns `this` for chaining.
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
runtime.config({
|
|
62
|
+
defaultLocale: "en",
|
|
63
|
+
currentLocale: "en",
|
|
64
|
+
baseNamespace: "web",
|
|
65
|
+
resources: { /* ... */ },
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
#### `use(fn)`
|
|
70
|
+
|
|
71
|
+
Registers a plugin function that transforms the runtime state. Plugins run sequentially in registration order. Returns `this` for chaining.
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
runtime.use(async (state) => {
|
|
75
|
+
const resources = await loadFromAPI();
|
|
76
|
+
return { ...state, resources };
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
#### `ready()`
|
|
81
|
+
|
|
82
|
+
Returns a `Promise<void>` that resolves when all queued plugins have completed. The runtime is also thenable, so `await runtime` works as a shorthand.
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
const runtime = new FaneeRuntime()
|
|
86
|
+
.use(asyncPlugin)
|
|
87
|
+
.config({ defaultLocale: "en" });
|
|
88
|
+
|
|
89
|
+
await runtime.ready();
|
|
90
|
+
// or: await runtime;
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Translation Methods
|
|
94
|
+
|
|
95
|
+
#### `t(key, vars?)`
|
|
96
|
+
|
|
97
|
+
Translates a key using the current locale and base namespace. Returns a string.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
runtime.t("key"); // looks up in base namespace with current locale
|
|
101
|
+
runtime.t("greeting", { name: "World" }); // with MF2 variable interpolation
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
#### `getT(context?)`
|
|
105
|
+
|
|
106
|
+
Returns a translation function bound to the given context. The `context.namespace` appends to the base namespace with `:` as separator.
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
const t = runtime.getT();
|
|
110
|
+
t("key"); // looks up in base namespace
|
|
111
|
+
|
|
112
|
+
const tAuth = runtime.getT({ namespace: "auth" });
|
|
113
|
+
tAuth("login"); // looks up in "base:auth" (or "auth" if base namespace is "")
|
|
114
|
+
|
|
115
|
+
const tFr = runtime.getT({ locale: "fr" });
|
|
116
|
+
tFr("key"); // looks up with locale "fr"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
#### `tAll(key, vars?)`
|
|
120
|
+
|
|
121
|
+
Returns translations for a key in all available locales within the base namespace.
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
runtime.tAll("greeting");
|
|
125
|
+
// { en: "Hello", fr: "Bonjour", de: "Hallo" }
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### `getLocale()`
|
|
129
|
+
|
|
130
|
+
Returns the current locale BCP 47 tag.
|
|
131
|
+
|
|
132
|
+
#### `getLocales()`
|
|
133
|
+
|
|
134
|
+
Returns a sorted array of all locales present in loaded resources.
|
|
135
|
+
|
|
136
|
+
#### `setLocale(locale)`
|
|
137
|
+
|
|
138
|
+
Sets the current locale. Subsequent `t()` calls use the new locale.
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
runtime.setLocale("fr");
|
|
142
|
+
runtime.t("greeting"); // "Bonjour"
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
#### `setNamespace(ns)`
|
|
146
|
+
|
|
147
|
+
Sets the base namespace. Subsequent lookups resolve against this namespace.
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
runtime.setNamespace("admin");
|
|
151
|
+
runtime.t("dashboard_title");
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
#### `getAllTranslations()`
|
|
155
|
+
|
|
156
|
+
Returns the full resource tree (`BundleResources`).
|
|
157
|
+
|
|
158
|
+
#### `getTranslationsForNamespace(ns)`
|
|
159
|
+
|
|
160
|
+
Returns locale-indexed messages for a namespace, or `undefined`.
|
|
161
|
+
|
|
162
|
+
#### `subscribe(callback)`
|
|
163
|
+
|
|
164
|
+
Subscribes to state changes. Returns an unsubscribe function.
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
const unsub = runtime.subscribe((state) => {
|
|
168
|
+
console.log("locale changed to", state.currentLocale);
|
|
169
|
+
});
|
|
170
|
+
unsub();
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### `i18n` singleton
|
|
174
|
+
|
|
175
|
+
A pre-instantiated `FaneeRuntime` exported for convenience.
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { i18n } from "@fanee/core";
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Translation Format
|
|
182
|
+
|
|
183
|
+
By default, messages are formatted using MF2 MessageFormat with variable interpolation:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
runtime.t("greeting", { name: "World" }); // "Hello, World!"
|
|
187
|
+
runtime.t("price", { amount: 1234.56 }); // "Total: $1,234.56"
|
|
188
|
+
runtime.t("date", { today: new Date("2024-02-02") }); // "Today is Feb 2, 2024"
|
|
189
|
+
runtime.t("items", { count: 1 }); // "a item"
|
|
190
|
+
runtime.t("items", { count: 5 }); // "5 items"
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Set `formatting` to `"identity"` to disable formatting.
|
|
194
|
+
|
|
195
|
+
## Locale Fallback
|
|
196
|
+
|
|
197
|
+
When a key is missing in the current locale, the runtime falls back to the default locale. If missing in both, the key itself is returned.
|
|
198
|
+
|
|
199
|
+
## License
|
|
200
|
+
|
|
201
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
/** A BCP 47 language tag, e.g. `"en"`, `"zh-CN"`. */
|
|
3
|
+
type Locale = string;
|
|
4
|
+
/** A named scope for grouping translations, e.g. `"common"`, `"errors"`. */
|
|
5
|
+
type Namespace = string;
|
|
6
|
+
/** A key that identifies a specific message within a locale. */
|
|
7
|
+
type MessageKey = string;
|
|
8
|
+
/**
|
|
9
|
+
* How a message string is formatted after variable interpolation.
|
|
10
|
+
* - `"mf2"` – MessageFormat 2.0
|
|
11
|
+
* - `"mf1"` – MessageFormat (legacy)
|
|
12
|
+
* - `"identity"` – simple string replacement
|
|
13
|
+
* - Custom string – a user-provided formatter name.
|
|
14
|
+
*/
|
|
15
|
+
type MessageFormattingMode = "mf2" | "mf1" | "identity" | (string & {});
|
|
16
|
+
/** A flat map of message keys to their translated strings for a single locale. */
|
|
17
|
+
type LocaleMessages = Record<MessageKey, string>;
|
|
18
|
+
/** Messages for all locales within a single namespace. */
|
|
19
|
+
type NamespaceResources = Record<Locale, LocaleMessages>;
|
|
20
|
+
/** The complete resource tree: namespace → locale → messages. */
|
|
21
|
+
type BundleResources = Record<Namespace, NamespaceResources>;
|
|
22
|
+
/** Options passed to the low-level translate function. */
|
|
23
|
+
interface TranslateOptions {
|
|
24
|
+
/** Target locale for the translation. */
|
|
25
|
+
locale: string;
|
|
26
|
+
/** Variable bag for message interpolation. */
|
|
27
|
+
vars?: Record<string, unknown>;
|
|
28
|
+
/** Override the default formatting mode for this call. */
|
|
29
|
+
formatting?: MessageFormattingMode;
|
|
30
|
+
}
|
|
31
|
+
/** Scoping hints used when obtaining a bound {@link TranslateFunction}. */
|
|
32
|
+
interface TranslateContext {
|
|
33
|
+
/** Override the active namespace. */
|
|
34
|
+
namespace?: Namespace;
|
|
35
|
+
/** Override the active locale. */
|
|
36
|
+
locale?: Locale;
|
|
37
|
+
}
|
|
38
|
+
/** A pre-bound translate function tied to a specific locale and namespace. */
|
|
39
|
+
type TranslateFunction = (key: MessageKey, vars?: Record<string, unknown>) => string;
|
|
40
|
+
/** A map of locale → translated string, typically the result of {@link runtime.FaneeRuntime.tAll | tAll}. */
|
|
41
|
+
type TranslationsByLocale = Record<Locale, string>;
|
|
42
|
+
/** Metadata descriptor for an OTB (Open Translation Bundle) package. */
|
|
43
|
+
interface OTBManifest {
|
|
44
|
+
/** Must be `"otb"`. */
|
|
45
|
+
format: "otb";
|
|
46
|
+
/** The spec version the bundle conforms to. */
|
|
47
|
+
specVersion: string;
|
|
48
|
+
/** Version of this particular bundle. */
|
|
49
|
+
bundleVersion?: string;
|
|
50
|
+
/** Whether the bundle contains all its dependencies inline. */
|
|
51
|
+
standalone?: boolean;
|
|
52
|
+
/** The locale the source messages are authored in. */
|
|
53
|
+
sourceLocale?: string;
|
|
54
|
+
/** Locales that this bundle provides translations for. */
|
|
55
|
+
targetLocales?: Locale[];
|
|
56
|
+
/** Human-readable name for the bundle. */
|
|
57
|
+
name?: string;
|
|
58
|
+
/** Default formatting mode for messages in this bundle. */
|
|
59
|
+
formatting?: MessageFormattingMode;
|
|
60
|
+
/** Extension keys (must be prefixed with `x-`). */
|
|
61
|
+
[key: `x-${string}`]: unknown;
|
|
62
|
+
}
|
|
63
|
+
/** Mutable state container consumed by plugins and the runtime. */
|
|
64
|
+
interface FaneeState {
|
|
65
|
+
/** The full translation resource tree. */
|
|
66
|
+
resources: BundleResources;
|
|
67
|
+
/** Fallback locale when a key is missing in the current locale. */
|
|
68
|
+
defaultLocale: Locale;
|
|
69
|
+
/** The currently active locale. */
|
|
70
|
+
currentLocale: Locale;
|
|
71
|
+
/** The default namespace used for unqualified lookups. */
|
|
72
|
+
baseNamespace: Namespace;
|
|
73
|
+
/** The active message formatting mode. */
|
|
74
|
+
formatting: MessageFormattingMode;
|
|
75
|
+
/** Low-level translate function (locale-aware, may be swapped by plugins). */
|
|
76
|
+
translate: (msg: string, options?: TranslateOptions) => string;
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/runtime.d.ts
|
|
80
|
+
/** Sequenced runtime that processes plugin setup before translations are available. */
|
|
81
|
+
declare class FaneeRuntime {
|
|
82
|
+
private state;
|
|
83
|
+
private queue;
|
|
84
|
+
private listeners;
|
|
85
|
+
constructor();
|
|
86
|
+
private notify;
|
|
87
|
+
/**
|
|
88
|
+
* Register a plugin function that transforms the runtime state.
|
|
89
|
+
*
|
|
90
|
+
* Plugins are executed sequentially (FIFO) in the order they are registered.
|
|
91
|
+
* Each plugin receives the current {@link FaneeState} and may return a new state
|
|
92
|
+
* (or a promise thereof). The entire chain must resolve before the runtime
|
|
93
|
+
* is considered "ready" (see {@link ready}).
|
|
94
|
+
*
|
|
95
|
+
* @param fn - A plugin function receiving the current state.
|
|
96
|
+
* @returns `this` for chaining.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* runtime.use((state) => ({ ...state, currentLocale: "zh-CN" }));
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
use(fn: (state: FaneeState) => FaneeState | Promise<FaneeState>): this;
|
|
104
|
+
/**
|
|
105
|
+
* Shallow-merge a partial state patch into the current state.
|
|
106
|
+
*
|
|
107
|
+
* This operation is queued and runs after all previously registered plugins.
|
|
108
|
+
* It is equivalent to a plugin that destructures the patch over the state.
|
|
109
|
+
*
|
|
110
|
+
* @param patch - A partial {@link FaneeState} whose properties override the current state.
|
|
111
|
+
* @returns `this` for chaining.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* runtime.config({ defaultLocale: "en", formatting: "mf2" });
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
config(patch: Partial<FaneeState>): this;
|
|
119
|
+
/**
|
|
120
|
+
* @internal
|
|
121
|
+
* Makes the runtime thenable so callers can `await` plugin setup before
|
|
122
|
+
* calling translation methods.
|
|
123
|
+
*
|
|
124
|
+
* @param onfulfilled - Handler for successful queue resolution.
|
|
125
|
+
* @param onrejected - Handler for queue rejection.
|
|
126
|
+
*/
|
|
127
|
+
then<TResult1 = void, TResult2 = never>(onfulfilled?: // biome-ignore lint/suspicious/noConfusingVoidType: matches Promise<void> queue resolution
|
|
128
|
+
((value: void) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
|
129
|
+
/** Resolve locale + namespace from an optional context, falling back to the current state. */
|
|
130
|
+
private resolveContext;
|
|
131
|
+
/**
|
|
132
|
+
* Look up a message in the resource tree and (if vars are provided)
|
|
133
|
+
* delegate to the configured formatter.
|
|
134
|
+
*/
|
|
135
|
+
private localize;
|
|
136
|
+
/**
|
|
137
|
+
* Translate a message key in the current locale and base namespace.
|
|
138
|
+
*
|
|
139
|
+
* Falls back through:
|
|
140
|
+
* 1. {@link FaneeState.currentLocale Current locale}
|
|
141
|
+
* 2. The {@link FaneeState.defaultLocale default locale}
|
|
142
|
+
* 3. Returns the key itself if no translation is found
|
|
143
|
+
*
|
|
144
|
+
* @param key - The message key to translate.
|
|
145
|
+
* @param vars - Optional variables for interpolation.
|
|
146
|
+
* @returns The translated string, or `key` if no translation exists.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```ts
|
|
150
|
+
* runtime.t("hello"); // "你好"
|
|
151
|
+
* runtime.t("greeting", { name: "Alice" }); // "Hello, Alice"
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
t(key: MessageKey, vars?: Record<string, unknown>): string;
|
|
155
|
+
/**
|
|
156
|
+
* Return a bound translate function pinned to a specific locale/namespace.
|
|
157
|
+
*
|
|
158
|
+
* Unlike {@link t}, this method captures the locale and namespace at call time
|
|
159
|
+
* so the returned function can be passed around (e.g. as a prop to a component)
|
|
160
|
+
* without retaining a reference to the runtime.
|
|
161
|
+
*
|
|
162
|
+
* @param context - Optional overrides for locale and/or namespace.
|
|
163
|
+
* If omitted, the current runtime values are captured.
|
|
164
|
+
* @returns A function with the signature `(key, vars?) => string`.
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```ts
|
|
168
|
+
* const t = runtime.getT({ locale: "ja", namespace: "errors" });
|
|
169
|
+
* t("not_found"); // "見つかりませんでした"
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
getT(context?: Partial<TranslateContext>): TranslateFunction;
|
|
173
|
+
/**
|
|
174
|
+
* Translate a message key into every available locale within the base namespace.
|
|
175
|
+
*
|
|
176
|
+
* Useful for generating locale-switching UIs, SEO hreflang tags, or
|
|
177
|
+
* pre-rendering all translations on the server.
|
|
178
|
+
*
|
|
179
|
+
* @param key - The message key to translate.
|
|
180
|
+
* @param vars - Optional variables for interpolation (applied to every locale).
|
|
181
|
+
* @returns A record mapping each locale to its translated string.
|
|
182
|
+
* Returns an empty object if the base namespace has no resources.
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```ts
|
|
186
|
+
* runtime.tAll("welcome");
|
|
187
|
+
* // { en: "Welcome", "es": "Bienvenido", "fr": "Bienvenue", "zh-CN": "欢迎", ja: "ようこそ" }
|
|
188
|
+
* ```
|
|
189
|
+
*/
|
|
190
|
+
tAll(key: MessageKey, vars?: Record<string, unknown>): TranslationsByLocale;
|
|
191
|
+
/**
|
|
192
|
+
* Get the currently active locale.
|
|
193
|
+
*
|
|
194
|
+
* @returns The BCP 47 tag of the active locale (e.g. `"en"`, `"zh-CN"`).
|
|
195
|
+
*/
|
|
196
|
+
getLocale(): Locale;
|
|
197
|
+
/**
|
|
198
|
+
* Collect every unique locale present across all loaded namespaces.
|
|
199
|
+
*
|
|
200
|
+
* @returns A sorted array of locale tags. Returns an empty array if no
|
|
201
|
+
* resources have been loaded.
|
|
202
|
+
*/
|
|
203
|
+
getLocales(): Locale[];
|
|
204
|
+
/**
|
|
205
|
+
* Return the complete resource tree.
|
|
206
|
+
*
|
|
207
|
+
* @returns The full {@link BundleResources} object (namespace → locale → messages).
|
|
208
|
+
*/
|
|
209
|
+
getAllTranslations(): BundleResources;
|
|
210
|
+
/**
|
|
211
|
+
* Return resources for a single namespace.
|
|
212
|
+
*
|
|
213
|
+
* @param ns - The namespace to look up.
|
|
214
|
+
* @returns The locale-indexed messages for that namespace, or `undefined`
|
|
215
|
+
* if the namespace has not been loaded.
|
|
216
|
+
*/
|
|
217
|
+
getTranslationsForNamespace(ns: string): NamespaceResources | undefined;
|
|
218
|
+
/**
|
|
219
|
+
* Wait for all queued plugins to finish.
|
|
220
|
+
*
|
|
221
|
+
* Because plugin registration and configuration are queued as a promise chain,
|
|
222
|
+
* you must `await runtime.ready()` (or `await runtime` directly) before calling
|
|
223
|
+
* translation methods if any plugin is asynchronous.
|
|
224
|
+
*
|
|
225
|
+
* @returns A promise that resolves once the plugin queue is empty.
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* ```ts
|
|
229
|
+
* const runtime = new FaneeRuntime()
|
|
230
|
+
* .use(asyncLoaderPlugin)
|
|
231
|
+
* .config({ defaultLocale: "en" });
|
|
232
|
+
*
|
|
233
|
+
* await runtime.ready();
|
|
234
|
+
* console.log(runtime.t("hello")); // safe
|
|
235
|
+
* ```
|
|
236
|
+
*/
|
|
237
|
+
ready(): Promise<void>;
|
|
238
|
+
/**
|
|
239
|
+
* Switch the active locale at runtime.
|
|
240
|
+
*
|
|
241
|
+
* This is a synchronous operation that updates the current locale instantly.
|
|
242
|
+
* Subsequent calls to {@link t} and {@link getT} (without an explicit locale)
|
|
243
|
+
* will use this new locale.
|
|
244
|
+
*
|
|
245
|
+
* @param locale - The BCP 47 tag of the target locale.
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* ```ts
|
|
249
|
+
* runtime.setLocale("fr");
|
|
250
|
+
* runtime.t("hello"); // "Bonjour"
|
|
251
|
+
* ```
|
|
252
|
+
*/
|
|
253
|
+
setLocale(locale: Locale): void;
|
|
254
|
+
/**
|
|
255
|
+
* Switch the base namespace at runtime.
|
|
256
|
+
*
|
|
257
|
+
* Subsequent calls to {@link t} and related methods will resolve keys against
|
|
258
|
+
* this namespace. When a scoped namespace is set via {@link getT},
|
|
259
|
+
* it is prefixed with the base namespace using `:` as separator.
|
|
260
|
+
*
|
|
261
|
+
* @param ns - The namespace to set as the base.
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* ```ts
|
|
265
|
+
* runtime.setNamespace("admin");
|
|
266
|
+
* runtime.t("dashboard_title"); // resolved from "admin" namespace
|
|
267
|
+
* ```
|
|
268
|
+
*/
|
|
269
|
+
setNamespace(ns: string): void;
|
|
270
|
+
/**
|
|
271
|
+
* Subscribe to state changes.
|
|
272
|
+
*
|
|
273
|
+
* The callback is invoked synchronously whenever the runtime state is mutated
|
|
274
|
+
* (via {@link setLocale}, {@link setNamespace}, or after a queued
|
|
275
|
+
* {@link use} / {@link config} operation resolves).
|
|
276
|
+
*
|
|
277
|
+
* @param callback - Called with the current {@link FaneeState} on every change.
|
|
278
|
+
* @returns An unsubscribe function. Call it to stop receiving notifications.
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* ```ts
|
|
282
|
+
* const unsub = runtime.subscribe((state) => {
|
|
283
|
+
* console.log("locale changed to", state.currentLocale);
|
|
284
|
+
* });
|
|
285
|
+
*
|
|
286
|
+
* runtime.setLocale("fr"); // logs "locale changed to fr"
|
|
287
|
+
* unsub();
|
|
288
|
+
* ```
|
|
289
|
+
*/
|
|
290
|
+
subscribe(callback: (state: FaneeState) => void): () => void;
|
|
291
|
+
}
|
|
292
|
+
//#endregion
|
|
293
|
+
//#region src/i18n.d.ts
|
|
294
|
+
declare const i18n: FaneeRuntime;
|
|
295
|
+
//#endregion
|
|
296
|
+
export { type BundleResources, FaneeRuntime, type FaneeState, type Locale, type LocaleMessages, type MessageKey, type Namespace, type NamespaceResources, type OTBManifest, type TranslateContext, type TranslateFunction, type TranslateOptions, type TranslationsByLocale, i18n };
|
|
297
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/runtime.ts","../src/i18n.ts"],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
const e=/^[\u061c\u200e\u200f\u2066-\u2069]+/,t=/^[-.+0-9A-Z_a-z\u{a1}-\u{61b}\u{61d}-\u{167f}\u{1681}-\u{1fff}\u{200b}-\u{200d}\u{2010}-\u{2027}\u{2030}-\u{205e}\u{2060}-\u{2065}\u{206a}-\u{2fff}\u{3001}-\u{d7ff}\u{e000}-\u{fdcf}\u{fdf0}-\u{fffd}\u{10000}-\u{1fffd}\u{20000}-\u{2fffd}\u{30000}-\u{3fffd}\u{40000}-\u{4fffd}\u{50000}-\u{5fffd}\u{60000}-\u{6fffd}\u{70000}-\u{7fffd}\u{80000}-\u{8fffd}\u{90000}-\u{9fffd}\u{a0000}-\u{afffd}\u{b0000}-\u{bfffd}\u{c0000}-\u{cfffd}\u{d0000}-\u{dfffd}\u{e0000}-\u{efffd}\u{f0000}-\u{ffffd}\u{100000}-\u{10fffd}]+/u,n=/^[-.0-9]/;function r(r,i){let a=i,o=r.slice(a).match(e);o&&(a+=o[0].length);let s=r.slice(a).match(t);if(!s)return null;let c=s[0];if(n.test(c))return null;a+=c.length;let l=r.slice(a).match(e);return l&&(a+=l[0].length),{value:c.normalize(),end:a}}const i=(e,n)=>e.slice(n).match(t)?.[0]??``,a=Symbol.for(`CST`);var o=class extends Error{type;constructor(e,t){super(t),this.type=e}},s=class extends o{start;end;constructor(e,t,n,r){let i=r?`Missing ${r}`:e;t>=0&&(i+=` at ${t}`),super(e,i),this.start=t,this.end=n??t+1}},c=class extends s{constructor(e,t){let{start:n,end:r}=t[a]??{start:-1,end:-1};super(e,n,r)}},l=class extends o{source;cause;constructor(e,t,n,r){super(e,t),this.source=n,r!==void 0&&(this.cause=r)}},u=class extends o{source;cause;constructor(e,t){super(e,t),this.source=`�`}};const d=new Set(``),f=new Set(`
|
|
2
|
+
\r `);let p,m;const h=(e,t)=>new s(`missing-syntax`,e,e+t.length,t),g=(...e)=>new s(...e);function _(e,t){if(m.startsWith(e,p))t&&(p+=e.length);else throw h(p,e)}function ee(e){p=0,m=e;let t=re();if(m.startsWith(`.match`,p))return te(t);let n=t.length>0||m.startsWith(`{{`,p);!n&&p>0&&(p=0);let r=v(n);if(n&&(D(),p<m.length))throw g(`extra-content`,p,m.length);return{type:`message`,declarations:t,pattern:r}}function te(e){p+=6,D(!0);let t=[];for(;m[p]===`$`;)t.push(C()),D(!0);if(t.length===0)throw g(`empty-token`,p);let n=[];for(;p<m.length;)n.push(ne()),D();return{type:`select`,declarations:e,selectors:t,variants:n}}function ne(){let e=[];for(;p<m.length;){D(e.length?`{`:!1);let t=m[p];if(t===`{`)break;if(t===`*`)e.push({type:`*`}),p+=1;else{let t=w(!0);t.value=t.value.normalize(),e.push(t)}}return{keys:e,value:v(!0)}}function v(e){if(e)if(m.startsWith(`{{`,p))p+=2;else throw h(p,`{{`);let t=[];loop:for(;p<m.length;)switch(m[p]){case`{`:t.push(b(!0));break;case`}`:if(!e)throw g(`parse-error`,p);break loop;default:t.push(oe())}if(e)if(m.startsWith(`}}`,p))p+=2;else throw h(p,`}}`);return t}function re(){let e=[];D();loop:for(;m[p]===`.`;){switch(m.substr(p,6)){case`.input`:e.push(ie());break;case`.local`:e.push(y());break;case`.match`:break loop;default:throw g(`parse-error`,p)}D()}return e}function ie(){p+=6,D(),_(`{`,!1);let e=p,t=b(!1);if(t.type===`expression`&&t.arg?.type===`variable`)return{type:`input`,name:t.arg.name,value:t};throw g(`bad-input-expression`,e,p)}function y(){p+=6,D(!0),_(`$`,!0);let e=E();return D(),_(`=`,!0),D(),_(`{`,!1),{type:`local`,name:e,value:b(!1)}}function b(e){let t=p;p+=1,D();let n=S(!1);n&&D(`}`);let r=m[p],i,a;switch(r){case`@`:case`}`:break;case`:`:{p+=1,i={type:`function`,name:T()};let e=x();e&&(i.options=e);break}case`#`:case`/`:{if(n||!e)throw g(`parse-error`,p);p+=1,a={type:`markup`,kind:r===`#`?`open`:`close`,name:T()};let t=x();t&&(a.options=t);break}default:throw g(`parse-error`,p)}let o=ae();if(a?.kind===`open`&&m[p]===`/`&&(a.kind=`standalone`,p+=1),_(`}`,!0),i){let e=n?{type:`expression`,arg:n,functionRef:i}:{type:`expression`,functionRef:i};return o&&(e.attributes=o),e}if(a)return o&&(a.attributes=o),a;if(!n)throw g(`empty-token`,t,p);return o?{type:`expression`,arg:n,attributes:o}:{type:`expression`,arg:n}}function x(){D(`/}`);let e={},t=!0;for(;p<m.length;){let n=m[p];if(n===`@`||n===`/`||n===`}`)break;let r=p,i=T();if(Object.hasOwn(e,i))throw g(`duplicate-option-name`,r,p);D(),_(`=`,!0),D(),e[i]=S(!0),t=!1,D(`/}`)}return t?null:e}function ae(){let e={},t=!0;for(;m[p]===`@`;){let n=p;p+=1;let r=T();if(Object.hasOwn(e,r))throw g(`duplicate-attribute`,n,p);D(`=/}`),m[p]===`=`?(p+=1,D(),e[r]=w(!0),D(`/}`)):e[r]=!0,t=!1}return t?null:e}function oe(){let e=``,t=p;loop:for(;t<m.length;++t)switch(m[t]){case`\\`:{let n=m[t+1];if(!`\\{|}`.includes(n))throw g(`bad-escape`,t,t+2);e+=m.substring(p,t)+n,t+=1,p=t+1;break}case`{`:case`}`:break loop}return e+=m.substring(p,t),p=t,e}function S(e){return m[p]===`$`?C():w(e)}function C(){return p+=1,{type:`variable`,name:E()}}function w(e){if(m[p]===`|`)return se();let t=i(m,p);if(!t){if(e)throw g(`empty-token`,p);return}return p+=t.length,{type:`literal`,value:t}}function se(){p+=1;let e=``;for(let t=p;t<m.length;++t)switch(m[t]){case`\\`:{let n=m[t+1];if(!`\\{|}`.includes(n))throw g(`bad-escape`,t,t+2);e+=m.substring(p,t)+n,t+=1,p=t+1;break}case`|`:return e+=m.substring(p,t),p=t+1,{type:`literal`,value:e}}throw h(m.length,`|`)}function T(){let e=E();return m[p]===`:`?(p+=1,e+`:`+E()):e}function E(){let e=r(m,p);if(!e)throw g(`empty-token`,p);return p=e.end,e.value}function D(e=!1){let t=m[p],n=!1;if(e){for(;d.has(t);)t=m[++p];for(;f.has(t);)t=m[++p],n=!0}for(;d.has(t)||f.has(t);)t=m[++p];if(e&&!n&&(e===!0||!e.includes(m[p])))throw h(p,`' '`)}function ce(e,t){let{node:n,pattern:r}=t,{functionRef:i=n,attributes:a=null,declaration:o=n,expression:s=n,key:c=n,markup:l=n,options:u=null,value:d=n,variant:f=n}=t,p=(e,t)=>{if(e){let n=u?.(e,t);if(d)for(let n of Object.values(e))d(n,t,`option`);n?.()}},m=(e,t)=>{if(e){let n=a?.(e,t);if(d)for(let n of Object.values(e))n!==!0&&d(n,t,`attribute`);n?.()}},h=(e,t)=>{if(typeof e==`object`){let n;switch(e.type){case`expression`:if(n=s?.(e,t),e.arg&&d?.(e.arg,t,`arg`),e.functionRef){let n=i?.(e.functionRef,t,e.arg);p(e.functionRef.options,t),n?.()}m(e.attributes,t);break;case`markup`:n=l?.(e,t),p(e.options,t),m(e.attributes,t);break}n?.()}},g=e=>{let t=r?.(e);for(let t of e)h(t,`placeholder`);t?.()};for(let t of e.declarations){let e=o?.(t);t.value&&h(t.value,`declaration`),e?.()}if(e.type===`message`)g(e.pattern);else{if(d)for(let t of e.selectors)d(t,`selector`,`arg`);for(let t of e.variants){let e=f?.(t);c&&t.keys.forEach(c),g(t.value),e?.()}}}function le(e,t=(e,t)=>{throw new c(e,t)}){let n=0,r=null,i=new Set,a=new Set,o=new Set,s=new Set,l=new Set,u=new Set,d=!0;ce(e,{declaration(e){if(e.name)return(e.value.functionRef||e.type===`local`&&e.value.arg?.type===`variable`&&i.has(e.value.arg.name))&&i.add(e.name),e.type===`local`&&s.add(e.name),d=e.type===`local`,()=>{a.has(e.name)?t(`duplicate-declaration`,e):a.add(e.name)}},expression({functionRef:e}){e&&o.add(e.name)},value(e,o,s){if(e.type===`variable`)switch(l.add(e.name),o){case`declaration`:(s!==`arg`||d)&&a.add(e.name);break;case`selector`:n+=1,r=e,i.has(e.name)||t(`missing-selector-annotation`,e)}},variant(e){let{keys:i}=e;i.length!==n&&t(`key-mismatch`,e);let a=JSON.stringify(i.map(e=>e.type===`literal`?e.value:0));u.has(a)?t(`duplicate-variant`,e):u.add(a),r&&=i.every(e=>e.type===`*`)?null:e}}),r&&t(`missing-fallback`,r);for(let e of s)l.delete(e);return{functions:o,variables:l}}function O(e){if(e)try{typeof e==`string`&&(e=new Intl.Locale(e));let t=e.getTextInfo?.()??e.textInfo;if(t?.direction)return t.direction;let n=e.maximize().script;if(n)return`Adlm,Arab,Hebr,Mand,Nkoo,Rohg,Syrc,Thaa`.includes(n)?`rtl`:`ltr`}catch{}return`auto`}function ue(e){if(e&&typeof e==`object`&&(e=e.valueOf()),typeof e==`boolean`)return e;if(e&&typeof e==`object`&&(e=String(e)),e===`true`)return!0;if(e===`false`)return!1;throw RangeError(`Not a boolean`)}function k(e){if(e&&typeof e==`object`&&(e=e.valueOf()),e&&typeof e==`object`&&(e=String(e)),typeof e==`string`&&/^(0|[1-9][0-9]*)$/.test(e)&&(e=Number(e)),typeof e==`number`&&e>=0&&Number.isInteger(e))return e;throw RangeError(`Not a positive integer`)}function A(e){if(e&&typeof e==`object`&&(e=e.valueOf()),typeof e==`string`)return e;if(e&&typeof e==`object`)return String(e);throw RangeError(`Not a string`)}function j(e){let t;if(typeof e==`object`){let n=e?.valueOf;typeof n==`function`&&(t=e.options,e=n.call(e))}if(typeof e==`string`)try{e=JSON.parse(e)}catch{}if(typeof e!=`bigint`&&typeof e!=`number`)throw new u(`bad-operand`,`Input is not numeric`);return{value:e,options:t}}function M(e,t,n,r){let{dir:i,locales:a}=e;n.useGrouping===`never`&&(n.useGrouping=!1),r&&`select`in n&&!e.literalOptionKeys.has(`select`)&&(e.onError(`bad-option`,`The option select may only be set by a literal value`),r=!1);let o,s,c,l;return{type:`number`,get dir(){return i??=(o??=Intl.NumberFormat.supportedLocalesOf(a,n)[0],O(o)),i},get options(){return{...n}},selectKey:r?e=>{let r=t;n.style===`percent`&&(typeof r==`bigint`?r*=100n:r*=100);let i=String(r);if(e.has(i))return i;if(n.select===`exact`)return null;let o=n.select?{...n,select:void 0,type:n.select}:n;return c??=new Intl.PluralRules(a,o).select(Number(r)),e.has(c)?c:null}:void 0,toParts(){s??=new Intl.NumberFormat(a,n);let e=s.formatToParts(t);return o??=s.resolvedOptions().locale,i??=O(o),i===`ltr`||i===`rtl`?[{type:`number`,dir:i,locale:o,parts:e}]:[{type:`number`,locale:o,parts:e}]},toString(){return s??=new Intl.NumberFormat(a,n),l??=s.format(t),l},valueOf:()=>t}}function N(e,t,n){let r=j(n),i=r.value,a=Object.assign({},r.options,{localeMatcher:e.localeMatcher,style:`decimal`});for(let[n,r]of Object.entries(t))if(r!==void 0)try{switch(n){case`minimumIntegerDigits`:case`minimumFractionDigits`:case`maximumFractionDigits`:case`minimumSignificantDigits`:case`maximumSignificantDigits`:case`roundingIncrement`:a[n]=k(r);break;case`roundingMode`:case`roundingPriority`:case`select`:case`signDisplay`:case`trailingZeroDisplay`:case`useGrouping`:a[n]=A(r)}}catch{e.onError(`bad-option`,`Value ${r} is not valid for :number option ${n}`)}return M(e,i,a,!0)}function de(e,t,n){let r=j(n),i=Number.isFinite(r.value)?Math.round(r.value):r.value,a=Object.assign({},r.options,{maximumFractionDigits:0,minimumFractionDigits:void 0,minimumSignificantDigits:void 0,style:`decimal`});for(let[n,r]of Object.entries(t))if(r!==void 0)try{switch(n){case`minimumIntegerDigits`:case`maximumSignificantDigits`:a[n]=k(r);break;case`select`:case`signDisplay`:case`useGrouping`:a[n]=A(r)}}catch{e.onError(`bad-option`,`Value ${r} is not valid for :integer option ${n}`)}return M(e,i,a,!0)}function fe(e,t,n){let r=j(n),i=Object.assign({},r.options,{localeMatcher:e.localeMatcher,style:`currency`});for(let[n,r]of Object.entries(t))if(r!==void 0)try{switch(n){case`currency`:case`currencySign`:case`roundingMode`:case`roundingPriority`:case`trailingZeroDisplay`:case`useGrouping`:i[n]=A(r);break;case`minimumIntegerDigits`:case`minimumSignificantDigits`:case`maximumSignificantDigits`:case`roundingIncrement`:i[n]=k(r);break;case`currencyDisplay`:{let t=A(r);t===`never`?e.onError(`unsupported-operation`,`Currency display "never" is not yet supported`):i[n]=t;break}case`fractionDigits`:{let e=A(r);if(e===`auto`)i.minimumFractionDigits=void 0,i.maximumFractionDigits=void 0;else{let t=k(e);i.minimumFractionDigits=t,i.maximumFractionDigits=t}break}}}catch{e.onError(`bad-option`,`Value ${r} is not valid for :currency option ${n}`)}if(!i.currency)throw new u(`bad-operand`,`A currency code is required for :currency`);return M(e,r.value,i,!1)}const pe=new Set([`weekday`,`day-weekday`,`month-day`,`month-day-weekday`,`year-month-day`,`year-month-day-weekday`]),me=new Set([`long`,`medium`,`short`]),he=new Set([`hour`,`minute`,`second`]),ge=new Set([`long`,`short`]),_e=(e,t,n)=>P(`datetime`,e,t,n),ve=(e,t,n)=>P(`date`,e,t,n),ye=(e,t,n)=>P(`time`,e,t,n);function P(e,t,n,r){let i={localeMatcher:t.localeMatcher},a=r;if(typeof a==`object`&&a){let t=a.options;t&&(i.calendar=t.calendar,e!==`date`&&(i.hour12=t.hour12),i.timeZone=t.timeZone),typeof a.valueOf==`function`&&(a=a.valueOf())}switch(typeof a){case`number`:case`string`:a=new Date(a)}if(!(a instanceof Date)||isNaN(a.getTime()))throw new u(`bad-operand`,`Input is not a valid date`);if(n.calendar!==void 0)try{i.calendar=A(n.calendar)}catch{t.onError(`bad-option`,`Invalid :${e} calendar option value`)}if(n.hour12!==void 0&&e!==`date`)try{i.hour12=ue(n.hour12)}catch{t.onError(`bad-option`,`Invalid :${e} hour12 option value`)}if(n.timeZone!==void 0){let r;try{r=A(n.timeZone)}catch{t.onError(`bad-option`,`Invalid :${e} timeZone option value`)}if(r===`input`)i.timeZone===void 0&&t.onError(`bad-operand`,`Missing input timeZone value for :${e}`);else if(r!==void 0){if(i.timeZone!==void 0&&r!==i.timeZone)throw new u(`bad-option`,`Time zone conversion is not supported`);i.timeZone=r}}if(e!==`time`){let r=e===`date`?`fields`:`dateFields`,a=e===`date`?`length`:`dateLength`,o=F(t,n,r,pe)??`year-month-day`,s=F(t,n,a,me),c=new Set(o.split(`-`));c.has(`year`)&&(i.year=`numeric`),c.has(`month`)&&(i.month=s===`long`?`long`:s===`short`?`numeric`:`short`),c.has(`day`)&&(i.day=`numeric`),c.has(`weekday`)&&(i.weekday=s===`long`?`long`:`short`)}if(e!==`date`){switch(F(t,n,e===`time`?`precision`:`timePrecision`,he)){case`hour`:i.hour=`numeric`;break;case`second`:i.hour=`numeric`,i.minute=`numeric`,i.second=`numeric`;break;default:i.hour=`numeric`,i.minute=`numeric`}i.timeZoneName=F(t,n,`timeZoneStyle`,ge)}let o=new Intl.DateTimeFormat(t.locales,i),s=t.dir,c,l;return{type:`datetime`,get dir(){return s??=(c??=o.resolvedOptions().locale,O(c)),s},get options(){return{...i}},toParts(){let e=o.formatToParts(a);return c??=o.resolvedOptions().locale,s??=O(c),s===`ltr`||s===`rtl`?[{type:`datetime`,dir:s,locale:c,parts:e}]:[{type:`datetime`,locale:c,parts:e}]},toString(){return l??=o.format(a),l},valueOf:()=>a}}function F(e,t,n,r){let i=t[n];if(i!==void 0)try{let e=A(i);if(r&&!r.has(e))throw Error();return e}catch{e.onError(`bad-option`,`Invalid value for ${n} option`)}}function be(e,t,n){let{value:r,options:i}=j(n),a;try{a=`add`in t?k(t.add):-1}catch{throw new u(`bad-option`,`Value ${t.add} is not valid for :offset option add`)}let o;try{o=`subtract`in t?k(t.subtract):-1}catch{throw new u(`bad-option`,`Value ${t.subtract} is not valid for :offset option subtract`)}if(a<0==o<0)throw new u(`bad-option`,`Exactly one of "add" or "subtract" is required as an :offset option`);let s=a<0?-o:a;return typeof r==`number`?r+=s:r+=BigInt(s),N(e,{},{valueOf:()=>r,options:i})}function xe(e,t,n){let r=j(n),i=Object.assign({},r.options,{localeMatcher:e.localeMatcher,style:`percent`});for(let[n,r]of Object.entries(t))if(r!==void 0)try{switch(n){case`roundingMode`:case`roundingPriority`:case`signDisplay`:case`trailingZeroDisplay`:case`useGrouping`:i[n]=A(r);break;case`minimumFractionDigits`:case`maximumFractionDigits`:case`minimumSignificantDigits`:case`maximumSignificantDigits`:i[n]=k(r);break}}catch{e.onError(`bad-option`,`Value ${r} is not valid for :percent option ${n}`)}return M(e,r.value,i,!0)}function I(e,t,n){let r=n===void 0?``:String(n),i=r.normalize();return{type:`string`,dir:e.dir??`auto`,selectKey:e=>e.has(i)?i:null,toParts(){let{dir:t}=e,n=e.locales[0];return t===`ltr`||t===`rtl`?[{type:`string`,dir:t,locale:n,value:r}]:[{type:`string`,locale:n,value:r}]},toString:()=>r,valueOf:()=>r}}function L(e,t,n){let r=j(n),i=Object.assign({},r.options,{localeMatcher:e.localeMatcher,style:`unit`});for(let[n,r]of Object.entries(t))if(r!==void 0)try{switch(n){case`signDisplay`:case`roundingMode`:case`roundingPriority`:case`trailingZeroDisplay`:case`unit`:case`unitDisplay`:case`useGrouping`:i[n]=A(r);break;case`minimumIntegerDigits`:case`minimumFractionDigits`:case`maximumFractionDigits`:case`minimumSignificantDigits`:case`maximumSignificantDigits`:case`roundingIncrement`:i[n]=k(r);break}}catch(t){t instanceof o?e.onError(t):e.onError(`bad-option`,`Value ${r} is not valid for :currency option ${n}`)}if(!i.unit)throw new u(`bad-operand`,`A unit identifier is required for :unit`);return M(e,r.value,i,!1)}let R={integer:de,number:N,offset:be,string:I};R=Object.freeze(Object.assign(Object.create(null),R));let z={currency:fe,date:ve,datetime:_e,percent:xe,time:ye,unit:L};z=Object.freeze(Object.assign(Object.create(null),z));const B=Symbol(`bidi-isolate`),V=(e=`�`)=>({type:`fallback`,source:e,toParts:()=>[{type:`fallback`,source:e}],toString:()=>`{${e}}`}),Se=(e,t)=>({type:`unknown`,source:e,dir:`auto`,toParts:()=>[{type:`unknown`,value:t}],toString:()=>String(t),valueOf:()=>t});var H=class{#e;#t;#n;dir;id;constructor(e,t,n){this.#e=e,this.#n=t,this.dir=void 0;let r=n&&Object.hasOwn(n,`u:dir`)?n[`u:dir`]:void 0;if(r){let t=String(J(e,r));if(t===`ltr`||t===`rtl`||t===`auto`)this.dir=t;else if(t!==`inherit`){let t=new u(`bad-option`,`Unsupported value for u:dir option`);t.source=Y(r),e.onError(t)}}let i=n&&Object.hasOwn(n,`u:id`)?n[`u:id`]:void 0;if(this.id=i?String(J(e,i)):void 0,n){this.#t=new Set;for(let[e,t]of Object.entries(n))t.type===`literal`&&this.#t.add(e)}}get literalOptionKeys(){return new Set(this.#t)}get localeMatcher(){return this.#e.localeMatcher}get locales(){return this.#e.locales.map(String)}onError(e,t){let n;e instanceof u?n=e:typeof e==`string`&&typeof t==`string`?n=new u(e,t):(n=new u(`function-error`,String(e)),n.cause=e),n.source=this.#n,this.#e.onError(n)}};function Ce(e,t,{name:n,options:r}){let i=`:${n}`,a=Y(t)??i;try{let o=t?[J(e,t)]:[],s=e.functions[n];if(!s)throw new l(`unknown-function`,`Unknown function ${i}`,a);let c=new H(e,a,r),u=s(c,we(e,r),...o);if(typeof u!=`object`||!u||typeof u.type!=`string`)throw new l(`bad-function-result`,`Function ${i} did not return a MessageValue`,a);let d={source:a};return c.dir&&(d.dir=c.dir,d[B]=!0),c.id&&typeof u.toParts==`function`&&(d.toParts=()=>{let e=u.toParts();for(let t of e)t.id=c.id;return e}),{...u,...d}}catch(t){return e.onError(t instanceof o?t:new l(`bad-function-result`,String(t),a,t)),V(a)}}function we(e,t){let n=Object.create(null);if(t)for(let[r,i]of Object.entries(t))r.startsWith(`u:`)||(n[r]=J(e,i));return n}function U(e,{arg:t,functionRef:n}){if(n)return Ce(e,t,n);switch(t?.type){case`literal`:{let n=`|${t.value}|`,r=I(new H(e,n),{},t.value);return r.source=n,r}case`variable`:return q(e,t);default:throw Error(`Unsupported expression: ${t?.type}`)}}var W=class{expression;scope;constructor(e,t){this.expression=e,this.scope=t}};const Te=e=>e!==null&&(typeof e==`object`||typeof e==`function`);function G(e,t){if(Te(e)){if(t in e)return e[t];let n=t.split(`.`);for(let t=n.length-1;t>0;--t){let r=n.slice(0,t).join(`.`);if(r in e){let i=n.slice(t).join(`.`);return G(e[r],i)}}for(let[n,r]of Object.entries(e))if(n.normalize()===t)return r}}function K(e,{name:t}){let n=G(e.scope,t);if(n===void 0){let n=`$`+t,r=`Variable not available: ${n}`;e.onError(new l(`unresolved-variable`,r,n))}else if(n instanceof W){let r=U(n.scope?{...e,scope:n.scope}:e,n.expression);return e.scope[t]=r,e.localVars.add(r),r}return n}function q(e,t){let n=`$`+t.name,r=K(e,t);if(r===void 0)return V(n);let i=typeof r;if(i===`object`){let t=r;if(t.type===`fallback`)return V(n);if(e.localVars.has(t))return t.source=n,t;r instanceof Number?i=`number`:r instanceof String&&(i=`string`)}let a;switch(i){case`bigint`:case`number`:a=e.functions.number;break;case`string`:a=e.functions.string;break;default:return Se(n,r)}let o=new H(e,n),s=a(o,{},r);return s.source=n,s}function J(e,t){switch(t.type){case`literal`:return t.value;case`variable`:return K(e,t);default:throw Error(`Unsupported value: ${t.type}`)}}function Y(e){switch(e?.type){case`literal`:return`|`+e.value.replaceAll(`\\`,`\\\\`).replaceAll(`|`,`\\|`)+`|`;case`variable`:return`$`+e.name;default:return}}function X(e,{kind:t,name:n,options:r}){let i={type:`markup`,kind:t,name:n},a=r?Object.entries(r):null;if(a?.length){i.options={};for(let[t,n]of a)if(t===`u:dir`){let r=new u(`bad-option`,`The option ${t} is not valid for markup`);r.source=Y(n),e.onError(r)}else{let r=J(e,n);typeof r==`object`&&typeof r?.valueOf==`function`&&(r=r.valueOf()),t===`u:id`?i.id=String(r):i.options[t]=r}}return i}function Z(e,t){if(t.type===`message`)return t.pattern;let n=t.selectors.map(t=>{let n=q(e,t),r;return typeof n.selectKey==`function`?r=n.selectKey.bind(n):(e.onError(new l(`bad-selector`,`Selector does not support selection`,n.source)),r=()=>null),{selectKey:r,source:n.source,best:null,keys:null}}),r=t.variants;loop:for(let i=0;i<n.length;++i){let a=n[i];if(!a.keys){a.keys=new Set;for(let{keys:e}of r){let t=e[i];if(!t)break loop;t.type!==`*`&&a.keys.add(t.value)}}try{a.best=a.keys.size?a.selectKey(a.keys):null}catch(t){e.onError(new l(`bad-selector`,`Selection failed`,a.source,t)),a.selectKey=()=>null,a.best=null}if(r=r.filter(e=>{let t=e.keys[i];return t.type===`*`?a.best==null:a.best===t.value}),r.length===0){if(i===0)break;let e=n[i-1];e.best==null?e.keys?.clear():e.keys?.delete(e.best);for(let e=i;e<n.length;++e)n[e].keys=null;r=t.variants,i=-1}}let i=r[0];return i?i.value:(e.onError(new l(`no-match`,`No variant was selected!?`,`.match`)),[])}var Ee=class{#e;#t;#n;#r;#i;#a;constructor(e,t,n){this.#e=n?.bidiIsolation!==`none`,this.#n=n?.localeMatcher??`best fit`,this.#r=Array.isArray(e)?e.map(e=>new Intl.Locale(e)):e?[new Intl.Locale(e)]:[],this.#t=n?.dir??O(this.#r[0]),this.#i=typeof t==`string`?ee(t):t,le(this.#i),this.#a=n?.functions?Object.assign(Object.create(null),R,n.functions):R}format(e,t){let n=this.#o(e,t),r=``;for(let e of Z(n,this.#i))if(typeof e==`string`)r+=e;else if(e.type===`markup`)X(n,e);else{let t;try{if(t=U(n,e),typeof t.toString==`function`)if(this.#e&&(this.#t!==`ltr`||t.dir!==`ltr`||t[B])){let e=t.dir===`ltr`?``:t.dir===`rtl`?``:``;r+=e+t.toString()+``}else r+=t.toString();else{let e=new u(`not-formattable`,`Message part is not formattable`);throw e.source=t.source,e}}catch(e){n.onError(e);let i=`{${t?.source??`�`}}`;r+=this.#e?``+i+``:i}}return r}formatToParts(e,t){let n=this.#o(e,t),r=[];for(let e of Z(n,this.#i))if(typeof e==`string`)r.push({type:`text`,value:e});else if(e.type===`markup`)r.push(X(n,e));else{let t;try{if(t=U(n,e),typeof t.toParts==`function`){let e=t.toParts();if(this.#e&&(this.#t!==`ltr`||t.dir!==`ltr`||t[B])){let n=t.dir===`ltr`?``:t.dir===`rtl`?``:``;r.push({type:`bidiIsolation`,value:n},...e,{type:`bidiIsolation`,value:``})}else r.push(...e)}else{let e=new u(`not-formattable`,`Message part is not formattable`);throw e.source=t.source,e}}catch(e){n.onError(e);let i={type:`fallback`,source:t?.source??`�`};this.#e?r.push({type:`bidiIsolation`,value:``},i,{type:`bidiIsolation`,value:``}):r.push(i)}}return r}#o(e,t=e=>{try{process.emitWarning(e)}catch{console.warn(e)}}){let n={...e};for(let t of this.#i.declarations)n[t.name]=new W(t.value,t.type===`input`?e??{}:void 0);return{onError:t,localeMatcher:this.#n,locales:this.#r,localVars:new WeakSet,functions:this.#a,scope:n}}};const Q=new Map;function De(e,t,n){let r=Q.get(e);r||(r=new Map,Q.set(e,r));let i=r.get(t);return i||(i=new Ee([e],n,{functions:z}),r.set(t,i),i)}function Oe(e,t){if(!t?.vars||Object.keys(t.vars).length===0||t.formatting===`identity`)return e;let{locale:n,vars:r}=t;return De(n,e,e).format(r,e=>{console.warn(`[fanee] Failed to format message: ${e}`)})}function ke(){return{resources:{},defaultLocale:`en`,currentLocale:`en`,baseNamespace:``,formatting:`mf2`,translate:Oe}}var $=class{state;queue;listeners;constructor(){this.state=ke(),this.queue=Promise.resolve(),this.listeners=new Set}notify(){let e=this.state;for(let t of this.listeners)t(e)}use(e){return this.queue=this.queue.then(async()=>{this.state=await e(this.state),this.notify()}),this}config(e){return this.queue=this.queue.then(async()=>{this.state={...this.state,...e},this.notify()}),this}then(e,t){return this.queue.then(e,t)}resolveContext(e){let{currentLocale:t,baseNamespace:n}=this.state;return{locale:e?.locale??t,ns:e?.namespace?n?`${n}:${e.namespace}`:e.namespace:n}}localize(e,t,n,r){if(!e)return n;let i=e[t];if(!i&&(i=e[this.state.defaultLocale],!i))return n;let a=i[n];return a===void 0?n:r===void 0||Object.keys(r).length===0?a:this.state.translate(a,{locale:t,vars:r,formatting:this.state.formatting})}t(e,t){let{locale:n,ns:r}=this.resolveContext();return this.localize(this.state.resources[r],n,e,t)}getT(e){let{locale:t,ns:n}=this.resolveContext(e),r=this.state.resources[n];return(e,n)=>this.localize(r,t,e,n)}tAll(e,t){let{baseNamespace:n,resources:r}=this.state,i=r[n],a={};if(!i)return a;for(let n of Object.keys(i))a[n]=this.localize(i,n,e,t);return a}getLocale(){return this.state.currentLocale}getLocales(){let e=new Set;for(let t of Object.values(this.state.resources))for(let n of Object.keys(t))e.add(n);return Array.from(e).sort()}getAllTranslations(){return this.state.resources}getTranslationsForNamespace(e){return this.state.resources[e]}ready(){return this.queue}setLocale(e){this.state.currentLocale=e,this.notify()}setNamespace(e){this.state.baseNamespace=e,this.notify()}subscribe(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}};const Ae=new $;export{$ as FaneeRuntime,Ae as i18n};
|
|
3
|
+
//# sourceMappingURL=index.js.map
|