@case-framework/ui-localization 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,25 @@
1
+ //#region src/index.d.ts
2
+ type LocalizationPrimitive = string | number | boolean | null | undefined;
3
+ type LocalizationValues = Record<string, LocalizationPrimitive>;
4
+ interface LocalizationMessages {
5
+ [key: string]: string | LocalizationMessages;
6
+ }
7
+ type LocalizationCatalog = Record<string, LocalizationMessages>;
8
+ type LocalizationTranslate = (key: string, values?: LocalizationValues) => string;
9
+ declare const normalizeLocalizationLocale: (locale: string) => string;
10
+ declare const mergeLocalizationCatalogs: (defaults: LocalizationCatalog, ...sources: Array<LocalizationCatalog | undefined>) => LocalizationCatalog;
11
+ declare const createLocalizationTranslator: ({
12
+ fallbackLocale,
13
+ locale,
14
+ localizations,
15
+ onMissing
16
+ }: {
17
+ fallbackLocale: string;
18
+ locale: string;
19
+ localizations: LocalizationCatalog;
20
+ onMissing?: (key: string, locale: string) => void;
21
+ }) => LocalizationTranslate;
22
+ declare const createNamespacedLocalizationTranslator: (translate: LocalizationTranslate, namespace?: string) => LocalizationTranslate;
23
+ //#endregion
24
+ export { LocalizationCatalog, LocalizationMessages, LocalizationPrimitive, LocalizationTranslate, LocalizationValues, createLocalizationTranslator, createNamespacedLocalizationTranslator, mergeLocalizationCatalogs, normalizeLocalizationLocale };
25
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";KAAY,qBAAA;AAAA,KAEA,kBAAA,GAAqB,MAAA,SAAe,qBAAA;AAAA,UAE/B,oBAAA;EAAA,CACd,GAAA,oBAAuB,oBAAA;AAAA;AAAA,KAGd,mBAAA,GAAsB,MAAA,SAAe,oBAAA;AAAA,KAErC,qBAAA,IAAyB,GAAA,UAAa,MAAA,GAAS,kBAAA;AAAA,cA0C9C,2BAAA,GAA+B,MAAA;AAAA,cAG/B,yBAAA,GACX,QAAA,EAAU,mBAAA,KACP,OAAA,EAAS,KAAA,CAAM,mBAAA,kBACjB,mBAAA;AAAA,cA2DU,4BAAA;EAAgC,cAAA;EAAA,MAAA;EAAA,aAAA;EAAA;AAAA;EAM3C,cAAA;EACA,MAAA;EACA,aAAA,EAAe,mBAAA;EACf,SAAA,IAAa,GAAA,UAAa,MAAA;AAAA,MACxB,qBAAA;AAAA,cAYS,sCAAA,GACX,SAAA,EAAW,qBAAA,EACX,SAAA,cACC,qBAAA"}
package/dist/index.mjs ADDED
@@ -0,0 +1,78 @@
1
+ //#region src/index.ts
2
+ const isPlainObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
3
+ const cloneMessages = (messages) => {
4
+ const clone = {};
5
+ for (const [key, value] of Object.entries(messages)) clone[key] = isPlainObject(value) ? cloneMessages(value) : value;
6
+ return clone;
7
+ };
8
+ const mergeMessages = (base, override) => {
9
+ const result = cloneMessages(base);
10
+ for (const [key, overrideValue] of Object.entries(override)) {
11
+ const baseValue = result[key];
12
+ if (isPlainObject(baseValue) && isPlainObject(overrideValue)) {
13
+ result[key] = mergeMessages(baseValue, overrideValue);
14
+ continue;
15
+ }
16
+ result[key] = isPlainObject(overrideValue) ? cloneMessages(overrideValue) : overrideValue;
17
+ }
18
+ return result;
19
+ };
20
+ const normalizeLocalizationLocale = (locale) => locale.trim().replaceAll("_", "-").toLowerCase();
21
+ const mergeLocalizationCatalogs = (defaults, ...sources) => {
22
+ const merged = {};
23
+ for (const source of [defaults, ...sources]) {
24
+ if (!source) continue;
25
+ for (const [locale, messages] of Object.entries(source)) {
26
+ const normalizedLocale = normalizeLocalizationLocale(locale);
27
+ if (!normalizedLocale) continue;
28
+ const existingMessages = merged[normalizedLocale];
29
+ merged[normalizedLocale] = existingMessages ? mergeMessages(existingMessages, messages) : cloneMessages(messages);
30
+ }
31
+ }
32
+ return merged;
33
+ };
34
+ const resolveLocaleCandidates = (locale, fallbackLocale) => {
35
+ const normalizedFallbackLocale = normalizeLocalizationLocale(fallbackLocale) || normalizeLocalizationLocale("en");
36
+ const normalizedLocale = normalizeLocalizationLocale(locale) || normalizedFallbackLocale;
37
+ return Array.from(new Set([
38
+ normalizedLocale,
39
+ normalizedLocale.split("-")[0],
40
+ normalizedFallbackLocale,
41
+ normalizedFallbackLocale.split("-")[0]
42
+ ]));
43
+ };
44
+ const getNestedMessage = (messages, path) => {
45
+ if (!messages) return void 0;
46
+ let current = messages;
47
+ for (const segment of path.split(".")) {
48
+ if (!isPlainObject(current) || !(segment in current)) return void 0;
49
+ current = current[segment];
50
+ }
51
+ return typeof current === "string" ? current : void 0;
52
+ };
53
+ const interpolateMessage = (message, values) => {
54
+ if (!values) return message;
55
+ return message.replace(/\{([a-zA-Z0-9_]+)\}/g, (_match, token) => {
56
+ const value = values[token];
57
+ return value === void 0 || value === null ? `{${token}}` : String(value);
58
+ });
59
+ };
60
+ const createLocalizationTranslator = ({ fallbackLocale, locale, localizations, onMissing }) => {
61
+ return (key, values) => {
62
+ for (const localeCandidate of resolveLocaleCandidates(locale, fallbackLocale)) {
63
+ const localizedMessage = getNestedMessage(localizations[localeCandidate], key);
64
+ if (localizedMessage !== void 0) return interpolateMessage(localizedMessage, values);
65
+ }
66
+ onMissing?.(key, locale);
67
+ return key;
68
+ };
69
+ };
70
+ const createNamespacedLocalizationTranslator = (translate, namespace) => {
71
+ const normalizedNamespace = namespace?.trim();
72
+ if (!normalizedNamespace) return translate;
73
+ return (key, values) => translate(`${normalizedNamespace}.${key}`, values);
74
+ };
75
+ //#endregion
76
+ export { createLocalizationTranslator, createNamespacedLocalizationTranslator, mergeLocalizationCatalogs, normalizeLocalizationLocale };
77
+
78
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["export type LocalizationPrimitive = string | number | boolean | null | undefined;\n\nexport type LocalizationValues = Record<string, LocalizationPrimitive>;\n\nexport interface LocalizationMessages {\n [key: string]: string | LocalizationMessages;\n}\n\nexport type LocalizationCatalog = Record<string, LocalizationMessages>;\n\nexport type LocalizationTranslate = (key: string, values?: LocalizationValues) => string;\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n value !== null && typeof value === \"object\" && !Array.isArray(value);\n\nconst cloneMessages = (messages: LocalizationMessages): LocalizationMessages => {\n const clone: LocalizationMessages = {};\n\n for (const [key, value] of Object.entries(messages)) {\n clone[key] = isPlainObject(value)\n ? cloneMessages(value as LocalizationMessages)\n : (value as string);\n }\n\n return clone;\n};\n\nconst mergeMessages = (\n base: LocalizationMessages,\n override: LocalizationMessages,\n): LocalizationMessages => {\n const result = cloneMessages(base);\n\n for (const [key, overrideValue] of Object.entries(override)) {\n const baseValue = result[key];\n\n if (isPlainObject(baseValue) && isPlainObject(overrideValue)) {\n result[key] = mergeMessages(\n baseValue as LocalizationMessages,\n overrideValue as LocalizationMessages,\n );\n continue;\n }\n\n result[key] = isPlainObject(overrideValue)\n ? cloneMessages(overrideValue as LocalizationMessages)\n : (overrideValue as string);\n }\n\n return result;\n};\n\nexport const normalizeLocalizationLocale = (locale: string): string =>\n locale.trim().replaceAll(\"_\", \"-\").toLowerCase();\n\nexport const mergeLocalizationCatalogs = (\n defaults: LocalizationCatalog,\n ...sources: Array<LocalizationCatalog | undefined>\n): LocalizationCatalog => {\n const merged: LocalizationCatalog = {};\n\n for (const source of [defaults, ...sources]) {\n if (!source) continue;\n\n for (const [locale, messages] of Object.entries(source)) {\n const normalizedLocale = normalizeLocalizationLocale(locale);\n if (!normalizedLocale) continue;\n\n const existingMessages = merged[normalizedLocale];\n merged[normalizedLocale] = existingMessages\n ? mergeMessages(existingMessages, messages)\n : cloneMessages(messages);\n }\n }\n\n return merged;\n};\n\nconst resolveLocaleCandidates = (locale: string, fallbackLocale: string): string[] => {\n const normalizedFallbackLocale =\n normalizeLocalizationLocale(fallbackLocale) || normalizeLocalizationLocale(\"en\");\n const normalizedLocale = normalizeLocalizationLocale(locale) || normalizedFallbackLocale;\n\n return Array.from(\n new Set([\n normalizedLocale,\n normalizedLocale.split(\"-\")[0],\n normalizedFallbackLocale,\n normalizedFallbackLocale.split(\"-\")[0],\n ]),\n );\n};\n\nconst getNestedMessage = (\n messages: LocalizationMessages | undefined,\n path: string,\n): string | undefined => {\n if (!messages) return undefined;\n\n let current: string | LocalizationMessages | undefined = messages;\n for (const segment of path.split(\".\")) {\n if (!isPlainObject(current) || !(segment in current)) return undefined;\n current = current[segment] as string | LocalizationMessages;\n }\n\n return typeof current === \"string\" ? current : undefined;\n};\n\nconst interpolateMessage = (message: string, values?: LocalizationValues): string => {\n if (!values) return message;\n\n return message.replace(/\\{([a-zA-Z0-9_]+)\\}/g, (_match, token: string) => {\n const value = values[token];\n return value === undefined || value === null ? `{${token}}` : String(value);\n });\n};\n\nexport const createLocalizationTranslator = ({\n fallbackLocale,\n locale,\n localizations,\n onMissing,\n}: {\n fallbackLocale: string;\n locale: string;\n localizations: LocalizationCatalog;\n onMissing?: (key: string, locale: string) => void;\n}): LocalizationTranslate => {\n return (key, values) => {\n for (const localeCandidate of resolveLocaleCandidates(locale, fallbackLocale)) {\n const localizedMessage = getNestedMessage(localizations[localeCandidate], key);\n if (localizedMessage !== undefined) return interpolateMessage(localizedMessage, values);\n }\n\n onMissing?.(key, locale);\n return key;\n };\n};\n\nexport const createNamespacedLocalizationTranslator = (\n translate: LocalizationTranslate,\n namespace?: string,\n): LocalizationTranslate => {\n const normalizedNamespace = namespace?.trim();\n if (!normalizedNamespace) return translate;\n\n return (key, values) => translate(`${normalizedNamespace}.${key}`, values);\n};\n"],"mappings":";AAYA,MAAM,iBAAiB,UACrB,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,MAAM;AAEtE,MAAM,iBAAiB,aAAyD;CAC9E,MAAM,QAA8B,EAAE;AAEtC,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,SAAS,CACjD,OAAM,OAAO,cAAc,MAAM,GAC7B,cAAc,MAA8B,GAC3C;AAGP,QAAO;;AAGT,MAAM,iBACJ,MACA,aACyB;CACzB,MAAM,SAAS,cAAc,KAAK;AAElC,MAAK,MAAM,CAAC,KAAK,kBAAkB,OAAO,QAAQ,SAAS,EAAE;EAC3D,MAAM,YAAY,OAAO;AAEzB,MAAI,cAAc,UAAU,IAAI,cAAc,cAAc,EAAE;AAC5D,UAAO,OAAO,cACZ,WACA,cACD;AACD;;AAGF,SAAO,OAAO,cAAc,cAAc,GACtC,cAAc,cAAsC,GACnD;;AAGP,QAAO;;AAGT,MAAa,+BAA+B,WAC1C,OAAO,MAAM,CAAC,WAAW,KAAK,IAAI,CAAC,aAAa;AAElD,MAAa,6BACX,UACA,GAAG,YACqB;CACxB,MAAM,SAA8B,EAAE;AAEtC,MAAK,MAAM,UAAU,CAAC,UAAU,GAAG,QAAQ,EAAE;AAC3C,MAAI,CAAC,OAAQ;AAEb,OAAK,MAAM,CAAC,QAAQ,aAAa,OAAO,QAAQ,OAAO,EAAE;GACvD,MAAM,mBAAmB,4BAA4B,OAAO;AAC5D,OAAI,CAAC,iBAAkB;GAEvB,MAAM,mBAAmB,OAAO;AAChC,UAAO,oBAAoB,mBACvB,cAAc,kBAAkB,SAAS,GACzC,cAAc,SAAS;;;AAI/B,QAAO;;AAGT,MAAM,2BAA2B,QAAgB,mBAAqC;CACpF,MAAM,2BACJ,4BAA4B,eAAe,IAAI,4BAA4B,KAAK;CAClF,MAAM,mBAAmB,4BAA4B,OAAO,IAAI;AAEhE,QAAO,MAAM,KACX,IAAI,IAAI;EACN;EACA,iBAAiB,MAAM,IAAI,CAAC;EAC5B;EACA,yBAAyB,MAAM,IAAI,CAAC;EACrC,CAAC,CACH;;AAGH,MAAM,oBACJ,UACA,SACuB;AACvB,KAAI,CAAC,SAAU,QAAO,KAAA;CAEtB,IAAI,UAAqD;AACzD,MAAK,MAAM,WAAW,KAAK,MAAM,IAAI,EAAE;AACrC,MAAI,CAAC,cAAc,QAAQ,IAAI,EAAE,WAAW,SAAU,QAAO,KAAA;AAC7D,YAAU,QAAQ;;AAGpB,QAAO,OAAO,YAAY,WAAW,UAAU,KAAA;;AAGjD,MAAM,sBAAsB,SAAiB,WAAwC;AACnF,KAAI,CAAC,OAAQ,QAAO;AAEpB,QAAO,QAAQ,QAAQ,yBAAyB,QAAQ,UAAkB;EACxE,MAAM,QAAQ,OAAO;AACrB,SAAO,UAAU,KAAA,KAAa,UAAU,OAAO,IAAI,MAAM,KAAK,OAAO,MAAM;GAC3E;;AAGJ,MAAa,gCAAgC,EAC3C,gBACA,QACA,eACA,gBAM2B;AAC3B,SAAQ,KAAK,WAAW;AACtB,OAAK,MAAM,mBAAmB,wBAAwB,QAAQ,eAAe,EAAE;GAC7E,MAAM,mBAAmB,iBAAiB,cAAc,kBAAkB,IAAI;AAC9E,OAAI,qBAAqB,KAAA,EAAW,QAAO,mBAAmB,kBAAkB,OAAO;;AAGzF,cAAY,KAAK,OAAO;AACxB,SAAO;;;AAIX,MAAa,0CACX,WACA,cAC0B;CAC1B,MAAM,sBAAsB,WAAW,MAAM;AAC7C,KAAI,CAAC,oBAAqB,QAAO;AAEjC,SAAQ,KAAK,WAAW,UAAU,GAAG,oBAAoB,GAAG,OAAO,OAAO"}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@case-framework/ui-localization",
3
+ "version": "0.2.0",
4
+ "description": "Shared UI localization primitives for CASE Framework packages.",
5
+ "license": "ISC",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "type": "module",
10
+ "sideEffects": false,
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.mts",
14
+ "import": "./dist/index.mjs"
15
+ },
16
+ "./package.json": "./package.json"
17
+ },
18
+ "devDependencies": {
19
+ "@jest/globals": "30.3.0",
20
+ "@types/jest": "30.0.0",
21
+ "jest": "30.3.0",
22
+ "ts-jest": "29.4.9",
23
+ "tsdown": "0.21.10",
24
+ "typescript": "6.0.3"
25
+ },
26
+ "scripts": {
27
+ "build": "tsdown",
28
+ "typecheck": "tsc --noEmit",
29
+ "test": "jest --config jest.config.cjs --runInBand",
30
+ "check": "pnpm typecheck && pnpm build && pnpm test"
31
+ }
32
+ }