@krizaka/i18n 0.1.0-beta.2 → 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 CHANGED
@@ -26,7 +26,7 @@ Node ≥ 20, ESM only. React (≥ 18) only for `./rich` and `./react`; TypeScrip
26
26
  | :-- | :-- | :-- |
27
27
  | `@krizaka/i18n` | `createI18n`, `format`, `placeholdersOf`, `isPluralMessage`, the types | no |
28
28
  | `@krizaka/i18n/rich` | `<Rich text href? renderLink? renderBold? slots?>` | no hook — Server Components too |
29
- | `@krizaka/i18n/react` | `createI18nReact(i18n)` → `I18nProvider`, `useI18n` | client (`"use client"`) |
29
+ | `@krizaka/i18n/react` | `createI18nReact(i18n \| { locales, defaultLocale })` → `I18nProvider` (`messages`), `useI18n` | client (`"use client"`) |
30
30
  | `@krizaka/i18n/check` | `checkMessages`, `findUnused`, `scanHardcoded`, `main` (the CLI, programmatic) | no |
31
31
  | `krizaka-i18n` (bin) | `check` · `scan` | no |
32
32
 
@@ -84,6 +84,27 @@ export const { I18nProvider, useI18n } = createI18nReact(i18n);
84
84
  `setLocale` is the app's (a route, a cookie); outside a provider `useI18n()` reads the default locale. Server Components
85
85
  skip the context and call `getDictionary(locale)`.
86
86
 
87
+ ### Ship one language, not all of them
88
+
89
+ Bound to the engine, the client bundle carries every catalogue. Bind the React side to the **locales alone** and let
90
+ the server pass the active catalogue — the browser then downloads one language:
91
+
92
+ ```tsx
93
+ // app/components/I18nProvider.tsx — no messages/*.json imported here
94
+ "use client";
95
+ import { createI18nReact } from "@krizaka/i18n/react";
96
+ import type en from "@/messages/en.json";
97
+
98
+ export const { I18nProvider, useI18n } = createI18nReact<typeof en, "en" | "fr">({ locales: ["en", "fr"], defaultLocale: "en" });
99
+
100
+ // app/[locale]/layout.tsx (server)
101
+ <I18nProvider locale={locale} messages={i18n.getDictionary(locale)}>{children}</I18nProvider>
102
+ ```
103
+
104
+ `messages` is required in that mode (and `useI18n()` throws outside a provider); over a full engine it is optional and
105
+ a key missing from it falls back to the default catalogue. `createTranslator(locale, messages, fallback?)` is the same
106
+ translator, exported from `@krizaka/i18n`.
107
+
87
108
  ## `<Rich>` — `<b>` and `<a>`, nothing else
88
109
 
