@sehv-oss/i18n 1.0.0 → 1.0.1

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.
@@ -39,4 +39,5 @@ declare class FormatRelativeTime {
39
39
  static format(value: number, unit: FormatRelativeTimeUnit, locale: string, options?: FormatRelativeTimeOptions): string;
40
40
  }
41
41
  //#endregion
42
- export { FormatCurrency, FormatCurrencyOptions, FormatDate, FormatDateOptions, FormatList, FormatListOptions, FormatNumber, FormatNumberOptions, FormatRelativeTime, FormatRelativeTimeOptions, FormatRelativeTimeUnit };
42
+ export { FormatCurrency, FormatCurrencyOptions, FormatDate, FormatDateOptions, FormatList, FormatListOptions, FormatNumber, FormatNumberOptions, FormatRelativeTime, FormatRelativeTimeOptions, FormatRelativeTimeUnit };
43
+ //# sourceMappingURL=formatters.d.mts.map
@@ -1 +1,2 @@
1
- var e=class{cache=new Map;constructor(e){this.Formatter=e}get(e,t){let n=this.buildKey(e,t),r=this.cache.get(n);return r||(r=new this.Formatter(e,t),this.cache.set(n,r)),r}clear(){this.cache.clear()}buildKey(e,t){return t?`${e}:${JSON.stringify(t)}`:e}};const t=new e(Intl.NumberFormat);var n=class{static format(e,n,r,i){let{locale:a,...o}=i??{};return t.get(r,{...o,style:`currency`,currency:n}).format(e)}};const r=new e(Intl.DateTimeFormat);var i=class{static format(e,t,n){let{locale:i,...a}=n??{};return r.get(t,a).format(e)}};const a=new e(Intl.ListFormat);var o=class{static format(e,t,n){let{locale:r,...i}=n??{};return a.get(t,i).format(e)}};const s=new e(Intl.NumberFormat);var c=class{static format(e,t,n){let{locale:r,...i}=n??{};return s.get(t,i).format(e)}};const l=new e(Intl.RelativeTimeFormat),u={year:`year`,years:`year`,quarter:`quarter`,quarters:`quarter`,month:`month`,months:`month`,week:`week`,weeks:`week`,day:`day`,days:`day`,hour:`hour`,hours:`hour`,minute:`minute`,minutes:`minute`,second:`second`,seconds:`second`};var d=class{static format(e,t,n,r){let{locale:i,...a}=r??{},o=l.get(n,a),s=u[t];return o.format(e,s)}};export{n as a,i,c as n,o as r,d as t};
1
+ var e=class{cache=new Map;constructor(e){this.Formatter=e}get(e,t){let n=this.buildKey(e,t),r=this.cache.get(n);return r||(r=new this.Formatter(e,t),this.cache.set(n,r)),r}clear(){this.cache.clear()}buildKey(e,t){return t?`${e}:${JSON.stringify(t)}`:e}};const t=new e(Intl.NumberFormat);var n=class{static format(e,n,r,i){let{locale:a,...o}=i??{};return t.get(r,{...o,style:`currency`,currency:n}).format(e)}};const r=new e(Intl.DateTimeFormat);var i=class{static format(e,t,n){let{locale:i,...a}=n??{};return r.get(t,a).format(e)}};const a=new e(Intl.ListFormat);var o=class{static format(e,t,n){let{locale:r,...i}=n??{};return a.get(t,i).format(e)}};const s=new e(Intl.NumberFormat);var c=class{static format(e,t,n){let{locale:r,...i}=n??{};return s.get(t,i).format(e)}};const l=new e(Intl.RelativeTimeFormat),u={year:`year`,years:`year`,quarter:`quarter`,quarters:`quarter`,month:`month`,months:`month`,week:`week`,weeks:`week`,day:`day`,days:`day`,hour:`hour`,hours:`hour`,minute:`minute`,minutes:`minute`,second:`second`,seconds:`second`};var d=class{static format(e,t,n,r){let{locale:i,...a}=r??{},o=l.get(n,a),s=u[t];return o.format(e,s)}};export{n as a,i,c as n,o as r,d as t};
2
+ //# sourceMappingURL=formatters-Bvs6JDy4.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatters-Bvs6JDy4.mjs","names":["cache","cache","cache","cache"],"sources":["../src/caches/formatter.ts","../src/formatters/currency.ts","../src/formatters/date.ts","../src/formatters/list.ts","../src/formatters/number.ts","../src/formatters/relative-time.ts"],"sourcesContent":["type FormatterConstructor<TFormatter, TFormatterOptions> = new (\n locale: string,\n options?: TFormatterOptions\n) => TFormatter;\n\nexport class FormatterCache<TFormatter, TFormatterOptions> {\n private cache = new Map<string, TFormatter>();\n\n constructor(\n private readonly Formatter: FormatterConstructor<\n TFormatter,\n TFormatterOptions\n >\n ) {}\n\n get(locale: string, options?: TFormatterOptions): TFormatter {\n const key = this.buildKey(locale, options);\n\n let formatter = this.cache.get(key);\n if (!formatter) {\n formatter = new this.Formatter(locale, options);\n this.cache.set(key, formatter);\n }\n\n return formatter;\n }\n\n clear(): void {\n this.cache.clear();\n }\n\n private buildKey(locale: string, options?: TFormatterOptions): string {\n if (!options) return locale;\n\n return `${locale}:${JSON.stringify(options)}`;\n }\n}\n","import { FormatterCache } from '../caches/formatter.ts';\n\nexport type FormatCurrencyOptions = Omit<Intl.NumberFormatOptions, 'style'> & {\n locale?: string;\n};\n\nconst cache = new FormatterCache(Intl.NumberFormat);\n\nexport class FormatCurrency {\n public static format(\n value: number,\n currency: string,\n locale: string,\n options?: FormatCurrencyOptions\n ): string {\n const { locale: _, ...formatOptions } = options ?? {};\n const formatter = cache.get(locale, {\n ...formatOptions,\n style: 'currency',\n currency,\n });\n\n return formatter.format(value);\n }\n}\n","import { FormatterCache } from '../caches/formatter.ts';\n\nexport type FormatDateOptions = Intl.DateTimeFormatOptions & {\n locale?: string;\n};\n\nconst cache = new FormatterCache(Intl.DateTimeFormat);\n\nexport class FormatDate {\n public static format(\n value: Date | number,\n locale: string,\n options?: FormatDateOptions\n ): string {\n const { locale: _, ...formatOptions } = options ?? {};\n const formatter = cache.get(locale, formatOptions);\n\n return formatter.format(value);\n }\n}\n","import { FormatterCache } from '../caches/formatter.ts';\n\nexport type FormatListOptions = Intl.ListFormatOptions & {\n locale?: string;\n};\n\nconst cache = new FormatterCache(Intl.ListFormat);\n\nexport class FormatList {\n public static format(\n values: string[],\n locale: string,\n options?: FormatListOptions\n ): string {\n const { locale: _, ...formatOptions } = options ?? {};\n const formatter = cache.get(locale, formatOptions);\n\n return formatter.format(values);\n }\n}\n","import { FormatterCache } from '../caches/formatter.ts';\n\nexport type FormatNumberOptions = Intl.NumberFormatOptions & {\n locale?: string;\n};\n\nconst cache = new FormatterCache(Intl.NumberFormat);\n\nexport class FormatNumber {\n public static format(\n value: number,\n locale: string,\n options?: FormatNumberOptions\n ): string {\n const { locale: _, ...formatOptions } = options ?? {};\n const formatter = cache.get(locale, formatOptions);\n\n return formatter.format(value);\n }\n}\n","import { FormatterCache } from '../caches/formatter.ts';\n\nexport type FormatRelativeTimeUnit =\n | 'year'\n | 'years'\n | 'quarter'\n | 'quarters'\n | 'month'\n | 'months'\n | 'week'\n | 'weeks'\n | 'day'\n | 'days'\n | 'hour'\n | 'hours'\n | 'minute'\n | 'minutes'\n | 'second'\n | 'seconds';\n\nexport type FormatRelativeTimeOptions = Intl.RelativeTimeFormatOptions & {\n locale?: string;\n};\n\nconst cache = new FormatterCache(Intl.RelativeTimeFormat);\n\nconst unitMap: Record<FormatRelativeTimeUnit, Intl.RelativeTimeFormatUnit> = {\n year: 'year',\n years: 'year',\n quarter: 'quarter',\n quarters: 'quarter',\n month: 'month',\n months: 'month',\n week: 'week',\n weeks: 'week',\n day: 'day',\n days: 'day',\n hour: 'hour',\n hours: 'hour',\n minute: 'minute',\n minutes: 'minute',\n second: 'second',\n seconds: 'second',\n};\n\nexport class FormatRelativeTime {\n public static format(\n value: number,\n unit: FormatRelativeTimeUnit,\n locale: string,\n options?: FormatRelativeTimeOptions\n ): string {\n const { locale: _, ...formatOptions } = options ?? {};\n const formatter = cache.get(locale, formatOptions);\n const normalizedUnit = unitMap[unit];\n\n return formatter.format(value, normalizedUnit);\n }\n}\n"],"mappings":"AAKA,IAAa,EAAb,KAA2D,CACzD,MAAgB,IAAI,IAEpB,YACE,EAIA,CAJiB,KAAA,UAAA,EAMnB,IAAI,EAAgB,EAAyC,CAC3D,IAAM,EAAM,KAAK,SAAS,EAAQ,EAAQ,CAEtC,EAAY,KAAK,MAAM,IAAI,EAAI,CAMnC,OALK,IACH,EAAY,IAAI,KAAK,UAAU,EAAQ,EAAQ,CAC/C,KAAK,MAAM,IAAI,EAAK,EAAU,EAGzB,EAGT,OAAc,CACZ,KAAK,MAAM,OAAO,CAGpB,SAAiB,EAAgB,EAAqC,CAGpE,OAFK,EAEE,GAAG,EAAO,GAAG,KAAK,UAAU,EAAQ,GAFtB,IC1BzB,MAAMA,EAAQ,IAAI,EAAe,KAAK,aAAa,CAEnD,IAAa,EAAb,KAA4B,CAC1B,OAAc,OACZ,EACA,EACA,EACA,EACQ,CACR,GAAM,CAAE,OAAQ,EAAG,GAAG,GAAkB,GAAW,EAAE,CAOrD,OANkBA,EAAM,IAAI,EAAQ,CAClC,GAAG,EACH,MAAO,WACP,WACD,CAAC,CAEe,OAAO,EAAM,GChBlC,MAAMC,EAAQ,IAAI,EAAe,KAAK,eAAe,CAErD,IAAa,EAAb,KAAwB,CACtB,OAAc,OACZ,EACA,EACA,EACQ,CACR,GAAM,CAAE,OAAQ,EAAG,GAAG,GAAkB,GAAW,EAAE,CAGrD,OAFkBA,EAAM,IAAI,EAAQ,EAAc,CAEjC,OAAO,EAAM,GCXlC,MAAMC,EAAQ,IAAI,EAAe,KAAK,WAAW,CAEjD,IAAa,EAAb,KAAwB,CACtB,OAAc,OACZ,EACA,EACA,EACQ,CACR,GAAM,CAAE,OAAQ,EAAG,GAAG,GAAkB,GAAW,EAAE,CAGrD,OAFkBA,EAAM,IAAI,EAAQ,EAAc,CAEjC,OAAO,EAAO,GCXnC,MAAMC,EAAQ,IAAI,EAAe,KAAK,aAAa,CAEnD,IAAa,EAAb,KAA0B,CACxB,OAAc,OACZ,EACA,EACA,EACQ,CACR,GAAM,CAAE,OAAQ,EAAG,GAAG,GAAkB,GAAW,EAAE,CAGrD,OAFkBA,EAAM,IAAI,EAAQ,EAAc,CAEjC,OAAO,EAAM,GCOlC,MAAM,EAAQ,IAAI,EAAe,KAAK,mBAAmB,CAEnD,EAAuE,CAC3E,KAAM,OACN,MAAO,OACP,QAAS,UACT,SAAU,UACV,MAAO,QACP,OAAQ,QACR,KAAM,OACN,MAAO,OACP,IAAK,MACL,KAAM,MACN,KAAM,OACN,MAAO,OACP,OAAQ,SACR,QAAS,SACT,OAAQ,SACR,QAAS,SACV,CAED,IAAa,EAAb,KAAgC,CAC9B,OAAc,OACZ,EACA,EACA,EACA,EACQ,CACR,GAAM,CAAE,OAAQ,EAAG,GAAG,GAAkB,GAAW,EAAE,CAC/C,EAAY,EAAM,IAAI,EAAQ,EAAc,CAC5C,EAAiB,EAAQ,GAE/B,OAAO,EAAU,OAAO,EAAO,EAAe"}
package/dist/i18n.d.mts CHANGED
@@ -45,4 +45,5 @@ declare class I18nInstance {
45
45
  }
