@murumets-ee/i18n 0.11.0 → 0.12.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/dist/index.d.mts CHANGED
@@ -1,24 +1,3 @@
1
- //#region src/loader.d.ts
2
- /**
3
- * Factory for creating message loader functions.
4
- *
5
- * Eliminates the per-package boilerplate of switching on locale,
6
- * catching import errors, and falling back to a default locale.
7
- *
8
- * @example
9
- * ```ts
10
- * // ticketing/src/i18n.ts
11
- * import { createMessageLoader } from '@murumets-ee/i18n'
12
- *
13
- * export const getTicketingMessages = createMessageLoader(
14
- * (locale) => import(`./messages/${locale}.json`).then(m => m.default),
15
- * )
16
- * ```
17
- */
18
- declare function createMessageLoader(importFn: (locale: string) => Promise<Record<string, unknown>>, options?: {
19
- fallbackLocale?: string;
20
- }): (locale: string) => Promise<Record<string, unknown>>;
21
- //#endregion
22
1
  //#region src/merge.d.ts
23
2
  /**
24
3
  * Deep-merge multiple message catalogs.
@@ -39,41 +18,5 @@ declare function createMessageLoader(importFn: (locale: string) => Promise<Recor
39
18
  */
40
19
  declare function mergeMessages(...catalogs: Record<string, unknown>[]): Record<string, unknown>;
41
20
  //#endregion