89
110
  ```tsx
package/dist/index.d.ts CHANGED
@@ -80,6 +80,12 @@ interface I18n<D extends Dictionary, L extends string = string> {
80
80
  /** `{placeholder}` replacement, the same function as the package's `format`. */
81
81
  format: typeof format;
82
82
  }
83
+ /**
84
+ * A translator over one catalogue: `messages` for `locale` (its plural rules), then `fallback` for a missing key, then
85
+ * the key itself. `createI18n` builds its translators with it; `@krizaka/i18n/react` uses it for a provider given only
86
+ * the active catalogue (`<I18nProvider messages>`).
87
+ */
88
+ declare function createTranslator<D extends Dictionary>(locale: string, messages: D, fallback?: Dictionary): Translate<D>;
83
89
  /**
84
90
  * The i18n engine of a Krizaka app, bound to its catalogues:
85
91
  *
@@ -97,4 +103,4 @@ interface I18n<D extends Dictionary, L extends string = string> {
97
103
  */
98
104
  declare function createI18n<D extends Dictionary, L extends string = string>(dictionaries: Readonly<Record<L, D>>, options: CreateI18nOptions<NoInfer<L>>): I18n<D, L>;
99
105
 
100
- export { type CreateI18nOptions, type Dictionary, type I18n, type MessageAt, type MessageKey, type MessageValue, PLURAL_CATEGORIES, type PlaceholderValue, type Placeholders, type PluralCategory, type PluralMessage, type Translate, type Values, type ValuesArgs, createI18n, format, isPluralMessage, placeholdersOf };
106
+ export { type CreateI18nOptions, type Dictionary, type I18n, type MessageAt, type MessageKey, type MessageValue, PLURAL_CATEGORIES, type PlaceholderValue, type Placeholders, type PluralCategory, type PluralMessage, type Translate, type Values, type ValuesArgs, createI18n, createTranslator, format, isPluralMessage, placeholdersOf };
package/dist/index.js CHANGED
@@ -15,6 +15,19 @@ function lookup(dictionary, key) {
15
15
  }
16
16
  return node;
17
17
  }
18
+ function createTranslator(locale, messages, fallback) {
19
+ const translate = (key, values) => {
20
+ let message = lookup(messages, key);
21
+ if (message === void 0 && fallback) message = lookup(fallback, key);
22
+ if (typeof message === "string") return format(message, values);
23
+ if (isPluralMessage(message)) {
24
+ const count = Number(values?.count);
25
+ return format(Number.isFinite(count) ? plural(count, locale, message) : message.other, values);
26
+ }
27
+ return key;
28
+ };
29
+ return translate;
30
+ }
18
31
  function createI18n(dictionaries, options) {
19
32
  const { defaultLocale } = options;
20
33
  const locales = Object.keys(dictionaries);
@@ -26,17 +39,7 @@ function createI18n(dictionaries, options) {
26
39
  const getDictionary = (locale) => dictionaries[asLocale(locale)];
27
40
  const translator = (locale) => {
28
41
  const current = asLocale(locale);
29
- const translate = (key, values) => {
30
- let message = lookup(dictionaries[current], key);
31
- if (message === void 0 && current !== defaultLocale) message = lookup(dictionaries[defaultLocale], key);
32
- if (typeof message === "string") return format(message, values);
33
- if (isPluralMessage(message)) {
34
- const count = Number(values?.count);
35
- return format(Number.isFinite(count) ? plural(count, current, message) : message.other, values);
36
- }
37
- return key;
38
- };
39
- return translate;
42
+ return createTranslator(current, dictionaries[current], current === defaultLocale ? void 0 : dictionaries[defaultLocale]);
40
43
  };
41
44
  return {
42
45
  locales,
@@ -52,6 +55,7 @@ function createI18n(dictionaries, options) {
52
55
  export {
53
56
  PLURAL_CATEGORIES,
54
57
  createI18n,
58
+ createTranslator,
55
59
  format,
56
60
  isPluralMessage,
57
61
  placeholdersOf
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/create.ts"],"sourcesContent":["import { plural } from \"@krizaka/intl/plural\";\n\nimport { format, isPluralMessage } from \"./format\";\nimport type { Dictionary, Translate, Values } from \"./types\";\n\nexport interface CreateI18nOptions<L extends string> {\n /** The reference locale: its catalogue gives the shape, and anything unknown falls back to it. */\n defaultLocale: L;\n}\n\nexport interface I18n<D extends Dictionary, L extends string = string> {\n /** The supported locales, in the order the catalogues were given. */\n readonly locales: readonly L[];\n readonly defaultLocale: L;\n /** True when the value is a supported locale. */\n isLocale(value: unknown): value is L;\n /** Narrows any value (a route segment, a cookie) to a supported locale: the default one for anything else. */\n asLocale(value: unknown): L;\n /** The whole catalogue of a locale (the default one for an unknown locale) — for Server Components and branches. */\n getDictionary(locale: unknown): D;\n /** The translator bound to a locale: `translator(\"fr\")(\"auction.bids\", { count: 3 })`. */\n translator(locale: unknown): Translate<D>;\n /** The translator of the default locale. */\n t: Translate<D>;\n /** `{placeholder}` replacement, the same function as the package's `format`. */\n format: typeof format;\n}\n\nfunction lookup(dictionary: Dictionary, key: string): unknown {\n let node: unknown = dictionary;\n for (const part of key.split(\".\")) {\n if (!node || typeof node !== \"object\" || Array.isArray(node) || !Object.hasOwn(node, part)) return undefined;\n node = (node as Record<string, unknown>)[part];\n }\n return node;\n}\n\n/**\n * The i18n engine of a Krizaka app, bound to its catalogues:\n *\n * ```ts\n * import en from \"@/messages/en.json\";\n * import fr from \"@/messages/fr.json\";\n * export const i18n = createI18n<typeof en, \"en\" | \"fr\">({ en, fr }, { defaultLocale: \"en\" });\n * i18n.t(\"auction.phase.SOLD\"); // typed key\n * i18n.translator(\"fr\")(\"auction.bids\", { count: 2 }); // plural set → @krizaka/intl's plural rules\n * ```\n *\n * Giving the type arguments makes every catalogue type-check against the reference: a key missing from `fr.json`\n * fails to compile. A key missing at run time falls back to the default locale, then to the key itself (visible,\n * never an empty string).\n */\nexport function createI18n<D extends Dictionary, L extends string = string>(\n dictionaries: Readonly<Record<L, D>>,\n options: CreateI18nOptions<NoInfer<L>>,\n): I18n<D, L> {\n const { defaultLocale } = options;\n const locales = Object.keys(dictionaries) as L[];\n if (!locales.includes(defaultLocale)) {\n throw new RangeError(`createI18n: no catalogue for the default locale \"${defaultLocale}\".`);\n }\n\n const isLocale = (value: unknown): value is L => typeof value === \"string\" && locales.includes(value as L);\n const asLocale = (value: unknown): L => (isLocale(value) ? value : defaultLocale);\n const getDictionary = (locale: unknown): D => dictionaries[asLocale(locale)];\n\n const translator = (locale: unknown): Translate<D> => {\n const current = asLocale(locale);\n const translate = (key: string, values?: Values): string => {\n let message = lookup(dictionaries[current], key);\n if (message === undefined && current !== defaultLocale) message = lookup(dictionaries[defaultLocale], key);\n if (typeof message === \"string\") return format(message, values);\n if (isPluralMessage(message)) {\n const count = Number(values?.count);\n return format(Number.isFinite(count) ? plural(count, current, message) : message.other, values);\n }\n return key;\n };\n return translate as Translate<D>;\n };\n\n return {\n locales,\n defaultLocale,\n isLocale,\n asLocale,\n getDictionary,\n translator,\n t: translator(defaultLocale),\n format,\n };\n}\n"],"mappings":";;;;;;;;AAAA,SAAS,cAAc;AA4BvB,SAAS,OAAO,YAAwB,KAAsB;AAC5D,MAAI,OAAgB;AACpB,aAAW,QAAQ,IAAI,MAAM,GAAG,GAAG;AACjC,QAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,KAAK,CAAC,OAAO,OAAO,MAAM,IAAI,EAAG,QAAO;AACnG,WAAQ,KAAiC,IAAI;AAAA,EAC/C;AACA,SAAO;AACT;AAiBO,SAAS,WACd,cACA,SACY;AACZ,QAAM,EAAE,cAAc,IAAI;AAC1B,QAAM,UAAU,OAAO,KAAK,YAAY;AACxC,MAAI,CAAC,QAAQ,SAAS,aAAa,GAAG;AACpC,UAAM,IAAI,WAAW,oDAAoD,aAAa,IAAI;AAAA,EAC5F;AAEA,QAAM,WAAW,CAAC,UAA+B,OAAO,UAAU,YAAY,QAAQ,SAAS,KAAU;AACzG,QAAM,WAAW,CAAC,UAAuB,SAAS,KAAK,IAAI,QAAQ;AACnE,QAAM,gBAAgB,CAAC,WAAuB,aAAa,SAAS,MAAM,CAAC;AAE3E,QAAM,aAAa,CAAC,WAAkC;AACpD,UAAM,UAAU,SAAS,MAAM;AAC/B,UAAM,YAAY,CAAC,KAAa,WAA4B;AAC1D,UAAI,UAAU,OAAO,aAAa,OAAO,GAAG,GAAG;AAC/C,UAAI,YAAY,UAAa,YAAY,cAAe,WAAU,OAAO,aAAa,aAAa,GAAG,GAAG;AACzG,UAAI,OAAO,YAAY,SAAU,QAAO,OAAO,SAAS,MAAM;AAC9D,UAAI,gBAAgB,OAAO,GAAG;AAC5B,cAAM,QAAQ,OAAO,QAAQ,KAAK;AAClC,eAAO,OAAO,OAAO,SAAS,KAAK,IAAI,OAAO,OAAO,SAAS,OAAO,IAAI,QAAQ,OAAO,MAAM;AAAA,MAChG;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG,WAAW,aAAa;AAAA,IAC3B;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/create.ts"],"sourcesContent":["import { plural } from \"@krizaka/intl/plural\";\n\nimport { format, isPluralMessage } from \"./format\";\nimport type { Dictionary, Translate, Values } from \"./types\";\n\nexport interface CreateI18nOptions<L extends string> {\n /** The reference locale: its catalogue gives the shape, and anything unknown falls back to it. */\n defaultLocale: L;\n}\n\nexport interface I18n<D extends Dictionary, L extends string = string> {\n /** The supported locales, in the order the catalogues were given. */\n readonly locales: readonly L[];\n readonly defaultLocale: L;\n /** True when the value is a supported locale. */\n isLocale(value: unknown): value is L;\n /** Narrows any value (a route segment, a cookie) to a supported locale: the default one for anything else. */\n asLocale(value: unknown): L;\n /** The whole catalogue of a locale (the default one for an unknown locale) — for Server Components and branches. */\n getDictionary(locale: unknown): D;\n /** The translator bound to a locale: `translator(\"fr\")(\"auction.bids\", { count: 3 })`. */\n translator(locale: unknown): Translate<D>;\n /** The translator of the default locale. */\n t: Translate<D>;\n /** `{placeholder}` replacement, the same function as the package's `format`. */\n format: typeof format;\n}\n\nfunction lookup(dictionary: Dictionary, key: string): unknown {\n let node: unknown = dictionary;\n for (const part of key.split(\".\")) {\n if (!node || typeof node !== \"object\" || Array.isArray(node) || !Object.hasOwn(node, part)) return undefined;\n node = (node as Record<string, unknown>)[part];\n }\n return node;\n}\n\n/**\n * A translator over one catalogue: `messages` for `locale` (its plural rules), then `fallback` for a missing key, then\n * the key itself. `createI18n` builds its translators with it; `@krizaka/i18n/react` uses it for a provider given only\n * the active catalogue (`<I18nProvider messages>`).\n */\nexport function createTranslator<D extends Dictionary>(locale: string, messages: D, fallback?: Dictionary): Translate<D> {\n const translate = (key: string, values?: Values): string => {\n let message = lookup(messages, key);\n if (message === undefined && fallback) message = lookup(fallback, key);\n if (typeof message === \"string\") return format(message, values);\n if (isPluralMessage(message)) {\n const count = Number(values?.count);\n return format(Number.isFinite(count) ? plural(count, locale, message) : message.other, values);\n }\n return key;\n };\n return translate as Translate<D>;\n}\n\n/**\n * The i18n engine of a Krizaka app, bound to its catalogues:\n *\n * ```ts\n * import en from \"@/messages/en.json\";\n * import fr from \"@/messages/fr.json\";\n * export const i18n = createI18n<typeof en, \"en\" | \"fr\">({ en, fr }, { defaultLocale: \"en\" });\n * i18n.t(\"auction.phase.SOLD\"); // typed key\n * i18n.translator(\"fr\")(\"auction.bids\", { count: 2 }); // plural set → @krizaka/intl's plural rules\n * ```\n *\n * Giving the type arguments makes every catalogue type-check against the reference: a key missing from `fr.json`\n * fails to compile. A key missing at run time falls back to the default locale, then to the key itself (visible,\n * never an empty string).\n */\nexport function createI18n<D extends Dictionary, L extends string = string>(\n dictionaries: Readonly<Record<L, D>>,\n options: CreateI18nOptions<NoInfer<L>>,\n): I18n<D, L> {\n const { defaultLocale } = options;\n const locales = Object.keys(dictionaries) as L[];\n if (!locales.includes(defaultLocale)) {\n throw new RangeError(`createI18n: no catalogue for the default locale \"${defaultLocale}\".`);\n }\n\n const isLocale = (value: unknown): value is L => typeof value === \"string\" && locales.includes(value as L);\n const asLocale = (value: unknown): L => (isLocale(value) ? value : defaultLocale);\n const getDictionary = (locale: unknown): D => dictionaries[asLocale(locale)];\n\n const translator = (locale: unknown): Translate<D> => {\n const current = asLocale(locale);\n return createTranslator(current, dictionaries[current], current === defaultLocale ? undefined : dictionaries[defaultLocale]);\n };\n\n return {\n locales,\n defaultLocale,\n isLocale,\n asLocale,\n getDictionary,\n translator,\n t: translator(defaultLocale),\n format,\n };\n}\n"],"mappings":";;;;;;;;AAAA,SAAS,cAAc;AA4BvB,SAAS,OAAO,YAAwB,KAAsB;AAC5D,MAAI,OAAgB;AACpB,aAAW,QAAQ,IAAI,MAAM,GAAG,GAAG;AACjC,QAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,KAAK,CAAC,OAAO,OAAO,MAAM,IAAI,EAAG,QAAO;AACnG,WAAQ,KAAiC,IAAI;AAAA,EAC/C;AACA,SAAO;AACT;AAOO,SAAS,iBAAuC,QAAgB,UAAa,UAAqC;AACvH,QAAM,YAAY,CAAC,KAAa,WAA4B;AAC1D,QAAI,UAAU,OAAO,UAAU,GAAG;AAClC,QAAI,YAAY,UAAa,SAAU,WAAU,OAAO,UAAU,GAAG;AACrE,QAAI,OAAO,YAAY,SAAU,QAAO,OAAO,SAAS,MAAM;AAC9D,QAAI,gBAAgB,OAAO,GAAG;AAC5B,YAAM,QAAQ,OAAO,QAAQ,KAAK;AAClC,aAAO,OAAO,OAAO,SAAS,KAAK,IAAI,OAAO,OAAO,QAAQ,OAAO,IAAI,QAAQ,OAAO,MAAM;AAAA,IAC/F;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAiBO,SAAS,WACd,cACA,SACY;AACZ,QAAM,EAAE,cAAc,IAAI;AAC1B,QAAM,UAAU,OAAO,KAAK,YAAY;AACxC,MAAI,CAAC,QAAQ,SAAS,aAAa,GAAG;AACpC,UAAM,IAAI,WAAW,oDAAoD,aAAa,IAAI;AAAA,EAC5F;AAEA,QAAM,WAAW,CAAC,UAA+B,OAAO,UAAU,YAAY,QAAQ,SAAS,KAAU;AACzG,QAAM,WAAW,CAAC,UAAuB,SAAS,KAAK,IAAI,QAAQ;AACnE,QAAM,gBAAgB,CAAC,WAAuB,aAAa,SAAS,MAAM,CAAC;AAE3E,QAAM,aAAa,CAAC,WAAkC;AACpD,UAAM,UAAU,SAAS,MAAM;AAC/B,WAAO,iBAAiB,SAAS,aAAa,OAAO,GAAG,YAAY,gBAAgB,SAAY,aAAa,aAAa,CAAC;AAAA,EAC7H;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG,WAAW,aAAa;AAAA,IAC3B;AAAA,EACF;AACF;","names":[]}
package/dist/react.d.ts CHANGED
@@ -16,8 +16,22 @@ interface I18nContextValue<D extends Dictionary, L extends string> {
16
16
  /** Asks the app to switch locale (what it does — a route, a cookie — is the app's): a no-op unless given. */
17
17
  setLocale: (locale: L) => void;
18
18
  }
