@canonical/i18n-core 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 (58) hide show
  1. package/README.md +139 -0
  2. package/dist/esm/index.js +10 -0
  3. package/dist/esm/index.js.map +1 -0
  4. package/dist/esm/lib/constants.js +5 -0
  5. package/dist/esm/lib/constants.js.map +1 -0
  6. package/dist/esm/lib/createFormatters.js +39 -0
  7. package/dist/esm/lib/createFormatters.js.map +1 -0
  8. package/dist/esm/lib/createLocaleSource.js +72 -0
  9. package/dist/esm/lib/createLocaleSource.js.map +1 -0
  10. package/dist/esm/lib/createTranslator.js +35 -0
  11. package/dist/esm/lib/createTranslator.js.map +1 -0
  12. package/dist/esm/lib/directionOf.js +13 -0
  13. package/dist/esm/lib/directionOf.js.map +1 -0
  14. package/dist/esm/lib/documentAttrs.js +13 -0
  15. package/dist/esm/lib/documentAttrs.js.map +1 -0
  16. package/dist/esm/lib/index.js +12 -0
  17. package/dist/esm/lib/index.js.map +1 -0
  18. package/dist/esm/lib/isSupportedLocale.js +10 -0
  19. package/dist/esm/lib/isSupportedLocale.js.map +1 -0
  20. package/dist/esm/lib/mergeCatalogs.js +13 -0
  21. package/dist/esm/lib/mergeCatalogs.js.map +1 -0
  22. package/dist/esm/lib/negotiateLocale.js +47 -0
  23. package/dist/esm/lib/negotiateLocale.js.map +1 -0
  24. package/dist/esm/lib/parseAcceptLanguage.js +27 -0
  25. package/dist/esm/lib/parseAcceptLanguage.js.map +1 -0
  26. package/dist/esm/lib/readCookie.js +30 -0
  27. package/dist/esm/lib/readCookie.js.map +1 -0
  28. package/dist/esm/lib/types.js +6 -0
  29. package/dist/esm/lib/types.js.map +1 -0
  30. package/dist/types/index.d.ts +10 -0
  31. package/dist/types/index.d.ts.map +1 -0
  32. package/dist/types/lib/constants.d.ts +5 -0
  33. package/dist/types/lib/constants.d.ts.map +1 -0
  34. package/dist/types/lib/createFormatters.d.ts +9 -0
  35. package/dist/types/lib/createFormatters.d.ts.map +1 -0
  36. package/dist/types/lib/createLocaleSource.d.ts +17 -0
  37. package/dist/types/lib/createLocaleSource.d.ts.map +1 -0
  38. package/dist/types/lib/createTranslator.d.ts +16 -0
  39. package/dist/types/lib/createTranslator.d.ts.map +1 -0
  40. package/dist/types/lib/directionOf.d.ts +9 -0
  41. package/dist/types/lib/directionOf.d.ts.map +1 -0
  42. package/dist/types/lib/documentAttrs.d.ts +14 -0
  43. package/dist/types/lib/documentAttrs.d.ts.map +1 -0
  44. package/dist/types/lib/index.d.ts +12 -0
  45. package/dist/types/lib/index.d.ts.map +1 -0
  46. package/dist/types/lib/isSupportedLocale.d.ts +9 -0
  47. package/dist/types/lib/isSupportedLocale.d.ts.map +1 -0
  48. package/dist/types/lib/mergeCatalogs.d.ts +12 -0
  49. package/dist/types/lib/mergeCatalogs.d.ts.map +1 -0
  50. package/dist/types/lib/negotiateLocale.d.ts +20 -0
  51. package/dist/types/lib/negotiateLocale.d.ts.map +1 -0
  52. package/dist/types/lib/parseAcceptLanguage.d.ts +10 -0
  53. package/dist/types/lib/parseAcceptLanguage.d.ts.map +1 -0
  54. package/dist/types/lib/readCookie.d.ts +11 -0
  55. package/dist/types/lib/readCookie.d.ts.map +1 -0
  56. package/dist/types/lib/types.d.ts +94 -0
  57. package/dist/types/lib/types.d.ts.map +1 -0
  58. package/package.json +55 -0
