@ls-stack/server-i18n 0.0.0-stage → 0.6.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.
@@ -0,0 +1,35 @@
1
+ //#region ../core/src/types.d.ts
2
+ type PluralTranslation = {
3
+ manyLimit?: number;
4
+ zero?: string;
5
+ one?: string;
6
+ '+2': string | null;
7
+ many?: string;
8
+ };
9
+ type TranslationValue = string | null | PluralTranslation;
10
+ type Locale = Record<string, TranslationValue>;
11
+ type LocaleConfig<T extends string = string> = Record<T, Locale>;
12
+ //#endregion
13
+ //#region src/main.d.ts
14
+ type I18nOptions<T extends string> = {
15
+ locales: Record<T, Locale>;
16
+ /**
17
+ * Locale used by `toAvailableLocale` and `withUnknownLocale` when no locale
18
+ * matches. Defaults to the first locale in `locales`
19
+ */
20
+ fallbackLocale?: NoInfer<T>;
21
+ };
22
+ declare function i18nitialize<T extends string>(options: I18nOptions<T>): {
23
+ with(localeId: T): I18n; /** `with` for an unvalidated language tag, see `toAvailableLocale` */
24
+ withUnknownLocale(localeTag: string | null | undefined): I18n;
25
+ toAvailableLocale: (localeTag: string | null | undefined) => T;
26
+ };
27
+ declare class I18n {
28
+ private localeId;
29
+ constructor(localeId: string);
30
+ __: (strings: TemplateStringsArray, ...exprs: (string | number)[]) => string;
31
+ __p: (num: number) => (strings: TemplateStringsArray, ...exprs: (string | number)[]) => string;
32
+ }
33
+ //#endregion
34
+ export { type I18n, type Locale, type LocaleConfig, type PluralTranslation, type TranslationValue, i18nitialize };
35
+ //# sourceMappingURL=main.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.mts","names":[],"sources":["../../core/src/types.ts","../src/main.ts"],"mappings":";KAAY,iBAAA;EACV,SAAA;EACA,IAAA;EACA,GAAA;EACA,IAAA;EACA,IAAA;AAAA;AAAA,KAGU,gBAAA,mBAAmC,iBAAA;AAAA,KAEnC,MAAA,GAAS,MAAA,SAAe,gBAAA;AAAA,KAExB,YAAA,8BAA0C,MAAA,CAAO,CAAA,EAAG,MAAA;;;KCL3D,WAAA;EACH,OAAA,EAAS,MAAA,CAAO,CAAA,EAAG,MAAA;;;;;EAKnB,cAAA,GAAiB,OAAA,CAAQ,CAAA;AAAA;AAAA,iBAgBX,YAAA,kBAAA,CAA+B,OAAA,EAAS,WAAA,CAAY,CAAA;iBAgEjD,CAAA,GAAI,IAAA,EDxFjB;2DC4FuD,IAAA;+DAvBO,CAAA;AAAA;AAAA,cAgC9D,IAAA;EAAA,QACgB,QAAA;cAAA,QAAA;EAEpB,EAAA,GAAM,OAAA,EAAS,oBAAA,KAAyB,KAAA;EA+BxC,GAAA,GAAO,GAAA,cACG,OAAA,EAAS,oBAAA,KAAyB,KAAA;AAAA"}
package/dist/main.mjs ADDED
@@ -0,0 +1,135 @@
1
+ //#region ../core/src/hash.ts
2
+ function createHashAndFallbackTranslation(strings, exprs) {
3
+ let hash = "";
4
+ let fallbackTranslation = "";
5
+ for (let i = 0; i < strings.length; i++) {
6
+ const string = strings[i] ?? "";
7
+ const expr = exprs[i];
8
+ hash += string + (i !== strings.length - 1 ? `{${i + 1}}` : "");
9
+ fallbackTranslation += string + (expr ?? "");
10
+ }
11
+ if (fallbackTranslation.includes("~~")) fallbackTranslation = fallbackTranslation.split("~~")[0] ?? fallbackTranslation;
12
+ return [hash, fallbackTranslation];
13
+ }
14
+
15
+ //#endregion
16
+ //#region ../core/src/interpolation.ts
17
+ const TRANSLATION_SPLITTER = /{(\d+)}/;
18
+ function interpolate(translation, exprs) {
19
+ if (exprs.length === 0) return translation;
20
+ let result = "";
21
+ const translationParts = translation.split(TRANSLATION_SPLITTER);
22
+ for (let i = 0; i < translationParts.length; i++) {
23
+ const part = translationParts[i];
24
+ if (i % 2 === 0) result += part;
25
+ else result += exprs[Number(part) - 1];
26
+ }
27
+ return result;
28
+ }
29
+
30
+ //#endregion
31
+ //#region ../core/src/pluralization.ts
32
+ function selectPluralForm(num, pluralTranslation) {
33
+ if (num === 0 && pluralTranslation.zero) return pluralTranslation.zero;
34
+ if (num === 1 && pluralTranslation.one) return pluralTranslation.one;
35
+ if (pluralTranslation.manyLimit && pluralTranslation.many && num > pluralTranslation.manyLimit) return pluralTranslation.many;
36
+ if (pluralTranslation["+2"]) return pluralTranslation["+2"].replace("#", String(num));
37
+ return null;
38
+ }
39
+
40
+ //#endregion
41
+ //#region src/main.ts
42
+ let config = null;
43
+ function normalizeLocaleTag(localeTag) {
44
+ return localeTag.trim().toLowerCase().replaceAll("_", "-");
45
+ }
46
+ function getBaseLanguage(normalizedTag) {
47
+ const separatorIndex = normalizedTag.indexOf("-");
48
+ return separatorIndex === -1 ? normalizedTag : normalizedTag.slice(0, separatorIndex);
49
+ }
50
+ function i18nitialize(options) {
51
+ config = options;
52
+ function isLocaleId(id) {
53
+ return Object.hasOwn(options.locales, id);
54
+ }
55
+ const localeIds = Object.keys(options.locales).filter(isLocaleId);
56
+ const fallbackLocale = options.fallbackLocale ?? localeIds[0];
57
+ const localeByTag = /* @__PURE__ */ new Map();
58
+ const localeByBaseLanguage = /* @__PURE__ */ new Map();
59
+ for (const localeId of localeIds) {
60
+ const normalizedId = normalizeLocaleTag(localeId);
61
+ localeByTag.set(normalizedId, localeId);
62
+ const baseLanguage = getBaseLanguage(normalizedId);
63
+ if (normalizedId === baseLanguage || !localeByBaseLanguage.has(baseLanguage)) localeByBaseLanguage.set(baseLanguage, localeId);
64
+ }
65
+ const instances = /* @__PURE__ */ new Map();
66
+ function getInstance(localeId) {
67
+ let instance = instances.get(localeId);
68
+ if (!instance) {
69
+ instance = new I18n(localeId);
70
+ instances.set(localeId, instance);
71
+ }
72
+ return instance;
73
+ }
74
+ /**
75
+ * Closest available locale of a language tag like `pt-BR` or `pt_br`,
76
+ * matching the exact tag first, then its base language. Returns the
77
+ * fallback locale if nothing matches
78
+ */
79
+ function toAvailableLocale(localeTag) {
80
+ if (localeTag) {
81
+ const normalizedTag = normalizeLocaleTag(localeTag);
82
+ const matchedLocale = localeByTag.get(normalizedTag) ?? localeByBaseLanguage.get(getBaseLanguage(normalizedTag));
83
+ if (matchedLocale !== void 0) return matchedLocale;
84
+ }
85
+ if (fallbackLocale === void 0) throw new Error("No locales configured");
86
+ return fallbackLocale;
87
+ }
88
+ return {
89
+ with(localeId) {
90
+ return getInstance(localeId);
91
+ },
92
+ withUnknownLocale(localeTag) {
93
+ return getInstance(toAvailableLocale(localeTag));
94
+ },
95
+ toAvailableLocale
96
+ };
97
+ }
98
+ var I18n = class {
99
+ constructor(localeId) {
100
+ this.localeId = localeId;
101
+ }
102
+ __ = (strings, ...exprs) => {
103
+ const [hash, fallbackTranslation] = createHashAndFallbackTranslation(strings, exprs);
104
+ const activeLocale = config?.locales[this.localeId];
105
+ if (!activeLocale) throw new Error("No active locale");
106
+ const selectedTranslation = activeLocale[hash];
107
+ if (selectedTranslation === void 0) return fallbackTranslation;
108
+ if (typeof selectedTranslation === "string" && selectedTranslation) return interpolate(selectedTranslation, exprs) || fallbackTranslation;
109
+ if (selectedTranslation) throw new Error("Invalid translation, this translation should use the plural `__p` method");
110
+ return fallbackTranslation;
111
+ };
112
+ __p = (num) => {
113
+ return (strings, ...exprs) => {
114
+ const [hash, fallbackTranslation] = createHashAndFallbackTranslation(strings, exprs);
115
+ const activeLocale = config?.locales[this.localeId];
116
+ if (!activeLocale) throw new Error("No active locale");
117
+ const selectedTranslation = activeLocale[hash];
118
+ if (selectedTranslation === void 0) return fallbackTranslation;
119
+ if (typeof selectedTranslation === "object" && selectedTranslation !== null) {
120
+ const translation = selectPluralForm(num, selectedTranslation);
121
+ if (translation === null) {
122
+ console.error(`No plural configured for hash: ${hash}`);
123
+ return fallbackTranslation;
124
+ }
125
+ return interpolate(translation, exprs) || fallbackTranslation;
126
+ }
127
+ if (selectedTranslation) throw new Error("Invalid translation, this translation should use the `__` method");
128
+ return fallbackTranslation;
129
+ };
130
+ };
131
+ };
132
+
133
+ //#endregion
134
+ export { i18nitialize };
135
+ //# sourceMappingURL=main.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.mjs","names":[],"sources":["../../core/src/hash.ts","../../core/src/interpolation.ts","../../core/src/pluralization.ts","../src/main.ts"],"sourcesContent":["export function createHashAndFallbackTranslation(\n strings: TemplateStringsArray,\n exprs: (string | number)[],\n): readonly [hash: string, fallbackTranslation: string] {\n let hash = '';\n let fallbackTranslation = '';\n\n for (let i = 0; i < strings.length; i++) {\n const string = strings[i] ?? '';\n const expr = exprs[i];\n\n hash += string + (i !== strings.length - 1 ? `{${i + 1}}` : '');\n fallbackTranslation += string + (expr ?? '');\n }\n\n if (fallbackTranslation.includes('~~')) {\n fallbackTranslation = fallbackTranslation.split('~~')[0] ?? fallbackTranslation;\n }\n\n return [hash, fallbackTranslation] as const;\n}\n","const TRANSLATION_SPLITTER = /{(\\d+)}/;\n\nexport function interpolate(\n translation: string,\n exprs: (string | number)[],\n): string {\n if (exprs.length === 0) {\n return translation;\n }\n\n let result = '';\n const translationParts = translation.split(TRANSLATION_SPLITTER);\n\n for (let i = 0; i < translationParts.length; i++) {\n const part = translationParts[i];\n\n if (i % 2 === 0) {\n result += part;\n } else {\n const interpolationPos = Number(part);\n result += exprs[interpolationPos - 1];\n }\n }\n\n return result;\n}\n","import type { PluralTranslation } from './types';\n\nexport function selectPluralForm(\n num: number,\n pluralTranslation: PluralTranslation,\n): string | null {\n if (num === 0 && pluralTranslation.zero) {\n return pluralTranslation.zero;\n }\n\n if (num === 1 && pluralTranslation.one) {\n return pluralTranslation.one;\n }\n\n if (\n pluralTranslation.manyLimit &&\n pluralTranslation.many &&\n num > pluralTranslation.manyLimit\n ) {\n return pluralTranslation.many;\n }\n\n if (pluralTranslation['+2']) {\n return pluralTranslation['+2'].replace('#', String(num));\n }\n\n return null;\n}\n","import {\n createHashAndFallbackTranslation,\n interpolate,\n type Locale,\n selectPluralForm,\n} from '@ls-stack/i18n-core';\n\ntype I18nOptions<T extends string> = {\n locales: Record<T, Locale>;\n /**\n * Locale used by `toAvailableLocale` and `withUnknownLocale` when no locale\n * matches. Defaults to the first locale in `locales`\n */\n fallbackLocale?: NoInfer<T>;\n};\n\nlet config: I18nOptions<string> | null = null;\n\nfunction normalizeLocaleTag(localeTag: string): string {\n return localeTag.trim().toLowerCase().replaceAll('_', '-');\n}\n\nfunction getBaseLanguage(normalizedTag: string): string {\n const separatorIndex = normalizedTag.indexOf('-');\n return separatorIndex === -1 ? normalizedTag : (\n normalizedTag.slice(0, separatorIndex)\n );\n}\n\nexport function i18nitialize<T extends string>(options: I18nOptions<T>) {\n config = options;\n\n function isLocaleId(id: string): id is T {\n return Object.hasOwn(options.locales, id);\n }\n\n const localeIds = Object.keys(options.locales).filter(isLocaleId);\n const fallbackLocale = options.fallbackLocale ?? localeIds[0];\n\n const localeByTag = new Map<string, T>();\n const localeByBaseLanguage = new Map<string, T>();\n\n for (const localeId of localeIds) {\n const normalizedId = normalizeLocaleTag(localeId);\n localeByTag.set(normalizedId, localeId);\n\n const baseLanguage = getBaseLanguage(normalizedId);\n // a locale without region (e.g. `pt`) wins over regional ones (e.g. `pt-PT`)\n if (\n normalizedId === baseLanguage ||\n !localeByBaseLanguage.has(baseLanguage)\n ) {\n localeByBaseLanguage.set(baseLanguage, localeId);\n }\n }\n\n const instances = new Map<T, I18n>();\n\n function getInstance(localeId: T): I18n {\n let instance = instances.get(localeId);\n\n if (!instance) {\n instance = new I18n(localeId);\n instances.set(localeId, instance);\n }\n\n return instance;\n }\n\n /**\n * Closest available locale of a language tag like `pt-BR` or `pt_br`,\n * matching the exact tag first, then its base language. Returns the\n * fallback locale if nothing matches\n */\n function toAvailableLocale(localeTag: string | null | undefined): T {\n if (localeTag) {\n const normalizedTag = normalizeLocaleTag(localeTag);\n\n const matchedLocale =\n localeByTag.get(normalizedTag) ??\n localeByBaseLanguage.get(getBaseLanguage(normalizedTag));\n\n if (matchedLocale !== undefined) return matchedLocale;\n }\n\n if (fallbackLocale === undefined) {\n throw new Error('No locales configured');\n }\n\n return fallbackLocale;\n }\n\n return {\n with(localeId: T): I18n {\n return getInstance(localeId);\n },\n /** `with` for an unvalidated language tag, see `toAvailableLocale` */\n withUnknownLocale(localeTag: string | null | undefined): I18n {\n return getInstance(toAvailableLocale(localeTag));\n },\n toAvailableLocale,\n };\n}\n\nexport type { I18n };\n\nclass I18n {\n constructor(private localeId: string) {}\n\n __ = (strings: TemplateStringsArray, ...exprs: (string | number)[]): string => {\n const [hash, fallbackTranslation] = createHashAndFallbackTranslation(\n strings,\n exprs,\n );\n\n const activeLocale = config?.locales[this.localeId];\n\n if (!activeLocale) {\n throw new Error('No active locale');\n }\n\n const selectedTranslation = activeLocale[hash];\n\n if (selectedTranslation === undefined) {\n return fallbackTranslation;\n }\n\n if (typeof selectedTranslation === 'string' && selectedTranslation) {\n return interpolate(selectedTranslation, exprs) || fallbackTranslation;\n }\n\n if (selectedTranslation) {\n throw new Error(\n 'Invalid translation, this translation should use the plural `__p` method',\n );\n }\n\n return fallbackTranslation;\n };\n\n __p = (num: number) => {\n return (strings: TemplateStringsArray, ...exprs: (string | number)[]) => {\n const [hash, fallbackTranslation] = createHashAndFallbackTranslation(\n strings,\n exprs,\n );\n\n const activeLocale = config?.locales[this.localeId];\n\n if (!activeLocale) {\n throw new Error('No active locale');\n }\n\n const selectedTranslation = activeLocale[hash];\n\n if (selectedTranslation === undefined) {\n return fallbackTranslation;\n }\n\n if (\n typeof selectedTranslation === 'object' &&\n selectedTranslation !== null\n ) {\n const translation = selectPluralForm(num, selectedTranslation);\n\n if (translation === null) {\n console.error(`No plural configured for hash: ${hash}`);\n return fallbackTranslation;\n }\n\n return interpolate(translation, exprs) || fallbackTranslation;\n }\n\n if (selectedTranslation) {\n throw new Error(\n 'Invalid translation, this translation should use the `__` method',\n );\n }\n\n return fallbackTranslation;\n };\n };\n}\n\nexport type { Locale, LocaleConfig, PluralTranslation, TranslationValue } from '@ls-stack/i18n-core';\n"],"mappings":"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"}