@canonical/i18n-react 0.29.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.
Files changed (50) hide show
  1. package/README.md +99 -0
  2. package/dist/esm/index.js +9 -0
  3. package/dist/esm/index.js.map +1 -0
  4. package/dist/esm/lib/I18nProvider/Context.js +8 -0
  5. package/dist/esm/lib/I18nProvider/Context.js.map +1 -0
  6. package/dist/esm/lib/I18nProvider/Provider.js +20 -0
  7. package/dist/esm/lib/I18nProvider/Provider.js.map +1 -0
  8. package/dist/esm/lib/I18nProvider/index.js +2 -0
  9. package/dist/esm/lib/I18nProvider/index.js.map +1 -0
  10. package/dist/esm/lib/I18nProvider/types.js +2 -0
  11. package/dist/esm/lib/I18nProvider/types.js.map +1 -0
  12. package/dist/esm/lib/hooks/index.js +6 -0
  13. package/dist/esm/lib/hooks/index.js.map +1 -0
  14. package/dist/esm/lib/hooks/types.js +2 -0
  15. package/dist/esm/lib/hooks/types.js.map +1 -0
  16. package/dist/esm/lib/hooks/useFormatters.js +13 -0
  17. package/dist/esm/lib/hooks/useFormatters.js.map +1 -0
  18. package/dist/esm/lib/hooks/useI18nContext.js +14 -0
  19. package/dist/esm/lib/hooks/useI18nContext.js.map +1 -0
  20. package/dist/esm/lib/hooks/useLocale.js +20 -0
  21. package/dist/esm/lib/hooks/useLocale.js.map +1 -0
  22. package/dist/esm/lib/hooks/useTranslation.js +14 -0
  23. package/dist/esm/lib/hooks/useTranslation.js.map +1 -0
  24. package/dist/esm/lib/index.js +3 -0
  25. package/dist/esm/lib/index.js.map +1 -0
  26. package/dist/types/index.d.ts +9 -0
  27. package/dist/types/index.d.ts.map +1 -0
  28. package/dist/types/lib/I18nProvider/Context.d.ts +8 -0
  29. package/dist/types/lib/I18nProvider/Context.d.ts.map +1 -0
  30. package/dist/types/lib/I18nProvider/Provider.d.ts +13 -0
  31. package/dist/types/lib/I18nProvider/Provider.d.ts.map +1 -0
  32. package/dist/types/lib/I18nProvider/index.d.ts +3 -0
  33. package/dist/types/lib/I18nProvider/index.d.ts.map +1 -0
  34. package/dist/types/lib/I18nProvider/types.d.ts +27 -0
  35. package/dist/types/lib/I18nProvider/types.d.ts.map +1 -0
  36. package/dist/types/lib/hooks/index.d.ts +6 -0
  37. package/dist/types/lib/hooks/index.d.ts.map +1 -0
  38. package/dist/types/lib/hooks/types.d.ts +22 -0
  39. package/dist/types/lib/hooks/types.d.ts.map +1 -0
  40. package/dist/types/lib/hooks/useFormatters.d.ts +7 -0
  41. package/dist/types/lib/hooks/useFormatters.d.ts.map +1 -0
  42. package/dist/types/lib/hooks/useI18nContext.d.ts +7 -0
  43. package/dist/types/lib/hooks/useI18nContext.d.ts.map +1 -0
  44. package/dist/types/lib/hooks/useLocale.d.ts +7 -0
  45. package/dist/types/lib/hooks/useLocale.d.ts.map +1 -0
  46. package/dist/types/lib/hooks/useTranslation.d.ts +7 -0
  47. package/dist/types/lib/hooks/useTranslation.d.ts.map +1 -0
  48. package/dist/types/lib/index.d.ts +3 -0
  49. package/dist/types/lib/index.d.ts.map +1 -0
  50. package/package.json +69 -0
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # @canonical/i18n-react
2
+
3
+ React bindings for [`@canonical/i18n-core`](../../runtime/i18n). Thin by design:
4
+ the core engine owns negotiation, catalogs, formatting, and the live locale
5
+ value; these hooks expose it to React through `useSyncExternalStore`, so updates
6
+ are SSR-safe and surgical — only components that read a locale re-render.
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ bun add @canonical/i18n-react @canonical/i18n-core
12
+ ```
13
+
14
+ ## Setup
15
+
16
+ Wrap the app once. Pass the SSR-negotiated locale so the server and the first
17
+ client render agree.
18
+
19
+ ```tsx
20
+ import type { I18nConfig, Locale, Messages } from "@canonical/i18n-core";
21
+ import { I18nProvider } from "@canonical/i18n-react";
22
+
23
+ const config: I18nConfig = {
24
+ locales: ["en", "fr", "ar"],
25
+ defaultLocale: "en",
26
+ rtlLocales: ["ar"],
27
+ };
28
+
29
+ const catalogs: Record<Locale, Messages> = {
30
+ en: { "nav.home": "Home", items: { one: "{count} item", other: "{count} items" } },
31
+ fr: { "nav.home": "Accueil", items: { one: "{count} article", other: "{count} articles" } },
32
+ };
33
+
34
+ <I18nProvider config={config} catalogs={catalogs} locale={negotiatedLocale}>
35
+ <App />
36
+ </I18nProvider>;
37
+ ```
38
+
39
+ ## Hooks
40
+
41
+ ```tsx
42
+ import { useFormatters, useLocale, useTranslation } from "@canonical/i18n-react";
43
+
44
+ function Home() {
45
+ const { t, direction } = useTranslation();
46
+ return <a dir={direction}>{t("nav.home")}</a>;
47
+ }
48
+
49
+ function Cart({ count }: { count: number }) {
50
+ const { t } = useTranslation();
51
+ return <span>{t("items", { count })}</span>; // plural via Intl.PluralRules
52
+ }
53
+
54
+ function Price({ amount }: { amount: number }) {
55
+ const f = useFormatters();
56
+ return <span>{f.currency(amount, "USD")}</span>;
57
+ }
58
+
59
+ ```
60
+
61
+ ## Language switcher
62
+
63
+ This package deliberately ships no visual components — semantic components
64
+ belong to the design-system tier. Pair `useLocale` with any control; listing
65
+ each locale by its **endonym** (the language's own name) and tagging each
66
+ `<option lang>` keeps the switcher accessible:
67
+
68
+ ```tsx
69
+ import { useLocale } from "@canonical/i18n-react";
70
+
71
+ function LanguageSwitcher() {
72
+ const { locale, locales, setLocale } = useLocale();
73
+ return (
74
+ <select
75
+ aria-label="Language"
76
+ value={locale}
77
+ onChange={(event) => setLocale(event.target.value)}
78
+ >
79
+ {locales.map((tag) => (
80
+ <option key={tag} value={tag} lang={tag}>
81
+ {new Intl.DisplayNames([tag], { type: "language" }).of(tag)}
82
+ </option>
83
+ ))}
84
+ </select>
85
+ );
86
+ }
87
+ ```
88
+
89
+ Changing the locale re-translates and re-formats every subscribed component and,
90
+ in the browser, persists the choice to a cookie and flips `<html dir>` for RTL
91
+ locales — all via the shared `@canonical/i18n-core` source.
92
+
93
+ For a correct first paint, set `<html lang dir>` on the server from the
94
+ negotiated locale with `documentAttrs` (see the
95
+ [`@canonical/i18n-core` README](../../runtime/i18n/README.md#server-side-rendering)).
96
+
97
+ ## License
98
+
99
+ LGPL-3.0
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @canonical/i18n-react — React bindings for @canonical/i18n-core: the
3
+ * `I18nProvider` and the `useTranslation`, `useLocale`, and `useFormatters`
4
+ * hooks, all backed by `useSyncExternalStore` for SSR-safe, surgical updates.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ export * from "./lib/index.js";
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { createContext } from "react";
2
+ /**
3
+ * React context carrying the i18n runtime. `I18nProvider` writes it; the
4
+ * i18n-react hooks read it.
5
+ */
6
+ const I18nContext = createContext(null);
7
+ export default I18nContext;
8
+ //# sourceMappingURL=Context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Context.js","sourceRoot":"","sources":["../../../../src/lib/I18nProvider/Context.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAGtC;;;GAGG;AACH,MAAM,WAAW,GAAG,aAAa,CAA0B,IAAI,CAAC,CAAC;AAEjE,eAAe,WAAW,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createLocaleSource } from "@canonical/i18n-core";
3
+ import { useMemo, useState } from "react";
4
+ import I18nContext from "./Context.js";
5
+ /**
6
+ * Provide the i18n runtime to the `useTranslation`, `useLocale`, and
7
+ * `useFormatters` hooks. Mount once around the subtree that renders localized
8
+ * content.
9
+ *
10
+ * Pass the SSR-negotiated `locale` so the server and first client render agree.
11
+ * Supply an external `source` only to share one locale value across frameworks;
12
+ * otherwise the provider owns it.
13
+ */
14
+ export default function I18nProvider({ children, config, catalogs, locale, source, }) {
15
+ const [ownSource] = useState(() => createLocaleSource(config, { initial: locale ?? config.defaultLocale }));
16
+ const activeSource = source ?? ownSource;
17
+ const value = useMemo(() => ({ source: activeSource, config, catalogs }), [activeSource, config, catalogs]);
18
+ return _jsx(I18nContext.Provider, { value: value, children: children });
19
+ }
20
+ //# sourceMappingURL=Provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Provider.js","sourceRoot":"","sources":["../../../../src/lib/I18nProvider/Provider.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAqB,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC7D,OAAO,WAAW,MAAM,cAAc,CAAC;AAGvC;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,MAAM,EACN,MAAM,GACY;IAClB,MAAM,CAAC,SAAS,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,CAChC,kBAAkB,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC,CACxE,CAAC;IACF,MAAM,YAAY,GAAG,MAAM,IAAI,SAAS,CAAC;IAEzC,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,EAClD,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,CAAC,CACjC,CAAC;IAEF,OAAO,KAAC,WAAW,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAAwB,CAAC;AAC/E,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { default as I18nProvider } from "./Provider.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/lib/I18nProvider/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/lib/I18nProvider/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ export * from "./types.js";
2
+ export { default as useFormatters } from "./useFormatters.js";
3
+ export { default as useI18nContext } from "./useI18nContext.js";
4
+ export { default as useLocale } from "./useLocale.js";
5
+ export { default as useTranslation } from "./useTranslation.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/lib/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/lib/hooks/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,13 @@
1
+ import { createFormatters } from "@canonical/i18n-core";
2
+ import { useMemo, useSyncExternalStore } from "react";
3
+ import useI18nContext from "./useI18nContext.js";
4
+ /**
5
+ * Access memoized {@link Formatters} (number, currency, date, time, relative
6
+ * time, list) for the active locale. Re-renders on locale change.
7
+ */
8
+ export default function useFormatters() {
9
+ const { source } = useI18nContext();
10
+ const locale = useSyncExternalStore(source.subscribe, source.get, source.get);
11
+ return useMemo(() => createFormatters(locale), [locale]);
12
+ }
13
+ //# sourceMappingURL=useFormatters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFormatters.js","sourceRoot":"","sources":["../../../../src/lib/hooks/useFormatters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAmB,MAAM,sBAAsB,CAAC;AACzE,OAAO,EAAE,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,cAAc,MAAM,qBAAqB,CAAC;AAEjD;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,aAAa;IACnC,MAAM,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;IACpC,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IAE9E,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { useContext } from "react";
2
+ import I18nContext from "../I18nProvider/Context.js";
3
+ /**
4
+ * Read the i18n context, throwing when used outside an `I18nProvider` so
5
+ * consumers fail fast instead of silently rendering untranslated content.
6
+ */
7
+ export default function useI18nContext() {
8
+ const value = useContext(I18nContext);
9
+ if (!value) {
10
+ throw new Error("I18nProvider is required to use i18n-react hooks.");
11
+ }
12
+ return value;
13
+ }
14
+ //# sourceMappingURL=useI18nContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useI18nContext.js","sourceRoot":"","sources":["../../../../src/lib/hooks/useI18nContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AACnC,OAAO,WAAW,MAAM,4BAA4B,CAAC;AAGrD;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,cAAc;IACpC,MAAM,KAAK,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IAEtC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IACvE,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { directionOf } from "@canonical/i18n-core";
2
+ import { useSyncExternalStore } from "react";
3
+ import useI18nContext from "./useI18nContext.js";
4
+ /**
5
+ * Read and change the active locale. Re-renders the calling component on every
6
+ * locale change — pair it with any locale-switching control (see the README for a `<select>` recipe).
7
+ */
8
+ export default function useLocale() {
9
+ const { source, config } = useI18nContext();
10
+ const locale = useSyncExternalStore(source.subscribe, source.get, source.get);
11
+ // Derive direction from the subscribed locale (not the live getter) so it is
12
+ // provably part of the reactive snapshot.
13
+ return {
14
+ locale,
15
+ direction: directionOf(config, locale),
16
+ setLocale: source.set,
17
+ locales: config.locales,
18
+ };
19
+ }
20
+ //# sourceMappingURL=useLocale.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useLocale.js","sourceRoot":"","sources":["../../../../src/lib/hooks/useLocale.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAE7C,OAAO,cAAc,MAAM,qBAAqB,CAAC;AAEjD;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,SAAS;IAC/B,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;IAC5C,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IAE9E,6EAA6E;IAC7E,0CAA0C;IAC1C,OAAO;QACL,MAAM;QACN,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC;QACtC,SAAS,EAAE,MAAM,CAAC,GAAG;QACrB,OAAO,EAAE,MAAM,CAAC,OAAO;KACxB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { createTranslator, directionOf } from "@canonical/i18n-core";
2
+ import { useMemo, useSyncExternalStore } from "react";
3
+ import useI18nContext from "./useI18nContext.js";
4
+ /**
5
+ * Access the translator for the active locale. Re-renders the calling component
6
+ * whenever the locale changes.
7
+ */
8
+ export default function useTranslation() {
9
+ const { source, catalogs, config } = useI18nContext();
10
+ const locale = useSyncExternalStore(source.subscribe, source.get, source.get);
11
+ const t = useMemo(() => createTranslator(locale, catalogs[locale] ?? {}), [locale, catalogs]);
12
+ return { t, locale, direction: directionOf(config, locale) };
13
+ }
14
+ //# sourceMappingURL=useTranslation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTranslation.js","sourceRoot":"","sources":["../../../../src/lib/hooks/useTranslation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,EAAE,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAEtD,OAAO,cAAc,MAAM,qBAAqB,CAAC;AAEjD;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,cAAc;IACpC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;IACtD,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IAC9E,MAAM,CAAC,GAAG,OAAO,CACf,GAAG,EAAE,CAAC,gBAAgB,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EACtD,CAAC,MAAM,EAAE,QAAQ,CAAC,CACnB,CAAC;IAEF,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;AAC/D,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from "./hooks/index.js";
2
+ export * from "./I18nProvider/index.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @canonical/i18n-react — React bindings for @canonical/i18n-core: the
3
+ * `I18nProvider` and the `useTranslation`, `useLocale`, and `useFormatters`
4
+ * hooks, all backed by `useSyncExternalStore` for SSR-safe, surgical updates.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ export * from "./lib/index.js";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { I18nContextValue } from "./types.js";
2
+ /**
3
+ * React context carrying the i18n runtime. `I18nProvider` writes it; the
4
+ * i18n-react hooks read it.
5
+ */
6
+ declare const I18nContext: import("react").Context<I18nContextValue | null>;
7
+ export default I18nContext;
8
+ //# sourceMappingURL=Context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Context.d.ts","sourceRoot":"","sources":["../../../../src/lib/I18nProvider/Context.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD;;;GAGG;AACH,QAAA,MAAM,WAAW,kDAA+C,CAAC;AAEjE,eAAe,WAAW,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { type ReactElement } from "react";
2
+ import type { I18nProviderProps } from "./types.js";
3
+ /**
4
+ * Provide the i18n runtime to the `useTranslation`, `useLocale`, and
5
+ * `useFormatters` hooks. Mount once around the subtree that renders localized
6
+ * content.
7
+ *
8
+ * Pass the SSR-negotiated `locale` so the server and first client render agree.
9
+ * Supply an external `source` only to share one locale value across frameworks;
10
+ * otherwise the provider owns it.
11
+ */
12
+ export default function I18nProvider({ children, config, catalogs, locale, source, }: I18nProviderProps): ReactElement;
13
+ //# sourceMappingURL=Provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Provider.d.ts","sourceRoot":"","sources":["../../../../src/lib/I18nProvider/Provider.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,YAAY,EAAqB,MAAM,OAAO,CAAC;AAE7D,OAAO,KAAK,EAAoB,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEtE;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,MAAM,EACN,MAAM,GACP,EAAE,iBAAiB,GAAG,YAAY,CAYlC"}
@@ -0,0 +1,3 @@
1
+ export { default as I18nProvider } from "./Provider.js";
2
+ export type { I18nContextValue, I18nProviderProps } from "./types.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/I18nProvider/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,eAAe,CAAC;AACxD,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,27 @@
1
+ import type { I18nConfig, Locale, LocaleSource, Messages } from "@canonical/i18n-core";
2
+ import type { ReactNode } from "react";
3
+ /** Value carried by the i18n context: the stable runtime wiring. */
4
+ export interface I18nContextValue {
5
+ /** Live locale value shared across the subtree. */
6
+ source: LocaleSource;
7
+ /** Active locale configuration. */
8
+ config: I18nConfig;
9
+ /** Message catalogs keyed by locale. */
10
+ catalogs: Record<Locale, Messages>;
11
+ }
12
+ /** Props for `I18nProvider`. */
13
+ export interface I18nProviderProps {
14
+ children: ReactNode;
15
+ /** Active locale configuration. */
16
+ config: I18nConfig;
17
+ /** Message catalogs keyed by locale. */
18
+ catalogs: Record<Locale, Messages>;
19
+ /** Initial locale (e.g. SSR-negotiated). Defaults to `config.defaultLocale`. */
20
+ locale?: Locale;
21
+ /**
22
+ * An existing {@link LocaleSource} to bind to — supply this only to share one
23
+ * locale value across frameworks. When omitted, the provider owns one.
24
+ */
25
+ source?: LocaleSource;
26
+ }
27
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/lib/I18nProvider/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,MAAM,EACN,YAAY,EACZ,QAAQ,EACT,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,oEAAoE;AACpE,MAAM,WAAW,gBAAgB;IAC/B,mDAAmD;IACnD,MAAM,EAAE,YAAY,CAAC;IACrB,mCAAmC;IACnC,MAAM,EAAE,UAAU,CAAC;IACnB,wCAAwC;IACxC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;CACpC;AAED,gCAAgC;AAChC,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,SAAS,CAAC;IACpB,mCAAmC;IACnC,MAAM,EAAE,UAAU,CAAC;IACnB,wCAAwC;IACxC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACnC,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB"}
@@ -0,0 +1,6 @@
1
+ export * from "./types.js";
2
+ export { default as useFormatters } from "./useFormatters.js";
3
+ export { default as useI18nContext } from "./useI18nContext.js";
4
+ export { default as useLocale } from "./useLocale.js";
5
+ export { default as useTranslation } from "./useTranslation.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { Direction, Locale, Translator } from "@canonical/i18n-core";
2
+ /** Result of `useTranslation`. */
3
+ export interface UseTranslationResult {
4
+ /** Translate a key for the active locale. */
5
+ t: Translator;
6
+ /** Active locale. */
7
+ locale: Locale;
8
+ /** Writing direction of the active locale. */
9
+ direction: Direction;
10
+ }
11
+ /** Result of `useLocale`. */
12
+ export interface UseLocaleResult {
13
+ /** Active locale. */
14
+ locale: Locale;
15
+ /** Writing direction of the active locale. */
16
+ direction: Direction;
17
+ /** Change the active locale. */
18
+ setLocale: (locale: Locale) => void;
19
+ /** Every configured locale, for building a selector. */
20
+ locales: readonly Locale[];
21
+ }
22
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/lib/hooks/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAE1E,kCAAkC;AAClC,MAAM,WAAW,oBAAoB;IACnC,6CAA6C;IAC7C,CAAC,EAAE,UAAU,CAAC;IACd,qBAAqB;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,6BAA6B;AAC7B,MAAM,WAAW,eAAe;IAC9B,qBAAqB;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,SAAS,EAAE,SAAS,CAAC;IACrB,gCAAgC;IAChC,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,wDAAwD;IACxD,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B"}
@@ -0,0 +1,7 @@
1
+ import { type Formatters } from "@canonical/i18n-core";
2
+ /**
3
+ * Access memoized {@link Formatters} (number, currency, date, time, relative
4
+ * time, list) for the active locale. Re-renders on locale change.
5
+ */
6
+ export default function useFormatters(): Formatters;
7
+ //# sourceMappingURL=useFormatters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFormatters.d.ts","sourceRoot":"","sources":["../../../../src/lib/hooks/useFormatters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAIzE;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,aAAa,IAAI,UAAU,CAKlD"}
@@ -0,0 +1,7 @@
1
+ import type { I18nContextValue } from "../I18nProvider/types.js";
2
+ /**
3
+ * Read the i18n context, throwing when used outside an `I18nProvider` so
4
+ * consumers fail fast instead of silently rendering untranslated content.
5
+ */
6
+ export default function useI18nContext(): I18nContextValue;
7
+ //# sourceMappingURL=useI18nContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useI18nContext.d.ts","sourceRoot":"","sources":["../../../../src/lib/hooks/useI18nContext.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAEjE;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,cAAc,IAAI,gBAAgB,CAQzD"}
@@ -0,0 +1,7 @@
1
+ import type { UseLocaleResult } from "./types.js";
2
+ /**
3
+ * Read and change the active locale. Re-renders the calling component on every
4
+ * locale change — pair it with any locale-switching control (see the README for a `<select>` recipe).
5
+ */
6
+ export default function useLocale(): UseLocaleResult;
7
+ //# sourceMappingURL=useLocale.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useLocale.d.ts","sourceRoot":"","sources":["../../../../src/lib/hooks/useLocale.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGlD;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,SAAS,IAAI,eAAe,CAYnD"}
@@ -0,0 +1,7 @@
1
+ import type { UseTranslationResult } from "./types.js";
2
+ /**
3
+ * Access the translator for the active locale. Re-renders the calling component
4
+ * whenever the locale changes.
5
+ */
6
+ export default function useTranslation(): UseTranslationResult;
7
+ //# sourceMappingURL=useTranslation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTranslation.d.ts","sourceRoot":"","sources":["../../../../src/lib/hooks/useTranslation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAGvD;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,cAAc,IAAI,oBAAoB,CAS7D"}
@@ -0,0 +1,3 @@
1
+ export * from "./hooks/index.js";
2
+ export * from "./I18nProvider/index.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@canonical/i18n-react",
3
+ "description": "React bindings for @canonical/i18n-core: I18nProvider and the useTranslation, useLocale, and useFormatters hooks.",
4
+ "version": "0.29.0",
5
+ "type": "module",
6
+ "module": "dist/esm/index.js",
7
+ "types": "dist/types/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/types/index.d.ts",
11
+ "import": "./dist/esm/index.js",
12
+ "default": "./dist/esm/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "author": {
19
+ "email": "webteam@canonical.com",
20
+ "name": "Canonical Webteam"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/canonical/pragma"
25
+ },
26
+ "license": "LGPL-3.0",
27
+ "bugs": {
28
+ "url": "https://github.com/canonical/pragma/issues"
29
+ },
30
+ "homepage": "https://github.com/canonical/pragma#readme",
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "scripts": {
35
+ "build": "tsc -p tsconfig.build.json",
36
+ "build:all": "bun run build",
37
+ "check": "bun run check:biome && bun run check:ts && bun run check:webarchitect",
38
+ "check:webarchitect": "webarchitect package-react",
39
+ "check:fix": "bun run check:biome:fix && bun run check:ts",
40
+ "check:biome": "biome check",
41
+ "check:biome:fix": "biome check --write",
42
+ "check:ts": "tsc --noEmit",
43
+ "test": "vitest run",
44
+ "test:watch": "vitest",
45
+ "test:coverage": "vitest run --coverage"
46
+ },
47
+ "dependencies": {
48
+ "@canonical/i18n-core": "^0.29.0",
49
+ "react": "^19.2.4",
50
+ "react-dom": "^19.2.4"
51
+ },
52
+ "devDependencies": {
53
+ "@biomejs/biome": "2.4.9",
54
+ "@canonical/biome-config": "^0.29.0",
55
+ "@canonical/typescript-config-react": "^0.29.0",
56
+ "@canonical/webarchitect": "^0.29.0",
57
+ "@testing-library/jest-dom": "^6.9.1",
58
+ "@testing-library/react": "^16.3.2",
59
+ "@types/node": "^24.12.0",
60
+ "@types/react": "^19.2.14",
61
+ "@types/react-dom": "^19.2.3",
62
+ "@vitejs/plugin-react": "^6.0.0",
63
+ "@vitest/coverage-v8": "^4.0.18",
64
+ "jsdom": "^28.1.0",
65
+ "typescript": "^5.9.3",
66
+ "vite": "^8.0.1",
67
+ "vitest": "^4.0.18"
68
+ }
69
+ }