package/README.md ADDED
@@ -0,0 +1,139 @@
1
+ # @canonical/i18n-core
2
+
3
+ Framework-agnostic internationalization for the Pragma design system, built on
4
+ the native [`Intl`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Intl)
5
+ API. Zero runtime dependencies.
6
+
7
+ `i18n-core` owns everything that is not framework reactivity — locale
8
+ negotiation, message catalogs and translation, memoized formatters, and a live
9
+ locale source. React, Svelte, and Lit bindings stay thin and share this one
10
+ engine.
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ bun add @canonical/i18n-core
16
+ ```
17
+
18
+ ## Configuration
19
+
20
+ Locales are declared explicitly as plain data — no magic discovery.
21
+
22
+ ```ts
23
+ import type { I18nConfig } from "@canonical/i18n-core";
24
+
25
+ const config: I18nConfig = {
26
+ locales: ["en", "fr", "ar"],
27
+ defaultLocale: "en",
28
+ rtlLocales: ["ar"],
29
+ };
30
+ ```
31
+
32
+ ## Locale negotiation (pure, SSR-safe)
33
+
34
+ ```ts
35
+ import { directionOf, negotiateLocale } from "@canonical/i18n-core";
36
+
37
+ const locale = negotiateLocale(config, {
38
+ cookieHeader: request.headers.get("cookie"),
39
+ acceptLanguage: request.headers.get("accept-language"),
40
+ });
41
+ const dir = directionOf(config, locale); // "ltr" | "rtl"
42
+ ```
43
+
44
+ An explicit cookie wins, then `Accept-Language` negotiation, then the default
45
+ locale. It takes raw header strings and returns a tag, so it behaves the same
46
+ on the server and the client.
47
+
48
+ ## Messages
49
+
50
+ ```ts
51
+ import { createTranslator, mergeCatalogs } from "@canonical/i18n-core";
52
+
53
+ const t = createTranslator("en", {
54
+ "nav.home": "Home",
55
+ greeting: "Hello, {name}!",
56
+ items: { one: "{count} item", other: "{count} items" },
57
+ });
58
+
59
+ t("greeting", { name: "Ada" }); // "Hello, Ada!"
60
+ t("items", { count: 3 }); // "3 items" — via Intl.PluralRules
61
+ ```
62
+
63
+ `mergeCatalogs(base, overrides)` layers app messages over a component's
64
+ built-in defaults. Resolution never throws: a missing key returns the key
65
+ itself, an absent placeholder is left verbatim.
66
+
67
+ ## Formatting
68
+
69
+ ```ts
70
+ import { createFormatters } from "@canonical/i18n-core";
71
+
72
+ const f = createFormatters("en-US");
73
+ f.number(1234.5); // "1,234.5"
74
+ f.currency(1234.5, "USD"); // "$1,234.50"
75
+ f.relativeTime(-3, "day", { numeric: "auto" }); // "3 days ago"
76
+ f.list(["A", "B", "C"]); // "A, B, and C"
77
+ ```
78
+
79
+ Formatters memoize their `Intl` instances by options, since constructing them
80
+ is comparatively expensive.
81
+
82
+ ## Reactive locale source
83
+
84
+ The cross-framework runtime channel. `subscribe` follows the Svelte store
85
+ contract — it calls back immediately and on every change — which also satisfies
86
+ React's `useSyncExternalStore` and a Lit reactive controller. One object drives
87
+ every framework. In the browser it persists the choice to a cookie and reflects
88
+ `<html lang dir>`; on the server those effects are inert.
89
+
90
+ ```ts
91
+ import { createLocaleSource } from "@canonical/i18n-core";
92
+
93
+ const source = createLocaleSource(config, { initial: locale });
94
+ const unsubscribe = source.subscribe((next) => render(next));
95
+ source.set("ar"); // notifies subscribers; persists + reflects dir="rtl"
96
+ ```
97
+
98
+ On the server, create one source **per request** — the React/Svelte/Lit
99
+ bindings already do this. A module-level singleton shared across concurrent
100
+ requests leaks locale state between them.
101
+
102
+ ## Server-side rendering
103
+
104
+ The source can only set `<html lang dir>` from the client. For a correct first
105
+ paint and assistive technology, render `<html lang dir>` on the server from the
106
+ negotiated locale with `documentAttrs`:
107
+
108
+ ```ts
109
+ import { documentAttrs, negotiateLocale } from "@canonical/i18n-core";
110
+
111
+ const locale = negotiateLocale(config, {
112
+ cookieHeader: request.headers.get("cookie"),
113
+ acceptLanguage: request.headers.get("accept-language"),
114
+ });
115
+ const { lang, dir } = documentAttrs(config, locale);
116
+ // render: <html lang={lang} dir={dir}> … </html>
117
+ ```
118
+
119
+ Pass the same `locale` to the client (`createLocaleSource({ initial: locale })`)
120
+ so the server and first client render agree.
121
+
122
+ ## Right-to-left & accessibility
123
+
124
+ `directionOf` resolves writing direction from `rtlLocales` (base languages); a
125
+ sensible default set is `["ar", "he", "fa", "ur"]`. Flipping `dir` is necessary
126
+ but not sufficient — author UI with CSS **logical properties**
127
+ (`margin-inline-start`, `inset-inline`, `text-align: start`) and mirror
128
+ directional icons so RTL is structural, not decorative.
129
+
130
+ Values in `config.locales` must be valid BCP-47 tags — they are reflected to
131
+ `<html lang>` and passed to `Intl`. A missing message key is returned verbatim
132
+ (`"nav.home"`), which is visible *and* read aloud, so treat catalog completeness
133
+ as an accessibility concern. `t()` returns plain strings that the framework
134
+ escapes on render; never pipe its output into `dangerouslySetInnerHTML` /
135
+ `unsafeHTML` / `{@html}`.
136
+
137
+ ## License
138
+
139
+ LGPL-3.0
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `@canonical/i18n-core` — framework-agnostic internationalization built on
3
+ * native `Intl`: locale negotiation, message catalogs and translators,
4
+ * memoized formatters, and a reactive locale source shared across React,
5
+ * Svelte, and Lit.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ export * from "./lib/index.js";
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1,5 @@
1
+ /** Cookie name used to persist a locale choice when none is configured. */
2
+ export const DEFAULT_COOKIE_NAME = "locale";
3
+ /** Lifetime, in seconds, of the persisted locale cookie (one year). */
4
+ export const COOKIE_MAX_AGE = 31_536_000;
5
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/lib/constants.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,MAAM,CAAC,MAAM,mBAAmB,GAAG,QAAQ,CAAC;AAE5C,uEAAuE;AACvE,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Wrap an `Intl` constructor in an options-keyed cache. Building `Intl`
3
+ * instances is comparatively expensive and formatter calls repeat with the
4
+ * same options, so memoizing by serialized options keeps reuse cheap
5
+ * (Constitution IX — optimise only where it pays).
6
+ */
7
+ function cached(build) {
8
+ const cache = new Map();
9
+ return (options) => {
10
+ const key = JSON.stringify(options ?? {});
11
+ let formatter = cache.get(key);
12
+ if (formatter === undefined) {
13
+ formatter = build(options);
14
+ cache.set(key, formatter);
15
+ }
16
+ return formatter;
17
+ };
18
+ }
19
+ /**
20
+ * Create locale-aware {@link Formatters} backed by memoized native `Intl`
21
+ * instances. Framework-agnostic and SSR-safe — `Intl` is a platform global.
22
+ *
23
+ * @param locale - BCP 47 tag every formatter is bound to.
24
+ */
25
+ export default function createFormatters(locale) {
26
+ const numberFormat = cached((options) => new Intl.NumberFormat(locale, options));
27
+ const dateFormat = cached((options) => new Intl.DateTimeFormat(locale, options));
28
+ const relativeTimeFormat = cached((options) => new Intl.RelativeTimeFormat(locale, options));
29
+ const listFormat = cached((options) => new Intl.ListFormat(locale, options));
30
+ return {
31
+ number: (value, options) => numberFormat(options).format(value),
32
+ currency: (value, currency, options) => numberFormat({ ...options, style: "currency", currency }).format(value),
33
+ date: (value, options) => dateFormat(options).format(value),
34
+ time: (value, options) => dateFormat(options ?? { timeStyle: "short" }).format(value),
35
+ relativeTime: (value, unit, options) => relativeTimeFormat(options).format(value, unit),
36
+ list: (items, options) => listFormat(options).format([...items]),
37
+ };
38
+ }
39
+ //# sourceMappingURL=createFormatters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createFormatters.js","sourceRoot":"","sources":["../../../src/lib/createFormatters.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,SAAS,MAAM,CACb,KAAoD;IAEpD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC5C,OAAO,CAAC,OAAO,EAAE,EAAE;QACjB,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;QAC1C,IAAI,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5B,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;YAC3B,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC5B,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,MAAc;IACrD,MAAM,YAAY,GAAG,MAAM,CACzB,CAAC,OAAkC,EAAE,EAAE,CACrC,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CACzC,CAAC;IACF,MAAM,UAAU,GAAG,MAAM,CACvB,CAAC,OAAoC,EAAE,EAAE,CACvC,IAAI,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,CAC3C,CAAC;IACF,MAAM,kBAAkB,GAAG,MAAM,CAC/B,CAAC,OAAwC,EAAE,EAAE,CAC3C,IAAI,IAAI,CAAC,kBAAkB,CAAC,MAAM,EAAE,OAAO,CAAC,CAC/C,CAAC;IACF,MAAM,UAAU,GAAG,MAAM,CACvB,CAAC,OAAgC,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAC3E,CAAC;IAEF,OAAO;QACL,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;QAC/D,QAAQ,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,CACrC,YAAY,CAAC,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;QACzE,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;QAC3D,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CACvB,UAAU,CAAC,OAAO,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;QAC7D,YAAY,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CACrC,kBAAkB,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC;QACjD,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;KACjE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,72 @@
1
+ import { COOKIE_MAX_AGE, DEFAULT_COOKIE_NAME } from "./constants.js";
2
+ import directionOf from "./directionOf.js";
3
+ /** Persist the locale to a cookie. Browser-only; a no-op on the server. */
4
+ function writeCookie(name, value) {
5
+ const env = globalThis;
6
+ /* v8 ignore start -- browser-only effect, exercised through framework bindings */
7
+ if (env.document === undefined)
8
+ return;
9
+ env.document.cookie = `${name}=${encodeURIComponent(value)}; path=/; max-age=${COOKIE_MAX_AGE}; SameSite=Lax`;
10
+ /* v8 ignore stop */
11
+ }
12
+ /** Reflect the locale onto `<html lang dir>`. Browser-only; a no-op on the server. */
13
+ function reflectDocument(locale, direction) {
14
+ const env = globalThis;
15
+ /* v8 ignore start -- browser-only effect, exercised through framework bindings */
16
+ if (env.document === undefined)
17
+ return;
18
+ env.document.documentElement.lang = locale;
19
+ env.document.documentElement.dir = direction;
20
+ /* v8 ignore stop */
21
+ }
22
+ /**
23
+ * Create a live, observable locale value — the single cross-framework runtime
24
+ * channel. It holds the current locale, notifies subscribers on change, and (in
25
+ * the browser) persists the choice to a cookie and reflects it onto
26
+ * `<html lang dir>`. On the server those effects are inert, so the same source
27
+ * is safe to construct during SSR.
28
+ *
29
+ * `subscribe` honors the Svelte store contract, so a Svelte component can use
30
+ * `$source` directly, React can pass it to `useSyncExternalStore`, and a Lit
31
+ * reactive controller can subscribe — all from this one object.
32
+ *
33
+ * @param config - Active locale configuration.
34
+ * @param options - Initial locale and effect toggles.
35
+ */
36
+ export default function createLocaleSource(config, options = {}) {
37
+ const { initial = config.defaultLocale, persist = true, reflect = true, } = options;
38
+ const cookieName = config.cookieName ?? DEFAULT_COOKIE_NAME;
39
+ const subscribers = new Set();
40
+ let current = initial;
41
+ // Reflect the initial locale onto `<html lang dir>` immediately (browser only;
42
+ // inert on the server). Without this, a page negotiated to a non-default
43
+ // locale keeps stale `<html lang dir>` until the first locale change — a WCAG
44
+ // 3.1.1 hole and wrong direction for RTL.
45
+ if (reflect)
46
+ reflectDocument(current, directionOf(config, current));
47
+ return {
48
+ get: () => current,
49
+ get direction() {
50
+ return directionOf(config, current);
51
+ },
52
+ set(locale) {
53
+ if (locale === current)
54
+ return;
55
+ current = locale;
56
+ if (persist)
57
+ writeCookie(cookieName, locale);
58
+ if (reflect)
59
+ reflectDocument(locale, directionOf(config, locale));
60
+ for (const run of [...subscribers])
61
+ run(current);
62
+ },
63
+ subscribe(run) {
64
+ subscribers.add(run);
65
+ run(current);
66
+ return () => {
67
+ subscribers.delete(run);
68
+ };
69
+ },
70
+ };
71
+ }
72
+ //# sourceMappingURL=createLocaleSource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createLocaleSource.js","sourceRoot":"","sources":["../../../src/lib/createLocaleSource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrE,OAAO,WAAW,MAAM,kBAAkB,CAAC;AAS3C,2EAA2E;AAC3E,SAAS,WAAW,CAAC,IAAY,EAAE,KAAa;IAC9C,MAAM,GAAG,GAAG,UAA+C,CAAC;IAC5D,kFAAkF;IAClF,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS;QAAE,OAAO;IACvC,GAAG,CAAC,QAAQ,CAAC,MAAM,GAAG,GAAG,IAAI,IAAI,kBAAkB,CAAC,KAAK,CAAC,qBAAqB,cAAc,gBAAgB,CAAC;IAC9G,oBAAoB;AACtB,CAAC;AAED,sFAAsF;AACtF,SAAS,eAAe,CAAC,MAAc,EAAE,SAAoB;IAC3D,MAAM,GAAG,GAAG,UAEX,CAAC;IACF,kFAAkF;IAClF,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS;QAAE,OAAO;IACvC,GAAG,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,GAAG,MAAM,CAAC;IAC3C,GAAG,CAAC,QAAQ,CAAC,eAAe,CAAC,GAAG,GAAG,SAAS,CAAC;IAC7C,oBAAoB;AACtB,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,OAAO,UAAU,kBAAkB,CACxC,MAAkB,EAClB,UAA+B,EAAE;IAEjC,MAAM,EACJ,OAAO,GAAG,MAAM,CAAC,aAAa,EAC9B,OAAO,GAAG,IAAI,EACd,OAAO,GAAG,IAAI,GACf,GAAG,OAAO,CAAC;IACZ,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,mBAAmB,CAAC;IAC5D,MAAM,WAAW,GAAG,IAAI,GAAG,EAA4B,CAAC;IACxD,IAAI,OAAO,GAAG,OAAO,CAAC;IAEtB,+EAA+E;IAC/E,yEAAyE;IACzE,8EAA8E;IAC9E,0CAA0C;IAC1C,IAAI,OAAO;QAAE,eAAe,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAEpE,OAAO;QACL,GAAG,EAAE,GAAG,EAAE,CAAC,OAAO;QAClB,IAAI,SAAS;YACX,OAAO,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACtC,CAAC;QACD,GAAG,CAAC,MAAM;YACR,IAAI,MAAM,KAAK,OAAO;gBAAE,OAAO;YAC/B,OAAO,GAAG,MAAM,CAAC;YACjB,IAAI,OAAO;gBAAE,WAAW,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;YAC7C,IAAI,OAAO;gBAAE,eAAe,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;YAClE,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,WAAW,CAAC;gBAAE,GAAG,CAAC,OAAO,CAAC,CAAC;QACnD,CAAC;QACD,SAAS,CAAC,GAAG;YACX,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACrB,GAAG,CAAC,OAAO,CAAC,CAAC;YACb,OAAO,GAAG,EAAE;gBACV,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC1B,CAAC,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,35 @@
1
+ const PLACEHOLDER = /\{(\w+)\}/g;
2
+ /**
3
+ * Create a {@link Translator} bound to one locale and message catalog.
4
+ *
5
+ * Resolution is non-throwing, so a bad key never breaks a render:
6
+ * - a missing key returns the key itself;
7
+ * - `{placeholder}` slots are filled from `vars`; an absent variable is left
8
+ * verbatim;
9
+ * - a plural entry selects a branch from `vars.count` via `Intl.PluralRules`,
10
+ * falling back to the required `other` branch.
11
+ *
12
+ * @param locale - BCP 47 tag driving plural selection.
13
+ * @param messages - Catalog for that locale.
14
+ */
15
+ export default function createTranslator(locale, messages) {
16
+ const plural = new Intl.PluralRules(locale);
17
+ return (key, vars = {}) => {
18
+ const entry = Object.hasOwn(messages, key) ? messages[key] : undefined;
19
+ if (entry === undefined)
20
+ return key;
21
+ let template;
22
+ if (typeof entry === "string") {
23
+ template = entry;
24
+ }
25
+ else {
26
+ const count = Number(vars.count);
27
+ const category = Number.isFinite(count)
28
+ ? plural.select(count)
29
+ : "other";
30
+ template = entry[category] ?? entry.other;
31
+ }
32
+ return template.replace(PLACEHOLDER, (whole, name) => Object.hasOwn(vars, name) ? String(vars[name]) : whole);
33
+ };
34
+ }
35
+ //# sourceMappingURL=createTranslator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createTranslator.js","sourceRoot":"","sources":["../../../src/lib/createTranslator.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,GAAG,YAAY,CAAC;AAEjC;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,OAAO,UAAU,gBAAgB,CACtC,MAAc,EACd,QAAkB;IAElB,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAE5C,OAAO,CAAC,GAAG,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE;QACxB,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACvE,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,GAAG,CAAC;QAEpC,IAAI,QAAgB,CAAC;QACrB,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,QAAQ,GAAG,KAAK,CAAC;QACnB,CAAC;aAAM,CAAC;YACN,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACjC,MAAM,QAAQ,GAAmB,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;gBACrD,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;gBACtB,CAAC,CAAC,OAAO,CAAC;YACZ,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC;QAC5C,CAAC;QAED,OAAO,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE,CAC3D,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CACvD,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Resolve the writing direction for a locale from its base language.
3
+ *
4
+ * @param config - Active locale configuration; its `rtlLocales` list decides.
5
+ * @param locale - Locale tag, e.g. `"ar-EG"`, or undefined.
6
+ */
7
+ export default function directionOf(config, locale) {
8
+ if (!locale)
9
+ return "ltr";
10
+ const base = locale.toLowerCase().split("-")[0];
11
+ return config.rtlLocales?.includes(base) ? "rtl" : "ltr";
12
+ }
13
+ //# sourceMappingURL=directionOf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directionOf.js","sourceRoot":"","sources":["../../../src/lib/directionOf.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,WAAW,CACjC,MAAkB,EAClB,MAA0B;IAE1B,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1B,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,OAAO,MAAM,CAAC,UAAU,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,13 @@
1
+ import directionOf from "./directionOf.js";
2
+ /**
3
+ * Compute the `<html>` attributes for a locale: `{ lang, dir }`. Render these on
4
+ * the server (`<html lang dir>`) from the negotiated locale so the first paint —
5
+ * and assistive technology — are correct before the client hydrates.
6
+ *
7
+ * @param config - Active locale configuration.
8
+ * @param locale - The resolved locale.
9
+ */
10
+ export default function documentAttrs(config, locale) {
11
+ return { lang: locale, dir: directionOf(config, locale) };
12
+ }
13
+ //# sourceMappingURL=documentAttrs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documentAttrs.js","sourceRoot":"","sources":["../../../src/lib/documentAttrs.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,kBAAkB,CAAC;AAG3C;;;;;;;GAOG;AACH,MAAM,CAAC,OAAO,UAAU,aAAa,CACnC,MAAkB,EAClB,MAAc;IAEd,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,12 @@
1
+ export { default as createFormatters } from "./createFormatters.js";
2
+ export { default as createLocaleSource } from "./createLocaleSource.js";
3
+ export { default as createTranslator } from "./createTranslator.js";
4
+ export { default as directionOf } from "./directionOf.js";
5
+ export { default as documentAttrs } from "./documentAttrs.js";
6
+ export { default as isSupportedLocale } from "./isSupportedLocale.js";
7
+ export { default as mergeCatalogs } from "./mergeCatalogs.js";
8
+ export { default as negotiateLocale, } from "./negotiateLocale.js";
9
+ export { default as parseAcceptLanguage } from "./parseAcceptLanguage.js";
10
+ export { default as readCookie } from "./readCookie.js";
11
+ export * from "./types.js";
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EACL,OAAO,IAAI,eAAe,GAE3B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC1E,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACxD,cAAc,YAAY,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Test whether `value` is one of the locales declared in `config`.
3
+ *
4
+ * @param config - Active locale configuration.
5
+ * @param value - Candidate tag (any string, or null/undefined).
6
+ */
7
+ export default function isSupportedLocale(config, value) {
8
+ return value != null && config.locales.includes(value);
9
+ }
10
+ //# sourceMappingURL=isSupportedLocale.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isSupportedLocale.js","sourceRoot":"","sources":["../../../src/lib/isSupportedLocale.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,iBAAiB,CACvC,MAAkB,EAClB,KAAgC;IAEhC,OAAO,KAAK,IAAI,IAAI,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACzD,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Merge a base catalog with overrides, key by key. Overrides win; keys absent
3
+ * from `overrides` keep their `base` value. Returns a new object and never
4
+ * mutates its inputs — useful for layering app messages over a component's
5
+ * built-in defaults.
6
+ *
7
+ * @param base - Default messages (e.g. a component's built-in catalog).
8
+ * @param overrides - Messages that take precedence (e.g. app or locale data).
9
+ */
10
+ export default function mergeCatalogs(base, overrides) {
11
+ return { ...base, ...overrides };
12
+ }
13
+ //# sourceMappingURL=mergeCatalogs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mergeCatalogs.js","sourceRoot":"","sources":["../../../src/lib/mergeCatalogs.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,UAAU,aAAa,CACnC,IAAc,EACd,SAAmB;IAEnB,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,SAAS,EAAE,CAAC;AACnC,CAAC"}
@@ -0,0 +1,47 @@
1
+ import { DEFAULT_COOKIE_NAME } from "./constants.js";
2
+ import parseAcceptLanguage from "./parseAcceptLanguage.js";
3
+ import readCookie from "./readCookie.js";
4
+ /**
5
+ * Find the configured locale that matches `value` case-insensitively, returned
6
+ * in its original configured casing — so downstream `catalogs[locale]` and
7
+ * `<html lang>` always use canonical BCP-47 tags (e.g. `"fr-CA"`).
8
+ */
9
+ function findByLower(config, value) {
10
+ if (value === undefined)
11
+ return undefined;
12
+ const lower = value.toLowerCase();
13
+ return config.locales.find((locale) => locale.toLowerCase() === lower);
14
+ }
15
+ /**
16
+ * Pick the first supported locale from a prioritized list of requested tags,
17
+ * matching an exact tag first (`fr-CA`) then its base language (`fr`).
18
+ */
19
+ function matchSupported(config, requested) {
20
+ for (const tag of requested) {
21
+ const exact = findByLower(config, tag);
22
+ if (exact !== undefined)
23
+ return exact;
24
+ const base = findByLower(config, tag.split("-")[0]);
25
+ if (base !== undefined)
26
+ return base;
27
+ }
28
+ return undefined;
29
+ }
30
+ /**
31
+ * Resolve the locale for an incoming request. An explicit cookie wins, then
32
+ * `Accept-Language` negotiation, then {@link I18nConfig.defaultLocale}. Both
33
+ * inputs are matched case-insensitively. Pure — it takes raw header strings and
34
+ * returns a tag, so it is transport-agnostic and runs the same on server and
35
+ * client (Constitution IV — functional core).
36
+ *
37
+ * @param config - Active locale configuration.
38
+ * @param sources - Raw request headers to negotiate from.
39
+ */
40
+ export default function negotiateLocale(config, sources = {}) {
41
+ const cookie = findByLower(config, readCookie(sources.cookieHeader, config.cookieName ?? DEFAULT_COOKIE_NAME));
42
+ if (cookie !== undefined)
43
+ return cookie;
44
+ return (matchSupported(config, parseAcceptLanguage(sources.acceptLanguage)) ??
45
+ config.defaultLocale);
46
+ }
47
+ //# sourceMappingURL=negotiateLocale.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"negotiateLocale.js","sourceRoot":"","sources":["../../../src/lib/negotiateLocale.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,mBAAmB,MAAM,0BAA0B,CAAC;AAC3D,OAAO,UAAU,MAAM,iBAAiB,CAAC;AAWzC;;;;GAIG;AACH,SAAS,WAAW,CAClB,MAAkB,EAClB,KAAyB;IAEzB,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IAClC,OAAO,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,CAAC;AACzE,CAAC;AAED;;;GAGG;AACH,SAAS,cAAc,CACrB,MAAkB,EAClB,SAA4B;IAE5B,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACvC,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QACtC,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACpD,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;IACtC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,OAAO,UAAU,eAAe,CACrC,MAAkB,EAClB,UAAyB,EAAE;IAE3B,MAAM,MAAM,GAAG,WAAW,CACxB,MAAM,EACN,UAAU,CAAC,OAAO,CAAC,YAAY,EAAE,MAAM,CAAC,UAAU,IAAI,mBAAmB,CAAC,CAC3E,CAAC;IACF,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IAExC,OAAO,CACL,cAAc,CAAC,MAAM,EAAE,mBAAmB,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QACnE,MAAM,CAAC,aAAa,CACrB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Parse an `Accept-Language` header into lowercase language tags ordered by
3
+ * descending quality (`q`) weight. Entries with `q=0` (RFC 7231 "not
4
+ * acceptable") or a malformed `q` are dropped. Pure.
5
+ *
6
+ * @example
7
+ * parseAcceptLanguage("fr-CA,fr;q=0.9,en;q=0.8"); // ["fr-ca", "fr", "en"]
8
+ */
9
+ export default function parseAcceptLanguage(header) {
10
+ if (!header)
11
+ return [];
12
+ return header
13
+ .split(",")
14
+ .map((part) => {
15
+ const [tag, ...params] = part.trim().split(";");
16
+ const q = params.map((p) => p.trim()).find((p) => p.startsWith("q="));
17
+ const weight = q ? Number.parseFloat(q.slice(2)) : 1;
18
+ return {
19
+ tag: tag.trim().toLowerCase(),
20
+ weight: Number.isNaN(weight) ? 0 : weight,
21
+ };
22
+ })
23
+ .filter((entry) => entry.tag.length > 0 && entry.weight > 0)
24
+ .sort((a, b) => b.weight - a.weight)
25
+ .map((entry) => entry.tag);
26
+ }
27
+ //# sourceMappingURL=parseAcceptLanguage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseAcceptLanguage.js","sourceRoot":"","sources":["../../../src/lib/parseAcceptLanguage.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,CAAC,OAAO,UAAU,mBAAmB,CACzC,MAAiC;IAEjC,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IAEvB,OAAO,MAAM;SACV,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACZ,MAAM,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAChD,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;QACtE,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACrD,OAAO;YACL,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE;YAC7B,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM;SAC1C,CAAC;IACJ,CAAC,CAAC;SACD,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;SAC3D,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;SACnC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Read a single cookie value from a raw `Cookie` header. Pure and
3
+ * transport-agnostic, so it behaves identically on the server and the client
4
+ * (Constitution IV — functional core).
5
+ *
6
+ * @param cookieHeader - Raw `Cookie` header value, or null/undefined.
7
+ * @param name - Cookie name to look up.
8
+ * @returns The decoded value, or `undefined` when absent.
9
+ */
10
+ export default function readCookie(cookieHeader, name) {
11
+ if (!cookieHeader)
12
+ return undefined;
13
+ for (const part of cookieHeader.split(";")) {
14
+ const separator = part.indexOf("=");
15
+ if (separator === -1)
16
+ continue;
17
+ if (part.slice(0, separator).trim() === name) {
18
+ const value = part.slice(separator + 1).trim();
19
+ try {
20
+ return decodeURIComponent(value);
21
+ }
22
+ catch {
23
+ // Malformed percent-encoding — treat the cookie as absent.
24
+ return undefined;
25
+ }
26
+ }
27
+ }
28
+ return undefined;
29
+ }
30
+ //# sourceMappingURL=readCookie.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readCookie.js","sourceRoot":"","sources":["../../../src/lib/readCookie.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,UAAU,UAAU,CAChC,YAAuC,EACvC,IAAY;IAEZ,IAAI,CAAC,YAAY;QAAE,OAAO,SAAS,CAAC;IAEpC,KAAK,MAAM,IAAI,IAAI,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3C,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,SAAS,KAAK,CAAC,CAAC;YAAE,SAAS;QAC/B,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;YAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAC/C,IAAI,CAAC;gBACH,OAAO,kBAAkB,CAAC,KAAK,CAAC,CAAC;YACnC,CAAC;YAAC,MAAM,CAAC;gBACP,2DAA2D;gBAC3D,OAAO,SAAS,CAAC;YACnB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Core type vocabulary for `@canonical/i18n-core`. Every type here is
3
+ * framework-agnostic — plain data and function signatures over native `Intl`.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/lib/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `@canonical/i18n-core` — framework-agnostic internationalization built on
3
+ * native `Intl`: locale negotiation, message catalogs and translators,
4
+ * memoized formatters, and a reactive locale source shared across React,
5
+ * Svelte, and Lit.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ export * from "./lib/index.js";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1,5 @@
1
+ /** Cookie name used to persist a locale choice when none is configured. */
2
+ export declare const DEFAULT_COOKIE_NAME = "locale";
3
+ /** Lifetime, in seconds, of the persisted locale cookie (one year). */
4
+ export declare const COOKIE_MAX_AGE = 31536000;
5
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/lib/constants.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,eAAO,MAAM,mBAAmB,WAAW,CAAC;AAE5C,uEAAuE;AACvE,eAAO,MAAM,cAAc,WAAa,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { Formatters, Locale } from "./types.js";
2
+ /**
3
+ * Create locale-aware {@link Formatters} backed by memoized native `Intl`
4
+ * instances. Framework-agnostic and SSR-safe — `Intl` is a platform global.
5
+ *
6
+ * @param locale - BCP 47 tag every formatter is bound to.
7
+ */
8
+ export default function createFormatters(locale: Locale): Formatters;
9
+ //# sourceMappingURL=createFormatters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createFormatters.d.ts","sourceRoot":"","sources":["../../../src/lib/createFormatters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAuBrD;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CA4BnE"}
@@ -0,0 +1,17 @@
1
+ import type { I18nConfig, LocaleSource, LocaleSourceOptions } from "./types.js";
2
+ /**
3
+ * Create a live, observable locale value — the single cross-framework runtime
4
+ * channel. It holds the current locale, notifies subscribers on change, and (in
5
+ * the browser) persists the choice to a cookie and reflects it onto
6
+ * `<html lang dir>`. On the server those effects are inert, so the same source
7
+ * is safe to construct during SSR.
8
+ *
9
+ * `subscribe` honors the Svelte store contract, so a Svelte component can use
10
+ * `$source` directly, React can pass it to `useSyncExternalStore`, and a Lit
11
+ * reactive controller can subscribe — all from this one object.
12
+ *
13
+ * @param config - Active locale configuration.
14
+ * @param options - Initial locale and effect toggles.
15
+ */
16
+ export default function createLocaleSource(config: I18nConfig, options?: LocaleSourceOptions): LocaleSource;
17
+ //# sourceMappingURL=createLocaleSource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createLocaleSource.d.ts","sourceRoot":"","sources":["../../../src/lib/createLocaleSource.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,UAAU,EAEV,YAAY,EACZ,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAuBpB;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,OAAO,UAAU,kBAAkB,CACxC,MAAM,EAAE,UAAU,EAClB,OAAO,GAAE,mBAAwB,GAChC,YAAY,CAoCd"}
@@ -0,0 +1,16 @@
1
+ import type { Locale, Messages, Translator } from "./types.js";
2
+ /**
3
+ * Create a {@link Translator} bound to one locale and message catalog.
4
+ *
5
+ * Resolution is non-throwing, so a bad key never breaks a render:
6
+ * - a missing key returns the key itself;
7
+ * - `{placeholder}` slots are filled from `vars`; an absent variable is left
8
+ * verbatim;
9
+ * - a plural entry selects a branch from `vars.count` via `Intl.PluralRules`,
10
+ * falling back to the required `other` branch.
11
+ *
12
+ * @param locale - BCP 47 tag driving plural selection.
13
+ * @param messages - Catalog for that locale.
14
+ */
15
+ export default function createTranslator(locale: Locale, messages: Messages): Translator;
16
+ //# sourceMappingURL=createTranslator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createTranslator.d.ts","sourceRoot":"","sources":["../../../src/lib/createTranslator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAkB,UAAU,EAAE,MAAM,YAAY,CAAC;AAI/E;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,OAAO,UAAU,gBAAgB,CACtC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,QAAQ,GACjB,UAAU,CAsBZ"}
@@ -0,0 +1,9 @@
1
+ import type { Direction, I18nConfig, Locale } from "./types.js";
2
+ /**
3
+ * Resolve the writing direction for a locale from its base language.
4
+ *
5
+ * @param config - Active locale configuration; its `rtlLocales` list decides.
6
+ * @param locale - Locale tag, e.g. `"ar-EG"`, or undefined.
7
+ */
8
+ export default function directionOf(config: I18nConfig, locale: Locale | undefined): Direction;
9
+ //# sourceMappingURL=directionOf.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directionOf.d.ts","sourceRoot":"","sources":["../../../src/lib/directionOf.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEhE;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,WAAW,CACjC,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,GAAG,SAAS,GACzB,SAAS,CAIX"}
@@ -0,0 +1,14 @@
1
+ import type { Direction, I18nConfig, Locale } from "./types.js";
2
+ /**
3
+ * Compute the `<html>` attributes for a locale: `{ lang, dir }`. Render these on
4
+ * the server (`<html lang dir>`) from the negotiated locale so the first paint —
5
+ * and assistive technology — are correct before the client hydrates.
6
+ *
7
+ * @param config - Active locale configuration.
8
+ * @param locale - The resolved locale.
9
+ */
10
+ export default function documentAttrs(config: I18nConfig, locale: Locale): {
11
+ lang: Locale;
12
+ dir: Direction;
13
+ };
14
+ //# sourceMappingURL=documentAttrs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documentAttrs.d.ts","sourceRoot":"","sources":["../../../src/lib/documentAttrs.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEhE;;;;;;;GAOG;AACH,MAAM,CAAC,OAAO,UAAU,aAAa,CACnC,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,GACb;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,SAAS,CAAA;CAAE,CAElC"}
@@ -0,0 +1,12 @@
1
+ export { default as createFormatters } from "./createFormatters.js";
2
+ export { default as createLocaleSource } from "./createLocaleSource.js";
3
+ export { default as createTranslator } from "./createTranslator.js";
4
+ export { default as directionOf } from "./directionOf.js";
5
+ export { default as documentAttrs } from "./documentAttrs.js";
6
+ export { default as isSupportedLocale } from "./isSupportedLocale.js";
7
+ export { default as mergeCatalogs } from "./mergeCatalogs.js";
8
+ export { default as negotiateLocale, type LocaleSources, } from "./negotiateLocale.js";
9
+ export { default as parseAcceptLanguage } from "./parseAcceptLanguage.js";
10
+ export { default as readCookie } from "./readCookie.js";
11
+ export * from "./types.js";
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EACL,OAAO,IAAI,eAAe,EAC1B,KAAK,aAAa,GACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC1E,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACxD,cAAc,YAAY,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { I18nConfig, Locale } from "./types.js";
2
+ /**
3
+ * Test whether `value` is one of the locales declared in `config`.
4
+ *
5
+ * @param config - Active locale configuration.
6
+ * @param value - Candidate tag (any string, or null/undefined).
7
+ */
8
+ export default function isSupportedLocale(config: I18nConfig, value: string | null | undefined): value is Locale;
9
+ //# sourceMappingURL=isSupportedLocale.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isSupportedLocale.d.ts","sourceRoot":"","sources":["../../../src/lib/isSupportedLocale.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAErD;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,iBAAiB,CACvC,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC/B,KAAK,IAAI,MAAM,CAEjB"}
@@ -0,0 +1,12 @@
1
+ import type { Messages } from "./types.js";
2
+ /**
3
+ * Merge a base catalog with overrides, key by key. Overrides win; keys absent
4
+ * from `overrides` keep their `base` value. Returns a new object and never
5
+ * mutates its inputs — useful for layering app messages over a component's
6
+ * built-in defaults.
7
+ *
8
+ * @param base - Default messages (e.g. a component's built-in catalog).
9
+ * @param overrides - Messages that take precedence (e.g. app or locale data).
10
+ */
11
+ export default function mergeCatalogs(base: Messages, overrides: Messages): Messages;
12
+ //# sourceMappingURL=mergeCatalogs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mergeCatalogs.d.ts","sourceRoot":"","sources":["../../../src/lib/mergeCatalogs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,UAAU,aAAa,CACnC,IAAI,EAAE,QAAQ,EACd,SAAS,EAAE,QAAQ,GAClB,QAAQ,CAEV"}
@@ -0,0 +1,20 @@
1
+ import type { I18nConfig, Locale } from "./types.js";
2
+ /** Raw request inputs negotiation reads from, in priority order. */
3
+ export interface LocaleSources {
4
+ /** Raw `Cookie` header — an explicit prior choice wins over everything. */
5
+ cookieHeader?: string | null;
6
+ /** Raw `Accept-Language` header, consulted when no cookie matches. */
7
+ acceptLanguage?: string | null;
8
+ }
9
+ /**
10
+ * Resolve the locale for an incoming request. An explicit cookie wins, then
11
+ * `Accept-Language` negotiation, then {@link I18nConfig.defaultLocale}. Both
12
+ * inputs are matched case-insensitively. Pure — it takes raw header strings and
13
+ * returns a tag, so it is transport-agnostic and runs the same on server and
14
+ * client (Constitution IV — functional core).
15
+ *
16
+ * @param config - Active locale configuration.
17
+ * @param sources - Raw request headers to negotiate from.
18
+ */
19
+ export default function negotiateLocale(config: I18nConfig, sources?: LocaleSources): Locale;
20
+ //# sourceMappingURL=negotiateLocale.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"negotiateLocale.d.ts","sourceRoot":"","sources":["../../../src/lib/negotiateLocale.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAErD,oEAAoE;AACpE,MAAM,WAAW,aAAa;IAC5B,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,sEAAsE;IACtE,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAiCD;;;;;;;;;GASG;AACH,MAAM,CAAC,OAAO,UAAU,eAAe,CACrC,MAAM,EAAE,UAAU,EAClB,OAAO,GAAE,aAAkB,GAC1B,MAAM,CAWR"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Parse an `Accept-Language` header into lowercase language tags ordered by
3
+ * descending quality (`q`) weight. Entries with `q=0` (RFC 7231 "not
4
+ * acceptable") or a malformed `q` are dropped. Pure.
5
+ *
6
+ * @example
7
+ * parseAcceptLanguage("fr-CA,fr;q=0.9,en;q=0.8"); // ["fr-ca", "fr", "en"]
8
+ */
9
+ export default function parseAcceptLanguage(header: string | null | undefined): string[];
10
+ //# sourceMappingURL=parseAcceptLanguage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseAcceptLanguage.d.ts","sourceRoot":"","sources":["../../../src/lib/parseAcceptLanguage.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,CAAC,OAAO,UAAU,mBAAmB,CACzC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAChC,MAAM,EAAE,CAiBV"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Read a single cookie value from a raw `Cookie` header. Pure and
3
+ * transport-agnostic, so it behaves identically on the server and the client
4
+ * (Constitution IV — functional core).
5
+ *
6
+ * @param cookieHeader - Raw `Cookie` header value, or null/undefined.
7
+ * @param name - Cookie name to look up.
8
+ * @returns The decoded value, or `undefined` when absent.
9
+ */
10
+ export default function readCookie(cookieHeader: string | null | undefined, name: string): string | undefined;
11
+ //# sourceMappingURL=readCookie.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readCookie.d.ts","sourceRoot":"","sources":["../../../src/lib/readCookie.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,UAAU,UAAU,CAChC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACvC,IAAI,EAAE,MAAM,GACX,MAAM,GAAG,SAAS,CAkBpB"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Core type vocabulary for `@canonical/i18n-core`. Every type here is
3
+ * framework-agnostic — plain data and function signatures over native `Intl`.
4
+ */
5
+ /** A BCP 47 language tag, e.g. `"en"`, `"fr-CA"`, `"ar"`. */
6
+ export type Locale = string;
7
+ /** Writing direction of a locale's script. */
8
+ export type Direction = "ltr" | "rtl";
9
+ /**
10
+ * CLDR plural categories. `Intl.PluralRules.select()` resolves a count to one
11
+ * of these.
12
+ */
13
+ export type PluralCategory = "zero" | "one" | "two" | "few" | "many" | "other";
14
+ /**
15
+ * A message whose wording varies by count. `other` is required — it is the
16
+ * fallback used whenever a locale does not define the selected category.
17
+ */
18
+ export type PluralRecord = Partial<Record<PluralCategory, string>> & {
19
+ other: string;
20
+ };
21
+ /** A single catalog entry: a flat string or a count-sensitive plural record. */
22
+ export type MessageValue = string | PluralRecord;
23
+ /**
24
+ * A flat map of message keys to values for one locale. Keys are dotted
25
+ * namespaces by convention (e.g. `"nav.home"`); the engine treats them as
26
+ * opaque strings.
27
+ */
28
+ export type Messages = Record<string, MessageValue>;
29
+ /**
30
+ * Values interpolated into `{placeholder}` slots. A `count` value additionally
31
+ * drives plural selection for a {@link PluralRecord} entry.
32
+ */
33
+ export type TranslateVars = Record<string, string | number>;
34
+ /** Resolves a message key (with optional variables) to a localized string. */
35
+ export type Translator = (key: string, vars?: TranslateVars) => string;
36
+ /**
37
+ * Locale-aware formatting helpers backed by memoized native `Intl` instances.
38
+ * Construct once per locale via `createFormatters`.
39
+ */
40
+ export interface Formatters {
41
+ /** Format a number for the locale. */
42
+ number(value: number, options?: Intl.NumberFormatOptions): string;
43
+ /** Format a number as currency (ISO 4217 code, e.g. `"USD"`). */
44
+ currency(value: number, currency: string, options?: Intl.NumberFormatOptions): string;
45
+ /** Format a date/timestamp for the locale. */
46
+ date(value: Date | number, options?: Intl.DateTimeFormatOptions): string;
47
+ /** Format the time portion of a date/timestamp for the locale. */
48
+ time(value: Date | number, options?: Intl.DateTimeFormatOptions): string;
49
+ /** Format a relative time, e.g. `(-3, "day")` → "3 days ago". */
50
+ relativeTime(value: number, unit: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions): string;
51
+ /** Join a list with locale-aware conjunctions, e.g. "A, B, and C". */
52
+ list(items: Iterable<string>, options?: Intl.ListFormatOptions): string;
53
+ }
54
+ /**
55
+ * Declarative, framework-agnostic locale configuration. Plain data, listed
56
+ * explicitly rather than discovered (Constitution VI — no magic).
57
+ */
58
+ export interface I18nConfig {
59
+ /** Supported BCP 47 tags, in preference order. */
60
+ locales: readonly Locale[];
61
+ /** Locale used when negotiation finds no supported match. */
62
+ defaultLocale: Locale;
63
+ /** Base languages whose script is right-to-left (e.g. `["ar", "he"]`). */
64
+ rtlLocales?: readonly string[];
65
+ /** Cookie that persists an explicit choice. Defaults to `"locale"`. */
66
+ cookieName?: string;
67
+ }
68
+ /** Options for `createLocaleSource`. */
69
+ export interface LocaleSourceOptions {
70
+ /** Starting locale. Defaults to {@link I18nConfig.defaultLocale}. */
71
+ initial?: Locale;
72
+ /** Persist changes to the cookie (browser only). Defaults to `true`. */
73
+ persist?: boolean;
74
+ /** Reflect changes onto `<html lang dir>` (browser only). Defaults to `true`. */
75
+ reflect?: boolean;
76
+ }
77
+ /**
78
+ * A live, observable locale value shared across frameworks. `subscribe` honors
79
+ * the Svelte store contract — it calls back immediately with the current value
80
+ * and returns an unsubscribe — which also satisfies React's
81
+ * `useSyncExternalStore` and Lit reactive controllers. One source, every
82
+ * framework.
83
+ */
84
+ export interface LocaleSource {
85
+ /** Read the current locale. */
86
+ get(): Locale;
87
+ /** Writing direction of the current locale. */
88
+ readonly direction: Direction;
89
+ /** Change the active locale (persists + reflects per options). */
90
+ set(locale: Locale): void;
91
+ /** Subscribe; invokes `run` immediately and on each change. Returns unsubscribe. */
92
+ subscribe(run: (locale: Locale) => void): () => void;
93
+ }
94
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,6DAA6D;AAC7D,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC;AAE5B,8CAA8C;AAC9C,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK,CAAC;AAEtC;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;AAE/E;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,GAAG;IACnE,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,gFAAgF;AAChF,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,YAAY,CAAC;AAEjD;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;AAEpD;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;AAE5D,8EAA8E;AAC9E,MAAM,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,KAAK,MAAM,CAAC;AAEvE;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,sCAAsC;IACtC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,mBAAmB,GAAG,MAAM,CAAC;IAClE,iEAAiE;IACjE,QAAQ,CACN,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,IAAI,CAAC,mBAAmB,GACjC,MAAM,CAAC;IACV,8CAA8C;IAC9C,IAAI,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,qBAAqB,GAAG,MAAM,CAAC;IACzE,kEAAkE;IAClE,IAAI,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,qBAAqB,GAAG,MAAM,CAAC;IACzE,iEAAiE;IACjE,YAAY,CACV,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,IAAI,CAAC,sBAAsB,EACjC,OAAO,CAAC,EAAE,IAAI,CAAC,yBAAyB,GACvC,MAAM,CAAC;IACV,sEAAsE;IACtE,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,iBAAiB,GAAG,MAAM,CAAC;CACzE;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,kDAAkD;IAClD,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,6DAA6D;IAC7D,aAAa,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wCAAwC;AACxC,MAAM,WAAW,mBAAmB;IAClC,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iFAAiF;IACjF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,+BAA+B;IAC/B,GAAG,IAAI,MAAM,CAAC;IACd,+CAA+C;IAC/C,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,kEAAkE;IAClE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,oFAAoF;IACpF,SAAS,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CACtD"}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@canonical/i18n-core",
3
+ "description": "Framework-agnostic internationalization core (native Intl): locale negotiation, message catalogs, translators, memoized formatters, and a reactive locale source.",
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 library",
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
+ "devDependencies": {
48
+ "@biomejs/biome": "2.4.9",
49
+ "@canonical/biome-config": "^0.27.1-experimental.0",
50
+ "@canonical/webarchitect": "^0.27.1-experimental.0",
51
+ "@types/node": "^24.12.0",
52
+ "typescript": "^5.9.3",
53
+ "vitest": "^4.0.18"
54
+ }
55
+ }