@ultimat3/i18n 27.3.0 → 27.5.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
@@ -99,6 +99,15 @@ catalog is a fallback chain, it reads as `isMiss === false`, and `assertCatalogs
99
99
  see it because `CatalogSet.catalogs` carries app strings only. Translate the framework keys your
100
100
  app renders into your own catalog — that is the one path, and it is the same merge an override is.
101
101
 
102
+ ## A catalog slice for an island
103
+
104
+ A translator is a function and cannot be an island's prop. `catalogSubset(t, keys)` resolves the
105
+ templates `keys` name — every plural variant included — into `{ locale, catalog }`, plain JSON;
106
+ the island builds `subsetTranslator(subset)` over it: the same lookup, interpolation, CLDR plural
107
+ pick and loud `⟦key⟧` miss as the server's. Both are on `@ultimat3/i18n/subset`, an entry that
108
+ installs no framework catalog (the barrel does), so an island chunk can import it.
109
+ `@ultimat3/ui`'s `uiCatalog(t)` is the design system's own slice.
110
+
102
111
  ## Reading an app's declared locales (tooling)
103
112
 
104
113
  `@ultimat3/i18n/app-catalogs` — server-only, its own entry so no island bundle carries it:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/i18n",
3
- "version": "27.3.0",
3
+ "version": "27.5.0",
4
4
  "description": "Dependency-free translator, catalog flattening, locale negotiation and loud missing-key rendering",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -19,7 +19,8 @@
19
19
  },
20
20
  "exports": {
21
21
  ".": "./src/index.ts",
22
- "./app-catalogs": "./src/app-catalogs.ts"
22
+ "./app-catalogs": "./src/app-catalogs.ts",
23
+ "./subset": "./src/subset.ts"
23
24
  },
24
25
  "files": [
25
26
  "src",
@@ -37,6 +38,6 @@
37
38
  "test": "bun test"
38
39
  },
39
40
  "dependencies": {
40
- "@ultimat3/core": "27.3.0"
41
+ "@ultimat3/core": "27.5.0"
41
42
  }
42
43
  }
package/src/index.ts CHANGED
@@ -109,6 +109,12 @@ export {
109
109
  type CatalogRegistrationGap,
110
110
  catalogRegistrationGaps,
111
111
  } from './registration';
112
+ export {
113
+ type CatalogSubset,
114
+ catalogSubset,
115
+ type RawTranslator,
116
+ subsetTranslator,
117
+ } from './subset';
112
118
  export {
113
119
  catalogTranslator,
114
120
  isMiss,
package/src/subset.ts ADDED
@@ -0,0 +1,48 @@
1
+ // A slice of a catalog that can cross into an island. A translator is a function and cannot be an
2
+ // island's prop, so the server resolves the raw templates the island will read — plural variants
3
+ // included — and the island builds a full translator over them: the same lookup, interpolation
4
+ // and loud `⟦key⟧` miss as the server's. Its own entry (`@ultimat3/i18n/subset`): the barrel
5
+ // installs the framework catalog at import, and an island chunk must not carry it.
6
+
7
+ import type { Catalog } from './catalog';
8
+ import { pluralVariantsOf } from './interpolate';
9
+ import type { Locale } from './locales';
10
+ import { catalogTranslator, type Translator } from './translator';
11
+
12
+ /**
13
+ * What an island receives: plain JSON — its locale and the templates it may render. A type alias,
14
+ * not an interface, so it is assignable to the JSON an island's props are checked against.
15
+ */
16
+ export type CatalogSubset = {
17
+ readonly locale: Locale;
18
+ readonly catalog: Readonly<Record<string, string>>;
19
+ };
20
+
21
+ /** The two members of a translator a subset is read through — any app's typed `t` has them. */
22
+ export interface RawTranslator {
23
+ raw(key: string): string | undefined;
24
+ readonly locale: Locale;
25
+ }
26
+
27
+ /**
28
+ * The templates `keys` name in `t`'s locale, each with every plural variant the catalog holds, so
29
+ * `t(key, { count })` in the island picks the form the server would. A key the catalog lacks is
30
+ * left out, and renders `⟦key⟧` in the island exactly as it would on the server.
31
+ */
32
+ export function catalogSubset(t: RawTranslator, keys: readonly string[]): CatalogSubset {
33
+ const catalog: Record<string, string> = Object.create(null) as Record<string, string>;
34
+ for (const key of keys) {
35
+ for (const name of [key, ...pluralVariantsOf(key)]) {
36
+ const template = t.raw(name);
37
+ if (template !== undefined) catalog[name] = template;
38
+ }
39
+ }
40
+ return { locale: t.locale, catalog };
41
+ }
42
+
43
+ /** The island half: a translator over the subset the server sent. */
44
+ export function subsetTranslator(subset: CatalogSubset): Translator {
45
+ // Null-prototyped again: the subset arrived as parsed JSON, where `__proto__` is an own key.
46
+ const catalog: Catalog = Object.assign(Object.create(null) as Catalog, subset.catalog);
47
+ return catalogTranslator(catalog, subset.locale);
48
+ }