42
- //#region src/options.d.ts
43
- /**
44
- * Build the i18n key for a select field option.
45
- *
46
- * Convention: `{Namespace}.{field}.{value}`
47
- *
48
- * @example
49
- * ```ts
50
- * optionKey('Ticketing', 'status', '0_open')
51
- * // → 'Ticketing.status.0_open'
52
- * ```
53
- */
54
- declare function optionKey(namespace: string, field: string, value: string): string;
55
- /**
56
- * Strip the numeric sort prefix from a select value.
57
- *
58
- * Values like `'0_open'`, `'2_high'` become `'open'`, `'high'`.
59
- * Values without a prefix are returned as-is.
60
- *
61
- * Useful as a display fallback when translations aren't available.
62
- *
63
- * @example
64
- * ```ts
65
- * stripPrefix('0_open') // → 'open'
66
- * stripPrefix('hello') // → 'hello'
67
- * stripPrefix('3_urgent') // → 'urgent'
68
- * ```
69
- */
70
- declare function stripPrefix(value: string): string;
71
- //#endregion
72
- //#region src/types.d.ts
73
- /** Nested message catalog — string leaves, object branches. */
74
- type MessageCatalog = {
75
- [key: string]: string | MessageCatalog;
76
- };
77
- //#endregion
78
- export { type MessageCatalog, createMessageLoader, mergeMessages, optionKey, stripPrefix };
21
+ export { mergeMessages };
79
22
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/loader.ts","../src/merge.ts","../src/options.ts","../src/types.ts"],"mappings":";;AAgBA;;;;;;;;;;;;;;;iBAAgB,mBAAA,CACd,QAAA,GAAW,MAAA,aAAmB,OAAA,CAAQ,MAAA,oBACtC,OAAA;EAAY,cAAA;AAAA,KACV,MAAA,aAAmB,OAAA,CAAQ,MAAA;;;;AAH/B;;;;;;;;;;;;;;;;iBCCgB,aAAA,CAAA,GACX,QAAA,EAAU,MAAA,sBACZ,MAAA;;;;ADHH;;;;;;;;;;iBELgB,SAAA,CAAU,SAAA,UAAmB,KAAA,UAAe,KAAA;;;;;;;;;;;;;ADM5D;;;iBCagB,WAAA,CAAY,KAAA;;;;KC7BhB,cAAA;EAAA,CACT,GAAA,oBAAuB,cAAA;AAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/merge.ts"],"mappings":";;AAiBA;;;;;;;;;;;;;;;;iBAAgB,aAAA,CAAA,GAAiB,QAAA,EAAU,MAAA,sBAA4B,MAAA"}
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- function e(e,t){let n=t?.fallbackLocale??`en`;return async t=>{try{return await e(t)}catch{if(t!==n)try{return await e(n)}catch{return{}}return{}}}}function t(...e){let n={};for(let r of e)for(let[e,i]of Object.entries(r)){let r=n[e];typeof r==`object`&&r&&!Array.isArray(r)&&typeof i==`object`&&i&&!Array.isArray(i)?n[e]=t(r,i):n[e]=i}return n}function n(e,t,n){return`${e}.${t}.${n}`}function r(e){return e.replace(/^\d+_/,``)}export{e as createMessageLoader,t as mergeMessages,n as optionKey,r as stripPrefix};
1
+ function e(...n){let r={};for(let i of n)for(let[n,a]of Object.entries(i)){if(n===`__proto__`||n===`constructor`||n===`prototype`)continue;let i=r[n];t(a)?r[n]=e(t(i)?i:{},a):r[n]=a}return r}function t(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}export{e as mergeMessages};
2
2
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/loader.ts","../src/merge.ts","../src/options.ts"],"sourcesContent":["/**\n * Factory for creating message loader functions.\n *\n * Eliminates the per-package boilerplate of switching on locale,\n * catching import errors, and falling back to a default locale.\n *\n * @example\n * ```ts\n * // ticketing/src/i18n.ts\n * import { createMessageLoader } from '@murumets-ee/i18n'\n *\n * export const getTicketingMessages = createMessageLoader(\n * (locale) => import(`./messages/${locale}.json`).then(m => m.default),\n * )\n * ```\n */\nexport function createMessageLoader(\n importFn: (locale: string) => Promise<Record<string, unknown>>,\n options?: { fallbackLocale?: string },\n): (locale: string) => Promise<Record<string, unknown>> {\n const fallback = options?.fallbackLocale ?? 'en'\n\n return async (locale: string): Promise<Record<string, unknown>> => {\n try {\n return await importFn(locale)\n } catch {\n // Locale not found — fall back\n if (locale !== fallback) {\n try {\n return await importFn(fallback)\n } catch {\n return {}\n }\n }\n return {}\n }\n }\n}\n","/**\n * Deep-merge multiple message catalogs.\n *\n * Unlike `{ ...a, ...b }`, this recursively merges nested objects so\n * that namespaced catalogs from different packages don't clobber each\n * other's keys.\n *\n * @example\n * ```ts\n * const merged = mergeMessages(\n * { Common: { save: 'Save' } },\n * { Ticketing: { status: { open: 'Open' } } },\n * { Common: { cancel: 'Cancel' } }, // merges into Common, doesn't overwrite\n * )\n * // → { Common: { save: 'Save', cancel: 'Cancel' }, Ticketing: { status: { open: 'Open' } } }\n * ```\n */\nexport function mergeMessages(\n ...catalogs: Record<string, unknown>[]\n): Record<string, unknown> {\n const result: Record<string, unknown> = {}\n\n for (const catalog of catalogs) {\n for (const [key, value] of Object.entries(catalog)) {\n const existing = result[key]\n if (\n existing != null &&\n typeof existing === 'object' &&\n !Array.isArray(existing) &&\n value != null &&\n typeof value === 'object' &&\n !Array.isArray(value)\n ) {\n // Both are objects — recurse\n result[key] = mergeMessages(\n existing as Record<string, unknown>,\n value as Record<string, unknown>,\n )\n } else {\n // Leaf or new key — last catalog wins\n result[key] = value\n }\n }\n }\n\n return result\n}\n","/**\n * Build the i18n key for a select field option.\n *\n * Convention: `{Namespace}.{field}.{value}`\n *\n * @example\n * ```ts\n * optionKey('Ticketing', 'status', '0_open')\n * // 'Ticketing.status.0_open'\n * ```\n */\nexport function optionKey(namespace: string, field: string, value: string): string {\n return `${namespace}.${field}.${value}`\n}\n\n/**\n * Strip the numeric sort prefix from a select value.\n *\n * Values like `'0_open'`, `'2_high'` become `'open'`, `'high'`.\n * Values without a prefix are returned as-is.\n *\n * Useful as a display fallback when translations aren't available.\n *\n * @example\n * ```ts\n * stripPrefix('0_open') // → 'open'\n * stripPrefix('hello') // → 'hello'\n * stripPrefix('3_urgent') // → 'urgent'\n * ```\n */\nexport function stripPrefix(value: string): string {\n return value.replace(/^\\d+_/, '')\n}\n"],"mappings":"AAgBA,SAAgB,EACd,EACA,EACsD,CACtD,IAAM,EAAW,GAAS,gBAAkB,KAE5C,OAAO,KAAO,IAAqD,CACjE,GAAI,CACF,OAAO,MAAM,EAAS,EAAO,MACvB,CAEN,GAAI,IAAW,EACb,GAAI,CACF,OAAO,MAAM,EAAS,EAAS,MACzB,CACN,MAAO,EAAE,CAGb,MAAO,EAAE,GCjBf,SAAgB,EACd,GAAG,EACsB,CACzB,IAAM,EAAkC,EAAE,CAE1C,IAAK,IAAM,KAAW,EACpB,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,EAAQ,CAAE,CAClD,IAAM,EAAW,EAAO,GAGtB,OAAO,GAAa,UADpB,GAEA,CAAC,MAAM,QAAQ,EAAS,EAExB,OAAO,GAAU,UADjB,GAEA,CAAC,MAAM,QAAQ,EAAM,CAGrB,EAAO,GAAO,EACZ,EACA,EACD,CAGD,EAAO,GAAO,EAKpB,OAAO,EClCT,SAAgB,EAAU,EAAmB,EAAe,EAAuB,CACjF,MAAO,GAAG,EAAU,GAAG,EAAM,GAAG,IAkBlC,SAAgB,EAAY,EAAuB,CACjD,OAAO,EAAM,QAAQ,QAAS,GAAG"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/merge.ts"],"sourcesContent":["/**\n * Deep-merge multiple message catalogs.\n *\n * Unlike `{ ...a, ...b }`, this recursively merges nested objects so\n * that namespaced catalogs from different packages don't clobber each\n * other's keys.\n *\n * @example\n * ```ts\n * const merged = mergeMessages(\n * { Common: { save: 'Save' } },\n * { Ticketing: { status: { open: 'Open' } } },\n * { Common: { cancel: 'Cancel' } }, // merges into Common, doesn't overwrite\n * )\n * // → { Common: { save: 'Save', cancel: 'Cancel' }, Ticketing: { status: { open: 'Open' } } }\n * ```\n */\nexport function mergeMessages(...catalogs: Record<string, unknown>[]): Record<string, unknown> {\n const result: Record<string, unknown> = {}\n\n for (const catalog of catalogs) {\n for (const [key, value] of Object.entries(catalog)) {\n // Skip prototype-pollution vectors. JSON.parse produces own enumerable\n // `__proto__` keys (`constructor`/`prototype` are belt-and-suspenders),\n // which would otherwise hijack a downstream consumer's prototype chain\n // when copied with `Object.assign({}, subtree)`.\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue\n\n const existing = result[key]\n if (isMergeable(value)) {\n // Always recurse on mergeable values, even when `existing` is absent.\n // This funnels every nested object through the skip rule above (so a\n // poisoned subtree like `{Common: {__proto__: {...}}}` is sanitized at\n // depth) and detaches the result from the input subtree (so callers\n // can mutate one without affecting the other).\n result[key] = mergeMessages(isMergeable(existing) ? existing : {}, value)\n } else {\n result[key] = value\n }\n }\n }\n\n return result\n}\n\nfunction isMergeable(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n}\n"],"mappings":"AAiBA,SAAgB,EAAc,GAAG,EAA8D,CAC7F,IAAM,EAAkC,EAAE,CAE1C,IAAK,IAAM,KAAW,EACpB,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,EAAQ,CAAE,CAKlD,GAAI,IAAQ,aAAe,IAAQ,eAAiB,IAAQ,YAAa,SAEzE,IAAM,EAAW,EAAO,GACpB,EAAY,EAAM,CAMpB,EAAO,GAAO,EAAc,EAAY,EAAS,CAAG,EAAW,EAAE,CAAE,EAAM,CAEzE,EAAO,GAAO,EAKpB,OAAO,EAGT,SAAS,EAAY,EAAkD,CACrE,OAAyB,OAAO,GAAU,YAAnC,GAA+C,CAAC,MAAM,QAAQ,EAAM"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@murumets-ee/i18n",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "license": "Elastic-2.0",
5
5
  "type": "module",
6
6
  "exports": {
@@ -13,7 +13,7 @@
13
13
  "dist"
14
14
  ],
15
15
  "devDependencies": {
16
- "tsdown": "^0.21.7",
16
+ "tsdown": "^0.21.10",
17
17
  "typescript": "^5.7.3",
18
18
  "vitest": "^2.1.8"
19
19
  },
@@ -21,7 +21,7 @@
21
21
  "atLeast": 100
22
22
  },
23
23
  "scripts": {
24
- "build": "tsdown && cp -r src/messages dist/messages",
24
+ "build": "tsdown",
25
25
  "dev": "tsdown --watch",
26
26
  "test": "vitest"
27
27
  }
@@ -1,22 +0,0 @@
1
- {
2
- "Common": {
3
- "save": "Save",
4
- "cancel": "Cancel",
5
- "delete": "Delete",
6
- "confirm": "Confirm",
7
- "close": "Close",
8
- "create": "Create",
9
- "edit": "Edit",
10
- "search": "Search...",
11
- "loading": "Loading...",
12
- "noResults": "No results found",
13
- "required": "Required",
14
- "selected": "{count} selected",
15
- "items": "{count, plural, one {# item} other {# items}}",
16
- "yes": "Yes",
17
- "no": "No",
18
- "back": "Back",
19
- "next": "Next",
20
- "previous": "Previous"
21
- }
22
- }
@@ -1,22 +0,0 @@
1
- {
2
- "Common": {
3
- "save": "Salvesta",
4
- "cancel": "Tühista",
5
- "delete": "Kustuta",
6
- "confirm": "Kinnita",
7
- "close": "Sulge",
8
- "create": "Loo",
9
- "edit": "Muuda",
10
- "search": "Otsi...",
11
- "loading": "Laadimine...",
12
- "noResults": "Tulemusi ei leitud",
13
- "required": "Kohustuslik",
14
- "selected": "{count} valitud",
15
- "items": "{count, plural, one {# kirje} other {# kirjet}}",
16
- "yes": "Jah",
17
- "no": "Ei",
18
- "back": "Tagasi",
19
- "next": "Edasi",
20
- "previous": "Eelmine"
21
- }
22
- }
@@ -1,22 +0,0 @@
1
- {
2
- "Common": {
3
- "save": "Сохранить",
4
- "cancel": "Отмена",
5
- "delete": "Удалить",
6
- "confirm": "Подтвердить",
7
- "close": "Закрыть",
8
- "create": "Создать",
9
- "edit": "Редактировать",
10
- "search": "Поиск...",
11
- "loading": "Загрузка...",
12
- "noResults": "Ничего не найдено",
13
- "required": "Обязательно",
14
- "selected": "{count} выбрано",
15
- "items": "{count, plural, one {# элемент} few {# элемента} other {# элементов}}",
16
- "yes": "Да",
17
- "no": "Нет",
18
- "back": "Назад",
19
- "next": "Далее",
20
- "previous": "Предыдущий"
21
- }
22
- }