@ultimat3/i18n 1.0.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/LICENSE +21 -0
- package/README.md +116 -0
- package/package.json +35 -0
- package/src/catalog.d.ts +28 -0
- package/src/catalog.d.ts.map +1 -0
- package/src/catalog.js +81 -0
- package/src/catalog.js.map +1 -0
- package/src/catalog.ts +132 -0
- package/src/catalogs/en.json +212 -0
- package/src/context.d.ts +64 -0
- package/src/context.d.ts.map +1 -0
- package/src/context.js +132 -0
- package/src/context.js.map +1 -0
- package/src/context.ts +195 -0
- package/src/define-catalogs.ts +56 -0
- package/src/errors.d.ts +22 -0
- package/src/errors.d.ts.map +1 -0
- package/src/errors.js +52 -0
- package/src/errors.js.map +1 -0
- package/src/errors.ts +73 -0
- package/src/extract.d.ts +67 -0
- package/src/extract.d.ts.map +1 -0
- package/src/extract.js +106 -0
- package/src/extract.js.map +1 -0
- package/src/extract.ts +184 -0
- package/src/framework.d.ts +12 -0
- package/src/framework.d.ts.map +1 -0
- package/src/framework.js +16 -0
- package/src/framework.js.map +1 -0
- package/src/framework.ts +19 -0
- package/src/index.d.ts +10 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.js +10 -0
- package/src/index.js.map +1 -0
- package/src/index.ts +101 -0
- package/src/interpolate.d.ts +36 -0
- package/src/interpolate.d.ts.map +1 -0
- package/src/interpolate.js +96 -0
- package/src/interpolate.js.map +1 -0
- package/src/interpolate.ts +103 -0
- package/src/locales.d.ts +36 -0
- package/src/locales.d.ts.map +1 -0
- package/src/locales.js +141 -0
- package/src/locales.js.map +1 -0
- package/src/locales.ts +166 -0
- package/src/translator.d.ts +25 -0
- package/src/translator.d.ts.map +1 -0
- package/src/translator.js +37 -0
- package/src/translator.js.map +1 -0
- package/src/translator.ts +87 -0
|
@@ -0,0 +1,87 @@
|
|
|
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
|
+
|
|
7
|
+
import type { Catalog } from './catalog';
|
|
8
|
+
import { type InterpolationVars, interpolate, selectPluralKey } from './interpolate';
|
|
9
|
+
import { DEFAULT_LOCALE, type Locale } from './locales';
|
|
10
|
+
|
|
11
|
+
export interface TranslateVars extends InterpolationVars {
|
|
12
|
+
/** Present ⇒ plural selection runs against the locale's CLDR categories. */
|
|
13
|
+
count?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Suffixes `selectPluralKey` probes: the CLDR categories plus the two-form shortcut. */
|
|
17
|
+
type PluralSuffix = 'zero' | 'one' | 'two' | 'few' | 'many' | 'other' | 'plural';
|
|
18
|
+
|
|
19
|
+
/** `items_one` → `items`. A leaf with no plural suffix contributes nothing. */
|
|
20
|
+
type PluralStem<TKey extends string> = TKey extends `${infer TStem}_${PluralSuffix}`
|
|
21
|
+
? TStem
|
|
22
|
+
: never;
|
|
23
|
+
|
|
24
|
+
/** Recursion budget — an unresolved generic catalog would otherwise never bottom out. */
|
|
25
|
+
type Depth = [never, 0, 1, 2, 3, 4, 5, 6, 7];
|
|
26
|
+
|
|
27
|
+
/** Depth-first dot-paths of an authoring object — the type-level twin of `flattenCatalog`. */
|
|
28
|
+
type CatalogPaths<TCatalog, TDepth extends number = 8> = [Depth[TDepth]] extends [never]
|
|
29
|
+
? never
|
|
30
|
+
: {
|
|
31
|
+
[TKey in keyof TCatalog & string]: TCatalog[TKey] extends string
|
|
32
|
+
? TKey | PluralStem<TKey>
|
|
33
|
+
: `${TKey}.${CatalogPaths<TCatalog[TKey], Depth[TDepth]> & string}`;
|
|
34
|
+
}[keyof TCatalog & string];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Dot-path keys of a nested catalog, plus the stem of every plural family — `items_one`
|
|
38
|
+
* also admits `items`, because `t('items', { count })` is how plural selection is called.
|
|
39
|
+
* An index-signature catalog (the untyped default) degrades to `string`.
|
|
40
|
+
*/
|
|
41
|
+
export type TranslationKey<TCatalog> = string extends keyof TCatalog
|
|
42
|
+
? string
|
|
43
|
+
: CatalogPaths<TCatalog>;
|
|
44
|
+
|
|
45
|
+
export interface Translator<TCatalog = Catalog> {
|
|
46
|
+
(key: TranslationKey<TCatalog>, vars?: TranslateVars): string;
|
|
47
|
+
/** Whether the key (or any of its plural variants) can be rendered. */
|
|
48
|
+
has(key: string): boolean;
|
|
49
|
+
/** The raw template, for tooling that needs the placeholders. */
|
|
50
|
+
raw(key: string): string | undefined;
|
|
51
|
+
keys(): string[];
|
|
52
|
+
readonly locale: Locale;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function createTranslator(catalog: Catalog, locale: Locale = DEFAULT_LOCALE): Translator {
|
|
56
|
+
const hasExact = (key: string): boolean => Object.hasOwn(catalog, key);
|
|
57
|
+
|
|
58
|
+
const resolveKey = (key: string, vars?: TranslateVars): string => {
|
|
59
|
+
const count = vars?.count;
|
|
60
|
+
if (typeof count === 'number' && Number.isFinite(count)) {
|
|
61
|
+
return selectPluralKey(key, count, locale, hasExact);
|
|
62
|
+
}
|
|
63
|
+
return key;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const translate = (key: string, vars?: TranslateVars): string => {
|
|
67
|
+
const template = catalog[resolveKey(key, vars)];
|
|
68
|
+
if (template === undefined) return `⟦${key}⟧`;
|
|
69
|
+
return vars === undefined ? template : interpolate(template, vars);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return Object.assign(translate, {
|
|
73
|
+
has: (key: string): boolean =>
|
|
74
|
+
hasExact(key) ||
|
|
75
|
+
hasExact(`${key}_other`) ||
|
|
76
|
+
hasExact(`${key}_plural`) ||
|
|
77
|
+
hasExact(`${key}_one`),
|
|
78
|
+
raw: (key: string): string | undefined => catalog[key],
|
|
79
|
+
keys: (): string[] => Object.keys(catalog).sort(),
|
|
80
|
+
locale,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** True when a rendered string is a loud miss — assert on this in tests, never on `''`. */
|
|
85
|
+
export function isMiss(rendered: string): boolean {
|
|
86
|
+
return rendered.startsWith('⟦') && rendered.endsWith('⟧');
|
|
87
|
+
}
|