@alxia/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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Steve Tsala
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # @alxia/i18n
2
+
3
+ Translations for [alxia](https://www.npmjs.com/package/@alxia/core), on
4
+ [`@nxgt/i18n`](https://www.npmjs.com/package/@nxgt/i18n): `t()` bound to the
5
+ request's language, keys typed by your catalogue, ICU messages — plurals,
6
+ selects, numbers.
7
+
8
+ ```sh
9
+ bun add @alxia/i18n @alxia/language @nxgt/i18n@^2 @alxia/core
10
+ bun add -d typescript
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ import { alxia } from '@alxia/core';
17
+ import { createI18n } from '@alxia/i18n';
18
+ import { resources as shared } from '@nxgt/i18n';
19
+
20
+ const en = { ...shared.en, cart: { items: '{count, plural, =0 {No items} one {One item} other {# items}}' } };
21
+ const fr = { ...shared.fr, cart: { items: '{count, plural, =0 {Aucun article} one {Un article} other {# articles}}' } };
22
+
23
+ export const i18n = createI18n({ resources: { en, fr }, fallback: 'en' });
24
+
25
+ const app = alxia()
26
+ .use(i18n)
27
+ .get('/cart', ({ t, reply }) => reply(200, t('cart.items', { count: 3 }))); // '3 articles' in French
28
+
29
+ app.listen(3000);
30
+ ```
31
+
32
+ - **The language** is `@alxia/language`'s, among the catalogues' languages:
33
+ the query, a cookie, `Accept-Language`, then `fallback`. Its options pass
34
+ through: `createI18n({ resources, fallback, order: ['path', 'header'] })`.
35
+ - **Keys** are typed by the fallback's catalogue: `t('cart.itmes')` is a
36
+ compile error. A key a language lacks answers itself.
37
+ - **`@nxgt/i18n`'s shared keys** — `errors.not-found`, `zod.*` — are yours by
38
+ spreading its `resources` into your catalogues.
39
+
40
+ ## Reading the app's context
41
+
42
+ Annotate `resolve`'s parameter to speak the language a signed-in user saved.
43
+ The plugin then requires what it reads: an app that does not give `user`
44
+ before it cannot use it.
45
+
46
+ ```ts
47
+ import { alxia, type BaseContext } from '@alxia/core';
48
+ import { createI18n } from '@alxia/i18n';
49
+
50
+ const en = { home: { title: 'Welcome' } };
51
+ const fr = { home: { title: 'Bienvenue' } };
52
+
53
+ const auth = alxia().derive(({ request }) => ({
54
+ user: request.headers.has('authorization') ? { language: 'fr' } : null, // your sign-in
55
+ }));
56
+
57
+ const byUser = createI18n({
58
+ resources: { en, fr },
59
+ fallback: 'en',
60
+ order: ['query'],
61
+ resolve: ({ user }: BaseContext & { user: { language: string } | null }) => user?.language,
62
+ });
63
+
64
+ alxia().use(auth).use(byUser); // auth derives user
65
+ alxia().use(byUser); // a compile error: this app gives no `user`
66
+ ```
67
+
68
+ Unannotated, `resolve` reads the request alone and the plugin requires
69
+ nothing; annotated `any`, the plugin is refused on every app.
70
+
71
+ ## Anywhere
72
+
73
+ ```ts
74
+ // a service, or an error's message: no language passed
75
+ export const describeCart = (count: number) => i18n.t('cart.items', { count });
76
+ ```
77
+
78
+ `i18n.t()` translates in the language of the request it runs in — through
79
+ every `await`, an `onError` hook included — and in the fallback outside
80
+ one, or before the language is read: in an `onRequest` hook, or a route
81
+ declared before the plugin
82
+ ([troubleshooting](https://github.com/softistx/alxia/blob/develop/packages/i18n/docs/troubleshooting.md#i18nt-answers-in-the-fallback-before-the-language-is-read)).
83
+ `i18n.language()` says which.
84
+
85
+ ### `@nxgt/i18n`'s own `translate`
86
+
87
+ `createI18n()` registers the request's language as one of `@nxgt/i18n`'s
88
+ language sources: its `getLanguage()` and `translate` — and every nxgt
89
+ package that translates through them, an error's message — speak the alxia
90
+ request's language too.
91
+
92
+ ## Cached responses
93
+
94
+ A response in the request's language varies by what decided it: give
95
+ `@alxia/cache` the same headers, `vary: ['accept-language', 'cookie']`.
96
+
97
+ ## API
98
+
99
+ | export | |
100
+ | --- | --- |
101
+ | `createI18n({ resources, fallback, …languageOptions })` | the plugin — routes after it read `t` and `language` — with `t()`, `language()` and `supported` |
102
+ | `I18nOptions` | its options: `resources`, `fallback`, and every `@alxia/language` option but `supported`; `resolve` may be annotated to read the app's context |
103
+ | `KeyOf<Catalogue>`, `Translate<Key>`, `Catalogues`, `I18nContext<Key>` | its types |
104
+
105
+ ## Documentation
106
+
107
+ - [Guide](https://github.com/softistx/alxia/tree/develop/packages/i18n/docs): the catalogues and every option, reading what an earlier plugin added, what the routes read, ICU messages and typed keys, `t()` outside a route, and `@nxgt/i18n`'s own `translate`.
108
+ - [Troubleshooting](https://github.com/softistx/alxia/blob/develop/packages/i18n/docs/troubleshooting.md): an error, a key shown instead of a message, or a response in the wrong language, and what to do about it.
109
+ - [Roadmap](https://github.com/softistx/alxia/blob/develop/packages/i18n/docs/roadmap.md): what is coming, and what is not planned.
package/dist/i18n.d.ts ADDED
@@ -0,0 +1,56 @@
1
+ import { type BaseContext, type RequiresOf } from '@alxia/core';
2
+ import { type LanguageOptions } from '@alxia/language';
3
+ import { type Path, type TranslationContext } from '@nxgt/i18n';
4
+ /** Catalogues by language: `{ en: { greeting: 'Hello {name}' }, fr: … }`. */
5
+ export type Catalogues = Readonly<Record<string, Readonly<Record<string, unknown>>>>;
6
+ /** Every key of a catalogue, dotted: `users.greeting`. */
7
+ export type KeyOf<Catalogue> = Path<Catalogue> & string;
8
+ /** A translation of a key, in a language, formatted with ICU's `context`. */
9
+ export type Translate<Key extends string> = (key: Key, context?: TranslationContext) => string;
10
+ /**
11
+ * `Ctx` is the type `resolve`'s parameter is annotated with —
12
+ * `BaseContext & { user: User }`, or `{ user: User }` alone — and
13
+ * `BaseContext` when it is not: what the plugin then requires of the app.
14
+ */
15
+ export interface I18nOptions<C extends Catalogues, Fallback extends keyof C & string, Ctx extends object = BaseContext> extends Omit<LanguageOptions<keyof C & string, Ctx>, 'supported' | 'fallback'> {
16
+ /**
17
+ * The catalogues, one per language: their keys are the languages
18
+ * supported. Spread `@nxgt/i18n`'s `resources` into them for its shared
19
+ * keys — `errors.not-found`, `zod.*`.
20
+ */
21
+ readonly resources: C;
22
+ /** The language spoken when the request names none, and whose keys type `t`. */
23
+ readonly fallback: Fallback;
24
+ }
25
+ /** What the routes behind the plugin read. */
26
+ export interface I18nContext<Key extends string> {
27
+ /** Translates into the request's language. */
28
+ readonly t: Translate<Key>;
29
+ }
30
+ /**
31
+ * Translations, as a plugin, on [`@nxgt/i18n`](https://www.npmjs.com/package/@nxgt/i18n):
32
+ * `@alxia/language` reads the request's language among the catalogues', and
33
+ * the routes declared after it read `language` and `t`, bound to it. Keys
34
+ * are typed by the fallback's catalogue; messages are ICU — plurals,
35
+ * selects, numbers — and a missing key answers itself.
36
+ *
37
+ * The plugin's own `t()` translates anywhere a request runs — a service, an
38
+ * error's message — in that request's language, and in the fallback outside.
39
+ *
40
+ * ```ts
41
+ * const i18n = createI18n({ resources: { en, fr }, fallback: 'en' });
42
+ * app.use(i18n).get('/', ({ t, reply }) => reply(200, t('home.title')));
43
+ * ```
44
+ *
45
+ * The plugin registers the request's language as one of `@nxgt/i18n`'s
46
+ * language sources, so its own `getLanguage()` and `translate` — and every
47
+ * nxgt package that translates through them — speak it too.
48
+ */
49
+ export declare function createI18n<const C extends Catalogues, const Fallback extends keyof C & string, Ctx extends object = BaseContext>(options: I18nOptions<C, Fallback, Ctx>): import("@alxia/core").Alxia<RequiresOf<Ctx, "resolve"> & import("@alxia/core").Empty & import("@alxia/language").LanguageContext<keyof C & string> & I18nContext<KeyOf<C[Fallback]>>, import("@alxia/core").Empty & {}, "", never> & import("@alxia/core").Requiring<RequiresOf<Ctx, "resolve">> & {
50
+ /** Translates into the current request's language, or the fallback outside one. */
51
+ t: Translate<KeyOf<C[Fallback]>>;
52
+ /** The current request's language, or the fallback outside one. */
53
+ language: () => keyof C & string;
54
+ supported: (keyof C & string)[];
55
+ };
56
+ //# sourceMappingURL=i18n.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,WAAW,EAAgB,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,KAAK,eAAe,EAAY,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAEN,KAAK,IAAI,EAET,KAAK,kBAAkB,EACvB,MAAM,YAAY,CAAC;AAEpB,6EAA6E;AAC7E,MAAM,MAAM,UAAU,GAAG,QAAQ,CAChC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CACjD,CAAC;AAEF,0DAA0D;AAC1D,MAAM,MAAM,KAAK,CAAC,SAAS,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC;AAExD,6EAA6E;AAC7E,MAAM,MAAM,SAAS,CAAC,GAAG,SAAS,MAAM,IAAI,CAC3C,GAAG,EAAE,GAAG,EACR,OAAO,CAAC,EAAE,kBAAkB,KACxB,MAAM,CAAC;AAEZ;;;;GAIG;AACH,MAAM,WAAW,WAAW,CAC3B,CAAC,SAAS,UAAU,EACpB,QAAQ,SAAS,MAAM,CAAC,GAAG,MAAM,EACjC,GAAG,SAAS,MAAM,GAAG,WAAW,CAC/B,SAAQ,IAAI,CACZ,eAAe,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,GAAG,CAAC,EACtC,WAAW,GAAG,UAAU,CACxB;IACD;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC;IACtB,gFAAgF;IAChF,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;CAC5B;AAWD,8CAA8C;AAC9C,MAAM,WAAW,WAAW,CAAC,GAAG,SAAS,MAAM;IAC9C,8CAA8C;IAC9C,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,UAAU,CACzB,KAAK,CAAC,CAAC,SAAS,UAAU,EAC1B,KAAK,CAAC,QAAQ,SAAS,MAAM,CAAC,GAAG,MAAM,EACvC,GAAG,SAAS,MAAM,GAAG,WAAW,EAC/B,OAAO,EAAE,WAAW,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC;IAiDtC,mFAAmF;OACvB,SAAS,oBAAK;IAC1E,mEAAmE;;;EAIpE"}
@@ -0,0 +1,2 @@
1
+ export { type Catalogues, createI18n, type I18nContext, type I18nOptions, type KeyOf, type Translate, } from './i18n';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,UAAU,EACf,UAAU,EACV,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,KAAK,EACV,KAAK,SAAS,GACd,MAAM,QAAQ,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,45 @@
1
+ // src/i18n.ts
2
+ import { AsyncLocalStorage } from "node:async_hooks";
3
+ import { definePlugin } from "@alxia/core";
4
+ import { language } from "@alxia/language";
5
+ import {
6
+ createTranslator,
7
+ registerLanguageSource
8
+ } from "@nxgt/i18n";
9
+ var requests = new AsyncLocalStorage;
10
+ var requestLanguage = () => requests.getStore()?.language;
11
+ function createI18n(options) {
12
+ const { resources, fallback, resolve, ...detect } = options;
13
+ const supported = Object.keys(resources);
14
+ const translator = createTranslator(resources);
15
+ const current = new AsyncLocalStorage;
16
+ const spoken = () => current.getStore()?.language ?? fallback;
17
+ const translate = (lang) => (key, context) => translator(key, context, lang);
18
+ registerLanguageSource(requestLanguage);
19
+ const detected = language({
20
+ ...detect,
21
+ supported,
22
+ fallback,
23
+ ...resolve === undefined ? {} : { resolve }
24
+ });
25
+ const plugin = definePlugin()((app) => app.around((_ctx, next) => current.run({}, () => requests.run({}, next))).use(detected).derive(({ language: lang }) => {
26
+ const own = current.getStore();
27
+ if (own !== undefined)
28
+ own.language = lang;
29
+ const heard = requests.getStore();
30
+ if (heard !== undefined)
31
+ heard.language ??= lang;
32
+ return { t: translate(lang) };
33
+ }));
34
+ return Object.assign(plugin, {
35
+ t: (key, context) => translate(spoken())(key, context),
36
+ language: spoken,
37
+ supported
38
+ });
39
+ }
40
+ export {
41
+ createI18n
42
+ };
43
+
44
+ //# debugId=700D328B5FADEAA064756E2164756E21
45
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/i18n.ts"],
4
+ "sourcesContent": [
5
+ "import { AsyncLocalStorage } from 'node:async_hooks';\nimport { type BaseContext, definePlugin, type RequiresOf } from '@alxia/core';\nimport { type LanguageOptions, language } from '@alxia/language';\nimport {\n\tcreateTranslator,\n\ttype Path,\n\tregisterLanguageSource,\n\ttype TranslationContext,\n} from '@nxgt/i18n';\n\n/** Catalogues by language: `{ en: { greeting: 'Hello {name}' }, fr: … }`. */\nexport type Catalogues = Readonly<\n\tRecord<string, Readonly<Record<string, unknown>>>\n>;\n\n/** Every key of a catalogue, dotted: `users.greeting`. */\nexport type KeyOf<Catalogue> = Path<Catalogue> & string;\n\n/** A translation of a key, in a language, formatted with ICU's `context`. */\nexport type Translate<Key extends string> = (\n\tkey: Key,\n\tcontext?: TranslationContext,\n) => string;\n\n/**\n * `Ctx` is the type `resolve`'s parameter is annotated with —\n * `BaseContext & { user: User }`, or `{ user: User }` alone — and\n * `BaseContext` when it is not: what the plugin then requires of the app.\n */\nexport interface I18nOptions<\n\tC extends Catalogues,\n\tFallback extends keyof C & string,\n\tCtx extends object = BaseContext,\n> extends Omit<\n\t\tLanguageOptions<keyof C & string, Ctx>,\n\t\t'supported' | 'fallback'\n\t> {\n\t/**\n\t * The catalogues, one per language: their keys are the languages\n\t * supported. Spread `@nxgt/i18n`'s `resources` into them for its shared\n\t * keys — `errors.not-found`, `zod.*`.\n\t */\n\treadonly resources: C;\n\t/** The language spoken when the request names none, and whose keys type `t`. */\n\treadonly fallback: Fallback;\n}\n\n/**\n * The request's language as `@nxgt/i18n` hears it: the first an i18n plugin\n * read, when an app uses several. Opened fresh by each plugin's `around`.\n */\nconst requests = new AsyncLocalStorage<{ language?: string }>();\n\n/** `@nxgt/i18n`'s source: one function, so registering it again keeps one. */\nconst requestLanguage = () => requests.getStore()?.language;\n\n/** What the routes behind the plugin read. */\nexport interface I18nContext<Key extends string> {\n\t/** Translates into the request's language. */\n\treadonly t: Translate<Key>;\n}\n\n/**\n * Translations, as a plugin, on [`@nxgt/i18n`](https://www.npmjs.com/package/@nxgt/i18n):\n * `@alxia/language` reads the request's language among the catalogues', and\n * the routes declared after it read `language` and `t`, bound to it. Keys\n * are typed by the fallback's catalogue; messages are ICU — plurals,\n * selects, numbers — and a missing key answers itself.\n *\n * The plugin's own `t()` translates anywhere a request runs — a service, an\n * error's message — in that request's language, and in the fallback outside.\n *\n * ```ts\n * const i18n = createI18n({ resources: { en, fr }, fallback: 'en' });\n * app.use(i18n).get('/', ({ t, reply }) => reply(200, t('home.title')));\n * ```\n *\n * The plugin registers the request's language as one of `@nxgt/i18n`'s\n * language sources, so its own `getLanguage()` and `translate` — and every\n * nxgt package that translates through them — speak it too.\n */\nexport function createI18n<\n\tconst C extends Catalogues,\n\tconst Fallback extends keyof C & string,\n\tCtx extends object = BaseContext,\n>(options: I18nOptions<C, Fallback, Ctx>) {\n\ttype Language = keyof C & string;\n\ttype Key = KeyOf<C[Fallback]>;\n\tconst { resources, fallback, resolve, ...detect } = options;\n\tconst supported = Object.keys(resources) as Language[];\n\tconst translator = createTranslator<Key>(\n\t\tresources as Record<string, unknown>,\n\t);\n\t/**\n\t * This plugin's language for the request running, once `@alxia/language`\n\t * has read it. A global `around` hook opens it fresh for each request —\n\t * one made from inside another included — so it holds for everything the\n\t * request runs, `onError` hooks too, which run after the route failed.\n\t */\n\tconst current = new AsyncLocalStorage<{ language?: Language }>();\n\tconst spoken = (): Language => current.getStore()?.language ?? fallback;\n\tconst translate =\n\t\t(lang: Language): Translate<Key> =>\n\t\t(key, context) =>\n\t\t\ttranslator(key, context, lang as never);\n\n\t// nxgt's own getLanguage() and translate speak the request's language too.\n\tregisterLanguageSource(requestLanguage);\n\n\t// The plugin requires what `resolve` reads, and `use` checks the app gives\n\t// it; the `language()` inside is then handed `resolve` as reading only\n\t// `BaseContext`, since a context that is a type parameter defers the check.\n\tconst detected = language<Language>({\n\t\t...detect,\n\t\tsupported,\n\t\tfallback,\n\t\t...(resolve === undefined\n\t\t\t? {}\n\t\t\t: { resolve: resolve as (ctx: BaseContext) => string | undefined }),\n\t});\n\tconst plugin = definePlugin<RequiresOf<Ctx, 'resolve'>>()((app) =>\n\t\tapp\n\t\t\t.around((_ctx, next) => current.run({}, () => requests.run({}, next)))\n\t\t\t.use(detected)\n\t\t\t.derive(({ language: lang }): I18nContext<Key> => {\n\t\t\t\tconst own = current.getStore();\n\t\t\t\tif (own !== undefined) own.language = lang;\n\t\t\t\tconst heard = requests.getStore();\n\t\t\t\tif (heard !== undefined) heard.language ??= lang;\n\t\t\t\treturn { t: translate(lang) };\n\t\t\t}),\n\t);\n\n\treturn Object.assign(plugin, {\n\t\t/** Translates into the current request's language, or the fallback outside one. */\n\t\tt: ((key, context) => translate(spoken())(key, context)) as Translate<Key>,\n\t\t/** The current request's language, or the fallback outside one. */\n\t\tlanguage: spoken,\n\t\tsupported,\n\t});\n}\n"
6
+ ],
7
+ "mappings": ";AAAA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAgDA,IAAM,WAAW,IAAI;AAGrB,IAAM,kBAAkB,MAAM,SAAS,SAAS,GAAG;AA2B5C,SAAS,UAIf,CAAC,SAAwC;AAAA,EAGzC,QAAQ,WAAW,UAAU,YAAY,WAAW;AAAA,EACpD,MAAM,YAAY,OAAO,KAAK,SAAS;AAAA,EACvC,MAAM,aAAa,iBAClB,SACD;AAAA,EAOA,MAAM,UAAU,IAAI;AAAA,EACpB,MAAM,SAAS,MAAgB,QAAQ,SAAS,GAAG,YAAY;AAAA,EAC/D,MAAM,YACL,CAAC,SACD,CAAC,KAAK,YACL,WAAW,KAAK,SAAS,IAAa;AAAA,EAGxC,uBAAuB,eAAe;AAAA,EAKtC,MAAM,WAAW,SAAmB;AAAA,OAChC;AAAA,IACH;AAAA,IACA;AAAA,OACI,YAAY,YACb,CAAC,IACD,EAAE,QAA6D;AAAA,EACnE,CAAC;AAAA,EACD,MAAM,SAAS,aAAyC,EAAE,CAAC,QAC1D,IACE,OAAO,CAAC,MAAM,SAAS,QAAQ,IAAI,CAAC,GAAG,MAAM,SAAS,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,EACpE,IAAI,QAAQ,EACZ,OAAO,GAAG,UAAU,WAA6B;AAAA,IACjD,MAAM,MAAM,QAAQ,SAAS;AAAA,IAC7B,IAAI,QAAQ;AAAA,MAAW,IAAI,WAAW;AAAA,IACtC,MAAM,QAAQ,SAAS,SAAS;AAAA,IAChC,IAAI,UAAU;AAAA,MAAW,MAAM,aAAa;AAAA,IAC5C,OAAO,EAAE,GAAG,UAAU,IAAI,EAAE;AAAA,GAC5B,CACH;AAAA,EAEA,OAAO,OAAO,OAAO,QAAQ;AAAA,IAE5B,GAAI,CAAC,KAAK,YAAY,UAAU,OAAO,CAAC,EAAE,KAAK,OAAO;AAAA,IAEtD,UAAU;AAAA,IACV;AAAA,EACD,CAAC;AAAA;",
8
+ "debugId": "700D328B5FADEAA064756E2164756E21",
9
+ "names": []
10
+ }
package/docs/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # @alxia/i18n documentation
2
+
3
+ The [package README](../README.md) is the short version. This folder is
4
+ the long one: the catalogues and every option, what the routes behind the
5
+ plugin read, how messages are formatted and keys typed, where the
6
+ request's language reaches and where it does not, and what to do when a
7
+ response shows a key or the wrong language.
8
+
9
+ | Page | Read it when |
10
+ | --- | --- |
11
+ | [Guide](guide.md) | writing the catalogues, choosing where the language is read from, reading a user's saved language from an earlier plugin, typing a function that translates, translating in a service or an error handler, caching the responses, or testing them |
12
+ | [Troubleshooting](troubleshooting.md) | `createI18n()` threw at start-up, `tsc` refused a key, an option or `use(i18n)`, the log shows an ICU error, or a response shows a key or the wrong language |
13
+ | [Roadmap](roadmap.md) | wondering what is coming, and what is not planned |