19
- interface I18nProviderProps<L extends string> {
19
+ /**
20
+ * The locales of an app without its catalogues: what the client needs when the server hands it only the active
21
+ * catalogue (`<I18nProvider messages>`), so no `messages/*.json` lands in the client bundle.
22
+ */
23
+ interface I18nLocales<L extends string> {
24
+ readonly locales: readonly L[];
25
+ readonly defaultLocale: L;
26
+ }
27
+ interface I18nProviderProps<D extends Dictionary, L extends string> {
20
28
  locale: L;
29
+ /**
30
+ * The catalogue of the active locale, resolved on the server (`i18n.getDictionary(locale)`) and passed down: the
31
+ * client then ships one language, not all of them. Required when the React side was created from `I18nLocales`;
32
+ * optional over a full engine (its catalogue of `locale` is used by default).
33
+ */
34
+ messages?: D;
21
35
  /** How the app switches locale (push a route, write a cookie). */
22
36
  setLocale?: (locale: L) => void;
23
37
  children?: ReactNode;
@@ -31,13 +45,24 @@ interface I18nProviderProps<L extends string> {
31
45
  * ```
32
46
  *
33
47
  * `I18nProvider` takes the locale the server resolved (and how to switch it); `useI18n()` gives the active locale, its
34
- * catalogue, its translator and `format`. Outside a provider, `useI18n()` reads the default locale. Server Components
35
- * do not use the context: they call `i18n.getDictionary(locale)` / `i18n.translator(locale)`.
48
+ * catalogue, its translator and `format`. Server Components do not use the context: they call
49
+ * `i18n.getDictionary(locale)` / `i18n.translator(locale)`.
50
+ *
51
+ * To ship only the active language to the browser, create it from the locales alone and let the server pass the
52
+ * catalogue:
53
+ *
54
+ * ```tsx
55
+ * "use client";
56
+ * export const { I18nProvider, useI18n } = createI18nReact<typeof en, Locale>({ locales: ["en", "fr"], defaultLocale: "en" });
57
+ * // layout (server): <I18nProvider locale={locale} messages={i18n.getDictionary(locale)}>
58
+ * ```
59
+ *
60
+ * Outside a provider, `useI18n()` reads the engine's default locale — or throws when there is no engine to read.
36
61
  */
37
- declare function createI18nReact<D extends Dictionary, L extends string>(i18n: I18n<D, L>): {
38
- I18nProvider: ({ locale, setLocale, children }: I18nProviderProps<L>) => react.JSX.Element;
62
+ declare function createI18nReact<D extends Dictionary, L extends string>(source: I18n<D, L> | I18nLocales<L>): {
63
+ I18nProvider: ({ locale, messages, setLocale, children }: I18nProviderProps<D, L>) => react.JSX.Element;
39
64
  useI18n: () => I18nContextValue<D, L>;
40
- I18nContext: react.Context<I18nContextValue<D, L>>;
65
+ I18nContext: react.Context<I18nContextValue<D, L> | null>;
41
66
  };
42
67
 
43
- export { type I18nContextValue, type I18nProviderProps, createI18nReact };
68
+ export { type I18nContextValue, type I18nLocales, type I18nProviderProps, createI18nReact };
package/dist/react.js CHANGED
@@ -3,36 +3,76 @@
3
3
  // src/react.tsx
4
4
  import { createContext, useContext, useMemo } from "react";
5
5
 
6
+ // src/create.ts
7
+ import { plural } from "@krizaka/intl/plural";
8
+
6
9
  // src/format.ts
7
10
  var PLACEHOLDER = /\{(\w+)\}/g;
11
+ var PLURAL_CATEGORIES = ["zero", "one", "two", "few", "many", "other"];
8
12
  function format(message, values = {}) {
9
13
  return message.replace(PLACEHOLDER, (match, name) => {
10
14
  const value = Object.hasOwn(values, name) ? values[name] : void 0;
11
15
  return value === void 0 || value === null ? match : String(value);
12
16
  });
13
17
  }
18
+ function isPluralMessage(value) {
19
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
20
+ const keys = Object.keys(value);
21
+ return typeof value.other === "string" && keys.every((k) => PLURAL_CATEGORIES.includes(k) && typeof value[k] === "string");
22
+ }
23
+
24
+ // src/create.ts
25
+ function lookup(dictionary, key) {
26
+ let node = dictionary;
27
+ for (const part of key.split(".")) {
28
+ if (!node || typeof node !== "object" || Array.isArray(node) || !Object.hasOwn(node, part)) return void 0;
29
+ node = node[part];
30
+ }
31
+ return node;
32
+ }
33
+ function createTranslator(locale, messages, fallback) {
34
+ const translate = (key, values) => {
35
+ let message = lookup(messages, key);
36
+ if (message === void 0 && fallback) message = lookup(fallback, key);
37
+ if (typeof message === "string") return format(message, values);
38
+ if (isPluralMessage(message)) {
39
+ const count = Number(values?.count);
40
+ return format(Number.isFinite(count) ? plural(count, locale, message) : message.other, values);
41
+ }
42
+ return key;
43
+ };
44
+ return translate;
45
+ }
14
46
 
15
47
  // src/react.tsx
16
48
  import { jsx } from "react/jsx-runtime";
17
49
  var noop = () => {
18
50
  };
19
- function createI18nReact(i18n) {
20
- const valueOf = (locale, setLocale) => ({
21
- locale,
22
- messages: i18n.getDictionary(locale),
23
- t: i18n.translator(locale),
24
- format,
25
- setLocale
26
- });
27
- const Context = createContext(valueOf(i18n.defaultLocale, noop));
51
+ var isEngine = (source) => typeof source.getDictionary === "function";
52
+ function createI18nReact(source) {
53
+ const engine = isEngine(source) ? source : void 0;
54
+ const asLocale = (value) => engine ? engine.asLocale(value) : source.locales.includes(value) ? value : source.defaultLocale;
55
+ const valueOf = (locale, setLocale, messages) => {
56
+ if (messages) {
57
+ const fallback = engine && locale !== engine.defaultLocale ? engine.getDictionary(engine.defaultLocale) : void 0;
58
+ return { locale, messages, t: createTranslator(locale, messages, fallback), format, setLocale };
59
+ }
60
+ if (!engine) {
61
+ throw new Error("@krizaka/i18n/react: <I18nProvider> needs `messages` (the active catalogue) \u2014 it was created from the locales alone.");
62
+ }
63
+ return { locale, messages: engine.getDictionary(locale), t: engine.translator(locale), format, setLocale };
64
+ };
65
+ const Context = createContext(engine ? valueOf(engine.defaultLocale, noop) : null);
28
66
  Context.displayName = "I18nContext";
29
- function I18nProvider({ locale, setLocale = noop, children }) {
30
- const current = i18n.asLocale(locale);
31
- const value = useMemo(() => valueOf(current, setLocale), [current, setLocale]);
67
+ function I18nProvider({ locale, messages, setLocale = noop, children }) {
68
+ const current = asLocale(locale);
69
+ const value = useMemo(() => valueOf(current, setLocale, messages), [current, setLocale, messages]);
32
70
  return /* @__PURE__ */ jsx(Context.Provider, { value, children });
33
71
  }
34
72
  function useI18n() {
35
- return useContext(Context);
73
+ const value = useContext(Context);
74
+ if (!value) throw new Error("@krizaka/i18n/react: useI18n() must be used inside <I18nProvider>.");
75
+ return value;
36
76
  }
37
77
  return { I18nProvider, useI18n, I18nContext: Context };
38
78
  }
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react.tsx","../src/format.ts"],"sourcesContent":["import { createContext, type ReactNode, useContext, useMemo } from \"react\";\n\nimport type { I18n } from \"./create\";\nimport { format } from \"./format\";\nimport type { Dictionary, Translate } from \"./types\";\n\n/** What `useI18n()` returns. */\nexport interface I18nContextValue<D extends Dictionary, L extends string> {\n /** The active locale. */\n locale: L;\n /** The whole catalogue of the active locale: `messages.home.title`. */\n messages: D;\n /** The translator of the active locale: `t(\"auction.bids\", { count: 3 })`. */\n t: Translate<D>;\n /** `{placeholder}` replacement. */\n format: typeof format;\n /** Asks the app to switch locale (what it does — a route, a cookie — is the app's): a no-op unless given. */\n setLocale: (locale: L) => void;\n}\n\nexport interface I18nProviderProps<L extends string> {\n locale: L;\n /** How the app switches locale (push a route, write a cookie). */\n setLocale?: (locale: L) => void;\n children?: ReactNode;\n}\n\nconst noop = (): void => {};\n\n/**\n * The React side of an i18n engine, bound to it once in the app:\n *\n * ```tsx\n * \"use client\";\n * export const { I18nProvider, useI18n } = createI18nReact(i18n);\n * ```\n *\n * `I18nProvider` takes the locale the server resolved (and how to switch it); `useI18n()` gives the active locale, its\n * catalogue, its translator and `format`. Outside a provider, `useI18n()` reads the default locale. Server Components\n * do not use the context: they call `i18n.getDictionary(locale)` / `i18n.translator(locale)`.\n */\nexport function createI18nReact<D extends Dictionary, L extends string>(i18n: I18n<D, L>) {\n const valueOf = (locale: L, setLocale: (locale: L) => void): I18nContextValue<D, L> => ({\n locale,\n messages: i18n.getDictionary(locale),\n t: i18n.translator(locale),\n format,\n setLocale,\n });\n\n const Context = createContext<I18nContextValue<D, L>>(valueOf(i18n.defaultLocale, noop));\n Context.displayName = \"I18nContext\";\n\n function I18nProvider({ locale, setLocale = noop, children }: I18nProviderProps<L>) {\n const current = i18n.asLocale(locale);\n const value = useMemo(() => valueOf(current, setLocale), [current, setLocale]);\n return <Context.Provider value={value}>{children}</Context.Provider>;\n }\n\n function useI18n(): I18nContextValue<D, L> {\n return useContext(Context);\n }\n\n return { I18nProvider, useI18n, I18nContext: Context };\n}\n","import type { PluralMessage, Values } from \"./types\";\n\n/** A `{name}` placeholder: letters, digits and `_`. */\nexport const PLACEHOLDER = /\\{(\\w+)\\}/g;\n\n/** The plural categories, in CLDR order (`zero` first: the explicit \"exactly 0\" form). */\nexport const PLURAL_CATEGORIES = [\"zero\", \"one\", \"two\", \"few\", \"many\", \"other\"] as const;\n\n/**\n * Replaces the `{name}` placeholders of a message: `format(\"{count} repositories\", { count: 23 })` → `\"23 repositories\"`.\n * A placeholder without a value (missing, `null` or `undefined`) stays as written, so a gap is visible, never silent.\n */\nexport function format(message: string, values: Values = {}): string {\n return message.replace(PLACEHOLDER, (match, name: string) => {\n const value = Object.hasOwn(values, name) ? values[name] : undefined;\n return value === undefined || value === null ? match : String(value);\n });\n}\n\n/** The placeholder names of a message, sorted and without duplicates: `\"{b} {a} {b}\"` → `[\"a\", \"b\"]`. */\nexport function placeholdersOf(message: string): string[] {\n return [...new Set([...message.matchAll(PLACEHOLDER)].map((m) => m[1] as string))].sort();\n}\n\n/** True when a catalogue value is a plural set: an object of plural categories with an `other` string. */\nexport function isPluralMessage(value: unknown): value is PluralMessage {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n const keys = Object.keys(value);\n return (\n typeof (value as { other?: unknown }).other === \"string\" &&\n keys.every((k) => (PLURAL_CATEGORIES as readonly string[]).includes(k) && typeof (value as Record<string, unknown>)[k] === \"string\")\n );\n}\n"],"mappings":";;;AAAA,SAAS,eAA+B,YAAY,eAAe;;;ACG5D,IAAM,cAAc;AASpB,SAAS,OAAO,SAAiB,SAAiB,CAAC,GAAW;AACnE,SAAO,QAAQ,QAAQ,aAAa,CAAC,OAAO,SAAiB;AAC3D,UAAM,QAAQ,OAAO,OAAO,QAAQ,IAAI,IAAI,OAAO,IAAI,IAAI;AAC3D,WAAO,UAAU,UAAa,UAAU,OAAO,QAAQ,OAAO,KAAK;AAAA,EACrE,CAAC;AACH;;;ADuCW;AA7BX,IAAM,OAAO,MAAY;AAAC;AAcnB,SAAS,gBAAwD,MAAkB;AACxF,QAAM,UAAU,CAAC,QAAW,eAA4D;AAAA,IACtF;AAAA,IACA,UAAU,KAAK,cAAc,MAAM;AAAA,IACnC,GAAG,KAAK,WAAW,MAAM;AAAA,IACzB;AAAA,IACA;AAAA,EACF;AAEA,QAAM,UAAU,cAAsC,QAAQ,KAAK,eAAe,IAAI,CAAC;AACvF,UAAQ,cAAc;AAEtB,WAAS,aAAa,EAAE,QAAQ,YAAY,MAAM,SAAS,GAAyB;AAClF,UAAM,UAAU,KAAK,SAAS,MAAM;AACpC,UAAM,QAAQ,QAAQ,MAAM,QAAQ,SAAS,SAAS,GAAG,CAAC,SAAS,SAAS,CAAC;AAC7E,WAAO,oBAAC,QAAQ,UAAR,EAAiB,OAAe,UAAS;AAAA,EACnD;AAEA,WAAS,UAAkC;AACzC,WAAO,WAAW,OAAO;AAAA,EAC3B;AAEA,SAAO,EAAE,cAAc,SAAS,aAAa,QAAQ;AACvD;","names":[]}
1
+ {"version":3,"sources":["../src/react.tsx","../src/create.ts","../src/format.ts"],"sourcesContent":["import { createContext, type ReactNode, useContext, useMemo } from \"react\";\n\nimport { createTranslator, type I18n } from \"./create\";\nimport { format } from \"./format\";\nimport type { Dictionary, Translate } from \"./types\";\n\n/** What `useI18n()` returns. */\nexport interface I18nContextValue<D extends Dictionary, L extends string> {\n /** The active locale. */\n locale: L;\n /** The whole catalogue of the active locale: `messages.home.title`. */\n messages: D;\n /** The translator of the active locale: `t(\"auction.bids\", { count: 3 })`. */\n t: Translate<D>;\n /** `{placeholder}` replacement. */\n format: typeof format;\n /** Asks the app to switch locale (what it does — a route, a cookie — is the app's): a no-op unless given. */\n setLocale: (locale: L) => void;\n}\n\n/**\n * The locales of an app without its catalogues: what the client needs when the server hands it only the active\n * catalogue (`<I18nProvider messages>`), so no `messages/*.json` lands in the client bundle.\n */\nexport interface I18nLocales<L extends string> {\n readonly locales: readonly L[];\n readonly defaultLocale: L;\n}\n\nexport interface I18nProviderProps<D extends Dictionary, L extends string> {\n locale: L;\n /**\n * The catalogue of the active locale, resolved on the server (`i18n.getDictionary(locale)`) and passed down: the\n * client then ships one language, not all of them. Required when the React side was created from `I18nLocales`;\n * optional over a full engine (its catalogue of `locale` is used by default).\n */\n messages?: D;\n /** How the app switches locale (push a route, write a cookie). */\n setLocale?: (locale: L) => void;\n children?: ReactNode;\n}\n\nconst noop = (): void => {};\n\nconst isEngine = <D extends Dictionary, L extends string>(source: I18n<D, L> | I18nLocales<L>): source is I18n<D, L> =>\n typeof (source as Partial<I18n<D, L>>).getDictionary === \"function\";\n\n/**\n * The React side of an i18n engine, bound to it once in the app:\n *\n * ```tsx\n * \"use client\";\n * export const { I18nProvider, useI18n } = createI18nReact(i18n);\n * ```\n *\n * `I18nProvider` takes the locale the server resolved (and how to switch it); `useI18n()` gives the active locale, its\n * catalogue, its translator and `format`. Server Components do not use the context: they call\n * `i18n.getDictionary(locale)` / `i18n.translator(locale)`.\n *\n * To ship only the active language to the browser, create it from the locales alone and let the server pass the\n * catalogue:\n *\n * ```tsx\n * \"use client\";\n * export const { I18nProvider, useI18n } = createI18nReact<typeof en, Locale>({ locales: [\"en\", \"fr\"], defaultLocale: \"en\" });\n * // layout (server): <I18nProvider locale={locale} messages={i18n.getDictionary(locale)}>\n * ```\n *\n * Outside a provider, `useI18n()` reads the engine's default locale — or throws when there is no engine to read.\n */\nexport function createI18nReact<D extends Dictionary, L extends string>(source: I18n<D, L> | I18nLocales<L>) {\n const engine = isEngine(source) ? source : undefined;\n const asLocale = (value: unknown): L =>\n engine ? engine.asLocale(value) : source.locales.includes(value as L) ? (value as L) : source.defaultLocale;\n\n const valueOf = (locale: L, setLocale: (locale: L) => void, messages?: D): I18nContextValue<D, L> => {\n if (messages) {\n // A missing key falls back to the default catalogue when the engine has it, then to the key.\n const fallback = engine && locale !== engine.defaultLocale ? engine.getDictionary(engine.defaultLocale) : undefined;\n return { locale, messages, t: createTranslator(locale, messages, fallback), format, setLocale };\n }\n if (!engine) {\n throw new Error(\"@krizaka/i18n/react: <I18nProvider> needs `messages` (the active catalogue) — it was created from the locales alone.\");\n }\n return { locale, messages: engine.getDictionary(locale), t: engine.translator(locale), format, setLocale };\n };\n\n const Context = createContext<I18nContextValue<D, L> | null>(engine ? valueOf(engine.defaultLocale, noop) : null);\n Context.displayName = \"I18nContext\";\n\n function I18nProvider({ locale, messages, setLocale = noop, children }: I18nProviderProps<D, L>) {\n const current = asLocale(locale);\n const value = useMemo(() => valueOf(current, setLocale, messages), [current, setLocale, messages]);\n return <Context.Provider value={value}>{children}</Context.Provider>;\n }\n\n function useI18n(): I18nContextValue<D, L> {\n const value = useContext(Context);\n if (!value) throw new Error(\"@krizaka/i18n/react: useI18n() must be used inside <I18nProvider>.\");\n return value;\n }\n\n return { I18nProvider, useI18n, I18nContext: Context };\n}\n","import { plural } from \"@krizaka/intl/plural\";\n\nimport { format, isPluralMessage } from \"./format\";\nimport type { Dictionary, Translate, Values } from \"./types\";\n\nexport interface CreateI18nOptions<L extends string> {\n /** The reference locale: its catalogue gives the shape, and anything unknown falls back to it. */\n defaultLocale: L;\n}\n\nexport interface I18n<D extends Dictionary, L extends string = string> {\n /** The supported locales, in the order the catalogues were given. */\n readonly locales: readonly L[];\n readonly defaultLocale: L;\n /** True when the value is a supported locale. */\n isLocale(value: unknown): value is L;\n /** Narrows any value (a route segment, a cookie) to a supported locale: the default one for anything else. */\n asLocale(value: unknown): L;\n /** The whole catalogue of a locale (the default one for an unknown locale) — for Server Components and branches. */\n getDictionary(locale: unknown): D;\n /** The translator bound to a locale: `translator(\"fr\")(\"auction.bids\", { count: 3 })`. */\n translator(locale: unknown): Translate<D>;\n /** The translator of the default locale. */\n t: Translate<D>;\n /** `{placeholder}` replacement, the same function as the package's `format`. */\n format: typeof format;\n}\n\nfunction lookup(dictionary: Dictionary, key: string): unknown {\n let node: unknown = dictionary;\n for (const part of key.split(\".\")) {\n if (!node || typeof node !== \"object\" || Array.isArray(node) || !Object.hasOwn(node, part)) return undefined;\n node = (node as Record<string, unknown>)[part];\n }\n return node;\n}\n\n/**\n * A translator over one catalogue: `messages` for `locale` (its plural rules), then `fallback` for a missing key, then\n * the key itself. `createI18n` builds its translators with it; `@krizaka/i18n/react` uses it for a provider given only\n * the active catalogue (`<I18nProvider messages>`).\n */\nexport function createTranslator<D extends Dictionary>(locale: string, messages: D, fallback?: Dictionary): Translate<D> {\n const translate = (key: string, values?: Values): string => {\n let message = lookup(messages, key);\n if (message === undefined && fallback) message = lookup(fallback, key);\n if (typeof message === \"string\") return format(message, values);\n if (isPluralMessage(message)) {\n const count = Number(values?.count);\n return format(Number.isFinite(count) ? plural(count, locale, message) : message.other, values);\n }\n return key;\n };\n return translate as Translate<D>;\n}\n\n/**\n * The i18n engine of a Krizaka app, bound to its catalogues:\n *\n * ```ts\n * import en from \"@/messages/en.json\";\n * import fr from \"@/messages/fr.json\";\n * export const i18n = createI18n<typeof en, \"en\" | \"fr\">({ en, fr }, { defaultLocale: \"en\" });\n * i18n.t(\"auction.phase.SOLD\"); // typed key\n * i18n.translator(\"fr\")(\"auction.bids\", { count: 2 }); // plural set → @krizaka/intl's plural rules\n * ```\n *\n * Giving the type arguments makes every catalogue type-check against the reference: a key missing from `fr.json`\n * fails to compile. A key missing at run time falls back to the default locale, then to the key itself (visible,\n * never an empty string).\n */\nexport function createI18n<D extends Dictionary, L extends string = string>(\n dictionaries: Readonly<Record<L, D>>,\n options: CreateI18nOptions<NoInfer<L>>,\n): I18n<D, L> {\n const { defaultLocale } = options;\n const locales = Object.keys(dictionaries) as L[];\n if (!locales.includes(defaultLocale)) {\n throw new RangeError(`createI18n: no catalogue for the default locale \"${defaultLocale}\".`);\n }\n\n const isLocale = (value: unknown): value is L => typeof value === \"string\" && locales.includes(value as L);\n const asLocale = (value: unknown): L => (isLocale(value) ? value : defaultLocale);\n const getDictionary = (locale: unknown): D => dictionaries[asLocale(locale)];\n\n const translator = (locale: unknown): Translate<D> => {\n const current = asLocale(locale);\n return createTranslator(current, dictionaries[current], current === defaultLocale ? undefined : dictionaries[defaultLocale]);\n };\n\n return {\n locales,\n defaultLocale,\n isLocale,\n asLocale,\n getDictionary,\n translator,\n t: translator(defaultLocale),\n format,\n };\n}\n","import type { PluralMessage, Values } from \"./types\";\n\n/** A `{name}` placeholder: letters, digits and `_`. */\nexport const PLACEHOLDER = /\\{(\\w+)\\}/g;\n\n/** The plural categories, in CLDR order (`zero` first: the explicit \"exactly 0\" form). */\nexport const PLURAL_CATEGORIES = [\"zero\", \"one\", \"two\", \"few\", \"many\", \"other\"] as const;\n\n/**\n * Replaces the `{name}` placeholders of a message: `format(\"{count} repositories\", { count: 23 })` → `\"23 repositories\"`.\n * A placeholder without a value (missing, `null` or `undefined`) stays as written, so a gap is visible, never silent.\n */\nexport function format(message: string, values: Values = {}): string {\n return message.replace(PLACEHOLDER, (match, name: string) => {\n const value = Object.hasOwn(values, name) ? values[name] : undefined;\n return value === undefined || value === null ? match : String(value);\n });\n}\n\n/** The placeholder names of a message, sorted and without duplicates: `\"{b} {a} {b}\"` → `[\"a\", \"b\"]`. */\nexport function placeholdersOf(message: string): string[] {\n return [...new Set([...message.matchAll(PLACEHOLDER)].map((m) => m[1] as string))].sort();\n}\n\n/** True when a catalogue value is a plural set: an object of plural categories with an `other` string. */\nexport function isPluralMessage(value: unknown): value is PluralMessage {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n const keys = Object.keys(value);\n return (\n typeof (value as { other?: unknown }).other === \"string\" &&\n keys.every((k) => (PLURAL_CATEGORIES as readonly string[]).includes(k) && typeof (value as Record<string, unknown>)[k] === \"string\")\n );\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krizaka/i18n",
3
- "version": "0.1.0-beta.2",
3
+ "version": "0.1.0",
4
4
  "description": "The i18n engine of every Krizaka app: typed message keys and placeholders from en.json, format(), plurals through @krizaka/intl, <Rich> (<b>, <a>), a React provider, and the krizaka-i18n CLI that checks catalogues and finds hard-coded strings.",
5
5
  "keywords": [
6
6
  "krizaka",
@@ -54,7 +54,7 @@
54
54
  ],
55
55
  "sideEffects": false,
56
56
  "dependencies": {
57
- "@krizaka/intl": "^0.1.0-beta.0"
57
+ "@krizaka/intl": "^0.1.0"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "react": ">=18",