@vielzeug/lingua 1.1.2 → 1.1.4
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/dist/_bounded-cache.cjs +2 -0
- package/dist/_bounded-cache.cjs.map +1 -0
- package/dist/_bounded-cache.d.ts +2 -0
- package/dist/_bounded-cache.d.ts.map +1 -0
- package/dist/_bounded-cache.js +10 -0
- package/dist/_bounded-cache.js.map +1 -0
- package/dist/_catalog-store.cjs +1 -1
- package/dist/_catalog-store.cjs.map +1 -1
- package/dist/_catalog-store.d.ts +5 -4
- package/dist/_catalog-store.d.ts.map +1 -1
- package/dist/_catalog-store.js +54 -47
- package/dist/_catalog-store.js.map +1 -1
- package/dist/_dev.cjs +1 -1
- package/dist/_dev.cjs.map +1 -1
- package/dist/_dev.js +4 -1
- package/dist/_dev.js.map +1 -1
- package/dist/_translate.cjs +2 -0
- package/dist/_translate.cjs.map +1 -0
- package/dist/_translate.d.ts +21 -0
- package/dist/_translate.d.ts.map +1 -0
- package/dist/_translate.js +54 -0
- package/dist/_translate.js.map +1 -0
- package/dist/format.cjs +1 -1
- package/dist/format.cjs.map +1 -1
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +39 -43
- package/dist/format.js.map +1 -1
- package/dist/i18n-types.d.ts +12 -1
- package/dist/i18n-types.d.ts.map +1 -1
- package/dist/i18n.cjs +1 -1
- package/dist/i18n.cjs.map +1 -1
- package/dist/i18n.d.ts +1 -29
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +91 -127
- package/dist/i18n.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/lingua.cjs +1 -1
- package/dist/lingua.cjs.map +1 -1
- package/dist/lingua.iife.js +1 -1
- package/dist/lingua.iife.js.map +1 -1
- package/dist/lingua.js +1 -1
- package/dist/lingua.js.map +1 -1
- package/dist/template-BZRFdmaQ.cjs +2 -0
- package/dist/template-BZRFdmaQ.cjs.map +1 -0
- package/dist/template-BihTfBYb.js +2 -0
- package/dist/template-BihTfBYb.js.map +1 -0
- package/dist/validate-D1U8TTeT.js +2 -0
- package/dist/validate-D1U8TTeT.js.map +1 -0
- package/dist/validate-x7lFFyV2.cjs +2 -0
- package/dist/validate-x7lFFyV2.cjs.map +1 -0
- package/dist/validate.cjs.map +1 -1
- package/dist/validate.d.ts +2 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_bounded-cache.cjs","names":[],"sources":["../src/_bounded-cache.ts"],"sourcesContent":["// Internal — not part of the public API.\n// Minimal bounded cache: evicts the oldest entry (insertion order) once `maxSize` is exceeded.\n// Not LRU — no access-time reordering, only insertion order matters. These caches exist purely\n// to avoid rebuilding cheap-but-not-free values (compiled `Intl` formatters, scoped translator\n// objects) for a bounded number of frequently-reused keys, not to model real recency.\n\nexport function getOrCreate<K, V>(cache: Map<K, V>, key: K, maxSize: number, build: () => V): V {\n if (cache.has(key)) return cache.get(key)!;\n\n const value = build();\n\n if (cache.size >= maxSize) cache.delete(cache.keys().next().value as K);\n\n cache.set(key, value);\n\n return value;\n}\n"],"mappings":"AAMA,SAAgB,EAAkB,EAAkB,EAAQ,EAAiB,EAAmB,CAC9F,GAAI,EAAM,IAAI,CAAG,EAAG,OAAO,EAAM,IAAI,CAAG,EAExC,IAAM,EAAQ,EAAM,EAMpB,OAJI,EAAM,MAAQ,GAAS,EAAM,OAAO,EAAM,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,KAAU,EAEtE,EAAM,IAAI,EAAK,CAAK,EAEb,CACT"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_bounded-cache.d.ts","sourceRoot":"","sources":["../src/_bounded-cache.ts"],"names":[],"mappings":"AAMA,wBAAgB,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,CAU9F"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/_bounded-cache.ts
|
|
2
|
+
function e(e, t, n, r) {
|
|
3
|
+
if (e.has(t)) return e.get(t);
|
|
4
|
+
let i = r();
|
|
5
|
+
return e.size >= n && e.delete(e.keys().next().value), e.set(t, i), i;
|
|
6
|
+
}
|
|
7
|
+
//#endregion
|
|
8
|
+
export { e as getOrCreate };
|
|
9
|
+
|
|
10
|
+
//# sourceMappingURL=_bounded-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_bounded-cache.js","names":[],"sources":["../src/_bounded-cache.ts"],"sourcesContent":["// Internal — not part of the public API.\n// Minimal bounded cache: evicts the oldest entry (insertion order) once `maxSize` is exceeded.\n// Not LRU — no access-time reordering, only insertion order matters. These caches exist purely\n// to avoid rebuilding cheap-but-not-free values (compiled `Intl` formatters, scoped translator\n// objects) for a bounded number of frequently-reused keys, not to model real recency.\n\nexport function getOrCreate<K, V>(cache: Map<K, V>, key: K, maxSize: number, build: () => V): V {\n if (cache.has(key)) return cache.get(key)!;\n\n const value = build();\n\n if (cache.size >= maxSize) cache.delete(cache.keys().next().value as K);\n\n cache.set(key, value);\n\n return value;\n}\n"],"mappings":";AAMA,SAAgB,EAAkB,GAAkB,GAAQ,GAAiB,GAAmB;CAC9F,IAAI,EAAM,IAAI,CAAG,GAAG,OAAO,EAAM,IAAI,CAAG;CAExC,IAAM,IAAQ,EAAM;CAMpB,OAJI,EAAM,QAAQ,KAAS,EAAM,OAAO,EAAM,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,KAAU,GAEtE,EAAM,IAAI,GAAK,CAAK,GAEb;AACT"}
|
package/dist/_catalog-store.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./_dev.cjs"),t=require("./_catalog.cjs"),n=require("./errors.cjs");function r(r){let i
|
|
1
|
+
const e=require("./_dev.cjs"),t=require("./_catalog.cjs"),n=require("./errors.cjs");function r(t,n){e.devOnly(()=>{Promise.resolve().then(()=>require("./validate.cjs")).then(({validateCatalog:r})=>{for(let i of r(n,t))e.warn(`catalog('${t}'): missing plural form '${i.form}' for key '${i.key}'.`)})})}function i(i){let a=new Map,o=new Map,s=new Map,c=new Set,l,u=(e,n)=>{let r=a.get(e);r||(r=new t.CatalogEntry,a.set(e,r)),r.setAll(n)},d=e=>{let c=n.checkDisposedAsync(i());if(c)return c;if(a.has(e))return Promise.resolve();let d=o.get(e);if(!d)return Promise.reject(new n.LinguaMissingLocaleError(`Missing locale source for "${e}".`));let f=s.get(e);if(f)return f;let p=d().then(n=>{o.get(e)===d&&(o.delete(e),u(e,t.flattenStrings(n)),r(e,n),s.delete(e),l?.(e))},t=>{throw o.get(e)===d&&s.delete(e),t});return s.set(e,p),p};return{dispose(){a.clear(),o.clear(),s.clear(),c.clear(),l=void 0},isLoaded(e){return a.has(e)},isRegistered(e){return c.has(e)},knownLocales(){return[...c]},get onChange(){return l},set onChange(e){l=e},patch(e,r){return n.checkDisposedAsync(i())||(o.has(e)?d(e).then(()=>{i()||(u(e,t.flattenStrings(r)),c.add(e),l?.(e))}):(u(e,t.flattenStrings(r)),c.add(e),l?.(e),Promise.resolve()))},preload:d,register(f,p,m){n.checkDisposed(i()),s.delete(f),c.add(f);let h=m.clearLocale(f);h.length>0&&e.warn(`register('${f}') cleared loaded namespace markers for: ${h.map(e=>`'${e}'`).join(`, `)}. Call loadNamespace() again to reload.`);let g;return typeof p==`function`?(o.set(f,p),a.delete(f),g=d(f)):(o.delete(f),a.delete(f),u(f,t.flattenStrings(p)),r(f,p),g=Promise.resolve(),l?.(f)),g},resolve(e){return a.get(e)},seedFrom(e,t){for(let[t,n]of e)a.set(t,n.clone()),c.add(t);for(let[e,n]of t)o.set(e,n),c.add(e)},snapshotCatalogs(){return a},snapshotLoaders(){return o}}}exports.createCatalogStore=i,exports.validateCatalogInDev=r;
|
|
2
2
|
//# sourceMappingURL=_catalog-store.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_catalog-store.cjs","names":[],"sources":["../src/_catalog-store.ts"],"sourcesContent":["// Internal — not part of the public API.\n// Owns all locale → CatalogEntry state, loaders, and in-flight loading tasks.\n\nimport type { NamespaceStore } from './_namespace-store';\n\nimport { CatalogEntry, type Messages, flattenStrings } from './_catalog';\nimport { warn } from './_dev';\nimport { LinguaMissingLocaleError, checkDisposed, checkDisposedAsync } from './errors';\n\nexport type Locale = string;\nexport type Loader<M extends Messages = Messages> = () => Promise<M>;\nexport type LocaleSource<M extends Messages = Messages> = M | Loader<M>;\n\nexport type CatalogStore<M extends Messages = Messages> = {\n /** Direct access to the raw catalogs map (for SSR serialization). */\n readonly catalogs: ReadonlyMap<Locale, CatalogEntry>;\n dispose(): void;\n isLoaded(loc: Locale): boolean;\n isRegistered(loc: Locale): boolean;\n /** All registered locale keys in insertion order. */\n knownLocales(): readonly Locale[];\n /** Callback invoked whenever a catalog changes (drives bump/notify). */\n onChange: ((loc: Locale) => void) | undefined;\n /** Patch a locale catalog with additional flat messages (used by loadNamespace). */\n patch(loc: Locale, messages: Messages): Promise<void>;\n /** Direct access to the pending loaders map (for fork propagation). */\n readonly pendingLoaders: ReadonlyMap<Locale, Loader<M>>;\n /** Pre-load a locale catalog without switching. */\n preload(loc: Locale): Promise<void>;\n /** Register (or replace) a locale's source. Returns a Promise that resolves when the catalog is loaded. */\n register(loc: Locale, source: LocaleSource<M>, nsStore: NamespaceStore): Promise<void>;\n /** Resolve a compiled CatalogEntry for a locale (undefined if not loaded). */\n resolve(loc: Locale): CatalogEntry | undefined;\n /**\n * Seed the store with pre-compiled entries from a parent fork.\n * Called during fork() — compiled templates are shared by reference.\n */\n seedFrom(entries: ReadonlyMap<Locale, CatalogEntry>, loaderMap: ReadonlyMap<Locale, Loader<M>>): void;\n};\n\nexport function createCatalogStore<M extends Messages = Messages>(disposed: () => boolean): CatalogStore<M> {\n const catalogs = new Map<Locale, CatalogEntry>();\n const loaders = new Map<Locale, Loader<M>>();\n const loadingTasks = new Map<Locale, Promise<void>>();\n const known = new Set<Locale>();\n\n let onChange: ((loc: Locale) => void) | undefined;\n\n const registerRaw = (normalized: Locale, flat: Map<string, string>): void => {\n let entry = catalogs.get(normalized);\n\n if (!entry) {\n entry = new CatalogEntry();\n catalogs.set(normalized, entry);\n }\n\n entry.setAll(flat);\n };\n\n const preload = (loc: Locale): Promise<void> => {\n const early = checkDisposedAsync(disposed());\n\n if (early) return early;\n\n if (catalogs.has(loc)) return Promise.resolve();\n\n const loader = loaders.get(loc);\n\n if (!loader) return Promise.reject(new LinguaMissingLocaleError(`Missing locale source for \"${loc}\".`));\n\n const existing = loadingTasks.get(loc);\n\n if (existing) return existing;\n\n const task = loader().then(\n (messages) => {\n if (loaders.get(loc) !== loader) return;\n\n loaders.delete(loc);\n registerRaw(loc, flattenStrings(messages));\n loadingTasks.delete(loc);\n onChange?.(loc);\n },\n (error: unknown) => {\n if (loaders.get(loc) === loader) loadingTasks.delete(loc);\n\n throw error;\n },\n );\n\n loadingTasks.set(loc, task);\n\n return task;\n };\n\n return {\n get catalogs() {\n return catalogs as ReadonlyMap<Locale, CatalogEntry>;\n },\n\n dispose() {\n catalogs.clear();\n loaders.clear();\n loadingTasks.clear();\n known.clear();\n onChange = undefined;\n },\n\n isLoaded(loc) {\n return catalogs.has(loc);\n },\n\n isRegistered(loc) {\n return known.has(loc);\n },\n\n knownLocales() {\n return [...known];\n },\n\n get onChange() {\n return onChange;\n },\n\n set onChange(fn: ((loc: Locale) => void) | undefined) {\n onChange = fn;\n },\n\n patch(loc, messages) {\n const early = checkDisposedAsync(disposed());\n\n if (early) return early;\n\n if (loaders.has(loc)) {\n return preload(loc).then(() => {\n if (disposed()) return;\n\n registerRaw(loc, flattenStrings(messages));\n known.add(loc);\n onChange?.(loc);\n });\n }\n\n registerRaw(loc, flattenStrings(messages));\n known.add(loc);\n onChange?.(loc);\n\n return Promise.resolve();\n },\n\n get pendingLoaders() {\n return loaders as ReadonlyMap<Locale, Loader<M>>;\n },\n\n preload,\n\n register(loc, source, nsStore) {\n checkDisposed(disposed());\n\n loadingTasks.delete(loc);\n known.add(loc);\n\n // Clear namespace loaded-markers for this locale so namespaces can be re-applied\n // after catalog replacement.\n const clearedNs = nsStore.clearLocale(loc);\n\n if (clearedNs.length > 0) {\n warn(\n `register('${loc}') cleared loaded namespace markers for: ${clearedNs.map((ns) => `'${ns}'`).join(', ')}. Call loadNamespace() again to reload.`,\n );\n }\n\n let loadPromise: Promise<void>;\n\n if (typeof source === 'function') {\n loaders.set(loc, source as Loader<M>);\n catalogs.delete(loc);\n loadPromise = preload(loc);\n } else {\n loaders.delete(loc);\n catalogs.delete(loc);\n registerRaw(loc, flattenStrings(source as M));\n loadPromise = Promise.resolve();\n onChange?.(loc);\n }\n\n return loadPromise;\n },\n\n resolve(loc) {\n return catalogs.get(loc);\n },\n\n seedFrom(entries, loaderMap) {\n for (const [loc, entry] of entries) {\n catalogs.set(loc, entry.clone());\n known.add(loc);\n }\n\n for (const [loc, loader] of loaderMap) {\n loaders.set(loc, loader);\n known.add(loc);\n }\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"_catalog-store.cjs","names":[],"sources":["../src/_catalog-store.ts"],"sourcesContent":["// Internal — not part of the public API.\n// Owns all locale → CatalogEntry state, loaders, and in-flight loading tasks.\n\nimport type { NamespaceStore } from './_namespace-store';\n\nimport { CatalogEntry, type Messages, flattenStrings } from './_catalog';\nimport { devOnly, warn } from './_dev';\nimport { LinguaMissingLocaleError, checkDisposed, checkDisposedAsync } from './errors';\n\nexport type Locale = string;\nexport type Loader<M extends Messages = Messages> = () => Promise<M>;\nexport type LocaleSource<M extends Messages = Messages> = M | Loader<M>;\n\n// Dev-only feedback for the same catalog-authoring mistakes `validateCatalog()` (the CI/build\n// tool at `@vielzeug/lingua/validate`) already catches — most projects never wire that up, so\n// this gives the same signal for free, in the console the author is already watching, the\n// moment a full catalog becomes available: at `createI18n({ catalogs })` construction, via\n// `register()`, or once an async loader resolves. Not run for `patch()` — a namespace overlay is\n// expected to add only a handful of keys, not a complete plural set.\n//\n// Dynamic import, not a static one: `validate.ts`'s own docs promise it stays out of production\n// bundles. A static `import { validateCatalog } from './validate'` here would pull it into every\n// consumer's main bundle regardless of `devOnly()`'s runtime check — bundlers only eliminate\n// that dead branch if the consumer's own build defines `__LINGUA_PROD__` as a literal. The\n// dynamic import keeps validate.ts a genuinely separate, lazily-fetched chunk that's never\n// requested at all when `isDev` is false, no consumer bundler config required.\nexport function validateCatalogInDev(loc: Locale, messages: Messages): void {\n devOnly(() => {\n void import('./validate').then(({ validateCatalog }) => {\n for (const w of validateCatalog(messages, loc)) {\n warn(`catalog('${loc}'): missing plural form '${w.form}' for key '${w.key}'.`);\n }\n });\n });\n}\n\nexport type CatalogStore<M extends Messages = Messages> = {\n dispose(): void;\n isLoaded(loc: Locale): boolean;\n isRegistered(loc: Locale): boolean;\n /** All registered locale keys in insertion order. */\n knownLocales(): readonly Locale[];\n /** Callback invoked whenever a catalog changes (drives bump/notify). */\n onChange: ((loc: Locale) => void) | undefined;\n /** Patch a locale catalog with additional flat messages (used by loadNamespace). */\n patch(loc: Locale, messages: Messages): Promise<void>;\n /** Pre-load a locale catalog without switching. */\n preload(loc: Locale): Promise<void>;\n /** Register (or replace) a locale's source. Returns a Promise that resolves when the catalog is loaded. */\n register(loc: Locale, source: LocaleSource<M>, nsStore: NamespaceStore): Promise<void>;\n /** Resolve a compiled CatalogEntry for a locale (undefined if not loaded). */\n resolve(loc: Locale): CatalogEntry | undefined;\n /**\n * Seed the store with pre-compiled entries from a parent fork.\n * Called during fork() — compiled templates are shared by reference.\n */\n seedFrom(entries: ReadonlyMap<Locale, CatalogEntry>, loaderMap: ReadonlyMap<Locale, Loader<M>>): void;\n /** Snapshot of the raw catalogs map — used for SSR serialization (`getState()`) and fork seeding. */\n snapshotCatalogs(): ReadonlyMap<Locale, CatalogEntry>;\n /** Snapshot of the pending (not-yet-resolved) loaders map — used for fork seeding. */\n snapshotLoaders(): ReadonlyMap<Locale, Loader<M>>;\n};\n\nexport function createCatalogStore<M extends Messages = Messages>(disposed: () => boolean): CatalogStore<M> {\n const catalogs = new Map<Locale, CatalogEntry>();\n const loaders = new Map<Locale, Loader<M>>();\n const loadingTasks = new Map<Locale, Promise<void>>();\n const known = new Set<Locale>();\n\n let onChange: ((loc: Locale) => void) | undefined;\n\n const registerRaw = (normalized: Locale, flat: Map<string, string>): void => {\n let entry = catalogs.get(normalized);\n\n if (!entry) {\n entry = new CatalogEntry();\n catalogs.set(normalized, entry);\n }\n\n entry.setAll(flat);\n };\n\n const preload = (loc: Locale): Promise<void> => {\n const early = checkDisposedAsync(disposed());\n\n if (early) return early;\n\n if (catalogs.has(loc)) return Promise.resolve();\n\n const loader = loaders.get(loc);\n\n if (!loader) return Promise.reject(new LinguaMissingLocaleError(`Missing locale source for \"${loc}\".`));\n\n const existing = loadingTasks.get(loc);\n\n if (existing) return existing;\n\n const task = loader().then(\n (messages) => {\n if (loaders.get(loc) !== loader) return;\n\n loaders.delete(loc);\n registerRaw(loc, flattenStrings(messages));\n validateCatalogInDev(loc, messages);\n loadingTasks.delete(loc);\n onChange?.(loc);\n },\n (error: unknown) => {\n if (loaders.get(loc) === loader) loadingTasks.delete(loc);\n\n throw error;\n },\n );\n\n loadingTasks.set(loc, task);\n\n return task;\n };\n\n return {\n dispose() {\n catalogs.clear();\n loaders.clear();\n loadingTasks.clear();\n known.clear();\n onChange = undefined;\n },\n\n isLoaded(loc) {\n return catalogs.has(loc);\n },\n\n isRegistered(loc) {\n return known.has(loc);\n },\n\n knownLocales() {\n return [...known];\n },\n\n get onChange() {\n return onChange;\n },\n\n set onChange(fn: ((loc: Locale) => void) | undefined) {\n onChange = fn;\n },\n\n patch(loc, messages) {\n const early = checkDisposedAsync(disposed());\n\n if (early) return early;\n\n if (loaders.has(loc)) {\n return preload(loc).then(() => {\n if (disposed()) return;\n\n registerRaw(loc, flattenStrings(messages));\n known.add(loc);\n onChange?.(loc);\n });\n }\n\n registerRaw(loc, flattenStrings(messages));\n known.add(loc);\n onChange?.(loc);\n\n return Promise.resolve();\n },\n\n preload,\n\n register(loc, source, nsStore) {\n checkDisposed(disposed());\n\n loadingTasks.delete(loc);\n known.add(loc);\n\n // Clear namespace loaded-markers for this locale so namespaces can be re-applied\n // after catalog replacement.\n const clearedNs = nsStore.clearLocale(loc);\n\n if (clearedNs.length > 0) {\n warn(\n `register('${loc}') cleared loaded namespace markers for: ${clearedNs.map((ns) => `'${ns}'`).join(', ')}. Call loadNamespace() again to reload.`,\n );\n }\n\n let loadPromise: Promise<void>;\n\n if (typeof source === 'function') {\n loaders.set(loc, source as Loader<M>);\n catalogs.delete(loc);\n loadPromise = preload(loc);\n } else {\n loaders.delete(loc);\n catalogs.delete(loc);\n registerRaw(loc, flattenStrings(source as M));\n validateCatalogInDev(loc, source as M);\n loadPromise = Promise.resolve();\n onChange?.(loc);\n }\n\n return loadPromise;\n },\n\n resolve(loc) {\n return catalogs.get(loc);\n },\n\n seedFrom(entries, loaderMap) {\n for (const [loc, entry] of entries) {\n catalogs.set(loc, entry.clone());\n known.add(loc);\n }\n\n for (const [loc, loader] of loaderMap) {\n loaders.set(loc, loader);\n known.add(loc);\n }\n },\n\n snapshotCatalogs() {\n return catalogs as ReadonlyMap<Locale, CatalogEntry>;\n },\n\n snapshotLoaders() {\n return loaders as ReadonlyMap<Locale, Loader<M>>;\n },\n };\n}\n"],"mappings":"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"}
|
package/dist/_catalog-store.d.ts
CHANGED
|
@@ -3,9 +3,8 @@ import { CatalogEntry, type Messages } from './_catalog';
|
|
|
3
3
|
export type Locale = string;
|
|
4
4
|
export type Loader<M extends Messages = Messages> = () => Promise<M>;
|
|
5
5
|
export type LocaleSource<M extends Messages = Messages> = M | Loader<M>;
|
|
6
|
+
export declare function validateCatalogInDev(loc: Locale, messages: Messages): void;
|
|
6
7
|
export type CatalogStore<M extends Messages = Messages> = {
|
|
7
|
-
/** Direct access to the raw catalogs map (for SSR serialization). */
|
|
8
|
-
readonly catalogs: ReadonlyMap<Locale, CatalogEntry>;
|
|
9
8
|
dispose(): void;
|
|
10
9
|
isLoaded(loc: Locale): boolean;
|
|
11
10
|
isRegistered(loc: Locale): boolean;
|
|
@@ -15,8 +14,6 @@ export type CatalogStore<M extends Messages = Messages> = {
|
|
|
15
14
|
onChange: ((loc: Locale) => void) | undefined;
|
|
16
15
|
/** Patch a locale catalog with additional flat messages (used by loadNamespace). */
|
|
17
16
|
patch(loc: Locale, messages: Messages): Promise<void>;
|
|
18
|
-
/** Direct access to the pending loaders map (for fork propagation). */
|
|
19
|
-
readonly pendingLoaders: ReadonlyMap<Locale, Loader<M>>;
|
|
20
17
|
/** Pre-load a locale catalog without switching. */
|
|
21
18
|
preload(loc: Locale): Promise<void>;
|
|
22
19
|
/** Register (or replace) a locale's source. Returns a Promise that resolves when the catalog is loaded. */
|
|
@@ -28,6 +25,10 @@ export type CatalogStore<M extends Messages = Messages> = {
|
|
|
28
25
|
* Called during fork() — compiled templates are shared by reference.
|
|
29
26
|
*/
|
|
30
27
|
seedFrom(entries: ReadonlyMap<Locale, CatalogEntry>, loaderMap: ReadonlyMap<Locale, Loader<M>>): void;
|
|
28
|
+
/** Snapshot of the raw catalogs map — used for SSR serialization (`getState()`) and fork seeding. */
|
|
29
|
+
snapshotCatalogs(): ReadonlyMap<Locale, CatalogEntry>;
|
|
30
|
+
/** Snapshot of the pending (not-yet-resolved) loaders map — used for fork seeding. */
|
|
31
|
+
snapshotLoaders(): ReadonlyMap<Locale, Loader<M>>;
|
|
31
32
|
};
|
|
32
33
|
export declare function createCatalogStore<M extends Messages = Messages>(disposed: () => boolean): CatalogStore<M>;
|
|
33
34
|
//# sourceMappingURL=_catalog-store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_catalog-store.d.ts","sourceRoot":"","sources":["../src/_catalog-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,OAAO,EAAE,YAAY,EAAE,KAAK,QAAQ,EAAkB,MAAM,YAAY,CAAC;AAIzE,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC;AAC5B,MAAM,MAAM,MAAM,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;AACrE,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"_catalog-store.d.ts","sourceRoot":"","sources":["../src/_catalog-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,OAAO,EAAE,YAAY,EAAE,KAAK,QAAQ,EAAkB,MAAM,YAAY,CAAC;AAIzE,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC;AAC5B,MAAM,MAAM,MAAM,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;AACrE,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;AAexE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAQ1E;AAED,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,IAAI;IACxD,OAAO,IAAI,IAAI,CAAC;IAChB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACnC,qDAAqD;IACrD,YAAY,IAAI,SAAS,MAAM,EAAE,CAAC;IAClC,wEAAwE;IACxE,QAAQ,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAC9C,oFAAoF;IACpF,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,mDAAmD;IACnD,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,2GAA2G;IAC3G,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvF,8EAA8E;IAC9E,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IAC/C;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACtG,qGAAqG;IACrG,gBAAgB,IAAI,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACtD,sFAAsF;IACtF,eAAe,IAAI,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;CACnD,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,EAAE,QAAQ,EAAE,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,CAuK1G"}
|
package/dist/_catalog-store.js
CHANGED
|
@@ -1,74 +1,81 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { CatalogEntry as
|
|
3
|
-
import { LinguaMissingLocaleError as
|
|
1
|
+
import { devOnly as e, warn as t } from "./_dev.js";
|
|
2
|
+
import { CatalogEntry as n, flattenStrings as r } from "./_catalog.js";
|
|
3
|
+
import { LinguaMissingLocaleError as i, checkDisposed as a, checkDisposedAsync as o } from "./errors.js";
|
|
4
4
|
//#region src/_catalog-store.ts
|
|
5
|
-
function
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}, p = (e) => {
|
|
10
|
-
let t = a(o());
|
|
11
|
-
if (t) return t;
|
|
12
|
-
if (s.has(e)) return Promise.resolve();
|
|
13
|
-
let i = c.get(e);
|
|
14
|
-
if (!i) return Promise.reject(new r(`Missing locale source for "${e}".`));
|
|
15
|
-
let u = l.get(e);
|
|
16
|
-
if (u) return u;
|
|
17
|
-
let p = i().then((t) => {
|
|
18
|
-
c.get(e) === i && (c.delete(e), f(e, n(t)), l.delete(e), d?.(e));
|
|
19
|
-
}, (t) => {
|
|
20
|
-
throw c.get(e) === i && l.delete(e), t;
|
|
5
|
+
function s(n, r) {
|
|
6
|
+
e(() => {
|
|
7
|
+
import("./validate.js").then(({ validateCatalog: e }) => {
|
|
8
|
+
for (let i of e(r, n)) t(`catalog('${n}'): missing plural form '${i.form}' for key '${i.key}'.`);
|
|
21
9
|
});
|
|
22
|
-
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
function c(e) {
|
|
13
|
+
let c = /* @__PURE__ */ new Map(), l = /* @__PURE__ */ new Map(), u = /* @__PURE__ */ new Map(), d = /* @__PURE__ */ new Set(), f, p = (e, t) => {
|
|
14
|
+
let r = c.get(e);
|
|
15
|
+
r || (r = new n(), c.set(e, r)), r.setAll(t);
|
|
16
|
+
}, m = (t) => {
|
|
17
|
+
let n = o(e());
|
|
18
|
+
if (n) return n;
|
|
19
|
+
if (c.has(t)) return Promise.resolve();
|
|
20
|
+
let a = l.get(t);
|
|
21
|
+
if (!a) return Promise.reject(new i(`Missing locale source for "${t}".`));
|
|
22
|
+
let d = u.get(t);
|
|
23
|
+
if (d) return d;
|
|
24
|
+
let m = a().then((e) => {
|
|
25
|
+
l.get(t) === a && (l.delete(t), p(t, r(e)), s(t, e), u.delete(t), f?.(t));
|
|
26
|
+
}, (e) => {
|
|
27
|
+
throw l.get(t) === a && u.delete(t), e;
|
|
28
|
+
});
|
|
29
|
+
return u.set(t, m), m;
|
|
23
30
|
};
|
|
24
31
|
return {
|
|
25
|
-
get catalogs() {
|
|
26
|
-
return s;
|
|
27
|
-
},
|
|
28
32
|
dispose() {
|
|
29
|
-
|
|
33
|
+
c.clear(), l.clear(), u.clear(), d.clear(), f = void 0;
|
|
30
34
|
},
|
|
31
35
|
isLoaded(e) {
|
|
32
|
-
return
|
|
36
|
+
return c.has(e);
|
|
33
37
|
},
|
|
34
38
|
isRegistered(e) {
|
|
35
|
-
return
|
|
39
|
+
return d.has(e);
|
|
36
40
|
},
|
|
37
41
|
knownLocales() {
|
|
38
|
-
return [...
|
|
42
|
+
return [...d];
|
|
39
43
|
},
|
|
40
44
|
get onChange() {
|
|
41
|
-
return
|
|
45
|
+
return f;
|
|
42
46
|
},
|
|
43
47
|
set onChange(e) {
|
|
44
|
-
|
|
45
|
-
},
|
|
46
|
-
patch(e, t) {
|
|
47
|
-
return a(o()) || (c.has(e) ? p(e).then(() => {
|
|
48
|
-
o() || (f(e, n(t)), u.add(e), d?.(e));
|
|
49
|
-
}) : (f(e, n(t)), u.add(e), d?.(e), Promise.resolve()));
|
|
48
|
+
f = e;
|
|
50
49
|
},
|
|
51
|
-
|
|
52
|
-
return
|
|
50
|
+
patch(t, n) {
|
|
51
|
+
return o(e()) || (l.has(t) ? m(t).then(() => {
|
|
52
|
+
e() || (p(t, r(n)), d.add(t), f?.(t));
|
|
53
|
+
}) : (p(t, r(n)), d.add(t), f?.(t), Promise.resolve()));
|
|
53
54
|
},
|
|
54
|
-
preload:
|
|
55
|
-
register(
|
|
56
|
-
|
|
57
|
-
let
|
|
58
|
-
|
|
59
|
-
let
|
|
60
|
-
return typeof
|
|
55
|
+
preload: m,
|
|
56
|
+
register(n, i, o) {
|
|
57
|
+
a(e()), u.delete(n), d.add(n);
|
|
58
|
+
let h = o.clearLocale(n);
|
|
59
|
+
h.length > 0 && t(`register('${n}') cleared loaded namespace markers for: ${h.map((e) => `'${e}'`).join(", ")}. Call loadNamespace() again to reload.`);
|
|
60
|
+
let g;
|
|
61
|
+
return typeof i == "function" ? (l.set(n, i), c.delete(n), g = m(n)) : (l.delete(n), c.delete(n), p(n, r(i)), s(n, i), g = Promise.resolve(), f?.(n)), g;
|
|
61
62
|
},
|
|
62
63
|
resolve(e) {
|
|
63
|
-
return
|
|
64
|
+
return c.get(e);
|
|
64
65
|
},
|
|
65
66
|
seedFrom(e, t) {
|
|
66
|
-
for (let [t, n] of e)
|
|
67
|
-
for (let [e, n] of t)
|
|
67
|
+
for (let [t, n] of e) c.set(t, n.clone()), d.add(t);
|
|
68
|
+
for (let [e, n] of t) l.set(e, n), d.add(e);
|
|
69
|
+
},
|
|
70
|
+
snapshotCatalogs() {
|
|
71
|
+
return c;
|
|
72
|
+
},
|
|
73
|
+
snapshotLoaders() {
|
|
74
|
+
return l;
|
|
68
75
|
}
|
|
69
76
|
};
|
|
70
77
|
}
|
|
71
78
|
//#endregion
|
|
72
|
-
export {
|
|
79
|
+
export { c as createCatalogStore, s as validateCatalogInDev };
|
|
73
80
|
|
|
74
81
|
//# sourceMappingURL=_catalog-store.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_catalog-store.js","names":[],"sources":["../src/_catalog-store.ts"],"sourcesContent":["// Internal — not part of the public API.\n// Owns all locale → CatalogEntry state, loaders, and in-flight loading tasks.\n\nimport type { NamespaceStore } from './_namespace-store';\n\nimport { CatalogEntry, type Messages, flattenStrings } from './_catalog';\nimport { warn } from './_dev';\nimport { LinguaMissingLocaleError, checkDisposed, checkDisposedAsync } from './errors';\n\nexport type Locale = string;\nexport type Loader<M extends Messages = Messages> = () => Promise<M>;\nexport type LocaleSource<M extends Messages = Messages> = M | Loader<M>;\n\nexport type CatalogStore<M extends Messages = Messages> = {\n /** Direct access to the raw catalogs map (for SSR serialization). */\n readonly catalogs: ReadonlyMap<Locale, CatalogEntry>;\n dispose(): void;\n isLoaded(loc: Locale): boolean;\n isRegistered(loc: Locale): boolean;\n /** All registered locale keys in insertion order. */\n knownLocales(): readonly Locale[];\n /** Callback invoked whenever a catalog changes (drives bump/notify). */\n onChange: ((loc: Locale) => void) | undefined;\n /** Patch a locale catalog with additional flat messages (used by loadNamespace). */\n patch(loc: Locale, messages: Messages): Promise<void>;\n /** Direct access to the pending loaders map (for fork propagation). */\n readonly pendingLoaders: ReadonlyMap<Locale, Loader<M>>;\n /** Pre-load a locale catalog without switching. */\n preload(loc: Locale): Promise<void>;\n /** Register (or replace) a locale's source. Returns a Promise that resolves when the catalog is loaded. */\n register(loc: Locale, source: LocaleSource<M>, nsStore: NamespaceStore): Promise<void>;\n /** Resolve a compiled CatalogEntry for a locale (undefined if not loaded). */\n resolve(loc: Locale): CatalogEntry | undefined;\n /**\n * Seed the store with pre-compiled entries from a parent fork.\n * Called during fork() — compiled templates are shared by reference.\n */\n seedFrom(entries: ReadonlyMap<Locale, CatalogEntry>, loaderMap: ReadonlyMap<Locale, Loader<M>>): void;\n};\n\nexport function createCatalogStore<M extends Messages = Messages>(disposed: () => boolean): CatalogStore<M> {\n const catalogs = new Map<Locale, CatalogEntry>();\n const loaders = new Map<Locale, Loader<M>>();\n const loadingTasks = new Map<Locale, Promise<void>>();\n const known = new Set<Locale>();\n\n let onChange: ((loc: Locale) => void) | undefined;\n\n const registerRaw = (normalized: Locale, flat: Map<string, string>): void => {\n let entry = catalogs.get(normalized);\n\n if (!entry) {\n entry = new CatalogEntry();\n catalogs.set(normalized, entry);\n }\n\n entry.setAll(flat);\n };\n\n const preload = (loc: Locale): Promise<void> => {\n const early = checkDisposedAsync(disposed());\n\n if (early) return early;\n\n if (catalogs.has(loc)) return Promise.resolve();\n\n const loader = loaders.get(loc);\n\n if (!loader) return Promise.reject(new LinguaMissingLocaleError(`Missing locale source for \"${loc}\".`));\n\n const existing = loadingTasks.get(loc);\n\n if (existing) return existing;\n\n const task = loader().then(\n (messages) => {\n if (loaders.get(loc) !== loader) return;\n\n loaders.delete(loc);\n registerRaw(loc, flattenStrings(messages));\n loadingTasks.delete(loc);\n onChange?.(loc);\n },\n (error: unknown) => {\n if (loaders.get(loc) === loader) loadingTasks.delete(loc);\n\n throw error;\n },\n );\n\n loadingTasks.set(loc, task);\n\n return task;\n };\n\n return {\n get catalogs() {\n return catalogs as ReadonlyMap<Locale, CatalogEntry>;\n },\n\n dispose() {\n catalogs.clear();\n loaders.clear();\n loadingTasks.clear();\n known.clear();\n onChange = undefined;\n },\n\n isLoaded(loc) {\n return catalogs.has(loc);\n },\n\n isRegistered(loc) {\n return known.has(loc);\n },\n\n knownLocales() {\n return [...known];\n },\n\n get onChange() {\n return onChange;\n },\n\n set onChange(fn: ((loc: Locale) => void) | undefined) {\n onChange = fn;\n },\n\n patch(loc, messages) {\n const early = checkDisposedAsync(disposed());\n\n if (early) return early;\n\n if (loaders.has(loc)) {\n return preload(loc).then(() => {\n if (disposed()) return;\n\n registerRaw(loc, flattenStrings(messages));\n known.add(loc);\n onChange?.(loc);\n });\n }\n\n registerRaw(loc, flattenStrings(messages));\n known.add(loc);\n onChange?.(loc);\n\n return Promise.resolve();\n },\n\n get pendingLoaders() {\n return loaders as ReadonlyMap<Locale, Loader<M>>;\n },\n\n preload,\n\n register(loc, source, nsStore) {\n checkDisposed(disposed());\n\n loadingTasks.delete(loc);\n known.add(loc);\n\n // Clear namespace loaded-markers for this locale so namespaces can be re-applied\n // after catalog replacement.\n const clearedNs = nsStore.clearLocale(loc);\n\n if (clearedNs.length > 0) {\n warn(\n `register('${loc}') cleared loaded namespace markers for: ${clearedNs.map((ns) => `'${ns}'`).join(', ')}. Call loadNamespace() again to reload.`,\n );\n }\n\n let loadPromise: Promise<void>;\n\n if (typeof source === 'function') {\n loaders.set(loc, source as Loader<M>);\n catalogs.delete(loc);\n loadPromise = preload(loc);\n } else {\n loaders.delete(loc);\n catalogs.delete(loc);\n registerRaw(loc, flattenStrings(source as M));\n loadPromise = Promise.resolve();\n onChange?.(loc);\n }\n\n return loadPromise;\n },\n\n resolve(loc) {\n return catalogs.get(loc);\n },\n\n seedFrom(entries, loaderMap) {\n for (const [loc, entry] of entries) {\n catalogs.set(loc, entry.clone());\n known.add(loc);\n }\n\n for (const [loc, loader] of loaderMap) {\n loaders.set(loc, loader);\n known.add(loc);\n }\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"_catalog-store.js","names":[],"sources":["../src/_catalog-store.ts"],"sourcesContent":["// Internal — not part of the public API.\n// Owns all locale → CatalogEntry state, loaders, and in-flight loading tasks.\n\nimport type { NamespaceStore } from './_namespace-store';\n\nimport { CatalogEntry, type Messages, flattenStrings } from './_catalog';\nimport { devOnly, warn } from './_dev';\nimport { LinguaMissingLocaleError, checkDisposed, checkDisposedAsync } from './errors';\n\nexport type Locale = string;\nexport type Loader<M extends Messages = Messages> = () => Promise<M>;\nexport type LocaleSource<M extends Messages = Messages> = M | Loader<M>;\n\n// Dev-only feedback for the same catalog-authoring mistakes `validateCatalog()` (the CI/build\n// tool at `@vielzeug/lingua/validate`) already catches — most projects never wire that up, so\n// this gives the same signal for free, in the console the author is already watching, the\n// moment a full catalog becomes available: at `createI18n({ catalogs })` construction, via\n// `register()`, or once an async loader resolves. Not run for `patch()` — a namespace overlay is\n// expected to add only a handful of keys, not a complete plural set.\n//\n// Dynamic import, not a static one: `validate.ts`'s own docs promise it stays out of production\n// bundles. A static `import { validateCatalog } from './validate'` here would pull it into every\n// consumer's main bundle regardless of `devOnly()`'s runtime check — bundlers only eliminate\n// that dead branch if the consumer's own build defines `__LINGUA_PROD__` as a literal. The\n// dynamic import keeps validate.ts a genuinely separate, lazily-fetched chunk that's never\n// requested at all when `isDev` is false, no consumer bundler config required.\nexport function validateCatalogInDev(loc: Locale, messages: Messages): void {\n devOnly(() => {\n void import('./validate').then(({ validateCatalog }) => {\n for (const w of validateCatalog(messages, loc)) {\n warn(`catalog('${loc}'): missing plural form '${w.form}' for key '${w.key}'.`);\n }\n });\n });\n}\n\nexport type CatalogStore<M extends Messages = Messages> = {\n dispose(): void;\n isLoaded(loc: Locale): boolean;\n isRegistered(loc: Locale): boolean;\n /** All registered locale keys in insertion order. */\n knownLocales(): readonly Locale[];\n /** Callback invoked whenever a catalog changes (drives bump/notify). */\n onChange: ((loc: Locale) => void) | undefined;\n /** Patch a locale catalog with additional flat messages (used by loadNamespace). */\n patch(loc: Locale, messages: Messages): Promise<void>;\n /** Pre-load a locale catalog without switching. */\n preload(loc: Locale): Promise<void>;\n /** Register (or replace) a locale's source. Returns a Promise that resolves when the catalog is loaded. */\n register(loc: Locale, source: LocaleSource<M>, nsStore: NamespaceStore): Promise<void>;\n /** Resolve a compiled CatalogEntry for a locale (undefined if not loaded). */\n resolve(loc: Locale): CatalogEntry | undefined;\n /**\n * Seed the store with pre-compiled entries from a parent fork.\n * Called during fork() — compiled templates are shared by reference.\n */\n seedFrom(entries: ReadonlyMap<Locale, CatalogEntry>, loaderMap: ReadonlyMap<Locale, Loader<M>>): void;\n /** Snapshot of the raw catalogs map — used for SSR serialization (`getState()`) and fork seeding. */\n snapshotCatalogs(): ReadonlyMap<Locale, CatalogEntry>;\n /** Snapshot of the pending (not-yet-resolved) loaders map — used for fork seeding. */\n snapshotLoaders(): ReadonlyMap<Locale, Loader<M>>;\n};\n\nexport function createCatalogStore<M extends Messages = Messages>(disposed: () => boolean): CatalogStore<M> {\n const catalogs = new Map<Locale, CatalogEntry>();\n const loaders = new Map<Locale, Loader<M>>();\n const loadingTasks = new Map<Locale, Promise<void>>();\n const known = new Set<Locale>();\n\n let onChange: ((loc: Locale) => void) | undefined;\n\n const registerRaw = (normalized: Locale, flat: Map<string, string>): void => {\n let entry = catalogs.get(normalized);\n\n if (!entry) {\n entry = new CatalogEntry();\n catalogs.set(normalized, entry);\n }\n\n entry.setAll(flat);\n };\n\n const preload = (loc: Locale): Promise<void> => {\n const early = checkDisposedAsync(disposed());\n\n if (early) return early;\n\n if (catalogs.has(loc)) return Promise.resolve();\n\n const loader = loaders.get(loc);\n\n if (!loader) return Promise.reject(new LinguaMissingLocaleError(`Missing locale source for \"${loc}\".`));\n\n const existing = loadingTasks.get(loc);\n\n if (existing) return existing;\n\n const task = loader().then(\n (messages) => {\n if (loaders.get(loc) !== loader) return;\n\n loaders.delete(loc);\n registerRaw(loc, flattenStrings(messages));\n validateCatalogInDev(loc, messages);\n loadingTasks.delete(loc);\n onChange?.(loc);\n },\n (error: unknown) => {\n if (loaders.get(loc) === loader) loadingTasks.delete(loc);\n\n throw error;\n },\n );\n\n loadingTasks.set(loc, task);\n\n return task;\n };\n\n return {\n dispose() {\n catalogs.clear();\n loaders.clear();\n loadingTasks.clear();\n known.clear();\n onChange = undefined;\n },\n\n isLoaded(loc) {\n return catalogs.has(loc);\n },\n\n isRegistered(loc) {\n return known.has(loc);\n },\n\n knownLocales() {\n return [...known];\n },\n\n get onChange() {\n return onChange;\n },\n\n set onChange(fn: ((loc: Locale) => void) | undefined) {\n onChange = fn;\n },\n\n patch(loc, messages) {\n const early = checkDisposedAsync(disposed());\n\n if (early) return early;\n\n if (loaders.has(loc)) {\n return preload(loc).then(() => {\n if (disposed()) return;\n\n registerRaw(loc, flattenStrings(messages));\n known.add(loc);\n onChange?.(loc);\n });\n }\n\n registerRaw(loc, flattenStrings(messages));\n known.add(loc);\n onChange?.(loc);\n\n return Promise.resolve();\n },\n\n preload,\n\n register(loc, source, nsStore) {\n checkDisposed(disposed());\n\n loadingTasks.delete(loc);\n known.add(loc);\n\n // Clear namespace loaded-markers for this locale so namespaces can be re-applied\n // after catalog replacement.\n const clearedNs = nsStore.clearLocale(loc);\n\n if (clearedNs.length > 0) {\n warn(\n `register('${loc}') cleared loaded namespace markers for: ${clearedNs.map((ns) => `'${ns}'`).join(', ')}. Call loadNamespace() again to reload.`,\n );\n }\n\n let loadPromise: Promise<void>;\n\n if (typeof source === 'function') {\n loaders.set(loc, source as Loader<M>);\n catalogs.delete(loc);\n loadPromise = preload(loc);\n } else {\n loaders.delete(loc);\n catalogs.delete(loc);\n registerRaw(loc, flattenStrings(source as M));\n validateCatalogInDev(loc, source as M);\n loadPromise = Promise.resolve();\n onChange?.(loc);\n }\n\n return loadPromise;\n },\n\n resolve(loc) {\n return catalogs.get(loc);\n },\n\n seedFrom(entries, loaderMap) {\n for (const [loc, entry] of entries) {\n catalogs.set(loc, entry.clone());\n known.add(loc);\n }\n\n for (const [loc, loader] of loaderMap) {\n loaders.set(loc, loader);\n known.add(loc);\n }\n },\n\n snapshotCatalogs() {\n return catalogs as ReadonlyMap<Locale, CatalogEntry>;\n },\n\n snapshotLoaders() {\n return loaders as ReadonlyMap<Locale, Loader<M>>;\n },\n };\n}\n"],"mappings":"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"}
|
package/dist/_dev.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var e=!globalThis.__LINGUA_PROD__;function t(t){e&&console.warn(`[@vielzeug/lingua] ${t}`)}function n(t,...n){e&&console.error(`[@vielzeug/lingua] ${t}`,...n)}exports.error=n,exports.warn=t;
|
|
1
|
+
var e=!globalThis.__LINGUA_PROD__;function t(t){e&&console.warn(`[@vielzeug/lingua] ${t}`)}function n(t,...n){e&&console.error(`[@vielzeug/lingua] ${t}`,...n)}function r(t){e&&t()}exports.devOnly=r,exports.error=n,exports.warn=t;
|
|
2
2
|
//# sourceMappingURL=_dev.cjs.map
|
package/dist/_dev.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_dev.cjs","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __LINGUA_PROD__?: boolean }).__LINGUA_PROD__;\n\n/** @internal @security Messages may include user-supplied data. */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/lingua] ${msg}`);\n}\n\n/** @internal */\nexport function error(msg: string, ...args: unknown[]): void {\n if (isDev) console.error(`[@vielzeug/lingua] ${msg}`, ...args);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n"],"mappings":"AAAA,IAAM,EAAQ,CAAE,WAA6C,gBAG7D,SAAgB,EAAK,EAAmB,CAClC,GAAO,QAAQ,KAAK,sBAAsB,GAAK,CACrD,CAGA,SAAgB,EAAM,EAAa,GAAG,EAAuB,CACvD,GAAO,QAAQ,MAAM,sBAAsB,IAAO,GAAG,CAAI,CAC/D"}
|
|
1
|
+
{"version":3,"file":"_dev.cjs","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __LINGUA_PROD__?: boolean }).__LINGUA_PROD__;\n\n/** @internal @security Messages may include user-supplied data. */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/lingua] ${msg}`);\n}\n\n/** @internal */\nexport function error(msg: string, ...args: unknown[]): void {\n if (isDev) console.error(`[@vielzeug/lingua] ${msg}`, ...args);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n"],"mappings":"AAAA,IAAM,EAAQ,CAAE,WAA6C,gBAG7D,SAAgB,EAAK,EAAmB,CAClC,GAAO,QAAQ,KAAK,sBAAsB,GAAK,CACrD,CAGA,SAAgB,EAAM,EAAa,GAAG,EAAuB,CACvD,GAAO,QAAQ,MAAM,sBAAsB,IAAO,GAAG,CAAI,CAC/D,CAGA,SAAgB,EAAQ,EAAsB,CACxC,GAAO,EAAG,CAChB"}
|
package/dist/_dev.js
CHANGED
|
@@ -6,7 +6,10 @@ function t(t) {
|
|
|
6
6
|
function n(t, ...n) {
|
|
7
7
|
e && console.error(`[@vielzeug/lingua] ${t}`, ...n);
|
|
8
8
|
}
|
|
9
|
+
function r(t) {
|
|
10
|
+
e && t();
|
|
11
|
+
}
|
|
9
12
|
//#endregion
|
|
10
|
-
export { n as error, t as warn };
|
|
13
|
+
export { r as devOnly, n as error, t as warn };
|
|
11
14
|
|
|
12
15
|
//# sourceMappingURL=_dev.js.map
|
package/dist/_dev.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_dev.js","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __LINGUA_PROD__?: boolean }).__LINGUA_PROD__;\n\n/** @internal @security Messages may include user-supplied data. */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/lingua] ${msg}`);\n}\n\n/** @internal */\nexport function error(msg: string, ...args: unknown[]): void {\n if (isDev) console.error(`[@vielzeug/lingua] ${msg}`, ...args);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n"],"mappings":";AAAA,IAAM,IAAQ,CAAE,WAA6C;AAG7D,SAAgB,EAAK,GAAmB;CACtC,AAAI,KAAO,QAAQ,KAAK,sBAAsB,GAAK;AACrD;AAGA,SAAgB,EAAM,GAAa,GAAG,GAAuB;CAC3D,AAAI,KAAO,QAAQ,MAAM,sBAAsB,KAAO,GAAG,CAAI;AAC/D"}
|
|
1
|
+
{"version":3,"file":"_dev.js","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __LINGUA_PROD__?: boolean }).__LINGUA_PROD__;\n\n/** @internal @security Messages may include user-supplied data. */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/lingua] ${msg}`);\n}\n\n/** @internal */\nexport function error(msg: string, ...args: unknown[]): void {\n if (isDev) console.error(`[@vielzeug/lingua] ${msg}`, ...args);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n"],"mappings":";AAAA,IAAM,IAAQ,CAAE,WAA6C;AAG7D,SAAgB,EAAK,GAAmB;CACtC,AAAI,KAAO,QAAQ,KAAK,sBAAsB,GAAK;AACrD;AAGA,SAAgB,EAAM,GAAa,GAAG,GAAuB;CAC3D,AAAI,KAAO,QAAQ,MAAM,sBAAsB,KAAO,GAAG,CAAI;AAC/D;AAGA,SAAgB,EAAQ,GAAsB;CAC5C,AAAI,KAAO,EAAG;AAChB"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./_dev.cjs"),t=require("./template.cjs"),n=require("./errors.cjs"),r=require("./_chain.cjs");function i(e,t){for(let n of e.chain){let r=e.catalogStore.resolve(n)?.get(t);if(r!==void 0)return r}}function a(e,t){for(let n of e.chain)if(e.catalogStore.resolve(n)?.prefixes.has(t))return!0;return!1}function o(e,t){return i(e,t)!==void 0||a(e,t)}function s(e,n,r,i){return t.renderTemplate(r.compiled,i,n,e.locale,e.onMissingVar)}function c(t,n,r){let o=i(t,n);return o?s(t,n,o,r):(e.devOnly(()=>{a(t,n)&&e.warn(`t('${n}') resolved to the missing-key fallback, but '${n}' exists as a plural branch — use tp('${n}', count) instead.`)}),t.onMissingKey(n,t.locale))}function l(e,t,n,r){let i=[];return!r&&n===0&&i.push(`${e}.zero`),(t!==`zero`||r)&&i.push(`${e}.${t}`),t!==`other`&&i.push(`${e}.other`),i}function u(e,t,i,a){if(!Number.isFinite(i))throw new n.LinguaInvalidCountError("`count` must be a finite number.");let o=a?.vars,c=a?.ordinal??!1;if(o&&Object.hasOwn(o,`count`))throw new n.LinguaCountInVarsError("`tp` does not allow `vars.count`; `count` is injected automatically.");let u=o?{count:i,...o}:{count:i};for(let n of e.chain){let a=e.catalogStore.resolve(n);if(!a)continue;let o=l(t,r.selectPluralForm(n,i,c,e.caches),i,c);for(let t of o){let n=a.get(t);if(n!==void 0)return s(e,t,n,u)}}return e.onMissingKey(t,e.locale)}exports.hasKey=o,exports.translate=c,exports.translatePlural=u;
|
|
2
|
+
//# sourceMappingURL=_translate.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_translate.cjs","names":[],"sources":["../src/_translate.ts"],"sourcesContent":["// Internal — not part of the public API.\n// The translation algorithm: fallback-chain key lookup, plural-form selection, and\n// interpolation. Extracted out of i18n.ts's factory closure so it has its own unit-test seam\n// independent of the full `createI18n()` setup — mirrors `_catalog-store.ts`/`_namespace-store.ts`,\n// which already got this treatment; this was the one piece of comparable complexity left inline.\n\nimport type { CatalogEntry, CatalogEntryData } from './_catalog';\nimport type { LocaleCaches } from './_chain';\nimport type { Locale, TpOptions, TranslateVars } from './i18n-types';\n\nimport { selectPluralForm } from './_chain';\nimport { devOnly, warn } from './_dev';\nimport { LinguaCountInVarsError, LinguaInvalidCountError } from './errors';\nimport { renderTemplate } from './template';\n\n/** Only the read-side of `CatalogStore` this module needs — avoids importing its `M` generic. */\nexport type CatalogResolver = { resolve(loc: Locale): CatalogEntry | undefined };\n\nexport type TranslateContext = {\n readonly caches: LocaleCaches;\n readonly catalogStore: CatalogResolver;\n /** Active fallback chain, most-specific locale first (e.g. `['en-US', 'en']`). */\n readonly chain: readonly Locale[];\n readonly locale: Locale;\n readonly onMissingKey: (key: string, locale: Locale) => string;\n readonly onMissingVar: (varName: string, key: string, locale: Locale) => string;\n};\n\n// Single-pass entry lookup across the active fallback chain.\nfunction findEntry(ctx: TranslateContext, key: string): CatalogEntryData | undefined {\n for (const candidate of ctx.chain) {\n const found = ctx.catalogStore.resolve(candidate)?.get(key);\n\n if (found !== undefined) return found;\n }\n\n return undefined;\n}\n\n// True if `base` exists as a plural branch prefix (e.g. `items` when only `items.one`/\n// `items.other` are registered) anywhere in the fallback chain. Shared by `hasKey()` and\n// `translate()`'s dev-mode miss diagnostic below — both need to distinguish \"key genuinely\n// doesn't exist\" from \"key exists, but only as a plural branch — you want tp(), not t()\".\nfunction isPluralBranch(ctx: TranslateContext, base: string): boolean {\n for (const candidate of ctx.chain) {\n const catalog = ctx.catalogStore.resolve(candidate);\n\n if (catalog?.prefixes.has(base)) return true;\n }\n\n return false;\n}\n\n/** True if `base` exists as a leaf key or a plural branch prefix, anywhere in the fallback chain. */\nexport function hasKey(ctx: TranslateContext, base: string): boolean {\n return findEntry(ctx, base) !== undefined || isPluralBranch(ctx, base);\n}\n\nfunction interpolate(\n ctx: TranslateContext,\n key: string,\n found: CatalogEntryData,\n vars: TranslateVars | undefined,\n): string {\n return renderTemplate(found.compiled, vars, key, ctx.locale, ctx.onMissingVar);\n}\n\nexport function translate(ctx: TranslateContext, key: string, vars?: TranslateVars): string {\n const found = findEntry(ctx, key);\n\n if (!found) {\n // has(key) returns true for a plural-branch-only key (no leaf entry), but t() can't resolve\n // one — the common `if (i18n.has(key)) i18n.t(key)` pattern silently returns the raw key in\n // that case, indistinguishable from a genuinely missing key without this warning.\n devOnly(() => {\n if (isPluralBranch(ctx, key)) {\n warn(\n `t('${key}') resolved to the missing-key fallback, but '${key}' exists as a plural branch — use tp('${key}', count) instead.`,\n );\n }\n });\n\n return ctx.onMissingKey(key, ctx.locale);\n }\n\n return interpolate(ctx, key, found, vars);\n}\n\n// Plural key priority: cardinal zero tries `.zero` override first, then the CLDR form, then\n// `.other` as the final fallback. Ordinal / non-zero skips the `.zero` special case.\nfunction pluralKeyPriority(base: string, form: string, count: number, ordinal: boolean): string[] {\n const keys: string[] = [];\n\n if (!ordinal && count === 0) keys.push(`${base}.zero`);\n\n if (form !== 'zero' || ordinal) keys.push(`${base}.${form}`);\n\n if (form !== 'other') keys.push(`${base}.other`);\n\n return keys;\n}\n\nexport function translatePlural(ctx: TranslateContext, key: string, count: number, options?: TpOptions): string {\n if (!Number.isFinite(count)) {\n throw new LinguaInvalidCountError('`count` must be a finite number.');\n }\n\n const vars = options?.vars;\n const ordinal = options?.ordinal ?? false;\n\n if (vars && Object.hasOwn(vars, 'count')) {\n throw new LinguaCountInVarsError('`tp` does not allow `vars.count`; `count` is injected automatically.');\n }\n\n const mergedVars = vars ? { count, ...vars } : { count };\n\n // Walk the fallback chain locale-by-locale, selecting CLDR plural form using each locale's\n // own rules. This ensures cross-locale fallbacks produce grammatically correct forms.\n for (const candidate of ctx.chain) {\n const catalog = ctx.catalogStore.resolve(candidate);\n\n if (!catalog) continue;\n\n const form = selectPluralForm(candidate, count, ordinal, ctx.caches);\n const keys = pluralKeyPriority(key, form, count, ordinal);\n\n for (const k of keys) {\n const found = catalog.get(k);\n\n if (found !== undefined) return interpolate(ctx, k, found, mergedVars);\n }\n }\n\n return ctx.onMissingKey(key, ctx.locale);\n}\n"],"mappings":"8GA6BA,SAAS,EAAU,EAAuB,EAA2C,CACnF,IAAK,IAAM,KAAa,EAAI,MAAO,CACjC,IAAM,EAAQ,EAAI,aAAa,QAAQ,CAAS,CAAC,EAAE,IAAI,CAAG,EAE1D,GAAI,IAAU,IAAA,GAAW,OAAO,CAClC,CAGF,CAMA,SAAS,EAAe,EAAuB,EAAuB,CACpE,IAAK,IAAM,KAAa,EAAI,MAG1B,GAFgB,EAAI,aAAa,QAAQ,CAErC,CAAA,EAAS,SAAS,IAAI,CAAI,EAAG,MAAO,GAG1C,MAAO,EACT,CAGA,SAAgB,EAAO,EAAuB,EAAuB,CACnE,OAAO,EAAU,EAAK,CAAI,IAAM,IAAA,IAAa,EAAe,EAAK,CAAI,CACvE,CAEA,SAAS,EACP,EACA,EACA,EACA,EACQ,CACR,OAAO,EAAA,eAAe,EAAM,SAAU,EAAM,EAAK,EAAI,OAAQ,EAAI,YAAY,CAC/E,CAEA,SAAgB,EAAU,EAAuB,EAAa,EAA8B,CAC1F,IAAM,EAAQ,EAAU,EAAK,CAAG,EAiBhC,OAfK,EAeE,EAAY,EAAK,EAAK,EAAO,CAAI,GAXtC,EAAA,YAAc,CACR,EAAe,EAAK,CAAG,GACzB,EAAA,KACE,MAAM,EAAI,gDAAgD,EAAI,wCAAwC,EAAI,mBAC5G,CAEJ,CAAC,EAEM,EAAI,aAAa,EAAK,EAAI,MAAM,EAI3C,CAIA,SAAS,EAAkB,EAAc,EAAc,EAAe,EAA4B,CAChG,IAAM,EAAiB,CAAC,EAQxB,MANI,CAAC,GAAW,IAAU,GAAG,EAAK,KAAK,GAAG,EAAK,MAAM,GAEjD,IAAS,QAAU,IAAS,EAAK,KAAK,GAAG,EAAK,GAAG,GAAM,EAEvD,IAAS,SAAS,EAAK,KAAK,GAAG,EAAK,OAAO,EAExC,CACT,CAEA,SAAgB,EAAgB,EAAuB,EAAa,EAAe,EAA6B,CAC9G,GAAI,CAAC,OAAO,SAAS,CAAK,EACxB,MAAM,IAAI,EAAA,wBAAwB,kCAAkC,EAGtE,IAAM,EAAO,GAAS,KAChB,EAAU,GAAS,SAAW,GAEpC,GAAI,GAAQ,OAAO,OAAO,EAAM,OAAO,EACrC,MAAM,IAAI,EAAA,uBAAuB,sEAAsE,EAGzG,IAAM,EAAa,EAAO,CAAE,QAAO,GAAG,CAAK,EAAI,CAAE,OAAM,EAIvD,IAAK,IAAM,KAAa,EAAI,MAAO,CACjC,IAAM,EAAU,EAAI,aAAa,QAAQ,CAAS,EAElD,GAAI,CAAC,EAAS,SAGd,IAAM,EAAO,EAAkB,EADlB,EAAA,iBAAiB,EAAW,EAAO,EAAS,EAAI,MACzB,EAAM,EAAO,CAAO,EAExD,IAAK,IAAM,KAAK,EAAM,CACpB,IAAM,EAAQ,EAAQ,IAAI,CAAC,EAE3B,GAAI,IAAU,IAAA,GAAW,OAAO,EAAY,EAAK,EAAG,EAAO,CAAU,CACvE,CACF,CAEA,OAAO,EAAI,aAAa,EAAK,EAAI,MAAM,CACzC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CatalogEntry } from './_catalog';
|
|
2
|
+
import type { LocaleCaches } from './_chain';
|
|
3
|
+
import type { Locale, TpOptions, TranslateVars } from './i18n-types';
|
|
4
|
+
/** Only the read-side of `CatalogStore` this module needs — avoids importing its `M` generic. */
|
|
5
|
+
export type CatalogResolver = {
|
|
6
|
+
resolve(loc: Locale): CatalogEntry | undefined;
|
|
7
|
+
};
|
|
8
|
+
export type TranslateContext = {
|
|
9
|
+
readonly caches: LocaleCaches;
|
|
10
|
+
readonly catalogStore: CatalogResolver;
|
|
11
|
+
/** Active fallback chain, most-specific locale first (e.g. `['en-US', 'en']`). */
|
|
12
|
+
readonly chain: readonly Locale[];
|
|
13
|
+
readonly locale: Locale;
|
|
14
|
+
readonly onMissingKey: (key: string, locale: Locale) => string;
|
|
15
|
+
readonly onMissingVar: (varName: string, key: string, locale: Locale) => string;
|
|
16
|
+
};
|
|
17
|
+
/** True if `base` exists as a leaf key or a plural branch prefix, anywhere in the fallback chain. */
|
|
18
|
+
export declare function hasKey(ctx: TranslateContext, base: string): boolean;
|
|
19
|
+
export declare function translate(ctx: TranslateContext, key: string, vars?: TranslateVars): string;
|
|
20
|
+
export declare function translatePlural(ctx: TranslateContext, key: string, count: number, options?: TpOptions): string;
|
|
21
|
+
//# sourceMappingURL=_translate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_translate.d.ts","sourceRoot":"","sources":["../src/_translate.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,YAAY,CAAC;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAOrE,iGAAiG;AACjG,MAAM,MAAM,eAAe,GAAG;IAAE,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAA;CAAE,CAAC;AAEjF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,eAAe,CAAC;IACvC,kFAAkF;IAClF,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/D,QAAQ,CAAC,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;CACjF,CAAC;AA2BF,qGAAqG;AACrG,wBAAgB,MAAM,CAAC,GAAG,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnE;AAWD,wBAAgB,SAAS,CAAC,GAAG,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,MAAM,CAmB1F;AAgBD,wBAAgB,eAAe,CAAC,GAAG,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,GAAG,MAAM,CAgC9G"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { devOnly as e, warn as t } from "./_dev.js";
|
|
2
|
+
import { renderTemplate as n } from "./template.js";
|
|
3
|
+
import { LinguaCountInVarsError as r, LinguaInvalidCountError as i } from "./errors.js";
|
|
4
|
+
import { selectPluralForm as a } from "./_chain.js";
|
|
5
|
+
//#region src/_translate.ts
|
|
6
|
+
function o(e, t) {
|
|
7
|
+
for (let n of e.chain) {
|
|
8
|
+
let r = e.catalogStore.resolve(n)?.get(t);
|
|
9
|
+
if (r !== void 0) return r;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function s(e, t) {
|
|
13
|
+
for (let n of e.chain) if (e.catalogStore.resolve(n)?.prefixes.has(t)) return !0;
|
|
14
|
+
return !1;
|
|
15
|
+
}
|
|
16
|
+
function c(e, t) {
|
|
17
|
+
return o(e, t) !== void 0 || s(e, t);
|
|
18
|
+
}
|
|
19
|
+
function l(e, t, r, i) {
|
|
20
|
+
return n(r.compiled, i, t, e.locale, e.onMissingVar);
|
|
21
|
+
}
|
|
22
|
+
function u(n, r, i) {
|
|
23
|
+
let a = o(n, r);
|
|
24
|
+
return a ? l(n, r, a, i) : (e(() => {
|
|
25
|
+
s(n, r) && t(`t('${r}') resolved to the missing-key fallback, but '${r}' exists as a plural branch — use tp('${r}', count) instead.`);
|
|
26
|
+
}), n.onMissingKey(r, n.locale));
|
|
27
|
+
}
|
|
28
|
+
function d(e, t, n, r) {
|
|
29
|
+
let i = [];
|
|
30
|
+
return !r && n === 0 && i.push(`${e}.zero`), (t !== "zero" || r) && i.push(`${e}.${t}`), t !== "other" && i.push(`${e}.other`), i;
|
|
31
|
+
}
|
|
32
|
+
function f(e, t, n, o) {
|
|
33
|
+
if (!Number.isFinite(n)) throw new i("`count` must be a finite number.");
|
|
34
|
+
let s = o?.vars, c = o?.ordinal ?? !1;
|
|
35
|
+
if (s && Object.hasOwn(s, "count")) throw new r("`tp` does not allow `vars.count`; `count` is injected automatically.");
|
|
36
|
+
let u = s ? {
|
|
37
|
+
count: n,
|
|
38
|
+
...s
|
|
39
|
+
} : { count: n };
|
|
40
|
+
for (let r of e.chain) {
|
|
41
|
+
let i = e.catalogStore.resolve(r);
|
|
42
|
+
if (!i) continue;
|
|
43
|
+
let o = d(t, a(r, n, c, e.caches), n, c);
|
|
44
|
+
for (let t of o) {
|
|
45
|
+
let n = i.get(t);
|
|
46
|
+
if (n !== void 0) return l(e, t, n, u);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return e.onMissingKey(t, e.locale);
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
export { c as hasKey, u as translate, f as translatePlural };
|
|
53
|
+
|
|
54
|
+
//# sourceMappingURL=_translate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_translate.js","names":[],"sources":["../src/_translate.ts"],"sourcesContent":["// Internal — not part of the public API.\n// The translation algorithm: fallback-chain key lookup, plural-form selection, and\n// interpolation. Extracted out of i18n.ts's factory closure so it has its own unit-test seam\n// independent of the full `createI18n()` setup — mirrors `_catalog-store.ts`/`_namespace-store.ts`,\n// which already got this treatment; this was the one piece of comparable complexity left inline.\n\nimport type { CatalogEntry, CatalogEntryData } from './_catalog';\nimport type { LocaleCaches } from './_chain';\nimport type { Locale, TpOptions, TranslateVars } from './i18n-types';\n\nimport { selectPluralForm } from './_chain';\nimport { devOnly, warn } from './_dev';\nimport { LinguaCountInVarsError, LinguaInvalidCountError } from './errors';\nimport { renderTemplate } from './template';\n\n/** Only the read-side of `CatalogStore` this module needs — avoids importing its `M` generic. */\nexport type CatalogResolver = { resolve(loc: Locale): CatalogEntry | undefined };\n\nexport type TranslateContext = {\n readonly caches: LocaleCaches;\n readonly catalogStore: CatalogResolver;\n /** Active fallback chain, most-specific locale first (e.g. `['en-US', 'en']`). */\n readonly chain: readonly Locale[];\n readonly locale: Locale;\n readonly onMissingKey: (key: string, locale: Locale) => string;\n readonly onMissingVar: (varName: string, key: string, locale: Locale) => string;\n};\n\n// Single-pass entry lookup across the active fallback chain.\nfunction findEntry(ctx: TranslateContext, key: string): CatalogEntryData | undefined {\n for (const candidate of ctx.chain) {\n const found = ctx.catalogStore.resolve(candidate)?.get(key);\n\n if (found !== undefined) return found;\n }\n\n return undefined;\n}\n\n// True if `base` exists as a plural branch prefix (e.g. `items` when only `items.one`/\n// `items.other` are registered) anywhere in the fallback chain. Shared by `hasKey()` and\n// `translate()`'s dev-mode miss diagnostic below — both need to distinguish \"key genuinely\n// doesn't exist\" from \"key exists, but only as a plural branch — you want tp(), not t()\".\nfunction isPluralBranch(ctx: TranslateContext, base: string): boolean {\n for (const candidate of ctx.chain) {\n const catalog = ctx.catalogStore.resolve(candidate);\n\n if (catalog?.prefixes.has(base)) return true;\n }\n\n return false;\n}\n\n/** True if `base` exists as a leaf key or a plural branch prefix, anywhere in the fallback chain. */\nexport function hasKey(ctx: TranslateContext, base: string): boolean {\n return findEntry(ctx, base) !== undefined || isPluralBranch(ctx, base);\n}\n\nfunction interpolate(\n ctx: TranslateContext,\n key: string,\n found: CatalogEntryData,\n vars: TranslateVars | undefined,\n): string {\n return renderTemplate(found.compiled, vars, key, ctx.locale, ctx.onMissingVar);\n}\n\nexport function translate(ctx: TranslateContext, key: string, vars?: TranslateVars): string {\n const found = findEntry(ctx, key);\n\n if (!found) {\n // has(key) returns true for a plural-branch-only key (no leaf entry), but t() can't resolve\n // one — the common `if (i18n.has(key)) i18n.t(key)` pattern silently returns the raw key in\n // that case, indistinguishable from a genuinely missing key without this warning.\n devOnly(() => {\n if (isPluralBranch(ctx, key)) {\n warn(\n `t('${key}') resolved to the missing-key fallback, but '${key}' exists as a plural branch — use tp('${key}', count) instead.`,\n );\n }\n });\n\n return ctx.onMissingKey(key, ctx.locale);\n }\n\n return interpolate(ctx, key, found, vars);\n}\n\n// Plural key priority: cardinal zero tries `.zero` override first, then the CLDR form, then\n// `.other` as the final fallback. Ordinal / non-zero skips the `.zero` special case.\nfunction pluralKeyPriority(base: string, form: string, count: number, ordinal: boolean): string[] {\n const keys: string[] = [];\n\n if (!ordinal && count === 0) keys.push(`${base}.zero`);\n\n if (form !== 'zero' || ordinal) keys.push(`${base}.${form}`);\n\n if (form !== 'other') keys.push(`${base}.other`);\n\n return keys;\n}\n\nexport function translatePlural(ctx: TranslateContext, key: string, count: number, options?: TpOptions): string {\n if (!Number.isFinite(count)) {\n throw new LinguaInvalidCountError('`count` must be a finite number.');\n }\n\n const vars = options?.vars;\n const ordinal = options?.ordinal ?? false;\n\n if (vars && Object.hasOwn(vars, 'count')) {\n throw new LinguaCountInVarsError('`tp` does not allow `vars.count`; `count` is injected automatically.');\n }\n\n const mergedVars = vars ? { count, ...vars } : { count };\n\n // Walk the fallback chain locale-by-locale, selecting CLDR plural form using each locale's\n // own rules. This ensures cross-locale fallbacks produce grammatically correct forms.\n for (const candidate of ctx.chain) {\n const catalog = ctx.catalogStore.resolve(candidate);\n\n if (!catalog) continue;\n\n const form = selectPluralForm(candidate, count, ordinal, ctx.caches);\n const keys = pluralKeyPriority(key, form, count, ordinal);\n\n for (const k of keys) {\n const found = catalog.get(k);\n\n if (found !== undefined) return interpolate(ctx, k, found, mergedVars);\n }\n }\n\n return ctx.onMissingKey(key, ctx.locale);\n}\n"],"mappings":";;;;;AA6BA,SAAS,EAAU,GAAuB,GAA2C;CACnF,KAAK,IAAM,KAAa,EAAI,OAAO;EACjC,IAAM,IAAQ,EAAI,aAAa,QAAQ,CAAS,CAAC,EAAE,IAAI,CAAG;EAE1D,IAAI,MAAU,KAAA,GAAW,OAAO;CAClC;AAGF;AAMA,SAAS,EAAe,GAAuB,GAAuB;CACpE,KAAK,IAAM,KAAa,EAAI,OAG1B,IAFgB,EAAI,aAAa,QAAQ,CAErC,CAAA,EAAS,SAAS,IAAI,CAAI,GAAG,OAAO;CAG1C,OAAO;AACT;AAGA,SAAgB,EAAO,GAAuB,GAAuB;CACnE,OAAO,EAAU,GAAK,CAAI,MAAM,KAAA,KAAa,EAAe,GAAK,CAAI;AACvE;AAEA,SAAS,EACP,GACA,GACA,GACA,GACQ;CACR,OAAO,EAAe,EAAM,UAAU,GAAM,GAAK,EAAI,QAAQ,EAAI,YAAY;AAC/E;AAEA,SAAgB,EAAU,GAAuB,GAAa,GAA8B;CAC1F,IAAM,IAAQ,EAAU,GAAK,CAAG;CAiBhC,OAfK,IAeE,EAAY,GAAK,GAAK,GAAO,CAAI,KAXtC,QAAc;EACZ,AAAI,EAAe,GAAK,CAAG,KACzB,EACE,MAAM,EAAI,gDAAgD,EAAI,wCAAwC,EAAI,mBAC5G;CAEJ,CAAC,GAEM,EAAI,aAAa,GAAK,EAAI,MAAM;AAI3C;AAIA,SAAS,EAAkB,GAAc,GAAc,GAAe,GAA4B;CAChG,IAAM,IAAiB,CAAC;CAQxB,OANI,CAAC,KAAW,MAAU,KAAG,EAAK,KAAK,GAAG,EAAK,MAAM,IAEjD,MAAS,UAAU,MAAS,EAAK,KAAK,GAAG,EAAK,GAAG,GAAM,GAEvD,MAAS,WAAS,EAAK,KAAK,GAAG,EAAK,OAAO,GAExC;AACT;AAEA,SAAgB,EAAgB,GAAuB,GAAa,GAAe,GAA6B;CAC9G,IAAI,CAAC,OAAO,SAAS,CAAK,GACxB,MAAM,IAAI,EAAwB,kCAAkC;CAGtE,IAAM,IAAO,GAAS,MAChB,IAAU,GAAS,WAAW;CAEpC,IAAI,KAAQ,OAAO,OAAO,GAAM,OAAO,GACrC,MAAM,IAAI,EAAuB,sEAAsE;CAGzG,IAAM,IAAa,IAAO;EAAE;EAAO,GAAG;CAAK,IAAI,EAAE,SAAM;CAIvD,KAAK,IAAM,KAAa,EAAI,OAAO;EACjC,IAAM,IAAU,EAAI,aAAa,QAAQ,CAAS;EAElD,IAAI,CAAC,GAAS;EAGd,IAAM,IAAO,EAAkB,GADlB,EAAiB,GAAW,GAAO,GAAS,EAAI,MACzB,GAAM,GAAO,CAAO;EAExD,KAAK,IAAM,KAAK,GAAM;GACpB,IAAM,IAAQ,EAAQ,IAAI,CAAC;GAE3B,IAAI,MAAU,KAAA,GAAW,OAAO,EAAY,GAAK,GAAG,GAAO,CAAU;EACvE;CACF;CAEA,OAAO,EAAI,aAAa,GAAK,EAAI,MAAM;AACzC"}
|
package/dist/format.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./_bounded-cache.cjs");var t=128,n=[`years`,`months`,`weeks`,`days`,`hours`,`minutes`,`seconds`,`milliseconds`,`microseconds`,`nanoseconds`],r={days:`d`,hours:`h`,microseconds:`us`,milliseconds:`ms`,minutes:`min`,months:`mo`,nanoseconds:`ns`,seconds:`s`,weeks:`w`,years:`y`};function i(e){return n.map(t=>{let n=e[t];return typeof n==`number`?`${n}${r[t]}`:void 0}).filter(e=>e!==void 0).join(` `)}function a(r){let a=new Map,o=new Map,s=new Map,c=new Map,l=new Map,u=new Map,d=typeof r==`string`?()=>r:r;function f(e,t){if(!t)return e;let n={};for(let e of Object.keys(t).sort())n[e]=t[e];try{return`${e}:${JSON.stringify(n)}`}catch{return e}}return{clear(){a.clear(),o.clear(),s.clear(),c.clear(),l.clear(),u.clear()},currency(n,r,i){let a=d(),s={...i,currency:r,style:`currency`};return e.getOrCreate(o,f(a,s),t,()=>new Intl.NumberFormat(a,s)).format(n)},date(n,r){let i=d();return e.getOrCreate(s,f(i,r),t,()=>new Intl.DateTimeFormat(i,r)).format(n)},duration(r,a){let o=d(),s=Intl;return s.DurationFormat?n.every(e=>r[e]===void 0)?``:e.getOrCreate(u,f(o,a),t,()=>new s.DurationFormat(o,a)).format(r):i(r)},list(n,r){let i=d(),a=n.map(e=>String(e)),o=r?.style??`long`,s=r?.type===`or`?`disjunction`:`conjunction`;return e.getOrCreate(l,f(i,{style:o,type:s}),t,()=>new Intl.ListFormat(i,{style:o,type:s})).format(a)},number(n,r){let i=d();return e.getOrCreate(a,f(i,r),t,()=>new Intl.NumberFormat(i,r)).format(n)},relative(n,r,i){let a=d();return e.getOrCreate(c,f(a,i),t,()=>new Intl.RelativeTimeFormat(a,i)).format(n,r)}}}exports.createFormatter=a;
|
|
2
2
|
//# sourceMappingURL=format.cjs.map
|
package/dist/format.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.cjs","names":[],"sources":["../src/format.ts"],"sourcesContent":["/**\n * /lingua/format\n *\n * Standalone Intl formatter factory. Import from `/lingua/format`.\n * Creates formatters for numbers, dates, currencies, lists, durations, and relative time.\n *\n * Pass a static locale string or a reactive locale getter.\n *\n * @example\n * ```ts\n * import { createFormatter } from '@vielzeug/lingua/format';\n *\n * // Static locale\n * const fmt = createFormatter('en-US');\n *\n * // Reactive — always reads the current locale from the i18n instance\n * const fmt = createFormatter(() => i18n.locale);\n *\n * fmt.number(1_234.56);\n * fmt.currency(9.99, 'USD');\n * fmt.date(new Date());\n * fmt.relative(-1, 'day');\n * fmt.list(['A', 'B', 'C']);\n * fmt.duration({ hours: 1, minutes: 30 });\n * ```\n */\n\nconst FORMAT_CACHE_MAX = 128;\n\nfunction getOrCreate<F>(cache: Map<string, F>, key: string, build: () => F): F {\n const cached = cache.get(key);\n\n if (cached !== undefined) return cached;\n\n const value = build();\n\n if (cache.size >= FORMAT_CACHE_MAX) cache.delete(cache.keys().next().value as string);\n\n cache.set(key, value);\n\n return value;\n}\n\nexport type DurationValue = Partial<\n Record<\n | 'days'\n | 'hours'\n | 'microseconds'\n | 'milliseconds'\n | 'minutes'\n | 'months'\n | 'nanoseconds'\n | 'seconds'\n | 'weeks'\n | 'years',\n number\n >\n>;\n\nexport type DurationFormatOptions = {\n hours?: '2-digit' | 'numeric';\n microseconds?: 'numeric';\n milliseconds?: 'numeric';\n minutes?: '2-digit' | 'numeric';\n nanoseconds?: 'numeric';\n seconds?: '2-digit' | 'numeric';\n style?: 'digital' | 'long' | 'narrow' | 'short';\n};\n\nexport type ListFormatOptions = {\n style?: 'long' | 'narrow' | 'short';\n type?: 'and' | 'or';\n};\n\nexport type Formatter = {\n /**\n * Clears all cached `Intl` formatter instances. Useful after a locale change on a long-running\n * SSR server, or to reclaim memory.\n *\n * @note This clears the cache for **all callers sharing this formatter instance**. When called on\n * `i18n.fmt`, the cache is cleared for every part of the application that reads `i18n.fmt`.\n * `setLocale()` already calls `clear()` automatically — manual calls are rarely needed.\n */\n clear(): void;\n currency(value: number, currency: string, options?: Omit<Intl.NumberFormatOptions, 'currency' | 'style'>): string;\n date(value: Date | number, options?: Intl.DateTimeFormatOptions): string;\n /**\n * Formats a duration using `Intl.DurationFormat` when available, or a compact labeled fallback\n * (e.g. `'1h 30min'`). Returns `''` when every field of `value` is `undefined`.\n *\n * @note The fallback labels (`h`, `min`, `s`, `ms`, etc.) are English-only and are not\n * locale-aware. If `Intl.DurationFormat` is unavailable in your target environment and\n * you need localised duration output, provide your own formatter using the individual\n * duration fields from `DurationValue`.\n */\n duration(value: DurationValue, options?: DurationFormatOptions): string;\n list(value: Array<string | number>, options?: ListFormatOptions): string;\n /** Formats a number with the current locale. */\n number(value: number, options?: Intl.NumberFormatOptions): string;\n relative(value: number, unit: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions): string;\n};\n\n// ─── Internal ─────────────────────────────────────────────────────────────────\n\nconst DURATION_UNITS: Array<keyof DurationValue> = [\n 'years',\n 'months',\n 'weeks',\n 'days',\n 'hours',\n 'minutes',\n 'seconds',\n 'milliseconds',\n 'microseconds',\n 'nanoseconds',\n];\n\nconst DURATION_LABELS: Record<keyof DurationValue, string> = {\n days: 'd',\n hours: 'h',\n microseconds: 'us',\n milliseconds: 'ms',\n minutes: 'min',\n months: 'mo',\n nanoseconds: 'ns',\n seconds: 's',\n weeks: 'w',\n years: 'y',\n};\n\ntype DurationFormatterCtor = new (\n locale: string,\n options?: DurationFormatOptions,\n) => { format(value: DurationValue): string };\n\nfunction durationLabeled(value: DurationValue): string {\n return DURATION_UNITS.map((unit) => {\n const amount = value[unit];\n\n return typeof amount === 'number' ? `${amount}${DURATION_LABELS[unit]}` : undefined;\n })\n .filter((part): part is string => part !== undefined)\n .join(' ');\n}\n\n// ─── Factory ──────────────────────────────────────────────────────────────────\n\n/**\n * Creates a formatter bound to a locale. Pass a static locale string or a\n * reactive getter function `() => string` (e.g. `() => i18n.locale`).\n */\nexport function createFormatter(source: string | (() => string)): Formatter {\n const numberCache = new Map<string, Intl.NumberFormat>();\n const currencyCache = new Map<string, Intl.NumberFormat>();\n const dateCache = new Map<string, Intl.DateTimeFormat>();\n const relativeCache = new Map<string, Intl.RelativeTimeFormat>();\n const listCache = new Map<string, Intl.ListFormat>();\n const durationCache = new Map<string, { format(value: DurationValue): string }>();\n\n const getLocale = typeof source === 'string' ? () => source : source;\n\n function cachedKey(locale: string, options?: object): string {\n if (!options) return locale;\n\n const sorted: Record<string, unknown> = {};\n\n for (const key of Object.keys(options as Record<string, unknown>).sort()) {\n sorted[key] = (options as Record<string, unknown>)[key];\n }\n\n try {\n return `${locale}:${JSON.stringify(sorted)}`;\n } catch {\n // Circular references or BigInt values — fall back to locale-only key.\n // Multiple callers with different unserializable options will share the same formatter instance.\n return locale;\n }\n }\n\n return {\n clear() {\n numberCache.clear();\n currencyCache.clear();\n dateCache.clear();\n relativeCache.clear();\n listCache.clear();\n durationCache.clear();\n },\n\n currency(value, currency, options) {\n const locale = getLocale();\n const opts: Intl.NumberFormatOptions = { ...options, currency, style: 'currency' };\n\n return getOrCreate(currencyCache, cachedKey(locale, opts), () => new Intl.NumberFormat(locale, opts)).format(\n value,\n );\n },\n\n date(value, options) {\n const locale = getLocale();\n\n return getOrCreate(dateCache, cachedKey(locale, options), () => new Intl.DateTimeFormat(locale, options)).format(\n value,\n );\n },\n\n duration(value, options) {\n const locale = getLocale();\n const IntlExt = Intl as typeof Intl & { DurationFormat?: DurationFormatterCtor };\n\n if (!IntlExt.DurationFormat) return durationLabeled(value);\n\n if (DURATION_UNITS.every((u) => value[u] === undefined)) return '';\n\n return getOrCreate(\n durationCache,\n cachedKey(locale, options),\n () => new IntlExt.DurationFormat!(locale, options),\n ).format(value);\n },\n\n list(value, options) {\n const locale = getLocale();\n const items = value.map((v) => String(v));\n // list() has exactly two string options with small finite value domains.\n // We normalize both to their resolved defaults and build the key directly\n // rather than using cachedKey(). This also prevents cache misses when\n // callers pass { style: 'long', type: 'and' } vs {} (same ListFormat).\n const style = options?.style ?? 'long';\n const type = options?.type === 'or' ? 'disjunction' : 'conjunction';\n\n return getOrCreate(\n listCache,\n cachedKey(locale, { style, type }),\n () => new Intl.ListFormat(locale, { style, type }),\n ).format(items);\n },\n\n number(value, options) {\n const locale = getLocale();\n\n return getOrCreate(numberCache, cachedKey(locale, options), () => new Intl.NumberFormat(locale, options)).format(\n value,\n );\n },\n\n relative(value, unit, options) {\n const locale = getLocale();\n\n return getOrCreate(\n relativeCache,\n cachedKey(locale, options),\n () => new Intl.RelativeTimeFormat(locale, options),\n ).format(value, unit);\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"format.cjs","names":[],"sources":["../src/format.ts"],"sourcesContent":["/**\n * /lingua/format\n *\n * Standalone Intl formatter factory. Import from `/lingua/format`.\n * Creates formatters for numbers, dates, currencies, lists, durations, and relative time.\n *\n * Pass a static locale string or a reactive locale getter.\n *\n * @example\n * ```ts\n * import { createFormatter } from '@vielzeug/lingua/format';\n *\n * // Static locale\n * const fmt = createFormatter('en-US');\n *\n * // Reactive — always reads the current locale from the i18n instance\n * const fmt = createFormatter(() => i18n.locale);\n *\n * fmt.number(1_234.56);\n * fmt.currency(9.99, 'USD');\n * fmt.date(new Date());\n * fmt.relative(-1, 'day');\n * fmt.list(['A', 'B', 'C']);\n * fmt.duration({ hours: 1, minutes: 30 });\n * ```\n */\n\nimport { getOrCreate } from './_bounded-cache';\n\nconst FORMAT_CACHE_MAX = 128;\n\nexport type DurationValue = Partial<\n Record<\n | 'days'\n | 'hours'\n | 'microseconds'\n | 'milliseconds'\n | 'minutes'\n | 'months'\n | 'nanoseconds'\n | 'seconds'\n | 'weeks'\n | 'years',\n number\n >\n>;\n\nexport type DurationFormatOptions = {\n hours?: '2-digit' | 'numeric';\n microseconds?: 'numeric';\n milliseconds?: 'numeric';\n minutes?: '2-digit' | 'numeric';\n nanoseconds?: 'numeric';\n seconds?: '2-digit' | 'numeric';\n style?: 'digital' | 'long' | 'narrow' | 'short';\n};\n\nexport type ListFormatOptions = {\n style?: 'long' | 'narrow' | 'short';\n type?: 'and' | 'or';\n};\n\nexport type Formatter = {\n /**\n * Clears all cached `Intl` formatter instances. Useful after a locale change on a long-running\n * SSR server, or to reclaim memory.\n *\n * @note This clears the cache for **all callers sharing this formatter instance**. When called on\n * `i18n.fmt`, the cache is cleared for every part of the application that reads `i18n.fmt`.\n * `setLocale()` already calls `clear()` automatically — manual calls are rarely needed.\n */\n clear(): void;\n currency(value: number, currency: string, options?: Omit<Intl.NumberFormatOptions, 'currency' | 'style'>): string;\n date(value: Date | number, options?: Intl.DateTimeFormatOptions): string;\n /**\n * Formats a duration using `Intl.DurationFormat` when available, or a compact labeled fallback\n * (e.g. `'1h 30min'`). Returns `''` when every field of `value` is `undefined`.\n *\n * @note The fallback labels (`h`, `min`, `s`, `ms`, etc.) are English-only and are not\n * locale-aware. If `Intl.DurationFormat` is unavailable in your target environment and\n * you need localised duration output, provide your own formatter using the individual\n * duration fields from `DurationValue`.\n */\n duration(value: DurationValue, options?: DurationFormatOptions): string;\n list(value: Array<string | number>, options?: ListFormatOptions): string;\n /** Formats a number with the current locale. */\n number(value: number, options?: Intl.NumberFormatOptions): string;\n relative(value: number, unit: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions): string;\n};\n\n// ─── Internal ─────────────────────────────────────────────────────────────────\n\nconst DURATION_UNITS: Array<keyof DurationValue> = [\n 'years',\n 'months',\n 'weeks',\n 'days',\n 'hours',\n 'minutes',\n 'seconds',\n 'milliseconds',\n 'microseconds',\n 'nanoseconds',\n];\n\nconst DURATION_LABELS: Record<keyof DurationValue, string> = {\n days: 'd',\n hours: 'h',\n microseconds: 'us',\n milliseconds: 'ms',\n minutes: 'min',\n months: 'mo',\n nanoseconds: 'ns',\n seconds: 's',\n weeks: 'w',\n years: 'y',\n};\n\ntype DurationFormatterCtor = new (\n locale: string,\n options?: DurationFormatOptions,\n) => { format(value: DurationValue): string };\n\nfunction durationLabeled(value: DurationValue): string {\n return DURATION_UNITS.map((unit) => {\n const amount = value[unit];\n\n return typeof amount === 'number' ? `${amount}${DURATION_LABELS[unit]}` : undefined;\n })\n .filter((part): part is string => part !== undefined)\n .join(' ');\n}\n\n// ─── Factory ──────────────────────────────────────────────────────────────────\n\n/**\n * Creates a formatter bound to a locale. Pass a static locale string or a\n * reactive getter function `() => string` (e.g. `() => i18n.locale`).\n */\nexport function createFormatter(source: string | (() => string)): Formatter {\n const numberCache = new Map<string, Intl.NumberFormat>();\n const currencyCache = new Map<string, Intl.NumberFormat>();\n const dateCache = new Map<string, Intl.DateTimeFormat>();\n const relativeCache = new Map<string, Intl.RelativeTimeFormat>();\n const listCache = new Map<string, Intl.ListFormat>();\n const durationCache = new Map<string, { format(value: DurationValue): string }>();\n\n const getLocale = typeof source === 'string' ? () => source : source;\n\n function cachedKey(locale: string, options?: object): string {\n if (!options) return locale;\n\n const sorted: Record<string, unknown> = {};\n\n for (const key of Object.keys(options as Record<string, unknown>).sort()) {\n sorted[key] = (options as Record<string, unknown>)[key];\n }\n\n try {\n return `${locale}:${JSON.stringify(sorted)}`;\n } catch {\n // Circular references or BigInt values — fall back to locale-only key.\n // Multiple callers with different unserializable options will share the same formatter instance.\n return locale;\n }\n }\n\n return {\n clear() {\n numberCache.clear();\n currencyCache.clear();\n dateCache.clear();\n relativeCache.clear();\n listCache.clear();\n durationCache.clear();\n },\n\n currency(value, currency, options) {\n const locale = getLocale();\n const opts: Intl.NumberFormatOptions = { ...options, currency, style: 'currency' };\n\n return getOrCreate(\n currencyCache,\n cachedKey(locale, opts),\n FORMAT_CACHE_MAX,\n () => new Intl.NumberFormat(locale, opts),\n ).format(value);\n },\n\n date(value, options) {\n const locale = getLocale();\n\n return getOrCreate(\n dateCache,\n cachedKey(locale, options),\n FORMAT_CACHE_MAX,\n () => new Intl.DateTimeFormat(locale, options),\n ).format(value);\n },\n\n duration(value, options) {\n const locale = getLocale();\n const IntlExt = Intl as typeof Intl & { DurationFormat?: DurationFormatterCtor };\n\n if (!IntlExt.DurationFormat) return durationLabeled(value);\n\n if (DURATION_UNITS.every((u) => value[u] === undefined)) return '';\n\n return getOrCreate(\n durationCache,\n cachedKey(locale, options),\n FORMAT_CACHE_MAX,\n () => new IntlExt.DurationFormat!(locale, options),\n ).format(value);\n },\n\n list(value, options) {\n const locale = getLocale();\n const items = value.map((v) => String(v));\n // list() has exactly two string options with small finite value domains.\n // We normalize both to their resolved defaults and build the key directly\n // rather than using cachedKey(). This also prevents cache misses when\n // callers pass { style: 'long', type: 'and' } vs {} (same ListFormat).\n const style = options?.style ?? 'long';\n const type = options?.type === 'or' ? 'disjunction' : 'conjunction';\n\n return getOrCreate(\n listCache,\n cachedKey(locale, { style, type }),\n FORMAT_CACHE_MAX,\n () => new Intl.ListFormat(locale, { style, type }),\n ).format(items);\n },\n\n number(value, options) {\n const locale = getLocale();\n\n return getOrCreate(\n numberCache,\n cachedKey(locale, options),\n FORMAT_CACHE_MAX,\n () => new Intl.NumberFormat(locale, options),\n ).format(value);\n },\n\n relative(value, unit, options) {\n const locale = getLocale();\n\n return getOrCreate(\n relativeCache,\n cachedKey(locale, options),\n FORMAT_CACHE_MAX,\n () => new Intl.RelativeTimeFormat(locale, options),\n ).format(value, unit);\n },\n };\n}\n"],"mappings":"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"}
|
package/dist/format.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAMH,MAAM,MAAM,aAAa,GAAG,OAAO,CACjC,MAAM,CACF,MAAM,GACN,OAAO,GACP,cAAc,GACd,cAAc,GACd,SAAS,GACT,QAAQ,GACR,aAAa,GACb,SAAS,GACT,OAAO,GACP,OAAO,EACT,MAAM,CACP,CACF,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAC9B,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAChC,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAChC,KAAK,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;CACjD,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpC,IAAI,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB;;;;;;;OAOG;IACH,KAAK,IAAI,IAAI,CAAC;IACd,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,UAAU,GAAG,OAAO,CAAC,GAAG,MAAM,CAAC;IAClH,IAAI,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,qBAAqB,GAAG,MAAM,CAAC;IACzE;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,MAAM,CAAC;IACxE,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,MAAM,CAAC;IACzE,gDAAgD;IAChD,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,mBAAmB,GAAG,MAAM,CAAC;IAClE,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,sBAAsB,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,yBAAyB,GAAG,MAAM,CAAC;CAC9G,CAAC;AA+CF;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAqH1E"}
|