@ultimat3/i18n 1.0.0 → 1.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/i18n",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Dependency-free translator, catalog flattening, locale negotiation and loud missing-key rendering",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -30,6 +30,6 @@
30
30
  "test": "bun test"
31
31
  },
32
32
  "dependencies": {
33
- "@ultimat3/core": "1.0.0"
33
+ "@ultimat3/core": "1.2.0"
34
34
  }
35
35
  }
package/src/catalog.d.ts DELETED
@@ -1,28 +0,0 @@
1
- /**
2
- * Catalog shape only: nested authoring (`{ nav: { home: 'Home' } }`) → flat dot-key
3
- * lookup (`nav.home`), plus merging framework strings under app strings.
4
- */
5
- /** What authors write, and what `catalogs/<locale>.json` contains. */
6
- export type NestedCatalog = {
7
- readonly [key: string]: string | NestedCatalog;
8
- };
9
- /** What the translator consumes: flat dot-keys → template strings. */
10
- export type Catalog = Readonly<Record<string, string>>;
11
- /**
12
- * Depth-first flatten. Throws `X_CATALOG_INVALID` on a non-string leaf (arrays and
13
- * numbers are the two mistakes translators actually make) or a duplicate flat key.
14
- */
15
- export declare function flattenCatalog(source: NestedCatalog, prefix?: string): Catalog;
16
- /** Validate an untrusted value (a parsed JSON file) as a nested catalog. */
17
- export declare function parseNestedCatalog(value: unknown, path?: string): NestedCatalog;
18
- /** Parse + flatten in one step — the loader every catalog file goes through. */
19
- export declare function loadCatalog(value: unknown, prefix?: string): Catalog;
20
- /**
21
- * Later catalogs win. Call order is framework strings first, app strings last, so an
22
- * app can override `errors.notFound.title` without forking the framework catalog.
23
- */
24
- export declare function mergeCatalogs(...catalogs: readonly Catalog[]): Catalog;
25
- export declare function catalogKeys(catalog: Catalog): string[];
26
- /** Keys present in `base` but absent from `target` — the extractor's diff primitive. */
27
- export declare function missingFrom(base: Catalog, target: Catalog): string[];
28
- //# sourceMappingURL=catalog.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["catalog.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,sEAAsE;AACtE,MAAM,MAAM,aAAa,GAAG;IAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,CAAA;CAAE,CAAC;AAE/E,sEAAsE;AACtE,MAAM,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAIvD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,SAAK,GAAG,OAAO,CAI1E;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,SAAK,GAAG,aAAa,CAa3E;AAED,gFAAgF;AAChF,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,SAAK,GAAG,OAAO,CAEhE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAStE;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,EAAE,CAEtD;AAED,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,EAAE,CAEpE"}
package/src/catalog.js DELETED
@@ -1,81 +0,0 @@
1
- /**
2
- * Catalog shape only: nested authoring (`{ nav: { home: 'Home' } }`) → flat dot-key
3
- * lookup (`nav.home`), plus merging framework strings under app strings.
4
- */
5
- import { catalogInvalid } from './errors';
6
- const KEY_SEGMENT = /^[A-Za-z0-9_-]+$/;
7
- /**
8
- * Depth-first flatten. Throws `X_CATALOG_INVALID` on a non-string leaf (arrays and
9
- * numbers are the two mistakes translators actually make) or a duplicate flat key.
10
- */
11
- export function flattenCatalog(source, prefix = '') {
12
- const flat = {};
13
- walk(source, prefix, flat);
14
- return flat;
15
- }
16
- /** Validate an untrusted value (a parsed JSON file) as a nested catalog. */
17
- export function parseNestedCatalog(value, path = '') {
18
- if (value === null || typeof value !== 'object' || Array.isArray(value)) {
19
- throw catalogInvalid(path || '<root>', `expected an object, got ${describe(value)}`);
20
- }
21
- for (const [key, child] of Object.entries(value)) {
22
- const childPath = path === '' ? key : `${path}.${key}`;
23
- if (!KEY_SEGMENT.test(key)) {
24
- throw catalogInvalid(childPath, 'key segments must match /^[A-Za-z0-9_-]+$/');
25
- }
26
- if (typeof child === 'string')
27
- continue;
28
- parseNestedCatalog(child, childPath);
29
- }
30
- return value;
31
- }
32
- /** Parse + flatten in one step — the loader every catalog file goes through. */
33
- export function loadCatalog(value, prefix = '') {
34
- return flattenCatalog(parseNestedCatalog(value), prefix);
35
- }
36
- /**
37
- * Later catalogs win. Call order is framework strings first, app strings last, so an
38
- * app can override `errors.notFound.title` without forking the framework catalog.
39
- */
40
- export function mergeCatalogs(...catalogs) {
41
- const merged = {};
42
- for (const catalog of catalogs) {
43
- for (const key of Object.keys(catalog)) {
44
- const value = catalog[key];
45
- if (value !== undefined)
46
- merged[key] = value;
47
- }
48
- }
49
- return merged;
50
- }
51
- export function catalogKeys(catalog) {
52
- return Object.keys(catalog).sort();
53
- }
54
- /** Keys present in `base` but absent from `target` — the extractor's diff primitive. */
55
- export function missingFrom(base, target) {
56
- return catalogKeys(base).filter((key) => !Object.hasOwn(target, key));
57
- }
58
- function walk(node, prefix, out) {
59
- for (const [key, value] of Object.entries(node)) {
60
- const path = prefix === '' ? key : `${prefix}.${key}`;
61
- if (typeof value === 'string') {
62
- if (Object.hasOwn(out, path)) {
63
- throw catalogInvalid(path, 'duplicate key — a nested branch and a dotted key collide');
64
- }
65
- out[path] = value;
66
- continue;
67
- }
68
- if (value === null || typeof value !== 'object' || Array.isArray(value)) {
69
- throw catalogInvalid(path, `expected a string or an object, got ${describe(value)}`);
70
- }
71
- walk(value, path, out);
72
- }
73
- }
74
- function describe(value) {
75
- if (value === null)
76
- return 'null';
77
- if (Array.isArray(value))
78
- return 'an array';
79
- return `a ${typeof value}`;
80
- }
81
- //# sourceMappingURL=catalog.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"catalog.js","sourceRoot":"","sources":["catalog.ts"],"names":[],"mappings":"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"}
package/src/context.d.ts DELETED
@@ -1,64 +0,0 @@
1
- /**
2
- * Resolve the request locale once, then read it from the ALS context.
3
- * No call site passes a locale by hand — `t()` here is the ambient translator.
4
- */
5
- import { type Ctx } from '@ultimat3/core';
6
- import { type Catalog } from './catalog';
7
- import { type Direction, type Locale } from './locales';
8
- import { type TranslateVars, type Translator } from './translator';
9
- /** The cookie an explicit language switcher writes. */
10
- export declare const LOCALE_COOKIE = "x_locale";
11
- export interface LocaleSources {
12
- /** Raw `Accept-Language` header value. */
13
- header?: string | null;
14
- /** Value of the `x_locale` cookie, already URL-decoded. */
15
- cookie?: string | null;
16
- /** `user.locale` from the authenticated user record. */
17
- user?: string | null;
18
- /** `?locale=es` — mobile and email preview links pass it per request. */
19
- query?: string | null;
20
- }
21
- export type LocaleSourceName = keyof LocaleSources;
22
- export interface LocaleResolution {
23
- locale: Locale;
24
- direction: Direction;
25
- /** Which source won — surfaced in the request log so a wrong locale is debuggable. */
26
- source: LocaleSourceName | 'default';
27
- }
28
- export interface LocaleConfig {
29
- supported: readonly Locale[];
30
- fallback: Locale;
31
- /** Precedence, highest first. */
32
- order: readonly LocaleSourceName[];
33
- }
34
- /** Called once at boot from `app.config.ts`. */
35
- export declare function configureLocales(partial: Partial<LocaleConfig>): LocaleConfig;
36
- export declare function localeConfig(): LocaleConfig;
37
- /**
38
- * Header → cookie → user record → query → default, per `config.order`.
39
- * An unsupported tag is skipped rather than thrown: a stale cookie must not 500 a page.
40
- */
41
- export declare function resolveLocale(sources: LocaleSources, overrides?: Partial<LocaleConfig>): LocaleResolution;
42
- /** Read `x_locale` out of a raw `Cookie` header. */
43
- export declare function localeCookieOf(cookieHeader?: string | null): string | undefined;
44
- /** The HTTP layer calls this once per request, before any render. */
45
- export declare function attachLocale(ctx: Ctx, locale: Locale): Locale;
46
- export declare function localeOf(ctx: Ctx): Locale;
47
- /** Ambient locale for the in-flight request; the configured fallback outside one. */
48
- export declare function currentLocale(): Locale;
49
- export declare function currentDirection(): Direction;
50
- /**
51
- * Register a locale's catalog. Called with the framework catalog first and the app
52
- * catalog second so app strings override framework strings for the same key.
53
- */
54
- export declare function registerCatalog(locale: Locale, catalog: Catalog): void;
55
- export declare function registeredLocales(): Locale[];
56
- export declare function catalogFor(locale: Locale): Catalog;
57
- /** Memoized per locale — building a translator per render is pure waste. */
58
- export declare function translatorFor(locale: Locale): Translator;
59
- export declare function currentTranslator(): Translator;
60
- /** The ambient `t` — what framework and app code calls. Never takes a locale. */
61
- export declare function t(key: string, vars?: TranslateVars): string;
62
- /** Test/CLI seam: drop every registered catalog. */
63
- export declare function resetCatalogs(): void;
64
- //# sourceMappingURL=context.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["context.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,KAAK,GAAG,EAAc,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,KAAK,OAAO,EAAiB,MAAM,WAAW,CAAC;AACxD,OAAO,EAEL,KAAK,SAAS,EAEd,KAAK,MAAM,EAIZ,MAAM,WAAW,CAAC;AACnB,OAAO,EAAoB,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAErF,uDAAuD;AACvD,eAAO,MAAM,aAAa,aAAa,CAAC;AAKxC,MAAM,WAAW,aAAa;IAC5B,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,wDAAwD;IACxD,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,aAAa,CAAC;AAEnD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,SAAS,CAAC;IACrB,sFAAsF;IACtF,MAAM,EAAE,gBAAgB,GAAG,SAAS,CAAC;CACtC;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,iCAAiC;IACjC,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;CACpC;AAUD,gDAAgD;AAChD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAG7E;AAED,wBAAgB,YAAY,IAAI,YAAY,CAE3C;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,aAAa,EACtB,SAAS,GAAE,OAAO,CAAC,YAAY,CAAM,GACpC,gBAAgB,CAYlB;AAED,oDAAoD;AACpD,wBAAgB,cAAc,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAS/E;AAED,qEAAqE;AACrE,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAG7D;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAEzC;AAED,qFAAqF;AACrF,wBAAgB,aAAa,IAAI,MAAM,CAGtC;AAED,wBAAgB,gBAAgB,IAAI,SAAS,CAE5C;AAKD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAItE;AAED,wBAAgB,iBAAiB,IAAI,MAAM,EAAE,CAE5C;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAElD;AAED,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAMxD;AAED,wBAAgB,iBAAiB,IAAI,UAAU,CAE9C;AAED,iFAAiF;AACjF,wBAAgB,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,MAAM,CAE3D;AAED,oDAAoD;AACpD,wBAAgB,aAAa,IAAI,IAAI,CAGpC"}
package/src/context.js DELETED
@@ -1,132 +0,0 @@
1
- /**
2
- * Resolve the request locale once, then read it from the ALS context.
3
- * No call site passes a locale by hand — `t()` here is the ambient translator.
4
- */
5
- import { useContext } from '@ultimat3/core';
6
- import { mergeCatalogs } from './catalog';
7
- import { DEFAULT_LOCALE, directionOf, negotiateLocale, normalizeLocale, SUPPORTED_LOCALES, } from './locales';
8
- import { createTranslator } from './translator';
9
- /** The cookie an explicit language switcher writes. */
10
- export const LOCALE_COOKIE = 'x_locale';
11
- /** Field name on the ALS context. Core owns `Ctx`; it stays a plain string there. */
12
- const CTX_LOCALE = 'locale';
13
- const DEFAULT_ORDER = ['header', 'cookie', 'user', 'query'];
14
- let config = {
15
- supported: SUPPORTED_LOCALES,
16
- fallback: DEFAULT_LOCALE,
17
- order: DEFAULT_ORDER,
18
- };
19
- /** Called once at boot from `app.config.ts`. */
20
- export function configureLocales(partial) {
21
- config = { ...config, ...partial };
22
- return config;
23
- }
24
- export function localeConfig() {
25
- return config;
26
- }
27
- /**
28
- * Header → cookie → user record → query → default, per `config.order`.
29
- * An unsupported tag is skipped rather than thrown: a stale cookie must not 500 a page.
30
- */
31
- export function resolveLocale(sources, overrides = {}) {
32
- const { supported, fallback, order } = { ...config, ...overrides };
33
- for (const name of order) {
34
- const raw = sources[name];
35
- if (raw === undefined || raw === null || raw === '')
36
- continue;
37
- const candidate = name === 'header' ? negotiateLocale(raw, supported, '') : normalizeLocale(raw, supported, '');
38
- if (candidate !== '') {
39
- return { locale: candidate, direction: directionOf(candidate), source: name };
40
- }
41
- }
42
- return { locale: fallback, direction: directionOf(fallback), source: 'default' };
43
- }
44
- /** Read `x_locale` out of a raw `Cookie` header. */
45
- export function localeCookieOf(cookieHeader) {
46
- if (!cookieHeader)
47
- return undefined;
48
- for (const pair of cookieHeader.split(';')) {
49
- const index = pair.indexOf('=');
50
- if (index === -1)
51
- continue;
52
- if (pair.slice(0, index).trim() !== LOCALE_COOKIE)
53
- continue;
54
- return decodeURIComponent(pair.slice(index + 1).trim());
55
- }
56
- return undefined;
57
- }
58
- /** The HTTP layer calls this once per request, before any render. */
59
- export function attachLocale(ctx, locale) {
60
- writeContextField(ctx, CTX_LOCALE, locale);
61
- return locale;
62
- }
63
- export function localeOf(ctx) {
64
- return readField(ctx, CTX_LOCALE) ?? config.fallback;
65
- }
66
- /** Ambient locale for the in-flight request; the configured fallback outside one. */
67
- export function currentLocale() {
68
- const ctx = tryContext();
69
- return (ctx === undefined ? undefined : readField(ctx, CTX_LOCALE)) ?? config.fallback;
70
- }
71
- export function currentDirection() {
72
- return directionOf(currentLocale());
73
- }
74
- const registry = new Map();
75
- const translators = new Map();
76
- /**
77
- * Register a locale's catalog. Called with the framework catalog first and the app
78
- * catalog second so app strings override framework strings for the same key.
79
- */
80
- export function registerCatalog(locale, catalog) {
81
- const existing = registry.get(locale);
82
- registry.set(locale, existing === undefined ? catalog : mergeCatalogs(existing, catalog));
83
- translators.delete(locale);
84
- }
85
- export function registeredLocales() {
86
- return [...registry.keys()].sort();
87
- }
88
- export function catalogFor(locale) {
89
- return registry.get(locale) ?? {};
90
- }
91
- /** Memoized per locale — building a translator per render is pure waste. */
92
- export function translatorFor(locale) {
93
- const cached = translators.get(locale);
94
- if (cached !== undefined)
95
- return cached;
96
- const translator = createTranslator(catalogFor(locale), locale);
97
- translators.set(locale, translator);
98
- return translator;
99
- }
100
- export function currentTranslator() {
101
- return translatorFor(currentLocale());
102
- }
103
- /** The ambient `t` — what framework and app code calls. Never takes a locale. */
104
- export function t(key, vars) {
105
- return currentTranslator()(key, vars);
106
- }
107
- /** Test/CLI seam: drop every registered catalog. */
108
- export function resetCatalogs() {
109
- registry.clear();
110
- translators.clear();
111
- }
112
- function tryContext() {
113
- try {
114
- return useContext();
115
- }
116
- catch {
117
- // Outside a request scope (boot, a CLI command, a worker tick) — no ambient locale.
118
- return undefined;
119
- }
120
- }
121
- /**
122
- * `Ctx` is owned by core (tier 0) and cannot reference i18n's `Locale`, so the field is
123
- * read structurally. One place, one cast, validated on the way out.
124
- */
125
- function readField(ctx, field) {
126
- const value = ctx[field];
127
- return typeof value === 'string' && value !== '' ? value : undefined;
128
- }
129
- function writeContextField(ctx, field, value) {
130
- ctx[field] = value;
131
- }
132
- //# sourceMappingURL=context.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"context.js","sourceRoot":"","sources":["context.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAY,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAgB,aAAa,EAAE,MAAM,WAAW,CAAC;AACxD,OAAO,EACL,cAAc,EAEd,WAAW,EAEX,eAAe,EACf,eAAe,EACf,iBAAiB,GAClB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,gBAAgB,EAAuC,MAAM,cAAc,CAAC;AAErF,uDAAuD;AACvD,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CAAC;AAExC,qFAAqF;AACrF,MAAM,UAAU,GAAG,QAAQ,CAAC;AA6B5B,MAAM,aAAa,GAAgC,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;AAEzF,IAAI,MAAM,GAAiB;IACzB,SAAS,EAAE,iBAAiB;IAC5B,QAAQ,EAAE,cAAc;IACxB,KAAK,EAAE,aAAa;CACrB,CAAC;AAEF,gDAAgD;AAChD,MAAM,UAAU,gBAAgB,CAAC,OAA8B;IAC7D,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,GAAG,OAAO,EAAE,CAAC;IACnC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAC3B,OAAsB,EACtB,SAAS,GAA0B,EAAE;IAErC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;IACnE,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAC1B,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,EAAE;YAAE,SAAS;QAC9D,MAAM,SAAS,GACb,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,eAAe,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;QAChG,IAAI,SAAS,KAAK,EAAE,EAAE,CAAC;YACrB,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;QAChF,CAAC;IACH,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AACnF,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,cAAc,CAAC,YAA4B;IACzD,IAAI,CAAC,YAAY;QAAE,OAAO,SAAS,CAAC;IACpC,KAAK,MAAM,IAAI,IAAI,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,KAAK,KAAK,CAAC,CAAC;YAAE,SAAS;QAC3B,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,KAAK,aAAa;YAAE,SAAS;QAC5D,OAAO,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,YAAY,CAAC,GAAQ,EAAE,MAAc;IACnD,iBAAiB,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;IAC3C,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,GAAQ;IAC/B,OAAO,SAAS,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC;AACvD,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,aAAa;IAC3B,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IACzB,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC;AACzF,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,OAAO,WAAW,CAAC,aAAa,EAAE,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAmB,CAAC;AAC5C,MAAM,WAAW,GAAG,IAAI,GAAG,EAAsB,CAAC;AAElD;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,MAAc,EAAE,OAAgB;IAC9D,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACtC,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;IAC1F,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,OAAO,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;AACpC,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACvC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IACxC,MAAM,UAAU,GAAG,gBAAgB,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IAChE,WAAW,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACpC,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,aAAa,CAAC,aAAa,EAAE,CAAC,CAAC;AACxC,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,CAAC,CAAC,GAAW,EAAE,IAAoB;IACjD,OAAO,iBAAiB,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AACxC,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,aAAa;IAC3B,QAAQ,CAAC,KAAK,EAAE,CAAC;IACjB,WAAW,CAAC,KAAK,EAAE,CAAC;AACtB,CAAC;AAED,SAAS,UAAU;IACjB,IAAI,CAAC;QACH,OAAO,UAAU,EAAE,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,oFAAoF;QACpF,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,SAAS,CAAC,GAAQ,EAAE,KAAa;IACxC,MAAM,KAAK,GAAI,GAA0C,CAAC,KAAK,CAAC,CAAC;IACjE,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACvE,CAAC;AAED,SAAS,iBAAiB,CAAC,GAAQ,EAAE,KAAa,EAAE,KAAa;IAC9D,GAA0C,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC;AAC7D,CAAC"}
package/src/errors.d.ts DELETED
@@ -1,22 +0,0 @@
1
- /**
2
- * The X_* error codes owned by @ultimat3/i18n.
3
- * Every throw carries a stable code, a cause, and a command that fixes it.
4
- */
5
- import { UltimateError } from '@ultimat3/core';
6
- export declare const I18N_ERROR_CODES: readonly ['X_LOCALE_UNSUPPORTED', 'X_CATALOG_MISSING_KEYS', 'X_CATALOG_INVALID'];
7
- export type I18nErrorCode = (typeof I18N_ERROR_CODES)[number];
8
- export declare class I18nError extends UltimateError {
9
- constructor(init: {
10
- code: I18nErrorCode;
11
- cause: string;
12
- fix: string;
13
- });
14
- }
15
- export declare function localeUnsupported(tag: string, supported: readonly string[]): I18nError;
16
- /**
17
- * `fix` must name the locale file and the exact keys — an agent reading this
18
- * error should not have to diff two catalogs to learn what to write.
19
- */
20
- export declare function catalogMissingKeys(locale: string, keys: readonly string[]): I18nError;
21
- export declare function catalogInvalid(path: string, cause: string): I18nError;
22
- //# sourceMappingURL=errors.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/C,eAAO,MAAM,gBAAgB,YAC3B,sBAAsB,EACtB,wBAAwB,EACxB,mBAAmB,CACX,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9D,qBAAa,SAAU,SAAQ,aAAa;IAC1C,YAAY,IAAI,EAAE;QAAE,IAAI,EAAE,aAAa,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,EAOpE;CACF;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAMtF;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAQrF;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,CAMrE"}
package/src/errors.js DELETED
@@ -1,52 +0,0 @@
1
- /**
2
- * The X_* error codes owned by @ultimat3/i18n.
3
- * Every throw carries a stable code, a cause, and a command that fixes it.
4
- */
5
- import { UltimateError } from '@ultimat3/core';
6
- export const I18N_ERROR_CODES = [
7
- 'X_LOCALE_UNSUPPORTED',
8
- 'X_CATALOG_MISSING_KEYS',
9
- 'X_CATALOG_INVALID',
10
- ];
11
- export class I18nError extends UltimateError {
12
- constructor(init) {
13
- super({
14
- code: init.code,
15
- cause: init.cause,
16
- fix: init.fix,
17
- docs: `https://ultimate.dev/errors/${init.code}`,
18
- });
19
- }
20
- }
21
- export function localeUnsupported(tag, supported) {
22
- return new I18nError({
23
- code: 'X_LOCALE_UNSUPPORTED',
24
- cause: `locale "${tag}" is not in the supported set [${supported.join(', ')}]`,
25
- fix: `x i18n add ${normalizeForFix(tag)}`,
26
- });
27
- }
28
- /**
29
- * `fix` must name the locale file and the exact keys — an agent reading this
30
- * error should not have to diff two catalogs to learn what to write.
31
- */
32
- export function catalogMissingKeys(locale, keys) {
33
- const shown = keys.slice(0, 12);
34
- const suffix = keys.length > shown.length ? `, +${keys.length - shown.length} more` : '';
35
- return new I18nError({
36
- code: 'X_CATALOG_MISSING_KEYS',
37
- cause: `packages/i18n/catalogs/${locale}.json is missing ${keys.length} key(s) used in source: ${shown.join(', ')}${suffix}`,
38
- fix: `x i18n sync ${locale}`,
39
- });
40
- }
41
- export function catalogInvalid(path, cause) {
42
- return new I18nError({
43
- code: 'X_CATALOG_INVALID',
44
- cause: `catalog entry "${path}": ${cause}`,
45
- fix: 'x i18n check --json',
46
- });
47
- }
48
- function normalizeForFix(tag) {
49
- const primary = tag.split('-')[0] ?? tag;
50
- return primary.toLowerCase().replace(/[^a-z]/g, '') || 'xx';
51
- }
52
- //# sourceMappingURL=errors.js.map
package/src/errors.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/C,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,sBAAsB;IACtB,wBAAwB;IACxB,mBAAmB;CACX,CAAC;AAIX,MAAM,OAAO,SAAU,SAAQ,aAAa;IAC1C,YAAY,IAAyD;QACnE,KAAK,CAAC;YACJ,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,IAAI,EAAE,+BAA+B,IAAI,CAAC,IAAI,EAAE;SACjD,CAAC,CAAC;IACL,CAAC;CACF;AAED,MAAM,UAAU,iBAAiB,CAAC,GAAW,EAAE,SAA4B;IACzE,OAAO,IAAI,SAAS,CAAC;QACnB,IAAI,EAAE,sBAAsB;QAC5B,KAAK,EAAE,WAAW,GAAG,kCAAkC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;QAC9E,GAAG,EAAE,cAAc,eAAe,CAAC,GAAG,CAAC,EAAE;KAC1C,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAc,EAAE,IAAuB;IACxE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IACzF,OAAO,IAAI,SAAS,CAAC;QACnB,IAAI,EAAE,wBAAwB;QAC9B,KAAK,EAAE,0BAA0B,MAAM,oBAAoB,IAAI,CAAC,MAAM,2BAA2B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,MAAM,EAAE;QAC5H,GAAG,EAAE,eAAe,MAAM,EAAE;KAC7B,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,KAAa;IACxD,OAAO,IAAI,SAAS,CAAC;QACnB,IAAI,EAAE,mBAAmB;QACzB,KAAK,EAAE,kBAAkB,IAAI,MAAM,KAAK,EAAE;QAC1C,GAAG,EAAE,qBAAqB;KAC3B,CAAC,CAAC;AACL,CAAC;AAED,SAAS,eAAe,CAAC,GAAW;IAClC,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;IACzC,OAAO,OAAO,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC;AAC9D,CAAC"}
package/src/extract.d.ts DELETED
@@ -1,67 +0,0 @@
1
- /**
2
- * Static extraction: which keys the source calls, which are missing per locale, which
3
- * are defined and never used. `x verify` fails on a missing key in a shipped locale —
4
- * a translation gap is a build error, not a ticket.
5
- */
6
- import { type Catalog } from './catalog';
7
- import type { Locale } from './locales';
8
- export interface KeyUsage {
9
- key: string;
10
- file: string;
11
- line: number;
12
- column: number;
13
- }
14
- /** `t(key)` where `key` is a variable — the extractor cannot verify it, so it says so. */
15
- export interface DynamicUsage {
16
- expression: string;
17
- file: string;
18
- line: number;
19
- column: number;
20
- }
21
- export interface Extraction {
22
- usages: KeyUsage[];
23
- dynamic: DynamicUsage[];
24
- }
25
- export interface LocaleAudit {
26
- locale: Locale;
27
- missing: string[];
28
- unused: string[];
29
- }
30
- export interface ExtractReport {
31
- /** Distinct static keys, sorted. */
32
- used: string[];
33
- usages: KeyUsage[];
34
- dynamic: DynamicUsage[];
35
- locales: LocaleAudit[];
36
- ok: boolean;
37
- }
38
- export interface ExtractOptions {
39
- /** Call names treated as translator calls. */
40
- callees?: readonly string[];
41
- }
42
- /**
43
- * Scan one source file. Deliberately a lexer-free scan: a translator call is a
44
- * recognisable shape, and a regex that runs in milliseconds over a whole repo beats a
45
- * parser that needs a tsconfig to start.
46
- */
47
- export declare function extractKeys(source: string, file?: string, options?: ExtractOptions): Extraction;
48
- export interface AuditInput {
49
- extraction: Extraction;
50
- /** Every locale that ships. A locale absent here is not audited. */
51
- catalogs: Readonly<Record<Locale, Catalog>>;
52
- /** Keys resolved at runtime (e.g. `time.cron.*`) — never reported unused. */
53
- ignoreUnused?: readonly string[];
54
- }
55
- /**
56
- * A used key counts as present when the locale defines it *or* any of its plural
57
- * variants: `pl` legitimately defines `items_one`/`items_few`/`items_many` and no bare
58
- * `items`, and that must not read as a missing key.
59
- */
60
- export declare function auditCatalogs(input: AuditInput): ExtractReport;
61
- /** The `x verify` gate. Throws on the first locale with a gap, naming file and keys. */
62
- export declare function assertCatalogsComplete(report: ExtractReport): void;
63
- /** Merge per-file extractions before auditing. */
64
- export declare function mergeExtractions(...extractions: readonly Extraction[]): Extraction;
65
- /** Read + scan real files. Bun-only by design; the CLI passes the glob results. */
66
- export declare function extractFromFiles(paths: readonly string[], options?: ExtractOptions): Promise<Extraction>;
67
- //# sourceMappingURL=extract.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["extract.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,OAAO,EAAe,MAAM,WAAW,CAAC;AAGtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAExC,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,0FAA0F;AAC1F,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,oCAAoC;IACpC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,EAAE,EAAE,OAAO,CAAC;CACb;AAED,MAAM,WAAW,cAAc;IAC7B,8CAA8C;IAC9C,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAKD;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,IAAI,SAAa,EACjB,OAAO,GAAE,cAAmB,GAC3B,UAAU,CA0BZ;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,UAAU,CAAC;IACvB,oEAAoE;IACpE,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC5C,6EAA6E;IAC7E,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,aAAa,CAuB9D;AAED,wFAAwF;AACxF,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAIlE;AAED,kDAAkD;AAClD,wBAAgB,gBAAgB,CAAC,GAAG,WAAW,EAAE,SAAS,UAAU,EAAE,GAAG,UAAU,CAKlF;AAED,mFAAmF;AACnF,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,UAAU,CAAC,CAKrB"}
package/src/extract.js DELETED
@@ -1,106 +0,0 @@
1
- /**
2
- * Static extraction: which keys the source calls, which are missing per locale, which
3
- * are defined and never used. `x verify` fails on a missing key in a shipped locale —
4
- * a translation gap is a build error, not a ticket.
5
- */
6
- import { catalogKeys } from './catalog';
7
- import { catalogMissingKeys } from './errors';
8
- import { PLURAL_CATEGORIES } from './interpolate';
9
- const DEFAULT_CALLEES = ['t', '$t', 'translate'];
10
- const STRING_LITERAL = /^(['"`])((?:[^\\]|\\.)*?)\1$/s;
11
- /**
12
- * Scan one source file. Deliberately a lexer-free scan: a translator call is a
13
- * recognisable shape, and a regex that runs in milliseconds over a whole repo beats a
14
- * parser that needs a tsconfig to start.
15
- */
16
- export function extractKeys(source, file = '<memory>', options = {}) {
17
- const callees = options.callees ?? DEFAULT_CALLEES;
18
- const pattern = new RegExp(
19
- // not preceded by an identifier char or `.`, so `ctx.t(` and `format(` don't match
20
- String.raw `(?<![\w$.])(?:${callees.map(escapeRegExp).join('|')})(?:\.has)?\s*\(\s*([^,)]*)`, 'g');
21
- const usages = [];
22
- const dynamic = [];
23
- for (const match of source.matchAll(pattern)) {
24
- const argument = (match[1] ?? '').trim();
25
- const index = match.index ?? 0;
26
- const position = positionOf(source, index);
27
- if (argument === '')
28
- continue;
29
- const literal = STRING_LITERAL.exec(argument);
30
- const key = literal?.[2];
31
- if (key === undefined || key.includes('${')) {
32
- dynamic.push({ expression: argument, file, ...position });
33
- continue;
34
- }
35
- usages.push({ key, file, ...position });
36
- }
37
- return { usages, dynamic };
38
- }
39
- /**
40
- * A used key counts as present when the locale defines it *or* any of its plural
41
- * variants: `pl` legitimately defines `items_one`/`items_few`/`items_many` and no bare
42
- * `items`, and that must not read as a missing key.
43
- */
44
- export function auditCatalogs(input) {
45
- const used = [...new Set(input.extraction.usages.map((usage) => usage.key))].sort();
46
- const ignore = input.ignoreUnused ?? [];
47
- const locales = Object.keys(input.catalogs)
48
- .sort()
49
- .map((locale) => {
50
- const catalog = input.catalogs[locale] ?? {};
51
- const missing = used.filter((key) => !definesKey(catalog, key));
52
- const usedWithVariants = new Set(used.flatMap(variantsOf));
53
- const unused = catalogKeys(catalog).filter((key) => !usedWithVariants.has(key) && !isIgnored(key, ignore));
54
- return { locale, missing, unused };
55
- });
56
- return {
57
- used,
58
- usages: input.extraction.usages,
59
- dynamic: input.extraction.dynamic,
60
- locales,
61
- ok: locales.every((audit) => audit.missing.length === 0),
62
- };
63
- }
64
- /** The `x verify` gate. Throws on the first locale with a gap, naming file and keys. */
65
- export function assertCatalogsComplete(report) {
66
- for (const audit of report.locales) {
67
- if (audit.missing.length > 0)
68
- throw catalogMissingKeys(audit.locale, audit.missing);
69
- }
70
- }
71
- /** Merge per-file extractions before auditing. */
72
- export function mergeExtractions(...extractions) {
73
- return {
74
- usages: extractions.flatMap((extraction) => extraction.usages),
75
- dynamic: extractions.flatMap((extraction) => extraction.dynamic),
76
- };
77
- }
78
- /** Read + scan real files. Bun-only by design; the CLI passes the glob results. */
79
- export async function extractFromFiles(paths, options = {}) {
80
- const extractions = await Promise.all(paths.map(async (path) => extractKeys(await Bun.file(path).text(), path, options)));
81
- return mergeExtractions(...extractions);
82
- }
83
- function definesKey(catalog, key) {
84
- return variantsOf(key).some((candidate) => Object.hasOwn(catalog, candidate));
85
- }
86
- function variantsOf(key) {
87
- return [key, `${key}_plural`, ...PLURAL_CATEGORIES.map((category) => `${key}_${category}`)];
88
- }
89
- function isIgnored(key, patterns) {
90
- return patterns.some((pattern) => pattern.endsWith('*') ? key.startsWith(pattern.slice(0, -1)) : key === pattern);
91
- }
92
- function positionOf(source, index) {
93
- let line = 1;
94
- let lineStart = 0;
95
- for (let i = 0; i < index; i += 1) {
96
- if (source[i] === '\n') {
97
- line += 1;
98
- lineStart = i + 1;
99
- }
100
- }
101
- return { line, column: index - lineStart + 1 };
102
- }
103
- function escapeRegExp(value) {
104
- return value.replace(/[.*+?^${}()|[\]\\]/g, String.raw `\$&`);
105
- }
106
- //# sourceMappingURL=extract.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"extract.js","sourceRoot":"","sources":["extract.ts"],"names":[],"mappings":"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"}
@@ -1,12 +0,0 @@
1
- /**
2
- * The framework's own strings, flattened once at import.
3
- * Register these before app catalogs so an app can override any framework key.
4
- */
5
- import { type Catalog } from './catalog';
6
- import { type Locale } from './locales';
7
- /** `errors.*`, `auth.*`, `pagination.*`, `admin.*`, `validation.*`, `common.*`, `time.*`. */
8
- export declare const FRAMEWORK_CATALOG: Catalog;
9
- export declare const FRAMEWORK_CATALOG_LOCALE: Locale;
10
- /** Called by the HTTP layer at boot. Idempotent. */
11
- export declare function registerFrameworkCatalog(locale?: Locale): void;
12
- //# sourceMappingURL=framework.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"framework.d.ts","sourceRoot":"","sources":["framework.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,KAAK,OAAO,EAAe,MAAM,WAAW,CAAC;AAGtD,OAAO,EAAkB,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAExD,6FAA6F;AAC7F,eAAO,MAAM,iBAAiB,EAAE,OAAyB,CAAC;AAE1D,eAAO,MAAM,wBAAwB,EAAE,MAAuB,CAAC;AAE/D,oDAAoD;AACpD,wBAAgB,wBAAwB,CAAC,MAAM,GAAE,MAAiC,GAAG,IAAI,CAExF"}
package/src/framework.js DELETED
@@ -1,16 +0,0 @@
1
- /**
2
- * The framework's own strings, flattened once at import.
3
- * Register these before app catalogs so an app can override any framework key.
4
- */
5
- import { loadCatalog } from './catalog';
6
- import en from './catalogs/en.json';
7
- import { registerCatalog } from './context';
8
- import { DEFAULT_LOCALE } from './locales';
9
- /** `errors.*`, `auth.*`, `pagination.*`, `admin.*`, `validation.*`, `common.*`, `time.*`. */
10
- export const FRAMEWORK_CATALOG = loadCatalog(en);
11
- export const FRAMEWORK_CATALOG_LOCALE = DEFAULT_LOCALE;
12
- /** Called by the HTTP layer at boot. Idempotent. */
13
- export function registerFrameworkCatalog(locale = FRAMEWORK_CATALOG_LOCALE) {
14
- registerCatalog(locale, FRAMEWORK_CATALOG);
15
- }
16
- //# sourceMappingURL=framework.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"framework.js","sourceRoot":"","sources":["framework.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAgB,WAAW,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,EAAE,MAAM,oBAAoB,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAe,MAAM,WAAW,CAAC;AAExD,6FAA6F;AAC7F,MAAM,CAAC,MAAM,iBAAiB,GAAY,WAAW,CAAC,EAAE,CAAC,CAAC;AAE1D,MAAM,CAAC,MAAM,wBAAwB,GAAW,cAAc,CAAC;AAE/D,oDAAoD;AACpD,MAAM,UAAU,wBAAwB,CAAC,MAAM,GAAW,wBAAwB;IAChF,eAAe,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;AAC7C,CAAC"}
package/src/index.d.ts DELETED
@@ -1,10 +0,0 @@
1
- /** Public surface of @ultimat3/i18n. Explicit exports only. */
2
- export { type Catalog, catalogKeys, flattenCatalog, loadCatalog, mergeCatalogs, missingFrom, type NestedCatalog, parseNestedCatalog, } from './catalog';
3
- export { attachLocale, catalogFor, configureLocales, currentDirection, currentLocale, currentTranslator, LOCALE_COOKIE, type LocaleConfig, type LocaleResolution, type LocaleSourceName, type LocaleSources, localeConfig, localeCookieOf, localeOf, registerCatalog, registeredLocales, resetCatalogs, resolveLocale, t, translatorFor, } from './context';
4
- export { catalogInvalid, catalogMissingKeys, I18N_ERROR_CODES, I18nError, type I18nErrorCode, localeUnsupported, } from './errors';
5
- export { type AuditInput, assertCatalogsComplete, auditCatalogs, type DynamicUsage, type Extraction, type ExtractOptions, type ExtractReport, extractFromFiles, extractKeys, type KeyUsage, type LocaleAudit, mergeExtractions, } from './extract';
6
- export { FRAMEWORK_CATALOG, FRAMEWORK_CATALOG_LOCALE, registerFrameworkCatalog, } from './framework';
7
- export { type InterpolationValue, type InterpolationVars, interpolate, PLURAL_CATEGORIES, placeholdersOf, pluralCategory, pluralKeyCandidates, pluralVariantsOf, selectPluralKey, } from './interpolate';
8
- export { assertSupportedLocale, DEFAULT_LOCALE, type Direction, directionOf, isRtl, isSupportedLocale, type LanguageRange, type Locale, negotiateLocale, normalizeLocale, parseAcceptLanguage, SUPPORTED_LOCALES, } from './locales';
9
- export { createTranslator, isMiss, type TranslateVars, type Translator } from './translator';
10
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAE/D,OAAO,EACL,KAAK,OAAO,EACZ,WAAW,EACX,cAAc,EACd,WAAW,EACX,aAAa,EACb,WAAW,EACX,KAAK,aAAa,EAClB,kBAAkB,GACnB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,YAAY,EACZ,cAAc,EACd,QAAQ,EACR,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,CAAC,EACD,aAAa,GACd,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,SAAS,EACT,KAAK,aAAa,EAClB,iBAAiB,GAClB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,KAAK,UAAU,EACf,sBAAsB,EACtB,aAAa,EACb,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,gBAAgB,EAChB,WAAW,EACX,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,gBAAgB,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,qBAAqB,EACrB,cAAc,EACd,KAAK,SAAS,EACd,WAAW,EACX,KAAK,EACL,iBAAiB,EACjB,KAAK,aAAa,EAClB,KAAK,MAAM,EACX,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC"}
package/src/index.js DELETED
@@ -1,10 +0,0 @@
1
- /** Public surface of @ultimat3/i18n. Explicit exports only. */
2
- export { catalogKeys, flattenCatalog, loadCatalog, mergeCatalogs, missingFrom, parseNestedCatalog, } from './catalog';
3
- export { attachLocale, catalogFor, configureLocales, currentDirection, currentLocale, currentTranslator, LOCALE_COOKIE, localeConfig, localeCookieOf, localeOf, registerCatalog, registeredLocales, resetCatalogs, resolveLocale, t, translatorFor, } from './context';
4
- export { catalogInvalid, catalogMissingKeys, I18N_ERROR_CODES, I18nError, localeUnsupported, } from './errors';
5
- export { assertCatalogsComplete, auditCatalogs, extractFromFiles, extractKeys, mergeExtractions, } from './extract';
6
- export { FRAMEWORK_CATALOG, FRAMEWORK_CATALOG_LOCALE, registerFrameworkCatalog, } from './framework';
7
- export { interpolate, PLURAL_CATEGORIES, placeholdersOf, pluralCategory, pluralKeyCandidates, pluralVariantsOf, selectPluralKey, } from './interpolate';
8
- export { assertSupportedLocale, DEFAULT_LOCALE, directionOf, isRtl, isSupportedLocale, negotiateLocale, normalizeLocale, parseAcceptLanguage, SUPPORTED_LOCALES, } from './locales';
9
- export { createTranslator, isMiss } from './translator';
10
- //# sourceMappingURL=index.js.map
package/src/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAE/D,OAAO,EAEL,WAAW,EACX,cAAc,EACd,WAAW,EACX,aAAa,EACb,WAAW,EAEX,kBAAkB,GACnB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,aAAa,EAKb,YAAY,EACZ,cAAc,EACd,QAAQ,EACR,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,CAAC,EACD,aAAa,GACd,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,SAAS,EAET,iBAAiB,GAClB,MAAM,UAAU,CAAC;AAClB,OAAO,EAEL,sBAAsB,EACtB,aAAa,EAKb,gBAAgB,EAChB,WAAW,EAGX,gBAAgB,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,aAAa,CAAC;AACrB,OAAO,EAGL,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,qBAAqB,EACrB,cAAc,EAEd,WAAW,EACX,KAAK,EACL,iBAAiB,EAGjB,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAuC,MAAM,cAAc,CAAC"}
@@ -1,36 +0,0 @@
1
- /**
2
- * `{var}` substitution and CLDR plural-form selection.
3
- * Pure string work: it never reads a catalog and never resolves a locale.
4
- */
5
- export type InterpolationValue = string | number | boolean;
6
- export type InterpolationVars = Record<string, InterpolationValue>;
7
- /** CLDR plural categories, in the order we probe catalog suffixes. */
8
- export declare const PLURAL_CATEGORIES: readonly Intl.LDMLPluralRule[];
9
- /**
10
- * Replace `{name}` with `vars.name`. `{{` and `}}` escape a literal brace.
11
- * An unknown variable renders loudly as `⟦name⟧` for the same reason a missing key
12
- * does: a silently blank slot ships to production, a bracketed one does not.
13
- */
14
- export declare function interpolate(template: string, vars?: InterpolationVars): string;
15
- /** Variable names a template expects — used by the extractor to flag arity drift. */
16
- export declare function placeholdersOf(template: string): string[];
17
- /**
18
- * Pick the plural category for `count` in `locale` via `Intl.PluralRules` — CLDR
19
- * categories, never an English `n === 1` check. `pl` has one/few/many/other, `ar`
20
- * adds zero/two: a two-branch translator is wrong in both.
21
- */
22
- export declare function pluralCategory(count: number, locale: string): Intl.LDMLPluralRule;
23
- /**
24
- * Candidate keys for a plural lookup, most specific first.
25
- *
26
- * `items` + count 3 in `pl` → `items_many`, `items_plural`, `items_other`, `items`.
27
- * `items` + count 1 in `pl` → `items_one`, `items`.
28
- * `_plural` is the two-form authoring shortcut from the house style; the `_<category>`
29
- * suffixes are what a 3+ form locale needs.
30
- */
31
- export declare function pluralKeyCandidates(key: string, count: number, locale: string): string[];
32
- /** First candidate that exists, per the caller's `has` predicate; falls back to `key`. */
33
- export declare function selectPluralKey(key: string, count: number, locale: string, has: (candidate: string) => boolean): string;
34
- /** All suffixed variants of `key` a locale could legitimately define. */
35
- export declare function pluralVariantsOf(key: string): string[];
36
- //# sourceMappingURL=interpolate.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"interpolate.d.ts","sourceRoot":"","sources":["interpolate.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAC3D,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;AAEnE,sEAAsE;AACtE,eAAO,MAAM,iBAAiB,EAAE,SAAS,IAAI,CAAC,cAAc,EAO3D,CAAC;AAIF;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,iBAAiB,GAAG,MAAM,CAU9E;AAED,qFAAqF;AACrF,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAOzD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,cAAc,CAEjF;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAIxF;AAED,0FAA0F;AAC1F,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,GAClC,MAAM,CAKR;AAED,yEAAyE;AACzE,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAEtD"}
@@ -1,96 +0,0 @@
1
- /**
2
- * `{var}` substitution and CLDR plural-form selection.
3
- * Pure string work: it never reads a catalog and never resolves a locale.
4
- */
5
- /** CLDR plural categories, in the order we probe catalog suffixes. */
6
- export const PLURAL_CATEGORIES = [
7
- 'zero',
8
- 'one',
9
- 'two',
10
- 'few',
11
- 'many',
12
- 'other',
13
- ];
14
- const PLACEHOLDER = /\{\{|\}\}|\{([^{}\s]+)\}/g;
15
- /**
16
- * Replace `{name}` with `vars.name`. `{{` and `}}` escape a literal brace.
17
- * An unknown variable renders loudly as `⟦name⟧` for the same reason a missing key
18
- * does: a silently blank slot ships to production, a bracketed one does not.
19
- */
20
- export function interpolate(template, vars) {
21
- if (!template.includes('{'))
22
- return template;
23
- return template.replace(PLACEHOLDER, (match, name) => {
24
- if (match === '{{')
25
- return '{';
26
- if (match === '}}')
27
- return '}';
28
- if (name === undefined)
29
- return match;
30
- const value = vars?.[name];
31
- if (value === undefined)
32
- return `⟦${name}⟧`;
33
- return String(value);
34
- });
35
- }
36
- /** Variable names a template expects — used by the extractor to flag arity drift. */
37
- export function placeholdersOf(template) {
38
- const names = new Set();
39
- for (const match of template.matchAll(PLACEHOLDER)) {
40
- const name = match[1];
41
- if (name !== undefined)
42
- names.add(name);
43
- }
44
- return [...names];
45
- }
46
- /**
47
- * Pick the plural category for `count` in `locale` via `Intl.PluralRules` — CLDR
48
- * categories, never an English `n === 1` check. `pl` has one/few/many/other, `ar`
49
- * adds zero/two: a two-branch translator is wrong in both.
50
- */
51
- export function pluralCategory(count, locale) {
52
- return pluralRulesFor(locale).select(count);
53
- }
54
- /**
55
- * Candidate keys for a plural lookup, most specific first.
56
- *
57
- * `items` + count 3 in `pl` → `items_many`, `items_plural`, `items_other`, `items`.
58
- * `items` + count 1 in `pl` → `items_one`, `items`.
59
- * `_plural` is the two-form authoring shortcut from the house style; the `_<category>`
60
- * suffixes are what a 3+ form locale needs.
61
- */
62
- export function pluralKeyCandidates(key, count, locale) {
63
- const category = pluralCategory(count, locale);
64
- if (category === 'one')
65
- return [`${key}_one`, key];
66
- return [`${key}_${category}`, `${key}_plural`, `${key}_other`, key];
67
- }
68
- /** First candidate that exists, per the caller's `has` predicate; falls back to `key`. */
69
- export function selectPluralKey(key, count, locale, has) {
70
- for (const candidate of pluralKeyCandidates(key, count, locale)) {
71
- if (has(candidate))
72
- return candidate;
73
- }
74
- return key;
75
- }
76
- /** All suffixed variants of `key` a locale could legitimately define. */
77
- export function pluralVariantsOf(key) {
78
- return [`${key}_plural`, ...PLURAL_CATEGORIES.map((category) => `${key}_${category}`)];
79
- }
80
- const rulesCache = new Map();
81
- function pluralRulesFor(locale) {
82
- const cached = rulesCache.get(locale);
83
- if (cached !== undefined)
84
- return cached;
85
- // An unknown tag would throw inside Intl; degrade to `en` rather than break a render.
86
- let rules;
87
- try {
88
- rules = new Intl.PluralRules(locale);
89
- }
90
- catch {
91
- rules = new Intl.PluralRules('en');
92
- }
93
- rulesCache.set(locale, rules);
94
- return rules;
95
- }
96
- //# sourceMappingURL=interpolate.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"interpolate.js","sourceRoot":"","sources":["interpolate.ts"],"names":[],"mappings":"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"}
package/src/locales.d.ts DELETED
@@ -1,36 +0,0 @@
1
- /**
2
- * Locale resolution only: the supported set, the default, tag normalization and
3
- * HTTP `Accept-Language` negotiation. Knows nothing about catalogs or interpolation.
4
- */
5
- /** A normalized BCP-47 tag — primary subtag, lowercase (`pt`), or `lang-script` (`zh-hant`). */
6
- export type Locale = string;
7
- export declare const DEFAULT_LOCALE: Locale;
8
- /** Locales the framework's own catalogs may ship for. Apps narrow or extend this set. */
9
- export declare const SUPPORTED_LOCALES: readonly Locale[];
10
- export type Direction = 'ltr' | 'rtl';
11
- /**
12
- * Strip region, lowercase, fall back to the default.
13
- * `pt-BR` → `pt`; `ZH-Hant-TW` → `zh-hant` when that tag is supported, else `zh`.
14
- */
15
- export declare function normalizeLocale(tag?: string | null, supported?: readonly Locale[], fallback?: Locale): Locale;
16
- /** Same as `normalizeLocale` but loud: use where an unknown locale is a caller bug. */
17
- export declare function assertSupportedLocale(tag: string, supported?: readonly Locale[]): Locale;
18
- export declare function isSupportedLocale(tag: string, supported?: readonly Locale[]): boolean;
19
- export interface LanguageRange {
20
- tag: string;
21
- quality: number;
22
- }
23
- /**
24
- * Parse `Accept-Language: en-GB,en;q=0.9,fr-CH;q=0.8,*;q=0.5` into ranges sorted by
25
- * quality descending, ties resolved by header order (stable sort).
26
- */
27
- export declare function parseAcceptLanguage(header?: string | null): LanguageRange[];
28
- /**
29
- * Pick the best supported locale for an `Accept-Language` header.
30
- * `*` matches the fallback rather than the first supported locale — a wildcard is
31
- * "no preference", not "any language you have".
32
- */
33
- export declare function negotiateLocale(acceptLanguage?: string | null, supported?: readonly Locale[], fallback?: Locale): Locale;
34
- export declare function isRtl(locale: Locale): boolean;
35
- export declare function directionOf(locale: Locale): Direction;
36
- //# sourceMappingURL=locales.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["locales.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,gGAAgG;AAChG,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC;AAE5B,eAAO,MAAM,cAAc,EAAE,MAAa,CAAC;AAE3C,yFAAyF;AACzF,eAAO,MAAM,iBAAiB,EAAE,SAAS,MAAM,EA+B9C,CAAC;AAuBF,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK,CAAC;AAEtC;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,EACnB,SAAS,GAAE,SAAS,MAAM,EAAsB,EAChD,QAAQ,GAAE,MAAuB,GAChC,MAAM,CAeR;AAED,uFAAuF;AACvF,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,MAAM,EACX,SAAS,GAAE,SAAS,MAAM,EAAsB,GAC/C,MAAM,CAIR;AAED,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,EACX,SAAS,GAAE,SAAS,MAAM,EAAsB,GAC/C,OAAO,CAET;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,aAAa,EAAE,CAkB3E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,EAC9B,SAAS,GAAE,SAAS,MAAM,EAAsB,EAChD,QAAQ,GAAE,MAAuB,GAChC,MAAM,CAOR;AAED,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAG7C;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAErD"}
package/src/locales.js DELETED
@@ -1,141 +0,0 @@
1
- /**
2
- * Locale resolution only: the supported set, the default, tag normalization and
3
- * HTTP `Accept-Language` negotiation. Knows nothing about catalogs or interpolation.
4
- */
5
- import { localeUnsupported } from './errors';
6
- export const DEFAULT_LOCALE = 'en';
7
- /** Locales the framework's own catalogs may ship for. Apps narrow or extend this set. */
8
- export const SUPPORTED_LOCALES = [
9
- 'en',
10
- 'es',
11
- 'pt',
12
- 'fr',
13
- 'de',
14
- 'it',
15
- 'nl',
16
- 'pl',
17
- 'ru',
18
- 'uk',
19
- 'tr',
20
- 'cs',
21
- 'sv',
22
- 'da',
23
- 'nb',
24
- 'fi',
25
- 'el',
26
- 'ro',
27
- 'hu',
28
- 'ar',
29
- 'he',
30
- 'fa',
31
- 'ur',
32
- 'hi',
33
- 'id',
34
- 'vi',
35
- 'th',
36
- 'ja',
37
- 'ko',
38
- 'zh',
39
- ];
40
- /**
41
- * Right-to-left scripts, by primary subtag. A static CLDR-derived set rather than
42
- * `Intl.Locale.prototype.getTextInfo` so direction is deterministic across runtimes.
43
- */
44
- const RTL_LOCALES = new Set([
45
- 'ar',
46
- 'arc',
47
- 'ckb',
48
- 'dv',
49
- 'fa',
50
- 'he',
51
- 'ks',
52
- 'ku',
53
- 'nqo',
54
- 'ps',
55
- 'sd',
56
- 'ug',
57
- 'ur',
58
- 'yi',
59
- ]);
60
- /**
61
- * Strip region, lowercase, fall back to the default.
62
- * `pt-BR` → `pt`; `ZH-Hant-TW` → `zh-hant` when that tag is supported, else `zh`.
63
- */
64
- export function normalizeLocale(tag, supported = SUPPORTED_LOCALES, fallback = DEFAULT_LOCALE) {
65
- if (!tag)
66
- return fallback;
67
- const lower = tag.trim().toLowerCase().replace(/_/g, '-');
68
- if (lower.length === 0 || lower === '*')
69
- return fallback;
70
- if (supported.includes(lower))
71
- return lower;
72
- const segments = lower.split('-');
73
- const primary = segments[0] ?? '';
74
- // `lang-script` is a distinct written form (zh-hant vs zh-hans) — try it before dropping to `lang`.
75
- const script = segments[1];
76
- if (script !== undefined && script.length === 4) {
77
- const withScript = `${primary}-${script}`;
78
- if (supported.includes(withScript))
79
- return withScript;
80
- }
81
- return supported.includes(primary) ? primary : fallback;
82
- }
83
- /** Same as `normalizeLocale` but loud: use where an unknown locale is a caller bug. */
84
- export function assertSupportedLocale(tag, supported = SUPPORTED_LOCALES) {
85
- const normalized = normalizeLocale(tag, supported, '');
86
- if (normalized === '')
87
- throw localeUnsupported(tag, supported);
88
- return normalized;
89
- }
90
- export function isSupportedLocale(tag, supported = SUPPORTED_LOCALES) {
91
- return normalizeLocale(tag, supported, '') !== '';
92
- }
93
- /**
94
- * Parse `Accept-Language: en-GB,en;q=0.9,fr-CH;q=0.8,*;q=0.5` into ranges sorted by
95
- * quality descending, ties resolved by header order (stable sort).
96
- */
97
- export function parseAcceptLanguage(header) {
98
- if (!header)
99
- return [];
100
- const ranges = [];
101
- for (const part of header.split(',')) {
102
- const [rawTag, ...params] = part.trim().split(';');
103
- const tag = rawTag?.trim();
104
- if (!tag)
105
- continue;
106
- let quality = 1;
107
- for (const param of params) {
108
- const match = /^\s*q\s*=\s*([0-9.]+)\s*$/i.exec(param);
109
- if (match?.[1] !== undefined) {
110
- const parsed = Number.parseFloat(match[1]);
111
- quality = Number.isFinite(parsed) ? Math.min(Math.max(parsed, 0), 1) : 0;
112
- }
113
- }
114
- if (quality > 0)
115
- ranges.push({ tag: tag.toLowerCase(), quality });
116
- }
117
- return ranges.sort((a, b) => b.quality - a.quality);
118
- }
119
- /**
120
- * Pick the best supported locale for an `Accept-Language` header.
121
- * `*` matches the fallback rather than the first supported locale — a wildcard is
122
- * "no preference", not "any language you have".
123
- */
124
- export function negotiateLocale(acceptLanguage, supported = SUPPORTED_LOCALES, fallback = DEFAULT_LOCALE) {
125
- for (const { tag } of parseAcceptLanguage(acceptLanguage)) {
126
- if (tag === '*')
127
- return fallback;
128
- const match = normalizeLocale(tag, supported, '');
129
- if (match !== '')
130
- return match;
131
- }
132
- return fallback;
133
- }
134
- export function isRtl(locale) {
135
- const primary = locale.split('-')[0]?.toLowerCase() ?? '';
136
- return RTL_LOCALES.has(primary);
137
- }
138
- export function directionOf(locale) {
139
- return isRtl(locale) ? 'rtl' : 'ltr';
140
- }
141
- //# sourceMappingURL=locales.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"locales.js","sourceRoot":"","sources":["locales.ts"],"names":[],"mappings":"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"}
@@ -1,25 +0,0 @@
1
- /**
2
- * Lookup + interpolation delegation, nothing else. Never throws, never falls back to
3
- * another locale, never returns the key bare: a miss renders `⟦key⟧` so gaps are
4
- * visible in dev and in review screenshots instead of invisible in production.
5
- */
6
- import type { Catalog } from './catalog';
7
- import { type InterpolationVars } from './interpolate';
8
- import { type Locale } from './locales';
9
- export interface TranslateVars extends InterpolationVars {
10
- /** Present ⇒ plural selection runs against the locale's CLDR categories. */
11
- count?: number;
12
- }
13
- export interface Translator {
14
- (key: string, vars?: TranslateVars): string;
15
- /** Whether the key (or any of its plural variants) can be rendered. */
16
- has(key: string): boolean;
17
- /** The raw template, for tooling that needs the placeholders. */
18
- raw(key: string): string | undefined;
19
- keys(): string[];
20
- readonly locale: Locale;
21
- }
22
- export declare function createTranslator(catalog: Catalog, locale?: Locale): Translator;
23
- /** True when a rendered string is a loud miss — assert on this in tests, never on `''`. */
24
- export declare function isMiss(rendered: string): boolean;
25
- //# sourceMappingURL=translator.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"translator.d.ts","sourceRoot":"","sources":["translator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,KAAK,iBAAiB,EAAgC,MAAM,eAAe,CAAC;AACrF,OAAO,EAAkB,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAExD,MAAM,WAAW,aAAc,SAAQ,iBAAiB;IACtD,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,MAAM,CAAC;IAC5C,uEAAuE;IACvE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,iEAAiE;IACjE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACrC,IAAI,IAAI,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,GAAE,MAAuB,GAAG,UAAU,CA2B9F;AAED,2FAA2F;AAC3F,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEhD"}
package/src/translator.js DELETED
@@ -1,37 +0,0 @@
1
- /**
2
- * Lookup + interpolation delegation, nothing else. Never throws, never falls back to
3
- * another locale, never returns the key bare: a miss renders `⟦key⟧` so gaps are
4
- * visible in dev and in review screenshots instead of invisible in production.
5
- */
6
- import { interpolate, selectPluralKey } from './interpolate';
7
- import { DEFAULT_LOCALE } from './locales';
8
- export function createTranslator(catalog, locale = DEFAULT_LOCALE) {
9
- const hasExact = (key) => Object.hasOwn(catalog, key);
10
- const resolveKey = (key, vars) => {
11
- const count = vars?.count;
12
- if (typeof count === 'number' && Number.isFinite(count)) {
13
- return selectPluralKey(key, count, locale, hasExact);
14
- }
15
- return key;
16
- };
17
- const translate = (key, vars) => {
18
- const template = catalog[resolveKey(key, vars)];
19
- if (template === undefined)
20
- return `⟦${key}⟧`;
21
- return vars === undefined ? template : interpolate(template, vars);
22
- };
23
- return Object.assign(translate, {
24
- has: (key) => hasExact(key) ||
25
- hasExact(`${key}_other`) ||
26
- hasExact(`${key}_plural`) ||
27
- hasExact(`${key}_one`),
28
- raw: (key) => catalog[key],
29
- keys: () => Object.keys(catalog).sort(),
30
- locale,
31
- });
32
- }
33
- /** True when a rendered string is a loud miss — assert on this in tests, never on `''`. */
34
- export function isMiss(rendered) {
35
- return rendered.startsWith('⟦') && rendered.endsWith('⟧');
36
- }
37
- //# sourceMappingURL=translator.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"translator.js","sourceRoot":"","sources":["translator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAA0B,WAAW,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrF,OAAO,EAAE,cAAc,EAAe,MAAM,WAAW,CAAC;AAiBxD,MAAM,UAAU,gBAAgB,CAAC,OAAgB,EAAE,MAAM,GAAW,cAAc;IAChF,MAAM,QAAQ,GAAG,CAAC,GAAW,EAAW,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAEvE,MAAM,UAAU,GAAG,CAAC,GAAW,EAAE,IAAoB,EAAU,EAAE;QAC/D,MAAM,KAAK,GAAG,IAAI,EAAE,KAAK,CAAC;QAC1B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACxD,OAAO,eAAe,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;QACvD,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,CAAC,GAAW,EAAE,IAAoB,EAAU,EAAE;QAC9D,MAAM,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;QAChD,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO,IAAI,GAAG,GAAG,CAAC;QAC9C,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACrE,CAAC,CAAC;IAEF,OAAO,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE;QAC9B,GAAG,EAAE,CAAC,GAAW,EAAW,EAAE,CAC5B,QAAQ,CAAC,GAAG,CAAC;YACb,QAAQ,CAAC,GAAG,GAAG,QAAQ,CAAC;YACxB,QAAQ,CAAC,GAAG,GAAG,SAAS,CAAC;YACzB,QAAQ,CAAC,GAAG,GAAG,MAAM,CAAC;QACxB,GAAG,EAAE,CAAC,GAAW,EAAsB,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC;QACtD,IAAI,EAAE,GAAa,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE;QACjD,MAAM;KACP,CAAC,CAAC;AACL,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,MAAM,CAAC,QAAgB;IACrC,OAAO,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AAC5D,CAAC"}