46
46
  declare function createI18n(config: I18nConfig): I18nInstance;
47
47
  //#endregion
48
- export { FormatCurrency, FormatCurrencyOptions, FormatDate, FormatDateOptions, FormatList, FormatListOptions, FormatNumber, FormatNumberOptions, FormatRelativeTime, FormatRelativeTimeOptions, FormatRelativeTimeUnit, I18nConfig, I18nInstance, ILoader, createI18n };
48
+ export { FormatCurrency, FormatCurrencyOptions, FormatDate, FormatDateOptions, FormatList, FormatListOptions, FormatNumber, FormatNumberOptions, FormatRelativeTime, FormatRelativeTimeOptions, FormatRelativeTimeUnit, I18nConfig, I18nInstance, ILoader, createI18n };
49
+ //# sourceMappingURL=i18n.d.mts.map
package/dist/i18n.mjs CHANGED
@@ -1,2 +1,3 @@
1
1
  import{a as e,i as t,n,r,t as i}from"./formatters-Bvs6JDy4.mjs";var a=class{cache=new Map;maxSize;constructor(e=1e3){this.maxSize=e}get(e){return this.cache.get(e)}set(e,t){if(this.cache.size>=this.maxSize){let e=this.cache.keys().next().value;e&&this.cache.delete(e)}this.cache.set(e,t)}has(e){return this.cache.has(e)}clear(){this.cache.clear()}},o=class{extensions=[`.json`];parse(e){try{let t=JSON.parse(e);if(!this.isValidMessages(t))throw Error(`Invalid messages format!`);return t}catch(e){throw e instanceof SyntaxError?Error(`Failed to parse JSON: ${e.message}.`):e}}isValidMessages(e){return typeof e!=`object`||!e||Array.isArray(e)?!1:Object.values(e).every(e=>typeof e==`string`)}},s=class{messages=new Map;get(e){return this.messages.get(e)}set(e,t){this.messages.set(e,t)}has(e){return this.messages.has(e)}getLocales(){return Array.from(this.messages.keys())}getMessage(e,t){let n=this.get(e);if(n)return n[t]}},c=class{pluralRules;constructor(e){this.pluralRules=new Intl.PluralRules(e)}parse(e,t={}){let n=e.trim();return n.startsWith(`.match`)?this.parseMatch(n,t):this.interpolate(n,t)}parseMatch(e,t){let n=this.parseMatchSyntax(e);if(!n.selectors?.length||!n.variants?.length)return e;let r=this.selectVariant(n.selectors,n.variants,t);return this.interpolate(r,t)}parseMatchSyntax(e){let t=e.split(`
2
- `).map(e=>e.trim()).filter(Boolean);if(t.length===0||!t[0]?.startsWith(`.match`))return{type:`simple`,pattern:e};let n=t[0];return{type:`match`,selectors:this.parseSelectors(n),variants:this.parseVariants(t.slice(1))}}parseSelectors(e){let t=[],n=/\{\s*\$(\w+)(?:\s+:(\w+))?\s*\}/g,r;for(;(r=n.exec(e))!==null;){let[,e,n]=r;e&&t.push({variable:e,function:n})}return t}parseVariants(e){let t=[],n=/^([\w\s*]+)\s*\{\{(.+?)\}\}$/;for(let r of e){let e=n.exec(r);if(!e)continue;let[,i,a]=e;if(!i||!a)continue;let o=i.trim().split(/\s+/),s=a.trim();t.push({keys:o,pattern:s})}return t}selectVariant(e,t,n){let r=e.map(e=>{let t=n[e.variable];return e.function===`number`&&typeof t==`number`?this.pluralRules.select(t):String(t??`*`)});for(let e of t)if(this.matchesVariant(e.keys,r))return e.pattern;return t.find(e=>e.keys.every(e=>e===`*`))?.pattern??``}matchesVariant(e,t){return e.length===t.length?e.every((e,n)=>e===`*`||e===t[n]):!1}interpolate(e,t){return e.replace(/\{\s*\$(\w+)\s*\}/g,(e,n)=>{let r=t[n];return r===void 0?`{$${n}}`:String(r)})}},l=class{locale;fallbackLocale;messagesManager;messageCache;loaders;mf2ParserByLocale;localeChangeListeners=new Set;constructor(e){let{locale:t,fallbackLocale:n,loaders:r,messages:i}=e;this.locale=t,this.fallbackLocale=n,this.messagesManager=new s,this.messageCache=new a,this.mf2ParserByLocale=new Map,this.loaders=[new o,...r??[]],i&&Object.entries(i).forEach(([e,t])=>{this.messagesManager.set(e,t)})}getLocale(){return this.locale}setLocale(e){this.locale=e,this.messageCache.clear(),this.localeChangeListeners.forEach(t=>t(e))}onLocaleChange(e){return this.localeChangeListeners.add(e),()=>{this.localeChangeListeners.delete(e)}}getFallbackLocale(){return this.fallbackLocale}getLocales(){return this.messagesManager.getLocales()}translate(e,t){let n=`${this.locale}:${e}:${JSON.stringify(t??{})}`;if(this.messageCache.has(n))return this.messageCache.get(n);let r=this.messagesManager.getMessage(this.locale,e);if(!r&&this.fallbackLocale&&(r=this.messagesManager.getMessage(this.fallbackLocale,e)),!r)return e;let i=this.getParser(this.locale).parse(r,t);return this.messageCache.set(n,i),i}formatNumber(e,t){let r=t?.locale??this.locale;return n.format(e,r,t)}formatCurrency(t,n,r){let i=r?.locale??this.locale;return e.format(t,n,i,r)}formatDate(e,n){let r=n?.locale??this.locale;return t.format(e,r,n)}formatList(e,t){let n=t?.locale??this.locale;return r.format(e,n,t)}formatRelativeTime(e,t,n){let r=n?.locale??this.locale;return i.format(e,t,r,n)}loadMessages(e,t){this.messagesManager.set(e,t),this.messageCache.clear()}async loadMessagesAsync(e){let t=await fetch(e);if(!t.ok)throw Error(`Failed to load messages from ${e}: ${t.statusText}.`);let n=await t.text(),r=this.findLoader(e);if(!r)throw Error(`No loader found for ${e}.`);let i=r.parse(n),a=this.extractLocaleFromUrl(e);this.loadMessages(a,i)}getParser(e){let t=this.mf2ParserByLocale.get(e);return t||(t=new c(e),this.mf2ParserByLocale.set(e,t)),t}findLoader(e){let t=this.getExtension(e);return this.loaders.find(e=>e.extensions.includes(t))}getExtension(e){return e.match(/\.[^.]+$/)?.[0]??``}extractLocaleFromUrl(e){let t=(e.split(`/`).pop()??``).replace(/\.[^.]+$/,``);try{let[e]=Intl.getCanonicalLocales(t);return e??this.locale}catch{return this.locale}}};function u(e){return new l(e)}export{e as FormatCurrency,t as FormatDate,r as FormatList,n as FormatNumber,i as FormatRelativeTime,l as I18nInstance,u as createI18n};
2
+ `).map(e=>e.trim()).filter(Boolean);if(t.length===0||!t[0]?.startsWith(`.match`))return{type:`simple`,pattern:e};let n=t[0];return{type:`match`,selectors:this.parseSelectors(n),variants:this.parseVariants(t.slice(1))}}parseSelectors(e){let t=[],n=/\{\s*\$(\w+)(?:\s+:(\w+))?\s*\}/g,r;for(;(r=n.exec(e))!==null;){let[,e,n]=r;e&&t.push({variable:e,function:n})}return t}parseVariants(e){let t=[],n=/^([\w\s*]+)\s*\{\{(.+?)\}\}$/;for(let r of e){let e=n.exec(r);if(!e)continue;let[,i,a]=e;if(!i||!a)continue;let o=i.trim().split(/\s+/),s=a.trim();t.push({keys:o,pattern:s})}return t}selectVariant(e,t,n){let r=e.map(e=>{let t=n[e.variable];return e.function===`number`&&typeof t==`number`?this.pluralRules.select(t):String(t??`*`)});for(let e of t)if(this.matchesVariant(e.keys,r))return e.pattern;return t.find(e=>e.keys.every(e=>e===`*`))?.pattern??``}matchesVariant(e,t){return e.length===t.length?e.every((e,n)=>e===`*`||e===t[n]):!1}interpolate(e,t){return e.replace(/\{\s*\$(\w+)\s*\}/g,(e,n)=>{let r=t[n];return r===void 0?`{$${n}}`:String(r)})}},l=class{locale;fallbackLocale;messagesManager;messageCache;loaders;mf2ParserByLocale;localeChangeListeners=new Set;constructor(e){let{locale:t,fallbackLocale:n,loaders:r,messages:i}=e;this.locale=t,this.fallbackLocale=n,this.messagesManager=new s,this.messageCache=new a,this.mf2ParserByLocale=new Map,this.loaders=[new o,...r??[]],i&&Object.entries(i).forEach(([e,t])=>{this.messagesManager.set(e,t)})}getLocale(){return this.locale}setLocale(e){this.locale=e,this.messageCache.clear(),this.localeChangeListeners.forEach(t=>t(e))}onLocaleChange(e){return this.localeChangeListeners.add(e),()=>{this.localeChangeListeners.delete(e)}}getFallbackLocale(){return this.fallbackLocale}getLocales(){return this.messagesManager.getLocales()}translate(e,t){let n=`${this.locale}:${e}:${JSON.stringify(t??{})}`;if(this.messageCache.has(n))return this.messageCache.get(n);let r=this.messagesManager.getMessage(this.locale,e);if(!r&&this.fallbackLocale&&(r=this.messagesManager.getMessage(this.fallbackLocale,e)),!r)return e;let i=this.getParser(this.locale).parse(r,t);return this.messageCache.set(n,i),i}formatNumber(e,t){let r=t?.locale??this.locale;return n.format(e,r,t)}formatCurrency(t,n,r){let i=r?.locale??this.locale;return e.format(t,n,i,r)}formatDate(e,n){let r=n?.locale??this.locale;return t.format(e,r,n)}formatList(e,t){let n=t?.locale??this.locale;return r.format(e,n,t)}formatRelativeTime(e,t,n){let r=n?.locale??this.locale;return i.format(e,t,r,n)}loadMessages(e,t){this.messagesManager.set(e,t),this.messageCache.clear()}async loadMessagesAsync(e){let t=await fetch(e);if(!t.ok)throw Error(`Failed to load messages from ${e}: ${t.statusText}.`);let n=await t.text(),r=this.findLoader(e);if(!r)throw Error(`No loader found for ${e}.`);let i=r.parse(n),a=this.extractLocaleFromUrl(e);this.loadMessages(a,i)}getParser(e){let t=this.mf2ParserByLocale.get(e);return t||(t=new c(e),this.mf2ParserByLocale.set(e,t)),t}findLoader(e){let t=this.getExtension(e);return this.loaders.find(e=>e.extensions.includes(t))}getExtension(e){return e.match(/\.[^.]+$/)?.[0]??``}extractLocaleFromUrl(e){let t=(e.split(`/`).pop()??``).replace(/\.[^.]+$/,``);try{let[e]=Intl.getCanonicalLocales(t);return e??this.locale}catch{return this.locale}}};function u(e){return new l(e)}export{e as FormatCurrency,t as FormatDate,r as FormatList,n as FormatNumber,i as FormatRelativeTime,l as I18nInstance,u as createI18n};
3
+ //# sourceMappingURL=i18n.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.mjs","names":[],"sources":["../src/caches/message.ts","../src/loaders/json-loader.ts","../src/messages/messages.ts","../src/parsers/mf2-parser.ts","../src/i18n.ts"],"sourcesContent":["export class MessageCache {\n private cache = new Map<string, string>();\n private maxSize: number;\n\n constructor(maxSize: number = 1000) {\n this.maxSize = maxSize;\n }\n\n get(key: string): string | undefined {\n return this.cache.get(key);\n }\n\n set(key: string, value: string): void {\n if (this.cache.size >= this.maxSize) {\n const firstKey = this.cache.keys().next().value;\n\n if (firstKey) {\n this.cache.delete(firstKey);\n }\n }\n\n this.cache.set(key, value);\n }\n\n has(key: string): boolean {\n return this.cache.has(key);\n }\n\n clear(): void {\n this.cache.clear();\n }\n}\n","import type { Messages } from '../messages/messages.ts';\nimport type { ILoader } from './loader.interface.ts';\n\nexport class JsonLoader implements ILoader {\n readonly extensions = ['.json'];\n\n parse(content: string): Messages {\n try {\n const parsed = JSON.parse(content) as unknown;\n\n if (!this.isValidMessages(parsed)) {\n throw new Error('Invalid messages format!');\n }\n\n return parsed;\n } catch (error) {\n if (error instanceof SyntaxError) {\n throw new Error(`Failed to parse JSON: ${error.message}.`);\n }\n\n throw error;\n }\n }\n\n private isValidMessages(value: unknown): value is Messages {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return false;\n }\n\n return Object.values(value).every((val) => typeof val === 'string');\n }\n}\n","export type Messages = Record<string, string>;\n\nexport class MessagesManager {\n private messages = new Map<string, Messages>();\n\n public get(locale: string): Messages | undefined {\n return this.messages.get(locale);\n }\n\n public set(locale: string, messages: Messages): void {\n this.messages.set(locale, messages);\n }\n\n public has(locale: string): boolean {\n return this.messages.has(locale);\n }\n\n public getLocales(): string[] {\n return Array.from(this.messages.keys());\n }\n\n public getMessage(locale: string, key: string): string | undefined {\n const dictionary = this.get(locale);\n if (!dictionary) return;\n\n return dictionary[key];\n }\n}\n","import type { IParser } from './parser.interface.ts';\n\ntype MF2ParsedMessage = {\n type: 'simple' | 'match';\n pattern?: string;\n selectors?: MF2Selector[];\n variants?: MF2Variant[];\n};\n\ntype MF2Selector = {\n variable: string;\n function: string | undefined;\n};\n\ntype MF2Variant = {\n keys: string[];\n pattern: string;\n};\n\n// TODO: use messageformat npm package\nexport class MF2Parser implements IParser {\n private pluralRules: Intl.PluralRules;\n\n constructor(locale: string) {\n this.pluralRules = new Intl.PluralRules(locale);\n }\n\n public parse(message: string, values: Record<string, unknown> = {}): string {\n const trimmed = message.trim();\n\n if (trimmed.startsWith('.match')) {\n return this.parseMatch(trimmed, values);\n }\n\n return this.interpolate(trimmed, values);\n }\n\n private parseMatch(message: string, values: Record<string, unknown>): string {\n const parsed = this.parseMatchSyntax(message);\n if (!parsed.selectors?.length || !parsed.variants?.length) {\n return message;\n }\n\n const selectedVariant = this.selectVariant(\n parsed.selectors,\n parsed.variants,\n values\n );\n\n return this.interpolate(selectedVariant, values);\n }\n\n private parseMatchSyntax(message: string): MF2ParsedMessage {\n const lines = message\n .split('\\n')\n .map((line) => line.trim())\n .filter(Boolean);\n\n if (lines.length === 0 || !lines[0]?.startsWith('.match')) {\n return { type: 'simple', pattern: message };\n }\n\n const matchLine = lines[0];\n const selectors = this.parseSelectors(matchLine);\n const variants = this.parseVariants(lines.slice(1));\n\n return {\n type: 'match',\n selectors,\n variants,\n };\n }\n\n private parseSelectors(matchLine: string): MF2Selector[] {\n const selectors: MF2Selector[] = [];\n const selectorRegex = /\\{\\s*\\$(\\w+)(?:\\s+:(\\w+))?\\s*\\}/g;\n\n let match;\n while ((match = selectorRegex.exec(matchLine)) !== null) {\n const [, variable, fn] = match;\n if (!variable) continue;\n\n selectors.push({\n variable,\n function: fn,\n });\n }\n\n return selectors;\n }\n\n private parseVariants(lines: string[]): MF2Variant[] {\n const variants: MF2Variant[] = [];\n const variantRegex = /^([\\w\\s*]+)\\s*\\{\\{(.+?)\\}\\}$/;\n\n for (const line of lines) {\n const match = variantRegex.exec(line);\n if (!match) continue;\n\n const [, keys, pattern] = match;\n if (!keys || !pattern) continue;\n\n const normalizedKeys = keys.trim().split(/\\s+/);\n const normalizedPattern = pattern.trim();\n variants.push({ keys: normalizedKeys, pattern: normalizedPattern });\n }\n\n return variants;\n }\n\n private selectVariant(\n selectors: MF2Selector[],\n variants: MF2Variant[],\n values: Record<string, unknown>\n ): string {\n const resolvedKeys = selectors.map((selector) => {\n const value = values[selector.variable];\n\n if (selector.function === 'number' && typeof value === 'number') {\n return this.pluralRules.select(value);\n }\n\n return String(value ?? '*');\n });\n\n for (const variant of variants) {\n if (this.matchesVariant(variant.keys, resolvedKeys)) {\n return variant.pattern;\n }\n }\n\n const fallback = variants.find((variant) =>\n variant.keys.every((key) => key === '*')\n );\n\n return fallback?.pattern ?? '';\n }\n\n private matchesVariant(\n variantKeys: string[],\n resolvedKeys: string[]\n ): boolean {\n if (variantKeys.length !== resolvedKeys.length) return false;\n\n return variantKeys.every((key, index) => {\n return key === '*' || key === resolvedKeys[index];\n });\n }\n\n private interpolate(\n pattern: string,\n values: Record<string, unknown>\n ): string {\n const regex = /\\{\\s*\\$(\\w+)\\s*\\}/g;\n\n return pattern.replace(regex, (_, key: string) => {\n const value = values[key];\n\n return value !== undefined ? String(value) : `{$${key}}`;\n });\n }\n}\n","import { MessageCache } from './caches/message.ts';\nimport {\n FormatCurrency,\n type FormatCurrencyOptions,\n} from './formatters/currency.ts';\nimport { FormatDate, type FormatDateOptions } from './formatters/date.ts';\nimport { FormatList, type FormatListOptions } from './formatters/list.ts';\nimport { FormatNumber, type FormatNumberOptions } from './formatters/number.ts';\nimport {\n FormatRelativeTime,\n type FormatRelativeTimeOptions,\n type FormatRelativeTimeUnit,\n} from './formatters/relative-time.ts';\nimport { JsonLoader } from './loaders/json-loader.ts';\nimport type { ILoader } from './loaders/loader.interface.ts';\nimport { type Messages, MessagesManager } from './messages/messages.ts';\nimport { MF2Parser } from './parsers/mf2-parser.ts';\n\nexport type I18nConfig = {\n locale: string;\n fallbackLocale?: string;\n messages?: Record<string, Messages>;\n loaders?: ILoader[];\n};\n\nexport class I18nInstance {\n private locale: string;\n private fallbackLocale: string | undefined;\n private messagesManager: MessagesManager;\n private messageCache: MessageCache;\n private loaders: ILoader[];\n private mf2ParserByLocale: Map<string, MF2Parser>;\n private localeChangeListeners = new Set<(locale: string) => void>();\n\n constructor(config: I18nConfig) {\n const { locale, fallbackLocale, loaders, messages } = config;\n\n this.locale = locale;\n this.fallbackLocale = fallbackLocale;\n this.messagesManager = new MessagesManager();\n this.messageCache = new MessageCache();\n this.mf2ParserByLocale = new Map();\n this.loaders = [new JsonLoader(), ...(loaders ?? [])];\n\n if (messages) {\n Object.entries(messages).forEach(([locale, messagesLocale]) => {\n this.messagesManager.set(locale, messagesLocale);\n });\n }\n }\n\n public getLocale(): string {\n return this.locale;\n }\n\n public setLocale(locale: string): void {\n this.locale = locale;\n this.messageCache.clear();\n this.localeChangeListeners.forEach((listener) => listener(locale));\n }\n\n public onLocaleChange(listener: (locale: string) => void): () => void {\n this.localeChangeListeners.add(listener);\n\n return () => {\n this.localeChangeListeners.delete(listener);\n };\n }\n\n public getFallbackLocale(): string | undefined {\n return this.fallbackLocale;\n }\n\n public getLocales(): string[] {\n return this.messagesManager.getLocales();\n }\n\n public translate(key: string, values?: Record<string, unknown>): string {\n const cacheKey = `${this.locale}:${key}:${JSON.stringify(values ?? {})}`;\n\n if (this.messageCache.has(cacheKey)) {\n return this.messageCache.get(cacheKey)!;\n }\n\n let message = this.messagesManager.getMessage(this.locale, key);\n if (!message && this.fallbackLocale) {\n message = this.messagesManager.getMessage(this.fallbackLocale, key);\n }\n\n if (!message) {\n return key;\n }\n\n const parser = this.getParser(this.locale);\n const result = parser.parse(message, values);\n\n this.messageCache.set(cacheKey, result);\n return result;\n }\n\n public formatNumber(value: number, options?: FormatNumberOptions): string {\n const locale = options?.locale ?? this.locale;\n\n return FormatNumber.format(value, locale, options);\n }\n\n public formatCurrency(\n value: number,\n currency: string,\n options?: FormatCurrencyOptions\n ): string {\n const locale = options?.locale ?? this.locale;\n\n return FormatCurrency.format(value, currency, locale, options);\n }\n\n public formatDate(value: Date | number, options?: FormatDateOptions): string {\n const locale = options?.locale ?? this.locale;\n\n return FormatDate.format(value, locale, options);\n }\n\n public formatList(values: string[], options?: FormatListOptions): string {\n const locale = options?.locale ?? this.locale;\n\n return FormatList.format(values, locale, options);\n }\n\n public formatRelativeTime(\n value: number,\n unit: FormatRelativeTimeUnit,\n options?: FormatRelativeTimeOptions\n ): string {\n const locale = options?.locale ?? this.locale;\n\n return FormatRelativeTime.format(value, unit, locale, options);\n }\n\n public loadMessages(locale: string, messages: Messages): void {\n this.messagesManager.set(locale, messages);\n this.messageCache.clear();\n }\n\n public async loadMessagesAsync(url: string): Promise<void> {\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new Error(\n `Failed to load messages from ${url}: ${response.statusText}.`\n );\n }\n\n const content = await response.text();\n const loader = this.findLoader(url);\n\n if (!loader) {\n throw new Error(`No loader found for ${url}.`);\n }\n\n const messages = loader.parse(content);\n const locale = this.extractLocaleFromUrl(url);\n\n this.loadMessages(locale, messages);\n }\n\n private getParser(locale: string): MF2Parser {\n let parser = this.mf2ParserByLocale.get(locale);\n if (!parser) {\n parser = new MF2Parser(locale);\n this.mf2ParserByLocale.set(locale, parser);\n }\n\n return parser;\n }\n\n private findLoader(url: string): ILoader | undefined {\n const extension = this.getExtension(url);\n\n return this.loaders.find((loader) => loader.extensions.includes(extension));\n }\n\n private getExtension(url: string): string {\n const match = url.match(/\\.[^.]+$/);\n\n return match?.[0] ?? '';\n }\n\n private extractLocaleFromUrl(url: string): string {\n const filename = url.split('/').pop() ?? '';\n const name = filename.replace(/\\.[^.]+$/, '');\n\n try {\n const [canonical] = Intl.getCanonicalLocales(name);\n return canonical ?? this.locale;\n } catch {\n return this.locale;\n }\n }\n}\n\nexport function createI18n(config: I18nConfig): I18nInstance {\n return new I18nInstance(config);\n}\n\nexport * from './formatters/formatters.ts';\nexport type * from './loaders/loader.interface.ts';\n"],"mappings":"gEAAA,IAAa,EAAb,KAA0B,CACxB,MAAgB,IAAI,IACpB,QAEA,YAAY,EAAkB,IAAM,CAClC,KAAK,QAAU,EAGjB,IAAI,EAAiC,CACnC,OAAO,KAAK,MAAM,IAAI,EAAI,CAG5B,IAAI,EAAa,EAAqB,CACpC,GAAI,KAAK,MAAM,MAAQ,KAAK,QAAS,CACnC,IAAM,EAAW,KAAK,MAAM,MAAM,CAAC,MAAM,CAAC,MAEtC,GACF,KAAK,MAAM,OAAO,EAAS,CAI/B,KAAK,MAAM,IAAI,EAAK,EAAM,CAG5B,IAAI,EAAsB,CACxB,OAAO,KAAK,MAAM,IAAI,EAAI,CAG5B,OAAc,CACZ,KAAK,MAAM,OAAO,GC1BT,EAAb,KAA2C,CACzC,WAAsB,CAAC,QAAQ,CAE/B,MAAM,EAA2B,CAC/B,GAAI,CACF,IAAM,EAAS,KAAK,MAAM,EAAQ,CAElC,GAAI,CAAC,KAAK,gBAAgB,EAAO,CAC/B,MAAU,MAAM,2BAA2B,CAG7C,OAAO,QACA,EAAO,CAKd,MAJI,aAAiB,YACT,MAAM,yBAAyB,EAAM,QAAQ,GAAG,CAGtD,GAIV,gBAAwB,EAAmC,CAKzD,OAJI,OAAO,GAAU,WAAY,GAAkB,MAAM,QAAQ,EAAM,CAC9D,GAGF,OAAO,OAAO,EAAM,CAAC,MAAO,GAAQ,OAAO,GAAQ,SAAS,GC3B1D,EAAb,KAA6B,CAC3B,SAAmB,IAAI,IAEvB,IAAW,EAAsC,CAC/C,OAAO,KAAK,SAAS,IAAI,EAAO,CAGlC,IAAW,EAAgB,EAA0B,CACnD,KAAK,SAAS,IAAI,EAAQ,EAAS,CAGrC,IAAW,EAAyB,CAClC,OAAO,KAAK,SAAS,IAAI,EAAO,CAGlC,YAA8B,CAC5B,OAAO,MAAM,KAAK,KAAK,SAAS,MAAM,CAAC,CAGzC,WAAkB,EAAgB,EAAiC,CACjE,IAAM,EAAa,KAAK,IAAI,EAAO,CAC9B,KAEL,OAAO,EAAW,KCLT,EAAb,KAA0C,CACxC,YAEA,YAAY,EAAgB,CAC1B,KAAK,YAAc,IAAI,KAAK,YAAY,EAAO,CAGjD,MAAa,EAAiB,EAAkC,EAAE,CAAU,CAC1E,IAAM,EAAU,EAAQ,MAAM,CAM9B,OAJI,EAAQ,WAAW,SAAS,CACvB,KAAK,WAAW,EAAS,EAAO,CAGlC,KAAK,YAAY,EAAS,EAAO,CAG1C,WAAmB,EAAiB,EAAyC,CAC3E,IAAM,EAAS,KAAK,iBAAiB,EAAQ,CAC7C,GAAI,CAAC,EAAO,WAAW,QAAU,CAAC,EAAO,UAAU,OACjD,OAAO,EAGT,IAAM,EAAkB,KAAK,cAC3B,EAAO,UACP,EAAO,SACP,EACD,CAED,OAAO,KAAK,YAAY,EAAiB,EAAO,CAGlD,iBAAyB,EAAmC,CAC1D,IAAM,EAAQ,EACX,MAAM;EAAK,CACX,IAAK,GAAS,EAAK,MAAM,CAAC,CAC1B,OAAO,QAAQ,CAElB,GAAI,EAAM,SAAW,GAAK,CAAC,EAAM,IAAI,WAAW,SAAS,CACvD,MAAO,CAAE,KAAM,SAAU,QAAS,EAAS,CAG7C,IAAM,EAAY,EAAM,GAIxB,MAAO,CACL,KAAM,QACN,UALgB,KAAK,eAAe,EAAU,CAM9C,SALe,KAAK,cAAc,EAAM,MAAM,EAAE,CAAC,CAMlD,CAGH,eAAuB,EAAkC,CACvD,IAAM,EAA2B,EAAE,CAC7B,EAAgB,mCAElB,EACJ,MAAQ,EAAQ,EAAc,KAAK,EAAU,IAAM,MAAM,CACvD,GAAM,EAAG,EAAU,GAAM,EACpB,GAEL,EAAU,KAAK,CACb,WACA,SAAU,EACX,CAAC,CAGJ,OAAO,EAGT,cAAsB,EAA+B,CACnD,IAAM,EAAyB,EAAE,CAC3B,EAAe,+BAErB,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAQ,EAAa,KAAK,EAAK,CACrC,GAAI,CAAC,EAAO,SAEZ,GAAM,EAAG,EAAM,GAAW,EAC1B,GAAI,CAAC,GAAQ,CAAC,EAAS,SAEvB,IAAM,EAAiB,EAAK,MAAM,CAAC,MAAM,MAAM,CACzC,EAAoB,EAAQ,MAAM,CACxC,EAAS,KAAK,CAAE,KAAM,EAAgB,QAAS,EAAmB,CAAC,CAGrE,OAAO,EAGT,cACE,EACA,EACA,EACQ,CACR,IAAM,EAAe,EAAU,IAAK,GAAa,CAC/C,IAAM,EAAQ,EAAO,EAAS,UAM9B,OAJI,EAAS,WAAa,UAAY,OAAO,GAAU,SAC9C,KAAK,YAAY,OAAO,EAAM,CAGhC,OAAO,GAAS,IAAI,EAC3B,CAEF,IAAK,IAAM,KAAW,EACpB,GAAI,KAAK,eAAe,EAAQ,KAAM,EAAa,CACjD,OAAO,EAAQ,QAQnB,OAJiB,EAAS,KAAM,GAC9B,EAAQ,KAAK,MAAO,GAAQ,IAAQ,IAAI,CACzC,EAEgB,SAAW,GAG9B,eACE,EACA,EACS,CAGT,OAFI,EAAY,SAAW,EAAa,OAEjC,EAAY,OAAO,EAAK,IACtB,IAAQ,KAAO,IAAQ,EAAa,GAC3C,CAJqD,GAOzD,YACE,EACA,EACQ,CAGR,OAAO,EAAQ,QAFD,sBAEiB,EAAG,IAAgB,CAChD,IAAM,EAAQ,EAAO,GAErB,OAAO,IAAU,IAAA,GAA4B,KAAK,EAAI,GAAzB,OAAO,EAAM,EAC1C,GCtIO,EAAb,KAA0B,CACxB,OACA,eACA,gBACA,aACA,QACA,kBACA,sBAAgC,IAAI,IAEpC,YAAY,EAAoB,CAC9B,GAAM,CAAE,SAAQ,iBAAgB,UAAS,YAAa,EAEtD,KAAK,OAAS,EACd,KAAK,eAAiB,EACtB,KAAK,gBAAkB,IAAI,EAC3B,KAAK,aAAe,IAAI,EACxB,KAAK,kBAAoB,IAAI,IAC7B,KAAK,QAAU,CAAC,IAAI,EAAc,GAAI,GAAW,EAAE,CAAE,CAEjD,GACF,OAAO,QAAQ,EAAS,CAAC,SAAS,CAAC,EAAQ,KAAoB,CAC7D,KAAK,gBAAgB,IAAI,EAAQ,EAAe,EAChD,CAIN,WAA2B,CACzB,OAAO,KAAK,OAGd,UAAiB,EAAsB,CACrC,KAAK,OAAS,EACd,KAAK,aAAa,OAAO,CACzB,KAAK,sBAAsB,QAAS,GAAa,EAAS,EAAO,CAAC,CAGpE,eAAsB,EAAgD,CAGpE,OAFA,KAAK,sBAAsB,IAAI,EAAS,KAE3B,CACX,KAAK,sBAAsB,OAAO,EAAS,EAI/C,mBAA+C,CAC7C,OAAO,KAAK,eAGd,YAA8B,CAC5B,OAAO,KAAK,gBAAgB,YAAY,CAG1C,UAAiB,EAAa,EAA0C,CACtE,IAAM,EAAW,GAAG,KAAK,OAAO,GAAG,EAAI,GAAG,KAAK,UAAU,GAAU,EAAE,CAAC,GAEtE,GAAI,KAAK,aAAa,IAAI,EAAS,CACjC,OAAO,KAAK,aAAa,IAAI,EAAS,CAGxC,IAAI,EAAU,KAAK,gBAAgB,WAAW,KAAK,OAAQ,EAAI,CAK/D,GAJI,CAAC,GAAW,KAAK,iBACnB,EAAU,KAAK,gBAAgB,WAAW,KAAK,eAAgB,EAAI,EAGjE,CAAC,EACH,OAAO,EAIT,IAAM,EADS,KAAK,UAAU,KAAK,OAAO,CACpB,MAAM,EAAS,EAAO,CAG5C,OADA,KAAK,aAAa,IAAI,EAAU,EAAO,CAChC,EAGT,aAAoB,EAAe,EAAuC,CACxE,IAAM,EAAS,GAAS,QAAU,KAAK,OAEvC,OAAO,EAAa,OAAO,EAAO,EAAQ,EAAQ,CAGpD,eACE,EACA,EACA,EACQ,CACR,IAAM,EAAS,GAAS,QAAU,KAAK,OAEvC,OAAO,EAAe,OAAO,EAAO,EAAU,EAAQ,EAAQ,CAGhE,WAAkB,EAAsB,EAAqC,CAC3E,IAAM,EAAS,GAAS,QAAU,KAAK,OAEvC,OAAO,EAAW,OAAO,EAAO,EAAQ,EAAQ,CAGlD,WAAkB,EAAkB,EAAqC,CACvE,IAAM,EAAS,GAAS,QAAU,KAAK,OAEvC,OAAO,EAAW,OAAO,EAAQ,EAAQ,EAAQ,CAGnD,mBACE,EACA,EACA,EACQ,CACR,IAAM,EAAS,GAAS,QAAU,KAAK,OAEvC,OAAO,EAAmB,OAAO,EAAO,EAAM,EAAQ,EAAQ,CAGhE,aAAoB,EAAgB,EAA0B,CAC5D,KAAK,gBAAgB,IAAI,EAAQ,EAAS,CAC1C,KAAK,aAAa,OAAO,CAG3B,MAAa,kBAAkB,EAA4B,CACzD,IAAM,EAAW,MAAM,MAAM,EAAI,CAEjC,GAAI,CAAC,EAAS,GACZ,MAAU,MACR,gCAAgC,EAAI,IAAI,EAAS,WAAW,GAC7D,CAGH,IAAM,EAAU,MAAM,EAAS,MAAM,CAC/B,EAAS,KAAK,WAAW,EAAI,CAEnC,GAAI,CAAC,EACH,MAAU,MAAM,uBAAuB,EAAI,GAAG,CAGhD,IAAM,EAAW,EAAO,MAAM,EAAQ,CAChC,EAAS,KAAK,qBAAqB,EAAI,CAE7C,KAAK,aAAa,EAAQ,EAAS,CAGrC,UAAkB,EAA2B,CAC3C,IAAI,EAAS,KAAK,kBAAkB,IAAI,EAAO,CAM/C,OALK,IACH,EAAS,IAAI,EAAU,EAAO,CAC9B,KAAK,kBAAkB,IAAI,EAAQ,EAAO,EAGrC,EAGT,WAAmB,EAAkC,CACnD,IAAM,EAAY,KAAK,aAAa,EAAI,CAExC,OAAO,KAAK,QAAQ,KAAM,GAAW,EAAO,WAAW,SAAS,EAAU,CAAC,CAG7E,aAAqB,EAAqB,CAGxC,OAFc,EAAI,MAAM,WAAW,GAEpB,IAAM,GAGvB,qBAA6B,EAAqB,CAEhD,IAAM,GADW,EAAI,MAAM,IAAI,CAAC,KAAK,EAAI,IACnB,QAAQ,WAAY,GAAG,CAE7C,GAAI,CACF,GAAM,CAAC,GAAa,KAAK,oBAAoB,EAAK,CAClD,OAAO,GAAa,KAAK,YACnB,CACN,OAAO,KAAK,UAKlB,SAAgB,EAAW,EAAkC,CAC3D,OAAO,IAAI,EAAa,EAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sehv-oss/i18n",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Modern i18n library for JavaScript. Web standards based, runs everywhere.",
5
5
  "type": "module",
6
6
  "files": [
@@ -32,6 +32,10 @@
32
32
  "url": "https://github.com/sehv-oss/i18n.git",
33
33
  "directory": "packages/core"
34
34
  },
35
+ "homepage": "https://sehv-oss.github.io/i18n/",
36
+ "bugs": {
37
+ "url": "https://github.com/sehv-oss/i18n/issues"
38
+ },
35
39
  "engines": {
36
40
  "node": ">=22 <25"
37
41
  },