@minejs/i18n 0.1.2 → 0.1.4

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/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
  </div>
9
9
 
10
10
  <div align="center">
11
- <img src="https://img.shields.io/badge/v-0.1.2-black"/>
11
+ <img src="https://img.shields.io/badge/v-0.1.4-black"/>
12
12
  <a href="https://github.com/minejs-org"><img src="https://img.shields.io/badge/🔥-@minejs-black"/></a>
13
13
  <br>
14
14
  <img src="https://img.shields.io/badge/coverage-94.12%25-brightgreen" alt="Test Coverage" />
@@ -204,7 +204,7 @@
204
204
  });
205
205
  ```
206
206
 
207
- - #### `t(key, params?)`
207
+ - #### `t(key, params?, fallback?)`
208
208
 
209
209
  > Translate with parameter replacement
210
210
 
@@ -212,7 +212,7 @@
212
212
  t('group.greeting', { name: 'John' }) // "Hello John"
213
213
  ```
214
214
 
215
- - #### `tLang(key, lang, params?)`
215
+ - #### `tLang(lang, key, params?, fallback?)`
216
216
 
217
217
  > Translate with specific language
218
218
 
@@ -220,7 +220,7 @@
220
220
  tLang('group.greeting', 'ar', { name: 'أحمد' })
221
221
  ```
222
222
 
223
- - #### `tParse(key, params?)`
223
+ - #### `tParse(key, params?, fallback?)`
224
224
 
225
225
  > Parse translation with HTML tags
226
226
 
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- 'use strict';Object.defineProperty(exports,'__esModule',{value:true});var u=class{constructor(e){this.translations={};this.currentLanguage="en";this.defaultLanguage="en";this.supportedLanguages=new Set(["en"]);this.rtlLanguages=new Set(["ar","he","fa","ur","yi","ji","iw","ku"]);this.listeners=new Set;e&&(this.defaultLanguage=e.defaultLanguage||"en",this.currentLanguage=e.defaultLanguage||"en",this.storage=e.storage,this.onLanguageChange=e.onLanguageChange,e.supportedLanguages&&(this.supportedLanguages=new Set(e.supportedLanguages)));}async init(){if(this.storage){let e=await this.storage.get("lang");e&&this.supportedLanguages.has(e)&&(this.currentLanguage=e);}}loadLanguage(e,t){this.translations[e]||(this.translations[e]={});let a=this.flattenObject(t);this.translations[e]={...this.translations[e],...a},this.supportedLanguages.add(e);}loadTranslations(e){Object.entries(e).forEach(([t,a])=>{this.loadLanguage(t,a);});}flattenObject(e,t=""){let a={};for(let s in e){if(!Object.prototype.hasOwnProperty.call(e,s))continue;let i=e[s],r=t?`${t}.${s}`:s;typeof i=="object"&&i!==null&&!Array.isArray(i)?Object.assign(a,this.flattenObject(i,r)):a[r]=String(i);}return a}t(e,t){let a=this.getTranslation(e);return t&&Object.entries(t).forEach(([s,i])=>{let r=this.getTranslation(i,i);a=a.replace(new RegExp(`\\{${s}\\}`,"g"),r);}),a}getTranslation(e,t){return this.translations[this.currentLanguage]?.[e]?this.translations[this.currentLanguage][e]:this.defaultLanguage!==this.currentLanguage&&this.translations[this.defaultLanguage]?.[e]?this.translations[this.defaultLanguage][e]:(console.warn(`[i18n] Translation key not found: "${e}" (lang: ${this.currentLanguage})`),t||e)}tLang(e,t,a){let s=this.currentLanguage;this.currentLanguage=t;let i=this.t(e,a);return this.currentLanguage=s,i}tParse(e,t){let a=this.t(e,t);a=a.replace(/\\n|\/n/g,"<br>");let s=[],i=/<([a-z]+)>([^<]*)<\/\1>|<([a-z]+)\s*\/?>|([^<]+)/gi,r;for(;(r=i.exec(a))!==null;)r[4]?s.push({type:"text",content:r[4]}):r[1]?s.push({type:"tag",tag:r[1],content:r[2]}):r[3]&&s.push({type:"tag",tag:r[3],content:""});return s.length>0?s:[{type:"text",content:a}]}async setLanguage(e){this.supportedLanguages.has(e)&&(this.currentLanguage=e,this.storage&&await this.storage.set("lang",e),typeof document<"u"&&(h("lang",e,365),document.documentElement.lang=e),this.listeners.forEach(t=>t(e)),this.onLanguageChange&&this.onLanguageChange(e));}getLanguage(){return this.currentLanguage}getSupportedLanguages(){return Array.from(this.supportedLanguages)}isLanguageSupported(e){return this.supportedLanguages.has(e)}hasKey(e){return !!(this.translations[this.currentLanguage]?.[e]||this.translations[this.defaultLanguage]?.[e])}getTranslations(){return this.translations[this.currentLanguage]||{}}isRTL(){return this.rtlLanguages.has(this.currentLanguage.toLowerCase().substring(0,2))}isRTLLanguage(e){return this.rtlLanguages.has(e.toLowerCase().substring(0,2))}onChange(e){return this.listeners.add(e),()=>this.listeners.delete(e)}};function h(n,e,t){let a="";{let s=new Date;s.setTime(s.getTime()+t*24*60*60*1e3),a="; expires="+s.toUTCString();}document.cookie=n+"="+(e||"")+a+"; path=/";}var L=()=>({get:n=>typeof localStorage>"u"?null:localStorage.getItem(n),set:(n,e)=>{typeof localStorage<"u"&&localStorage.setItem(n,e);}}),f=()=>{let n=new Map;return {get:e=>n.get(e)||null,set:(e,t)=>{n.set(e,t);}}},y=()=>typeof localStorage<"u"?L():f(),d=class{constructor(e,t,a="json"){this.loading=new Map;this.loaded=new Set;this.baseUrl=e.endsWith("/")?e:e+"/",this.manager=t,this.fileExtension=a,this.isServerSide=typeof fetch>"u";}async load(e){if(this.loaded.has(e))return;if(this.loading.has(e))return this.loading.get(e);let t=this.doLoad(e);this.loading.set(e,t);try{await t,this.loaded.add(e);}finally{this.loading.delete(e);}}async doLoad(e){try{let t=`${this.baseUrl}${e}.${this.fileExtension}`,a;t.startsWith(".")||t.startsWith("/")||/^[a-zA-Z]:/.test(t)||this.isServerSide?a=await this.loadFromFile(t):a=await this.loadFromUrl(t),a&&this.manager.loadLanguage(e,a);}catch(t){console.warn(`[i18n] Error loading language: ${e}`,t);}}async loadFromUrl(e){try{let t=await fetch(e);return t.ok?await t.json():(console.warn(`[i18n] Failed to load language from URL: ${e} (${t.status})`),null)}catch(t){return console.warn(`[i18n] Error fetching from URL: ${e}`,t),null}}async loadFromFile(e){try{let t=await import('fs').then(r=>r.promises).catch(()=>null),a=await import('path').then(r=>r).catch(()=>null);if(!t)return console.warn("[i18n] fs module not available. Running in browser?"),null;let s=e;if(a&&!a.isAbsolute(e)){let r=await import('process').then(c=>c).catch(()=>null);r&&(s=a.resolve(r.cwd(),e));}let i=await t.readFile(s,"utf-8");return JSON.parse(i)}catch(t){return console.warn(`[i18n] Error reading file: ${e}`,t),null}}isLoaded(e){return this.loaded.has(e)}},g=null,l=null;function m(){return typeof navigator<"u"&&navigator.language?navigator.language.split("-")[0].toLowerCase():"en"}function w(){return typeof fetch<"u"&&typeof window<"u"}function o(){return g||(g=new u),g}function v(){return l}async function b(n){if(w()&&!n.defaultLanguage){let e=m();n.supportedLanguages?.includes(e)?n.defaultLanguage=e:n.defaultLanguage=n.supportedLanguages?.[0]||"en";}if(n.storage||(n.storage=y()),g=new u(n),await g.init(),n.basePath){let e=n.fileExtension||"json";l=new d(n.basePath,g,e),await l.load(g.getLanguage());}return g}var p=(n,e)=>o().t(n,e),C=(n,e,t)=>o().tLang(n,e,t),x=(n,e)=>o().tParse(n,e),S=n=>l&&!l.isLoaded(n)?l.load(n).then(()=>o().setLanguage(n)):o().setLanguage(n),R=()=>o().getLanguage(),T=()=>o().getSupportedLanguages(),I=n=>o().hasKey(n),P=()=>o().isRTL(),E=n=>o().isRTLLanguage(n),$=n=>o().onChange(n),j=(n,e)=>o().loadLanguage(n,e),M=n=>o().loadTranslations(n);function O(n,e,t){return p(n===1?e:t,{count:String(n)})}var U={setupI18n:b,getI18n:o,getLazyLoader:v,I18nManager:u,LazyLoader:d,t:p,tLang:C,tParse:x,setLanguage:S,getLanguage:R,getSupportedLanguages:T,hasKey:I,isRTL:P,isRTLLanguage:E,onChange:$,loadLanguage:j,loadTranslations:M,plural:O};exports.I18nManager=u;exports.LazyLoader=d;exports.default=U;exports.getI18n=o;exports.getLanguage=R;exports.getLazyLoader=v;exports.getSupportedLanguages=T;exports.hasKey=I;exports.isRTL=P;exports.isRTLLanguage=E;exports.loadLanguage=j;exports.loadTranslations=M;exports.onChange=$;exports.plural=O;exports.setLanguage=S;exports.setupI18n=b;exports.t=p;exports.tLang=C;exports.tParse=x;//# sourceMappingURL=index.cjs.map
1
+ 'use strict';Object.defineProperty(exports,'__esModule',{value:true});var d=class{constructor(e){this.translations={};this.currentLanguage="en";this.defaultLanguage="en";this.supportedLanguages=new Set(["en"]);this.rtlLanguages=new Set(["ar","he","fa","ur","yi","ji","iw","ku"]);this.listeners=new Set;e&&(this.defaultLanguage=e.defaultLanguage||"en",this.currentLanguage=e.defaultLanguage||"en",this.storage=e.storage,this.onLanguageChange=e.onLanguageChange,e.supportedLanguages&&(this.supportedLanguages=new Set(e.supportedLanguages)));}async init(){if(this.storage){let e=await this.storage.get("i18n-language");e&&this.supportedLanguages.has(e)&&(this.currentLanguage=e);}}loadLanguage(e,t){this.translations[e]||(this.translations[e]={});let a=this.flattenObject(t);this.translations[e]={...this.translations[e],...a},this.supportedLanguages.add(e);}loadTranslations(e){Object.entries(e).forEach(([t,a])=>{this.loadLanguage(t,a);});}flattenObject(e,t=""){let a={};for(let s in e){if(!Object.prototype.hasOwnProperty.call(e,s))continue;let r=e[s],i=t?`${t}.${s}`:s;typeof r=="object"&&r!==null&&!Array.isArray(r)?Object.assign(a,this.flattenObject(r,i)):a[i]=String(r);}return a}t(e,t,a){let s=this.getTranslation(e,a);return t&&Object.entries(t).forEach(([r,i])=>{let g=this.getTranslation(i,i);s=s.replace(new RegExp(`\\{${r}\\}`,"g"),g);}),s}getTranslation(e,t){return this.translations[this.currentLanguage]?.[e]?this.translations[this.currentLanguage][e]:this.defaultLanguage!==this.currentLanguage&&this.translations[this.defaultLanguage]?.[e]?this.translations[this.defaultLanguage][e]:(console.warn(`[i18n] Translation key not found: "${e}" (lang: ${this.currentLanguage})`),t||e)}tLang(e,t,a,s){let r=this.currentLanguage;this.currentLanguage=e;let i=this.t(t,a,s);return this.currentLanguage=r,i}tParse(e,t,a){let s=this.t(e,t,a);s=s.replace(/\\n|\/n/g,"<br>");let r=[],i=/<([a-z]+)>([^<]*)<\/\1>|<([a-z]+)\s*\/?>|([^<]+)/gi,g;for(;(g=i.exec(s))!==null;)g[4]?r.push({type:"text",content:g[4]}):g[1]?r.push({type:"tag",tag:g[1],content:g[2]}):g[3]&&r.push({type:"tag",tag:g[3],content:""});return r.length>0?r:[{type:"text",content:s}]}async setLanguage(e){if(!this.supportedLanguages.has(e)){console.warn(`[i18n] Language "${e}" not supported, aborting setLanguage()`);return}this.currentLanguage=e,this.storage&&await this.storage.set("i18n-language",e),typeof document<"u"&&(L("lang",e,365),document.documentElement.lang=e),this.listeners.forEach(t=>t(e)),this.onLanguageChange&&this.onLanguageChange(e);}getLanguage(){return this.currentLanguage}getSupportedLanguages(){return Array.from(this.supportedLanguages)}isLanguageSupported(e){return this.supportedLanguages.has(e)}hasKey(e){return !!(this.translations[this.currentLanguage]?.[e]||this.translations[this.defaultLanguage]?.[e])}getTranslations(){return this.translations[this.currentLanguage]||{}}isRTL(){return this.rtlLanguages.has(this.currentLanguage.toLowerCase().substring(0,2))}isRTLLanguage(e){return this.rtlLanguages.has(e.toLowerCase().substring(0,2))}onChange(e){return this.listeners.add(e),()=>this.listeners.delete(e)}};function L(n,e,t){let a="";{let s=new Date;s.setTime(s.getTime()+t*24*60*60*1e3),a="; expires="+s.toUTCString();}document.cookie=n+"="+(e||"")+a+"; path=/";}var h=()=>({get:n=>typeof localStorage>"u"?null:localStorage.getItem(n),set:(n,e)=>{typeof localStorage<"u"&&localStorage.setItem(n,e);}}),f=()=>{let n=new Map;return {get:e=>n.get(e)||null,set:(e,t)=>{n.set(e,t);}}},y=()=>typeof localStorage<"u"?h():f(),p=class{constructor(e,t,a="json"){this.loading=new Map;this.loaded=new Set;this.baseUrl=e.endsWith("/")?e:e+"/",this.manager=t,this.fileExtension=a,this.isServerSide=typeof fetch>"u";}async load(e){if(this.loaded.has(e))return;if(this.loading.has(e))return this.loading.get(e);let t=this.doLoad(e);this.loading.set(e,t);try{await t,this.loaded.add(e);}catch(a){console.error(`[LazyLoader] Error loading language "${e}":`,a);}finally{this.loading.delete(e);}}async doLoad(e){try{let t=`${this.baseUrl}${e}.${this.fileExtension}`,a;t.startsWith(".")||t.startsWith("/")||/^[a-zA-Z]:/.test(t)||this.isServerSide?a=await this.loadFromFile(t):a=await this.loadFromUrl(t),a?this.manager.loadLanguage(e,a):console.warn(`[LazyLoader] No data loaded for language: "${e}"`);}catch(t){console.warn(`[i18n] Error loading language: ${e}`,t);}}async loadFromUrl(e){try{let t=await fetch(e);return t.ok?await t.json():(console.warn(`[i18n] Failed to load language from URL: ${e} (${t.status})`),null)}catch(t){return console.warn(`[i18n] Error fetching from URL: ${e}`,t),null}}async loadFromFile(e){try{let t=await import('fs').then(i=>i.promises).catch(()=>null),a=await import('path').then(i=>i).catch(()=>null);if(!t)return console.warn("[i18n] fs module not available. Running in browser?"),null;let s=e;if(a&&!a.isAbsolute(e)){let i=await import('process').then(g=>g).catch(()=>null);i&&(s=a.resolve(i.cwd(),e));}let r=await t.readFile(s,"utf-8");return JSON.parse(r)}catch(t){return console.warn(`[i18n] Error reading file: ${e}`,t),null}}isLoaded(e){return this.loaded.has(e)}},l=null,u=null;function w(){return typeof navigator<"u"&&navigator.language?navigator.language.split("-")[0].toLowerCase():"en"}function m(){return typeof fetch<"u"&&typeof window<"u"}function o(){return l||(l=new d),l}function v(){return u}async function b(n){if(m()&&!n.defaultLanguage){let e=w();n.supportedLanguages?.includes(e)?n.defaultLanguage=e:n.defaultLanguage=n.supportedLanguages?.[0]||"en";}if(n.storage||(n.storage=y()),l=new d(n),await l.init(),n.basePath){let e=n.fileExtension||"json";u=new p(n.basePath,l,e),await u.load(l.getLanguage());}return l}var c=(n,e,t)=>o().t(n,e,t),C=(n,e,t,a)=>o().tLang(n,e,t,a),A=async(n,e,t,a)=>(u&&!u.isLoaded(n)&&await u.load(n),o().tLang(n,e,t,a)),x=(n,e,t)=>o().tParse(n,e,t),S=n=>u&&!u.isLoaded(n)?u.load(n).then(()=>o().setLanguage(n)):o().setLanguage(n),R=()=>o().getLanguage(),T=()=>o().getSupportedLanguages(),I=n=>o().hasKey(n),P=()=>o().isRTL(),$=n=>o().isRTLLanguage(n),E=n=>o().onChange(n),j=(n,e)=>o().loadLanguage(n,e),z=n=>o().loadTranslations(n);function M(n,e,t){return c(n===1?e:t,{count:String(n)})}var U={setupI18n:b,getI18n:o,getLazyLoader:v,I18nManager:d,LazyLoader:p,t:c,tLang:C,tParse:x,setLanguage:S,getLanguage:R,getSupportedLanguages:T,hasKey:I,isRTL:P,isRTLLanguage:$,onChange:E,loadLanguage:j,loadTranslations:z,plural:M};exports.I18nManager=d;exports.LazyLoader=p;exports.default=U;exports.getI18n=o;exports.getLanguage=R;exports.getLazyLoader=v;exports.getSupportedLanguages=T;exports.hasKey=I;exports.isRTL=P;exports.isRTLLanguage=$;exports.loadLanguage=j;exports.loadTranslations=z;exports.onChange=E;exports.plural=M;exports.setLanguage=S;exports.setupI18n=b;exports.t=c;exports.tLang=C;exports.tLangAsync=A;exports.tParse=x;//# sourceMappingURL=index.cjs.map
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/mod/i18n.ts","../src/index.ts"],"names":["I18nManager","config","stored","lang","translations","flattened","trans","obj","prefix","key","value","newKey","params","translation","param","paramValue","fallback","original","result","tokens","regex","match","setCookie","fn","callback","name","days","expires","date","createBrowserStorage","createMemoryStorage","store","getDefaultStorage","LazyLoader","baseUrl","manager","fileExtension","promise","filePath","data","error","url","response","fs","m","path","resolvedPath","process","content","instance","lazyLoader","detectBrowserLanguage","isBrowser","getI18n","getLazyLoader","setupI18n","detectedLang","t","tLang","tParse","setLanguage","getLanguage","getSupportedLanguages","hasKey","isRTL","isRTLLanguage","onChange","loadLanguage","loadTranslations","plural","count","singleKey","pluralKey","index_default"],"mappings":"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","file":"index.cjs","sourcesContent":["// src/mod/i18n.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import * as types from '../types';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n export class I18nManager {\r\n\r\n // ┌──────────────────────────────── INIT ──────────────────────────────┐\r\n\r\n private translations : types.TranslationSet = {};\r\n private currentLanguage : types.LanguageCode = 'en';\r\n private defaultLanguage : types.LanguageCode = 'en';\r\n private supportedLanguages = new Set<types.LanguageCode>(['en']);\r\n private rtlLanguages = new Set<string>(['ar', 'he', 'fa', 'ur', 'yi', 'ji', 'iw', 'ku']);\r\n private listeners = new Set<(lang: types.LanguageCode) => void>();\r\n private storage? : types.I18nStorage;\r\n private onLanguageChange? : (lang: types.LanguageCode) => void;\r\n\r\n constructor(config?: types.I18nConfig) {\r\n if (config) {\r\n this.defaultLanguage = config.defaultLanguage || 'en';\r\n this.currentLanguage = config.defaultLanguage || 'en';\r\n this.storage = config.storage;\r\n this.onLanguageChange = config.onLanguageChange;\r\n\r\n if (config.supportedLanguages) {\r\n this.supportedLanguages = new Set(config.supportedLanguages);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Initialize with stored language preference\r\n */\r\n public async init(): Promise<void> {\r\n\r\n if (this.storage) {\r\n const stored = await this.storage.get('lang');\r\n if (stored && this.supportedLanguages.has(stored)) {\r\n this.currentLanguage = stored;\r\n }\r\n }\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── LOAD ──────────────────────────────┐\r\n\r\n /**\r\n * Load translations for a specific language\r\n * @param lang Language code\r\n * @param translations Translation object (can be nested)\r\n */\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n public loadLanguage(lang: types.LanguageCode, translations: Record<string, any>): void {\r\n if (!this.translations[lang]) {\r\n this.translations[lang] = {};\r\n }\r\n\r\n const flattened = this.flattenObject(translations);\r\n this.translations[lang] = { ...this.translations[lang], ...flattened };\r\n this.supportedLanguages.add(lang);\r\n }\r\n\r\n /**\r\n * Load multiple languages at once\r\n * @param translations Object with language codes as keys\r\n */\r\n public loadTranslations(translations: types.TranslationSet): void {\r\n Object.entries(translations).forEach(([lang, trans]) => {\r\n this.loadLanguage(lang, trans);\r\n });\r\n }\r\n\r\n /**\r\n * Flatten nested object into dot notation\r\n * @private\r\n */\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n private flattenObject(obj: Record<string, any>, prefix: string = ''): Record<string, string> {\r\n const flattened: Record<string, string> = {};\r\n\r\n for (const key in obj) {\r\n if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;\r\n\r\n const value = obj[key];\r\n const newKey = prefix ? `${prefix}.${key}` : key;\r\n\r\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\r\n Object.assign(flattened, this.flattenObject(value, newKey));\r\n } else {\r\n flattened[newKey] = String(value);\r\n }\r\n }\r\n\r\n return flattened;\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌───────────────────────────── TRANSLATION ──────────────────────────┐\r\n\r\n /**\r\n * Translate a key with parameter replacement\r\n *\r\n * @example\r\n * t('welcome.message', { name: 'John' })\r\n * // => \"Welcome, John!\"\r\n *\r\n * @param key Translation key (dot notation)\r\n * @param params Optional parameters for replacement\r\n * @returns Translated string\r\n */\r\n public t(key: string, params?: Record<string, string>): string {\r\n\r\n let translation = this.getTranslation(key);\r\n\r\n if (params) {\r\n Object.entries(params).forEach(([param, value]) => {\r\n // Check if parameter value is itself a translation key\r\n const paramValue = this.getTranslation(value, value);\r\n translation = translation.replace(\r\n new RegExp(`\\\\{${param}\\\\}`, 'g'),\r\n paramValue\r\n );\r\n });\r\n }\r\n\r\n return translation;\r\n }\r\n\r\n /**\r\n * Get raw translation without parameter replacement\r\n * @private\r\n */\r\n private getTranslation(key: string, fallback?: string): string {\r\n // Try current language\r\n if (this.translations[this.currentLanguage]?.[key]) {\r\n return this.translations[this.currentLanguage][key];\r\n }\r\n\r\n // Try default language\r\n if (this.defaultLanguage !== this.currentLanguage &&\r\n this.translations[this.defaultLanguage]?.[key]) {\r\n return this.translations[this.defaultLanguage][key];\r\n }\r\n\r\n // Warn and return fallback\r\n console.warn(`[i18n] Translation key not found: \"${key}\" (lang: ${this.currentLanguage})`);\r\n return fallback || key;\r\n }\r\n\r\n /**\r\n * Translate with a specific language temporarily\r\n *\r\n * @param key Translation key\r\n * @param lang Language code\r\n * @param params Optional parameters\r\n */\r\n public tLang(key: string, lang: types.LanguageCode, params?: Record<string, string>): string {\r\n const original = this.currentLanguage;\r\n this.currentLanguage = lang;\r\n const result = this.t(key, params);\r\n this.currentLanguage = original;\r\n return result;\r\n }\r\n\r\n /**\r\n * Translate and parse HTML-like tags into tokens\r\n * Converts \\n or /n to line breaks\r\n *\r\n * @example\r\n * // Translation: \"Hello\\nWorld <strong>here</strong>\"\r\n * tParse('message')\r\n * // => [\r\n * // { type: 'text', content: 'Hello' },\r\n * // { type: 'tag', tag: 'br', content: '' },\r\n * // { type: 'text', content: 'World ' },\r\n * // { type: 'tag', tag: 'strong', content: 'here' }\r\n * // ]\r\n *\r\n * @param key Translation key\r\n * @param params Optional parameters\r\n * @returns Array of tokens\r\n */\r\n public tParse(key: string, params?: Record<string, string>): types.TranslationToken[] {\r\n let translation = this.t(key, params);\r\n\r\n // Convert newlines to <br> tags\r\n translation = translation.replace(/\\\\n|\\/n/g, '<br>');\r\n\r\n const tokens: types.TranslationToken[] = [];\r\n const regex = /<([a-z]+)>([^<]*)<\\/\\1>|<([a-z]+)\\s*\\/?>|([^<]+)/gi;\r\n let match;\r\n\r\n while ((match = regex.exec(translation)) !== null) {\r\n if (match[4]) {\r\n // Plain text\r\n tokens.push({ type: 'text', content: match[4] });\r\n } else if (match[1]) {\r\n // Paired tag: <strong>text</strong>\r\n tokens.push({ type: 'tag', tag: match[1], content: match[2] });\r\n } else if (match[3]) {\r\n // Self-closing: <br> or <br/>\r\n tokens.push({ type: 'tag', tag: match[3], content: '' });\r\n }\r\n }\r\n\r\n return tokens.length > 0 ? tokens : [{ type: 'text', content: translation }];\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌────────────────────────────── LANGUAGE ────────────────────────────┐\r\n\r\n /**\r\n * Set current language\r\n */\r\n public async setLanguage(lang: types.LanguageCode): Promise<void> {\r\n if (!this.supportedLanguages.has(lang)) {\r\n // console.warn(`[i18n] Language \"${lang}\" not supported`);\r\n return;\r\n }\r\n\r\n this.currentLanguage = lang;\r\n\r\n // Persist if storage available\r\n if (this.storage) {\r\n await this.storage.set('lang', lang);\r\n }\r\n\r\n // client\r\n if( typeof document !== 'undefined' ) {\r\n // cookie\r\n setCookie('lang', lang, 365);\r\n\r\n // html lang attribute\r\n document.documentElement.lang = lang;\r\n }\r\n\r\n // Notify listeners\r\n this.listeners.forEach(fn => fn(lang));\r\n\r\n if (this.onLanguageChange) {\r\n this.onLanguageChange(lang);\r\n }\r\n }\r\n\r\n /**\r\n * Get current language\r\n */\r\n public getLanguage(): types.LanguageCode {\r\n return this.currentLanguage;\r\n }\r\n\r\n /**\r\n * Get all supported languages\r\n */\r\n public getSupportedLanguages(): types.LanguageCode[] {\r\n return Array.from(this.supportedLanguages);\r\n }\r\n\r\n /**\r\n * Check if language is supported\r\n */\r\n public isLanguageSupported(lang: types.LanguageCode): boolean {\r\n return this.supportedLanguages.has(lang);\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌─────────────────────────────── HELPERS ────────────────────────────┐\r\n\r\n /**\r\n * Check if a translation key exists\r\n */\r\n public hasKey(key: string): boolean {\r\n return !!(\r\n this.translations[this.currentLanguage]?.[key] ||\r\n this.translations[this.defaultLanguage]?.[key]\r\n );\r\n }\r\n\r\n /**\r\n * Get all translations for current language\r\n */\r\n public getTranslations(): Record<string, string> {\r\n return this.translations[this.currentLanguage] || {};\r\n }\r\n\r\n /**\r\n * Check if current language is RTL\r\n */\r\n public isRTL(): boolean {\r\n return this.rtlLanguages.has(this.currentLanguage.toLowerCase().substring(0, 2));\r\n }\r\n\r\n /**\r\n * Check if specific language is RTL\r\n */\r\n public isRTLLanguage(lang: types.LanguageCode): boolean {\r\n return this.rtlLanguages.has(lang.toLowerCase().substring(0, 2));\r\n }\r\n\r\n /**\r\n * Subscribe to language changes\r\n * @returns Unsubscribe function\r\n */\r\n public onChange(callback: (lang: types.LanguageCode) => void): () => void {\r\n this.listeners.add(callback);\r\n return () => this.listeners.delete(callback);\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ HELP ════════════════════════════════════════╗\r\n\r\n // Function to set a cookie with name, value, and days to expire\r\n function setCookie(name: string, value: string, days: number) {\r\n let expires = \"\";\r\n if (days) {\r\n const date = new Date();\r\n date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));\r\n expires = \"; expires=\" + date.toUTCString();\r\n }\r\n document.cookie = name + \"=\" + (value || \"\") + expires + \"; path=/\";\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","// src/index.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import * as types from './types';\r\n import { I18nManager } from './mod/i18n';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n /**\r\n * Storage adapter for browser (localStorage)\r\n */\r\n const createBrowserStorage = (): types.I18nStorage => ({\r\n get: (key: string) => {\r\n if (typeof localStorage === 'undefined') return null;\r\n return localStorage.getItem(key);\r\n },\r\n set: (key: string, value: string) => {\r\n if (typeof localStorage !== 'undefined') {\r\n localStorage.setItem(key, value);\r\n }\r\n }\r\n });\r\n\r\n /**\r\n * Storage adapter for memory (in-process storage)\r\n */\r\n const createMemoryStorage = (): types.I18nStorage => {\r\n const store = new Map<string, string>();\r\n return {\r\n get: (key: string) => store.get(key) || null,\r\n set: (key: string, value: string) => { store.set(key, value); }\r\n };\r\n };\r\n\r\n /**\r\n * Auto-select appropriate storage based on environment\r\n */\r\n const getDefaultStorage = (): types.I18nStorage => {\r\n return typeof localStorage !== 'undefined' ? createBrowserStorage() : createMemoryStorage();\r\n };\r\n\r\n /**\r\n * Lazy loader: fetch language on-demand\r\n * Supports both URL-based (browser) and file-based (server) loading\r\n */\r\n export class LazyLoader {\r\n private baseUrl: string;\r\n private manager: I18nManager;\r\n private loading = new Map<types.LanguageCode, Promise<void>>();\r\n private loaded = new Set<types.LanguageCode>();\r\n private isServerSide: boolean;\r\n private fileExtension: string;\r\n\r\n constructor(baseUrl: string, manager: I18nManager, fileExtension: string = 'json') {\r\n this.baseUrl = baseUrl.endsWith('/') ? baseUrl : baseUrl + '/';\r\n this.manager = manager;\r\n this.fileExtension = fileExtension;\r\n this.isServerSide = typeof fetch === 'undefined';\r\n }\r\n\r\n /**\r\n * Load a language file on-demand\r\n * Caches the promise to prevent duplicate requests\r\n */\r\n async load(lang: types.LanguageCode): Promise<void> {\r\n // Already loaded\r\n if (this.loaded.has(lang)) {\r\n return;\r\n }\r\n\r\n // Currently loading\r\n if (this.loading.has(lang)) {\r\n return this.loading.get(lang);\r\n }\r\n\r\n // Start loading\r\n const promise = this.doLoad(lang);\r\n this.loading.set(lang, promise);\r\n\r\n try {\r\n await promise;\r\n this.loaded.add(lang);\r\n } finally {\r\n this.loading.delete(lang);\r\n }\r\n }\r\n\r\n private async doLoad(lang: types.LanguageCode): Promise<void> {\r\n try {\r\n const filePath = `${this.baseUrl}${lang}.${this.fileExtension}`;\r\n\r\n let data: Record<string, string> | null;\r\n\r\n // Check if it's a local file path (relative or absolute)\r\n const isLocalPath = filePath.startsWith('.') || filePath.startsWith('/') || /^[a-zA-Z]:/.test(filePath);\r\n\r\n if (isLocalPath || this.isServerSide) {\r\n // Node.js/local: Read from filesystem\r\n data = await this.loadFromFile(filePath);\r\n } else {\r\n // Browser: Fetch from URL\r\n data = await this.loadFromUrl(filePath);\r\n }\r\n\r\n if (data) {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n this.manager.loadLanguage(lang, data as Record<string, any>);\r\n }\r\n } catch (error) {\r\n console.warn(`[i18n] Error loading language: ${lang}`, error);\r\n }\r\n }\r\n\r\n private async loadFromUrl(url: string): Promise<Record<string, string> | null> {\r\n try {\r\n const response = await fetch(url);\r\n\r\n if (response.ok) {\r\n return await response.json();\r\n } else {\r\n console.warn(`[i18n] Failed to load language from URL: ${url} (${response.status})`);\r\n return null;\r\n }\r\n } catch (error) {\r\n console.warn(`[i18n] Error fetching from URL: ${url}`, error);\r\n return null;\r\n }\r\n }\r\n\r\n private async loadFromFile(filePath: string): Promise<Record<string, string> | null> {\r\n try {\r\n // Dynamic import to avoid issues in browsers\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const fs = await import('fs').then(m => m.promises).catch((): any => null);\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const path = await import('path').then(m => m).catch((): any => null);\r\n\r\n if (!fs) {\r\n console.warn('[i18n] fs module not available. Running in browser?');\r\n return null;\r\n }\r\n\r\n // Resolve relative paths to absolute paths\r\n let resolvedPath = filePath;\r\n if (path && !path.isAbsolute(filePath)) {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const process = await import('process').then(m => m).catch((): any => null);\r\n if (process) {\r\n resolvedPath = path.resolve(process.cwd(), filePath);\r\n }\r\n }\r\n\r\n const content = await fs.readFile(resolvedPath, 'utf-8');\r\n return JSON.parse(content);\r\n } catch (error) {\r\n console.warn(`[i18n] Error reading file: ${filePath}`, error);\r\n return null;\r\n }\r\n }\r\n\r\n isLoaded(lang: types.LanguageCode): boolean {\r\n return this.loaded.has(lang);\r\n }\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ ════ ════════════════════════════════════════╗\r\n\r\n let instance: I18nManager | null = null;\r\n let lazyLoader: LazyLoader | null = null;\r\n\r\n /**\r\n * Get browser language preference\r\n * Uses navigator.language if available (browser environment)\r\n * @private\r\n */\r\n function detectBrowserLanguage(): string {\r\n if (typeof navigator !== 'undefined' && navigator.language) {\r\n return navigator.language.split('-')[0].toLowerCase();\r\n }\r\n return 'en';\r\n }\r\n\r\n /**\r\n * Check if running in browser environment\r\n * @private\r\n */\r\n function isBrowser(): boolean {\r\n return typeof fetch !== 'undefined' && typeof window !== 'undefined';\r\n }\r\n\r\n /**\r\n * Get or create the global i18n instance\r\n */\r\n export function getI18n(): I18nManager {\r\n if (!instance) {\r\n instance = new I18nManager();\r\n }\r\n return instance;\r\n }\r\n\r\n /**\r\n * Get the lazy loader instance (only available after setupI18n with basePath)\r\n */\r\n export function getLazyLoader(): LazyLoader | null {\r\n return lazyLoader;\r\n }\r\n\r\n /**\r\n * Main setup function - Single, simple, auto-detecting initialization\r\n *\r\n * Auto-detects environment and handles both browser and server:\r\n * - Browser: Auto-detects language, loads from URL path\r\n * - Server: Uses defaultLanguage, loads from file system\r\n *\r\n * Call this ONCE at app startup.\r\n *\r\n * @example\r\n * // Browser - Auto-detects language, lazy-loads from URL\r\n * await setupI18n({\r\n * supportedLanguages: ['en', 'ar', 'fr'],\r\n * basePath: '/i18n/'\r\n * });\r\n *\r\n * @example\r\n * // Server - Uses default language, lazy-loads from filesystem\r\n * await setupI18n({\r\n * defaultLanguage: 'en',\r\n * supportedLanguages: ['en', 'ar', 'fr'],\r\n * basePath: './locales/'\r\n * });\r\n */\r\n export async function setupI18n(\r\n config: types.I18nConfig & { basePath?: string }\r\n ): Promise<I18nManager> {\r\n\r\n // Auto-detect browser language if in browser and no defaultLanguage specified\r\n if (isBrowser() && !config.defaultLanguage) {\r\n const detectedLang = detectBrowserLanguage();\r\n if (config.supportedLanguages?.includes(detectedLang)) {\r\n config.defaultLanguage = detectedLang;\r\n } else {\r\n config.defaultLanguage = config.supportedLanguages?.[0] || 'en';\r\n }\r\n }\r\n\r\n // Use appropriate storage based on environment\r\n if (!config.storage) {\r\n config.storage = getDefaultStorage();\r\n }\r\n\r\n // Create and initialize manager\r\n instance = new I18nManager(config);\r\n await instance.init();\r\n\r\n // Setup lazy loading if basePath provided\r\n if (config.basePath) {\r\n const fileExtension = config.fileExtension || 'json';\r\n lazyLoader = new LazyLoader(config.basePath, instance, fileExtension);\r\n await lazyLoader.load(instance.getLanguage());\r\n }\r\n\r\n return instance;\r\n }\r\n\r\n\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CONVENIENCE FUNCTIONS ════════════════════════════════════════╗\r\n\r\n /**\r\n * Translate a key with optional parameter replacement\r\n */\r\n export const t = (key: string, params?: Record<string, string>) =>\r\n getI18n().t(key, params);\r\n\r\n /**\r\n * Translate a key with a specific language temporarily\r\n */\r\n export const tLang = (key: string, lang: types.LanguageCode, params?: Record<string, string>) =>\r\n getI18n().tLang(key, lang, params);\r\n\r\n /**\r\n * Parse translation with HTML tags into tokens\r\n */\r\n export const tParse = (key: string, params?: Record<string, string>) =>\r\n getI18n().tParse(key, params);\r\n\r\n /**\r\n * Set current language and trigger listeners\r\n */\r\n export const setLanguage = (lang: types.LanguageCode): Promise<void> => {\r\n // Load language if lazy loader available\r\n if (lazyLoader && !lazyLoader.isLoaded(lang)) {\r\n return lazyLoader.load(lang).then(() => getI18n().setLanguage(lang));\r\n }\r\n return getI18n().setLanguage(lang);\r\n };\r\n\r\n /**\r\n * Get current language code\r\n */\r\n export const getLanguage = () =>\r\n getI18n().getLanguage();\r\n\r\n /**\r\n * Get all supported languages\r\n */\r\n export const getSupportedLanguages = () =>\r\n getI18n().getSupportedLanguages();\r\n\r\n /**\r\n * Check if translation key exists\r\n */\r\n export const hasKey = (key: string) =>\r\n getI18n().hasKey(key);\r\n\r\n /**\r\n * Check if current language is RTL\r\n */\r\n export const isRTL = () =>\r\n getI18n().isRTL();\r\n\r\n /**\r\n * Check if specific language is RTL\r\n */\r\n export const isRTLLanguage = (lang: types.LanguageCode) =>\r\n getI18n().isRTLLanguage(lang);\r\n\r\n /**\r\n * Subscribe to language changes\r\n */\r\n export const onChange = (callback: (lang: types.LanguageCode) => void) =>\r\n getI18n().onChange(callback);\r\n\r\n /**\r\n * Load translations for a specific language\r\n */\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n export const loadLanguage = (lang: types.LanguageCode, translations: Record<string, any>) =>\r\n getI18n().loadLanguage(lang, translations);\r\n\r\n /**\r\n * Load multiple languages at once\r\n */\r\n export const loadTranslations = (translations: types.TranslationSet) =>\r\n getI18n().loadTranslations(translations);\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTILITY FUNCTIONS ════════════════════════════════════════╗\r\n\r\n /**\r\n * Pluralization helper - select translation based on count\r\n *\r\n * @example\r\n * plural(1, 'item.single', 'item.plural') // \"1 item\"\r\n * plural(5, 'item.single', 'item.plural') // \"5 items\"\r\n */\r\n export function plural(count: number, singleKey: string, pluralKey: string): string {\r\n const key = count === 1 ? singleKey : pluralKey;\r\n return t(key, { count: String(count) });\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ EXPORTS ════════════════════════════════════════╗\r\n\r\n export default {\r\n setupI18n,\r\n getI18n,\r\n getLazyLoader,\r\n I18nManager,\r\n LazyLoader,\r\n t,\r\n tLang,\r\n tParse,\r\n setLanguage,\r\n getLanguage,\r\n getSupportedLanguages,\r\n hasKey,\r\n isRTL,\r\n isRTLLanguage,\r\n onChange,\r\n loadLanguage,\r\n loadTranslations,\r\n plural,\r\n };\r\n\r\n export type I18nManagerInstance = InstanceType<typeof I18nManager>;\r\n export type LazyLoaderInstance = InstanceType<typeof LazyLoader>;\r\n\r\n export * from './mod/i18n';\r\n export * from './types';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n"]}
1
+ {"version":3,"sources":["../src/mod/i18n.ts","../src/index.ts"],"names":["I18nManager","config","stored","lang","translations","flattened","trans","obj","prefix","key","value","newKey","params","fallback","translation","param","paramValue","original","result","tokens","regex","match","setCookie","fn","callback","name","days","expires","date","createBrowserStorage","createMemoryStorage","store","getDefaultStorage","LazyLoader","baseUrl","manager","fileExtension","promise","error","filePath","data","url","response","fs","m","path","resolvedPath","process","content","instance","lazyLoader","detectBrowserLanguage","isBrowser","getI18n","getLazyLoader","setupI18n","detectedLang","t","tLang","tLangAsync","tParse","setLanguage","getLanguage","getSupportedLanguages","hasKey","isRTL","isRTLLanguage","onChange","loadLanguage","loadTranslations","plural","count","singleKey","pluralKey","index_default"],"mappings":"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","file":"index.cjs","sourcesContent":["// src/mod/i18n.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import * as types from '../types';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n export class I18nManager {\r\n\r\n // ┌──────────────────────────────── INIT ──────────────────────────────┐\r\n\r\n private translations : types.TranslationSet = {};\r\n private currentLanguage : types.LanguageCode = 'en';\r\n private defaultLanguage : types.LanguageCode = 'en';\r\n private supportedLanguages = new Set<types.LanguageCode>(['en']);\r\n private rtlLanguages = new Set<string>(['ar', 'he', 'fa', 'ur', 'yi', 'ji', 'iw', 'ku']);\r\n private listeners = new Set<(lang: types.LanguageCode) => void>();\r\n private storage? : types.I18nStorage;\r\n private onLanguageChange? : (lang: types.LanguageCode) => void;\r\n\r\n constructor(config?: types.I18nConfig) {\r\n if (config) {\r\n this.defaultLanguage = config.defaultLanguage || 'en';\r\n this.currentLanguage = config.defaultLanguage || 'en';\r\n this.storage = config.storage;\r\n this.onLanguageChange = config.onLanguageChange;\r\n\r\n if (config.supportedLanguages) {\r\n this.supportedLanguages = new Set(config.supportedLanguages);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Initialize with stored language preference\r\n */\r\n public async init(): Promise<void> {\r\n // console.log('[i18n] Initializing manager...');\r\n // console.log('[i18n] Current language:', this.currentLanguage);\r\n // console.log('[i18n] Default language:', this.defaultLanguage);\r\n\r\n if (this.storage) {\r\n // console.log('[i18n] Storage available, checking for stored language...');\r\n const stored = await this.storage.get('i18n-language');\r\n // console.log('[i18n] Stored language from storage:', stored);\r\n if (stored && this.supportedLanguages.has(stored)) {\r\n // console.log('[i18n] Stored language is supported, setting to:', stored);\r\n this.currentLanguage = stored;\r\n } else {\r\n // console.log('[i18n] Stored language not supported or not found, keeping:', this.currentLanguage);\r\n }\r\n } else {\r\n // console.log('[i18n] No storage available');\r\n }\r\n // console.log('[i18n] Init complete. Current language:', this.currentLanguage);\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── LOAD ──────────────────────────────┐\r\n\r\n /**\r\n * Load translations for a specific language\r\n * @param lang Language code\r\n * @param translations Translation object (can be nested)\r\n */\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n public loadLanguage(lang: types.LanguageCode, translations: Record<string, any>): void {\r\n // console.log(`[i18n] Loading language: \"${lang}\"`);\r\n // console.log(`[i18n] Translation keys count:`, Object.keys(translations).length);\r\n \r\n if (!this.translations[lang]) {\r\n this.translations[lang] = {};\r\n }\r\n\r\n const flattened = this.flattenObject(translations);\r\n // console.log(`[i18n] Flattened translation keys count for \"${lang}\":`, Object.keys(flattened).length);\r\n \r\n this.translations[lang] = { ...this.translations[lang], ...flattened };\r\n this.supportedLanguages.add(lang);\r\n \r\n // console.log(`[i18n] Language \"${lang}\" loaded successfully. Total keys:`, Object.keys(this.translations[lang]).length);\r\n // console.log(`[i18n] Supported languages:`, Array.from(this.supportedLanguages));\r\n }\r\n\r\n /**\r\n * Load multiple languages at once\r\n * @param translations Object with language codes as keys\r\n */\r\n public loadTranslations(translations: types.TranslationSet): void {\r\n Object.entries(translations).forEach(([lang, trans]) => {\r\n this.loadLanguage(lang, trans);\r\n });\r\n }\r\n\r\n /**\r\n * Flatten nested object into dot notation\r\n * @private\r\n */\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n private flattenObject(obj: Record<string, any>, prefix: string = ''): Record<string, string> {\r\n const flattened: Record<string, string> = {};\r\n\r\n for (const key in obj) {\r\n if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;\r\n\r\n const value = obj[key];\r\n const newKey = prefix ? `${prefix}.${key}` : key;\r\n\r\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\r\n Object.assign(flattened, this.flattenObject(value, newKey));\r\n } else {\r\n flattened[newKey] = String(value);\r\n }\r\n }\r\n\r\n return flattened;\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌───────────────────────────── TRANSLATION ──────────────────────────┐\r\n\r\n /**\r\n * Translate a key with parameter replacement\r\n *\r\n * @example\r\n * t('welcome.message', { name: 'John' })\r\n * // => \"Welcome, John!\"\r\n *\r\n * @param key Translation key (dot notation)\r\n * @param params Optional parameters for replacement\r\n * @param fallback Optional fallback string if key not found\r\n * @returns Translated string\r\n */\r\n public t(key: string, params?: Record<string, string>, fallback?: string): string {\r\n\r\n let translation = this.getTranslation(key, fallback);\r\n\r\n if (params) {\r\n Object.entries(params).forEach(([param, value]) => {\r\n // Check if parameter value is itself a translation key\r\n const paramValue = this.getTranslation(value, value);\r\n translation = translation.replace(\r\n new RegExp(`\\\\{${param}\\\\}`, 'g'),\r\n paramValue\r\n );\r\n });\r\n }\r\n\r\n return translation;\r\n }\r\n\r\n /**\r\n * Get raw translation without parameter replacement\r\n * @private\r\n */\r\n private getTranslation(key: string, fallback?: string): string {\r\n // console.log(`[i18n] getTranslation() - Looking up key: \"${key}\" in language: \"${this.currentLanguage}\"`);\r\n // console.log(`[i18n] Translations available for \"${this.currentLanguage}\":`, this.translations[this.currentLanguage] ? 'YES' : 'NO');\r\n \r\n // Try current language\r\n if (this.translations[this.currentLanguage]?.[key]) {\r\n const value = this.translations[this.currentLanguage][key];\r\n // console.log(`[i18n] Found in current language \"${this.currentLanguage}\": \"${value}\"`);\r\n return value;\r\n }\r\n\r\n // console.log(`[i18n] Translation key \"${key}\" not found in language \"${this.currentLanguage}\"`);\r\n\r\n // Try default language\r\n if (this.defaultLanguage !== this.currentLanguage &&\r\n this.translations[this.defaultLanguage]?.[key]) {\r\n const value = this.translations[this.defaultLanguage][key];\r\n // console.log(`[i18n] Found in default language \"${this.defaultLanguage}\": \"${value}\"`);\r\n return value;\r\n }\r\n\r\n // Warn and return fallback\r\n console.warn(`[i18n] Translation key not found: \"${key}\" (lang: ${this.currentLanguage})`);\r\n return fallback || key;\r\n }\r\n\r\n /**\r\n * Translate with a specific language temporarily\r\n *\r\n * @param lang Language code\r\n * @param key Translation key\r\n * @param params Optional parameters\r\n * @param fallback Optional fallback string if key not found\r\n */\r\n public tLang(lang: types.LanguageCode, key: string, params?: Record<string, string>, fallback?: string): string {\r\n // console.log(`[i18n] tLang() called: lang=\"${lang}\", key=\"${key}\"`);\r\n // console.log(`[i18n] Current language before switch: \"${this.currentLanguage}\"`);\r\n // console.log(`[i18n] Available translations for \"${lang}\":`, this.translations[lang] ? Object.keys(this.translations[lang]).slice(0, 5) : 'NONE');\r\n \r\n const original = this.currentLanguage;\r\n this.currentLanguage = lang;\r\n // console.log(`[i18n] Temporarily switched to: \"${lang}\"`);\r\n \r\n const result = this.t(key, params, fallback);\r\n \r\n this.currentLanguage = original;\r\n // console.log(`[i18n] Switched back to original language: \"${original}\"`);\r\n // console.log(`[i18n] tLang() result for key \"${key}\":`, result);\r\n \r\n return result;\r\n }\r\n\r\n /**\r\n * Translate and parse HTML-like tags into tokens\r\n * Converts \\n or /n to line breaks\r\n *\r\n * @example\r\n * // Translation: \"Hello\\nWorld <strong>here</strong>\"\r\n * tParse('message')\r\n * // => [\r\n * // { type: 'text', content: 'Hello' },\r\n * // { type: 'tag', tag: 'br', content: '' },\r\n * // { type: 'text', content: 'World ' },\r\n * // { type: 'tag', tag: 'strong', content: 'here' }\r\n * // ]\r\n *\r\n * @param key Translation key\r\n * @param params Optional parameters\r\n * @param fallback Optional fallback string if key not found\r\n * @returns Array of tokens\r\n */\r\n public tParse(key: string, params?: Record<string, string>, fallback?: string): types.TranslationToken[] {\r\n let translation = this.t(key, params, fallback);\r\n // Convert newlines to <br> tags\r\n translation = translation.replace(/\\\\n|\\/n/g, '<br>');\r\n\r\n const tokens: types.TranslationToken[] = [];\r\n const regex = /<([a-z]+)>([^<]*)<\\/\\1>|<([a-z]+)\\s*\\/?>|([^<]+)/gi;\r\n let match;\r\n\r\n while ((match = regex.exec(translation)) !== null) {\r\n if (match[4]) {\r\n // Plain text\r\n tokens.push({ type: 'text', content: match[4] });\r\n } else if (match[1]) {\r\n // Paired tag: <strong>text</strong>\r\n tokens.push({ type: 'tag', tag: match[1], content: match[2] });\r\n } else if (match[3]) {\r\n // Self-closing: <br> or <br/>\r\n tokens.push({ type: 'tag', tag: match[3], content: '' });\r\n }\r\n }\r\n\r\n return tokens.length > 0 ? tokens : [{ type: 'text', content: translation }];\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌────────────────────────────── LANGUAGE ────────────────────────────┐\r\n\r\n /**\r\n * Set current language\r\n */\r\n public async setLanguage(lang: types.LanguageCode): Promise<void> {\r\n // console.log(`[i18n] setLanguage() called with lang: \"${lang}\"`);\r\n // console.log(`[i18n] Checking if \"${lang}\" is supported...`);\r\n // console.log(`[i18n] Supported languages:`, Array.from(this.supportedLanguages));\r\n \r\n if (!this.supportedLanguages.has(lang)) {\r\n console.warn(`[i18n] Language \"${lang}\" not supported, aborting setLanguage()`);\r\n return;\r\n }\r\n\r\n // console.log(`[i18n] Language \"${lang}\" is supported. Setting current language...`);\r\n this.currentLanguage = lang;\r\n // console.log(`[i18n] Current language set to: \"${lang}\"`);\r\n\r\n // Persist if storage available\r\n if (this.storage) {\r\n // console.log(`[i18n] Persisting language \"${lang}\" to storage...`);\r\n await this.storage.set('i18n-language', lang);\r\n // console.log(`[i18n] Language persisted to storage`);\r\n } else {\r\n // console.log(`[i18n] No storage available for persistence`);\r\n }\r\n\r\n // client\r\n if( typeof document !== 'undefined' ) {\r\n // cookie\r\n setCookie('lang', lang, 365);\r\n\r\n // html lang attribute\r\n document.documentElement.lang = lang;\r\n // console.log(`[i18n] Set document.lang to \"${lang}\"`);\r\n }\r\n\r\n // Notify listeners\r\n // console.log(`[i18n] Notifying ${this.listeners.size} listeners...`);\r\n this.listeners.forEach(fn => fn(lang));\r\n\r\n if (this.onLanguageChange) {\r\n // console.log(`[i18n] Calling onLanguageChange callback...`);\r\n this.onLanguageChange(lang);\r\n }\r\n \r\n // console.log(`[i18n] setLanguage() completed for \"${lang}\"`);\r\n }\r\n\r\n /**\r\n * Get current language\r\n */\r\n public getLanguage(): types.LanguageCode {\r\n return this.currentLanguage;\r\n }\r\n\r\n /**\r\n * Get all supported languages\r\n */\r\n public getSupportedLanguages(): types.LanguageCode[] {\r\n return Array.from(this.supportedLanguages);\r\n }\r\n\r\n /**\r\n * Check if language is supported\r\n */\r\n public isLanguageSupported(lang: types.LanguageCode): boolean {\r\n return this.supportedLanguages.has(lang);\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌─────────────────────────────── HELPERS ────────────────────────────┐\r\n\r\n /**\r\n * Check if a translation key exists\r\n */\r\n public hasKey(key: string): boolean {\r\n return !!(\r\n this.translations[this.currentLanguage]?.[key] ||\r\n this.translations[this.defaultLanguage]?.[key]\r\n );\r\n }\r\n\r\n /**\r\n * Get all translations for current language\r\n */\r\n public getTranslations(): Record<string, string> {\r\n return this.translations[this.currentLanguage] || {};\r\n }\r\n\r\n /**\r\n * Check if current language is RTL\r\n */\r\n public isRTL(): boolean {\r\n return this.rtlLanguages.has(this.currentLanguage.toLowerCase().substring(0, 2));\r\n }\r\n\r\n /**\r\n * Check if specific language is RTL\r\n */\r\n public isRTLLanguage(lang: types.LanguageCode): boolean {\r\n return this.rtlLanguages.has(lang.toLowerCase().substring(0, 2));\r\n }\r\n\r\n /**\r\n * Subscribe to language changes\r\n * @returns Unsubscribe function\r\n */\r\n public onChange(callback: (lang: types.LanguageCode) => void): () => void {\r\n this.listeners.add(callback);\r\n return () => this.listeners.delete(callback);\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ HELP ════════════════════════════════════════╗\r\n\r\n // Function to set a cookie with name, value, and days to expire\r\n function setCookie(name: string, value: string, days: number) {\r\n let expires = \"\";\r\n if (days) {\r\n const date = new Date();\r\n date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));\r\n expires = \"; expires=\" + date.toUTCString();\r\n }\r\n document.cookie = name + \"=\" + (value || \"\") + expires + \"; path=/\";\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","// src/index.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import * as types from './types';\r\n import { I18nManager } from './mod/i18n';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n /**\r\n * Storage adapter for browser (localStorage)\r\n */\r\n const createBrowserStorage = (): types.I18nStorage => ({\r\n get: (key: string) => {\r\n if (typeof localStorage === 'undefined') return null;\r\n return localStorage.getItem(key);\r\n },\r\n set: (key: string, value: string) => {\r\n if (typeof localStorage !== 'undefined') {\r\n localStorage.setItem(key, value);\r\n }\r\n }\r\n });\r\n\r\n /**\r\n * Storage adapter for memory (in-process storage)\r\n */\r\n const createMemoryStorage = (): types.I18nStorage => {\r\n const store = new Map<string, string>();\r\n return {\r\n get: (key: string) => store.get(key) || null,\r\n set: (key: string, value: string) => { store.set(key, value); }\r\n };\r\n };\r\n\r\n /**\r\n * Auto-select appropriate storage based on environment\r\n */\r\n const getDefaultStorage = (): types.I18nStorage => {\r\n return typeof localStorage !== 'undefined' ? createBrowserStorage() : createMemoryStorage();\r\n };\r\n\r\n /**\r\n * Lazy loader: fetch language on-demand\r\n * Supports both URL-based (browser) and file-based (server) loading\r\n */\r\n export class LazyLoader {\r\n private baseUrl: string;\r\n private manager: I18nManager;\r\n private loading = new Map<types.LanguageCode, Promise<void>>();\r\n private loaded = new Set<types.LanguageCode>();\r\n private isServerSide: boolean;\r\n private fileExtension: string;\r\n\r\n constructor(baseUrl: string, manager: I18nManager, fileExtension: string = 'json') {\r\n this.baseUrl = baseUrl.endsWith('/') ? baseUrl : baseUrl + '/';\r\n this.manager = manager;\r\n this.fileExtension = fileExtension;\r\n this.isServerSide = typeof fetch === 'undefined';\r\n }\r\n\r\n /**\r\n * Load a language file on-demand\r\n * Caches the promise to prevent duplicate requests\r\n */\r\n async load(lang: types.LanguageCode): Promise<void> {\r\n // console.log(`[LazyLoader] load() called for language: \"${lang}\"`);\r\n\r\n // Already loaded\r\n if (this.loaded.has(lang)) {\r\n // console.log(`[LazyLoader] Language \"${lang}\" already loaded, returning immediately`);\r\n return;\r\n }\r\n\r\n // Currently loading\r\n if (this.loading.has(lang)) {\r\n // console.log(`[LazyLoader] Language \"${lang}\" is currently loading, returning existing promise`);\r\n return this.loading.get(lang);\r\n }\r\n\r\n // Start loading\r\n // console.log(`[LazyLoader] Starting to load language: \"${lang}\"`);\r\n const promise = this.doLoad(lang);\r\n this.loading.set(lang, promise);\r\n\r\n try {\r\n await promise;\r\n this.loaded.add(lang);\r\n // console.log(`[LazyLoader] Language \"${lang}\" loaded and marked as loaded`);\r\n } catch (error) {\r\n console.error(`[LazyLoader] Error loading language \"${lang}\":`, error);\r\n } finally {\r\n this.loading.delete(lang);\r\n }\r\n }\r\n\r\n private async doLoad(lang: types.LanguageCode): Promise<void> {\r\n try {\r\n const filePath = `${this.baseUrl}${lang}.${this.fileExtension}`;\r\n // console.log(`[LazyLoader] doLoad() - filePath: \"${filePath}\"`);\r\n // console.log(`[LazyLoader] isServerSide: ${this.isServerSide}`);\r\n\r\n let data: Record<string, string> | null;\r\n\r\n // Check if it's a local file path (relative or absolute)\r\n const isLocalPath = filePath.startsWith('.') || filePath.startsWith('/') || /^[a-zA-Z]:/.test(filePath);\r\n // console.log(`[LazyLoader] isLocalPath: ${isLocalPath}`);\r\n\r\n if (isLocalPath || this.isServerSide) {\r\n // Node.js/local: Read from filesystem\r\n // console.log(`[LazyLoader] Loading from file...`);\r\n data = await this.loadFromFile(filePath);\r\n } else {\r\n // Browser: Fetch from URL\r\n // console.log(`[LazyLoader] Loading from URL...`);\r\n data = await this.loadFromUrl(filePath);\r\n }\r\n\r\n if (data) {\r\n // console.log(`[LazyLoader] Data loaded successfully, keys:`, Object.keys(data).length);\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n this.manager.loadLanguage(lang, data as Record<string, any>);\r\n } else {\r\n console.warn(`[LazyLoader] No data loaded for language: \"${lang}\"`);\r\n }\r\n } catch (error) {\r\n console.warn(`[i18n] Error loading language: ${lang}`, error);\r\n }\r\n }\r\n\r\n private async loadFromUrl(url: string): Promise<Record<string, string> | null> {\r\n try {\r\n const response = await fetch(url);\r\n\r\n if (response.ok) {\r\n return await response.json();\r\n } else {\r\n console.warn(`[i18n] Failed to load language from URL: ${url} (${response.status})`);\r\n return null;\r\n }\r\n } catch (error) {\r\n console.warn(`[i18n] Error fetching from URL: ${url}`, error);\r\n return null;\r\n }\r\n }\r\n\r\n private async loadFromFile(filePath: string): Promise<Record<string, string> | null> {\r\n try {\r\n // Dynamic import to avoid issues in browsers\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const fs = await import('fs').then(m => m.promises).catch((): any => null);\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const path = await import('path').then(m => m).catch((): any => null);\r\n\r\n if (!fs) {\r\n console.warn('[i18n] fs module not available. Running in browser?');\r\n return null;\r\n }\r\n\r\n // Resolve relative paths to absolute paths\r\n let resolvedPath = filePath;\r\n if (path && !path.isAbsolute(filePath)) {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const process = await import('process').then(m => m).catch((): any => null);\r\n if (process) {\r\n resolvedPath = path.resolve(process.cwd(), filePath);\r\n }\r\n }\r\n\r\n const content = await fs.readFile(resolvedPath, 'utf-8');\r\n return JSON.parse(content);\r\n } catch (error) {\r\n console.warn(`[i18n] Error reading file: ${filePath}`, error);\r\n return null;\r\n }\r\n }\r\n\r\n isLoaded(lang: types.LanguageCode): boolean {\r\n return this.loaded.has(lang);\r\n }\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ ════ ════════════════════════════════════════╗\r\n\r\n let instance: I18nManager | null = null;\r\n let lazyLoader: LazyLoader | null = null;\r\n\r\n /**\r\n * Get browser language preference\r\n * Uses navigator.language if available (browser environment)\r\n * @private\r\n */\r\n function detectBrowserLanguage(): string {\r\n if (typeof navigator !== 'undefined' && navigator.language) {\r\n return navigator.language.split('-')[0].toLowerCase();\r\n }\r\n return 'en';\r\n }\r\n\r\n /**\r\n * Check if running in browser environment\r\n * @private\r\n */\r\n function isBrowser(): boolean {\r\n return typeof fetch !== 'undefined' && typeof window !== 'undefined';\r\n }\r\n\r\n /**\r\n * Get or create the global i18n instance\r\n */\r\n export function getI18n(): I18nManager {\r\n if (!instance) {\r\n instance = new I18nManager();\r\n }\r\n return instance;\r\n }\r\n\r\n /**\r\n * Get the lazy loader instance (only available after setupI18n with basePath)\r\n */\r\n export function getLazyLoader(): LazyLoader | null {\r\n return lazyLoader;\r\n }\r\n\r\n /**\r\n * Main setup function - Single, simple, auto-detecting initialization\r\n *\r\n * Auto-detects environment and handles both browser and server:\r\n * - Browser: Auto-detects language, loads from URL path\r\n * - Server: Uses defaultLanguage, loads from file system\r\n *\r\n * Call this ONCE at app startup.\r\n *\r\n * @example\r\n * // Browser - Auto-detects language, lazy-loads from URL\r\n * await setupI18n({\r\n * supportedLanguages: ['en', 'ar', 'fr'],\r\n * basePath: '/i18n/'\r\n * });\r\n *\r\n * @example\r\n * // Server - Uses default language, lazy-loads from filesystem\r\n * await setupI18n({\r\n * defaultLanguage: 'en',\r\n * supportedLanguages: ['en', 'ar', 'fr'],\r\n * basePath: './locales/'\r\n * });\r\n */\r\n export async function setupI18n(\r\n config: types.I18nConfig & { basePath?: string }\r\n ): Promise<I18nManager> {\r\n\r\n // Auto-detect browser language if in browser and no defaultLanguage specified\r\n if (isBrowser() && !config.defaultLanguage) {\r\n const detectedLang = detectBrowserLanguage();\r\n if (config.supportedLanguages?.includes(detectedLang)) {\r\n config.defaultLanguage = detectedLang;\r\n } else {\r\n config.defaultLanguage = config.supportedLanguages?.[0] || 'en';\r\n }\r\n }\r\n\r\n // Use appropriate storage based on environment\r\n if (!config.storage) {\r\n config.storage = getDefaultStorage();\r\n }\r\n\r\n // Create and initialize manager\r\n instance = new I18nManager(config);\r\n await instance.init();\r\n\r\n // Setup lazy loading if basePath provided\r\n if (config.basePath) {\r\n const fileExtension = config.fileExtension || 'json';\r\n lazyLoader = new LazyLoader(config.basePath, instance, fileExtension);\r\n await lazyLoader.load(instance.getLanguage());\r\n }\r\n\r\n return instance;\r\n }\r\n\r\n\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CONVENIENCE FUNCTIONS ════════════════════════════════════════╗\r\n\r\n /**\r\n * Translate a key with optional parameter replacement\r\n */\r\n export const t = (key: string, params?: Record<string, string>, fallback?: string) =>\r\n getI18n().t(key, params, fallback);\r\n\r\n /**\r\n * Translate a key with a specific language temporarily\r\n */\r\n export const tLang = (lang: types.LanguageCode, key: string, params?: Record<string, string>, fallback?: string) =>\r\n getI18n().tLang(lang, key, params, fallback);\r\n\r\n /**\r\n * Translate a key with a specific language - async version that loads language if needed\r\n * Use this on server-side with lazy loading to ensure language is loaded first\r\n */\r\n export const tLangAsync = async (lang: types.LanguageCode, key: string, params?: Record<string, string>, fallback?: string): Promise<string> => {\r\n // console.log(`[i18n] tLangAsync() called: lang=\"${lang}\", key=\"${key}\"`);\r\n\r\n // Load language if lazy loader available\r\n if (lazyLoader && !lazyLoader.isLoaded(lang)) {\r\n // console.log(`[i18n] Language \"${lang}\" not loaded, loading now...`);\r\n await lazyLoader.load(lang);\r\n // console.log(`[i18n] Language \"${lang}\" loaded successfully`);\r\n } else if (lazyLoader) {\r\n // console.log(`[i18n] Language \"${lang}\" already loaded`);\r\n } else {\r\n // console.log(`[i18n] No LazyLoader available`);\r\n }\r\n\r\n const result = getI18n().tLang(lang, key, params, fallback);\r\n // console.log(`[i18n] tLangAsync() result: \"${result}\"`);\r\n return result;\r\n };\r\n\r\n /**\r\n * Parse translation with HTML tags into tokens\r\n */\r\n export const tParse = (key: string, params?: Record<string, string>, fallback?: string) =>\r\n getI18n().tParse(key, params, fallback);\r\n\r\n /**\r\n * Set current language and trigger listeners\r\n */\r\n export const setLanguage = (lang: types.LanguageCode): Promise<void> => {\r\n // Load language if lazy loader available\r\n if (lazyLoader && !lazyLoader.isLoaded(lang)) {\r\n return lazyLoader.load(lang).then(() => getI18n().setLanguage(lang));\r\n }\r\n return getI18n().setLanguage(lang);\r\n };\r\n\r\n /**\r\n * Get current language code\r\n */\r\n export const getLanguage = () =>\r\n getI18n().getLanguage();\r\n\r\n /**\r\n * Get all supported languages\r\n */\r\n export const getSupportedLanguages = () =>\r\n getI18n().getSupportedLanguages();\r\n\r\n /**\r\n * Check if translation key exists\r\n */\r\n export const hasKey = (key: string) =>\r\n getI18n().hasKey(key);\r\n\r\n /**\r\n * Check if current language is RTL\r\n */\r\n export const isRTL = () =>\r\n getI18n().isRTL();\r\n\r\n /**\r\n * Check if specific language is RTL\r\n */\r\n export const isRTLLanguage = (lang: types.LanguageCode) =>\r\n getI18n().isRTLLanguage(lang);\r\n\r\n /**\r\n * Subscribe to language changes\r\n */\r\n export const onChange = (callback: (lang: types.LanguageCode) => void) =>\r\n getI18n().onChange(callback);\r\n\r\n /**\r\n * Load translations for a specific language\r\n */\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n export const loadLanguage = (lang: types.LanguageCode, translations: Record<string, any>) =>\r\n getI18n().loadLanguage(lang, translations);\r\n\r\n /**\r\n * Load multiple languages at once\r\n */\r\n export const loadTranslations = (translations: types.TranslationSet) =>\r\n getI18n().loadTranslations(translations);\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTILITY FUNCTIONS ════════════════════════════════════════╗\r\n\r\n /**\r\n * Pluralization helper - select translation based on count\r\n *\r\n * @example\r\n * plural(1, 'item.single', 'item.plural') // \"1 item\"\r\n * plural(5, 'item.single', 'item.plural') // \"5 items\"\r\n */\r\n export function plural(count: number, singleKey: string, pluralKey: string): string {\r\n const key = count === 1 ? singleKey : pluralKey;\r\n return t(key, { count: String(count) });\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ EXPORTS ════════════════════════════════════════╗\r\n\r\n export default {\r\n setupI18n,\r\n getI18n,\r\n getLazyLoader,\r\n I18nManager,\r\n LazyLoader,\r\n t,\r\n tLang,\r\n tParse,\r\n setLanguage,\r\n getLanguage,\r\n getSupportedLanguages,\r\n hasKey,\r\n isRTL,\r\n isRTLLanguage,\r\n onChange,\r\n loadLanguage,\r\n loadTranslations,\r\n plural,\r\n };\r\n\r\n export type I18nManagerInstance = InstanceType<typeof I18nManager>;\r\n export type LazyLoaderInstance = InstanceType<typeof LazyLoader>;\r\n\r\n export * from './mod/i18n';\r\n export * from './types';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n"]}
package/dist/index.d.cts CHANGED
@@ -58,9 +58,10 @@ declare class I18nManager {
58
58
  *
59
59
  * @param key Translation key (dot notation)
60
60
  * @param params Optional parameters for replacement
61
+ * @param fallback Optional fallback string if key not found
61
62
  * @returns Translated string
62
63
  */
63
- t(key: string, params?: Record<string, string>): string;
64
+ t(key: string, params?: Record<string, string>, fallback?: string): string;
64
65
  /**
65
66
  * Get raw translation without parameter replacement
66
67
  * @private
@@ -69,11 +70,12 @@ declare class I18nManager {
69
70
  /**
70
71
  * Translate with a specific language temporarily
71
72
  *
72
- * @param key Translation key
73
73
  * @param lang Language code
74
+ * @param key Translation key
74
75
  * @param params Optional parameters
76
+ * @param fallback Optional fallback string if key not found
75
77
  */
76
- tLang(key: string, lang: LanguageCode, params?: Record<string, string>): string;
78
+ tLang(lang: LanguageCode, key: string, params?: Record<string, string>, fallback?: string): string;
77
79
  /**
78
80
  * Translate and parse HTML-like tags into tokens
79
81
  * Converts \n or /n to line breaks
@@ -90,9 +92,10 @@ declare class I18nManager {
90
92
  *
91
93
  * @param key Translation key
92
94
  * @param params Optional parameters
95
+ * @param fallback Optional fallback string if key not found
93
96
  * @returns Array of tokens
94
97
  */
95
- tParse(key: string, params?: Record<string, string>): TranslationToken[];
98
+ tParse(key: string, params?: Record<string, string>, fallback?: string): TranslationToken[];
96
99
  /**
97
100
  * Set current language
98
101
  */
@@ -192,15 +195,20 @@ declare function setupI18n(config: I18nConfig & {
192
195
  /**
193
196
  * Translate a key with optional parameter replacement
194
197
  */
195
- declare const t: (key: string, params?: Record<string, string>) => string;
198
+ declare const t: (key: string, params?: Record<string, string>, fallback?: string) => string;
196
199
  /**
197
200
  * Translate a key with a specific language temporarily
198
201
  */
199
- declare const tLang: (key: string, lang: LanguageCode, params?: Record<string, string>) => string;
202
+ declare const tLang: (lang: LanguageCode, key: string, params?: Record<string, string>, fallback?: string) => string;
203
+ /**
204
+ * Translate a key with a specific language - async version that loads language if needed
205
+ * Use this on server-side with lazy loading to ensure language is loaded first
206
+ */
207
+ declare const tLangAsync: (lang: LanguageCode, key: string, params?: Record<string, string>, fallback?: string) => Promise<string>;
200
208
  /**
201
209
  * Parse translation with HTML tags into tokens
202
210
  */
203
- declare const tParse: (key: string, params?: Record<string, string>) => TranslationToken[];
211
+ declare const tParse: (key: string, params?: Record<string, string>, fallback?: string) => TranslationToken[];
204
212
  /**
205
213
  * Set current language and trigger listeners
206
214
  */
@@ -251,9 +259,9 @@ declare const _default: {
251
259
  getLazyLoader: typeof getLazyLoader;
252
260
  I18nManager: typeof I18nManager;
253
261
  LazyLoader: typeof LazyLoader;
254
- t: (key: string, params?: Record<string, string>) => string;
255
- tLang: (key: string, lang: LanguageCode, params?: Record<string, string>) => string;
256
- tParse: (key: string, params?: Record<string, string>) => TranslationToken[];
262
+ t: (key: string, params?: Record<string, string>, fallback?: string) => string;
263
+ tLang: (lang: LanguageCode, key: string, params?: Record<string, string>, fallback?: string) => string;
264
+ tParse: (key: string, params?: Record<string, string>, fallback?: string) => TranslationToken[];
257
265
  setLanguage: (lang: LanguageCode) => Promise<void>;
258
266
  getLanguage: () => string;
259
267
  getSupportedLanguages: () => string[];
@@ -269,4 +277,4 @@ declare const _default: {
269
277
  type I18nManagerInstance = InstanceType<typeof I18nManager>;
270
278
  type LazyLoaderInstance = InstanceType<typeof LazyLoader>;
271
279
 
272
- export { type I18nConfig, I18nManager, type I18nManagerInstance, type I18nStorage, type LanguageCode, LazyLoader, type LazyLoaderInstance, type TranslationSet, type TranslationToken, _default as default, getI18n, getLanguage, getLazyLoader, getSupportedLanguages, hasKey, isRTL, isRTLLanguage, loadLanguage, loadTranslations, onChange, plural, setLanguage, setupI18n, t, tLang, tParse };
280
+ export { type I18nConfig, I18nManager, type I18nManagerInstance, type I18nStorage, type LanguageCode, LazyLoader, type LazyLoaderInstance, type TranslationSet, type TranslationToken, _default as default, getI18n, getLanguage, getLazyLoader, getSupportedLanguages, hasKey, isRTL, isRTLLanguage, loadLanguage, loadTranslations, onChange, plural, setLanguage, setupI18n, t, tLang, tLangAsync, tParse };
package/dist/index.d.ts CHANGED
@@ -58,9 +58,10 @@ declare class I18nManager {
58
58
  *
59
59
  * @param key Translation key (dot notation)
60
60
  * @param params Optional parameters for replacement
61
+ * @param fallback Optional fallback string if key not found
61
62
  * @returns Translated string
62
63
  */
63
- t(key: string, params?: Record<string, string>): string;
64
+ t(key: string, params?: Record<string, string>, fallback?: string): string;
64
65
  /**
65
66
  * Get raw translation without parameter replacement
66
67
  * @private
@@ -69,11 +70,12 @@ declare class I18nManager {
69
70
  /**
70
71
  * Translate with a specific language temporarily
71
72
  *
72
- * @param key Translation key
73
73
  * @param lang Language code
74
+ * @param key Translation key
74
75
  * @param params Optional parameters
76
+ * @param fallback Optional fallback string if key not found
75
77
  */
76
- tLang(key: string, lang: LanguageCode, params?: Record<string, string>): string;
78
+ tLang(lang: LanguageCode, key: string, params?: Record<string, string>, fallback?: string): string;
77
79
  /**
78
80
  * Translate and parse HTML-like tags into tokens
79
81
  * Converts \n or /n to line breaks
@@ -90,9 +92,10 @@ declare class I18nManager {
90
92
  *
91
93
  * @param key Translation key
92
94
  * @param params Optional parameters
95
+ * @param fallback Optional fallback string if key not found
93
96
  * @returns Array of tokens
94
97
  */
95
- tParse(key: string, params?: Record<string, string>): TranslationToken[];
98
+ tParse(key: string, params?: Record<string, string>, fallback?: string): TranslationToken[];
96
99
  /**
97
100
  * Set current language
98
101
  */
@@ -192,15 +195,20 @@ declare function setupI18n(config: I18nConfig & {
192
195
  /**
193
196
  * Translate a key with optional parameter replacement
194
197
  */
195
- declare const t: (key: string, params?: Record<string, string>) => string;
198
+ declare const t: (key: string, params?: Record<string, string>, fallback?: string) => string;
196
199
  /**
197
200
  * Translate a key with a specific language temporarily
198
201
  */
199
- declare const tLang: (key: string, lang: LanguageCode, params?: Record<string, string>) => string;
202
+ declare const tLang: (lang: LanguageCode, key: string, params?: Record<string, string>, fallback?: string) => string;
203
+ /**
204
+ * Translate a key with a specific language - async version that loads language if needed
205
+ * Use this on server-side with lazy loading to ensure language is loaded first
206
+ */
207
+ declare const tLangAsync: (lang: LanguageCode, key: string, params?: Record<string, string>, fallback?: string) => Promise<string>;
200
208
  /**
201
209
  * Parse translation with HTML tags into tokens
202
210
  */
203
- declare const tParse: (key: string, params?: Record<string, string>) => TranslationToken[];
211
+ declare const tParse: (key: string, params?: Record<string, string>, fallback?: string) => TranslationToken[];
204
212
  /**
205
213
  * Set current language and trigger listeners
206
214
  */
@@ -251,9 +259,9 @@ declare const _default: {
251
259
  getLazyLoader: typeof getLazyLoader;
252
260
  I18nManager: typeof I18nManager;
253
261
  LazyLoader: typeof LazyLoader;
254
- t: (key: string, params?: Record<string, string>) => string;
255
- tLang: (key: string, lang: LanguageCode, params?: Record<string, string>) => string;
256
- tParse: (key: string, params?: Record<string, string>) => TranslationToken[];
262
+ t: (key: string, params?: Record<string, string>, fallback?: string) => string;
263
+ tLang: (lang: LanguageCode, key: string, params?: Record<string, string>, fallback?: string) => string;
264
+ tParse: (key: string, params?: Record<string, string>, fallback?: string) => TranslationToken[];
257
265
  setLanguage: (lang: LanguageCode) => Promise<void>;
258
266
  getLanguage: () => string;
259
267
  getSupportedLanguages: () => string[];
@@ -269,4 +277,4 @@ declare const _default: {
269
277
  type I18nManagerInstance = InstanceType<typeof I18nManager>;
270
278
  type LazyLoaderInstance = InstanceType<typeof LazyLoader>;
271
279
 
272
- export { type I18nConfig, I18nManager, type I18nManagerInstance, type I18nStorage, type LanguageCode, LazyLoader, type LazyLoaderInstance, type TranslationSet, type TranslationToken, _default as default, getI18n, getLanguage, getLazyLoader, getSupportedLanguages, hasKey, isRTL, isRTLLanguage, loadLanguage, loadTranslations, onChange, plural, setLanguage, setupI18n, t, tLang, tParse };
280
+ export { type I18nConfig, I18nManager, type I18nManagerInstance, type I18nStorage, type LanguageCode, LazyLoader, type LazyLoaderInstance, type TranslationSet, type TranslationToken, _default as default, getI18n, getLanguage, getLazyLoader, getSupportedLanguages, hasKey, isRTL, isRTLLanguage, loadLanguage, loadTranslations, onChange, plural, setLanguage, setupI18n, t, tLang, tLangAsync, tParse };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- var u=class{constructor(e){this.translations={};this.currentLanguage="en";this.defaultLanguage="en";this.supportedLanguages=new Set(["en"]);this.rtlLanguages=new Set(["ar","he","fa","ur","yi","ji","iw","ku"]);this.listeners=new Set;e&&(this.defaultLanguage=e.defaultLanguage||"en",this.currentLanguage=e.defaultLanguage||"en",this.storage=e.storage,this.onLanguageChange=e.onLanguageChange,e.supportedLanguages&&(this.supportedLanguages=new Set(e.supportedLanguages)));}async init(){if(this.storage){let e=await this.storage.get("lang");e&&this.supportedLanguages.has(e)&&(this.currentLanguage=e);}}loadLanguage(e,t){this.translations[e]||(this.translations[e]={});let a=this.flattenObject(t);this.translations[e]={...this.translations[e],...a},this.supportedLanguages.add(e);}loadTranslations(e){Object.entries(e).forEach(([t,a])=>{this.loadLanguage(t,a);});}flattenObject(e,t=""){let a={};for(let s in e){if(!Object.prototype.hasOwnProperty.call(e,s))continue;let i=e[s],r=t?`${t}.${s}`:s;typeof i=="object"&&i!==null&&!Array.isArray(i)?Object.assign(a,this.flattenObject(i,r)):a[r]=String(i);}return a}t(e,t){let a=this.getTranslation(e);return t&&Object.entries(t).forEach(([s,i])=>{let r=this.getTranslation(i,i);a=a.replace(new RegExp(`\\{${s}\\}`,"g"),r);}),a}getTranslation(e,t){return this.translations[this.currentLanguage]?.[e]?this.translations[this.currentLanguage][e]:this.defaultLanguage!==this.currentLanguage&&this.translations[this.defaultLanguage]?.[e]?this.translations[this.defaultLanguage][e]:(console.warn(`[i18n] Translation key not found: "${e}" (lang: ${this.currentLanguage})`),t||e)}tLang(e,t,a){let s=this.currentLanguage;this.currentLanguage=t;let i=this.t(e,a);return this.currentLanguage=s,i}tParse(e,t){let a=this.t(e,t);a=a.replace(/\\n|\/n/g,"<br>");let s=[],i=/<([a-z]+)>([^<]*)<\/\1>|<([a-z]+)\s*\/?>|([^<]+)/gi,r;for(;(r=i.exec(a))!==null;)r[4]?s.push({type:"text",content:r[4]}):r[1]?s.push({type:"tag",tag:r[1],content:r[2]}):r[3]&&s.push({type:"tag",tag:r[3],content:""});return s.length>0?s:[{type:"text",content:a}]}async setLanguage(e){this.supportedLanguages.has(e)&&(this.currentLanguage=e,this.storage&&await this.storage.set("lang",e),typeof document<"u"&&(h("lang",e,365),document.documentElement.lang=e),this.listeners.forEach(t=>t(e)),this.onLanguageChange&&this.onLanguageChange(e));}getLanguage(){return this.currentLanguage}getSupportedLanguages(){return Array.from(this.supportedLanguages)}isLanguageSupported(e){return this.supportedLanguages.has(e)}hasKey(e){return !!(this.translations[this.currentLanguage]?.[e]||this.translations[this.defaultLanguage]?.[e])}getTranslations(){return this.translations[this.currentLanguage]||{}}isRTL(){return this.rtlLanguages.has(this.currentLanguage.toLowerCase().substring(0,2))}isRTLLanguage(e){return this.rtlLanguages.has(e.toLowerCase().substring(0,2))}onChange(e){return this.listeners.add(e),()=>this.listeners.delete(e)}};function h(n,e,t){let a="";{let s=new Date;s.setTime(s.getTime()+t*24*60*60*1e3),a="; expires="+s.toUTCString();}document.cookie=n+"="+(e||"")+a+"; path=/";}var L=()=>({get:n=>typeof localStorage>"u"?null:localStorage.getItem(n),set:(n,e)=>{typeof localStorage<"u"&&localStorage.setItem(n,e);}}),f=()=>{let n=new Map;return {get:e=>n.get(e)||null,set:(e,t)=>{n.set(e,t);}}},y=()=>typeof localStorage<"u"?L():f(),d=class{constructor(e,t,a="json"){this.loading=new Map;this.loaded=new Set;this.baseUrl=e.endsWith("/")?e:e+"/",this.manager=t,this.fileExtension=a,this.isServerSide=typeof fetch>"u";}async load(e){if(this.loaded.has(e))return;if(this.loading.has(e))return this.loading.get(e);let t=this.doLoad(e);this.loading.set(e,t);try{await t,this.loaded.add(e);}finally{this.loading.delete(e);}}async doLoad(e){try{let t=`${this.baseUrl}${e}.${this.fileExtension}`,a;t.startsWith(".")||t.startsWith("/")||/^[a-zA-Z]:/.test(t)||this.isServerSide?a=await this.loadFromFile(t):a=await this.loadFromUrl(t),a&&this.manager.loadLanguage(e,a);}catch(t){console.warn(`[i18n] Error loading language: ${e}`,t);}}async loadFromUrl(e){try{let t=await fetch(e);return t.ok?await t.json():(console.warn(`[i18n] Failed to load language from URL: ${e} (${t.status})`),null)}catch(t){return console.warn(`[i18n] Error fetching from URL: ${e}`,t),null}}async loadFromFile(e){try{let t=await import('fs').then(r=>r.promises).catch(()=>null),a=await import('path').then(r=>r).catch(()=>null);if(!t)return console.warn("[i18n] fs module not available. Running in browser?"),null;let s=e;if(a&&!a.isAbsolute(e)){let r=await import('process').then(c=>c).catch(()=>null);r&&(s=a.resolve(r.cwd(),e));}let i=await t.readFile(s,"utf-8");return JSON.parse(i)}catch(t){return console.warn(`[i18n] Error reading file: ${e}`,t),null}}isLoaded(e){return this.loaded.has(e)}},g=null,l=null;function m(){return typeof navigator<"u"&&navigator.language?navigator.language.split("-")[0].toLowerCase():"en"}function w(){return typeof fetch<"u"&&typeof window<"u"}function o(){return g||(g=new u),g}function v(){return l}async function b(n){if(w()&&!n.defaultLanguage){let e=m();n.supportedLanguages?.includes(e)?n.defaultLanguage=e:n.defaultLanguage=n.supportedLanguages?.[0]||"en";}if(n.storage||(n.storage=y()),g=new u(n),await g.init(),n.basePath){let e=n.fileExtension||"json";l=new d(n.basePath,g,e),await l.load(g.getLanguage());}return g}var p=(n,e)=>o().t(n,e),C=(n,e,t)=>o().tLang(n,e,t),x=(n,e)=>o().tParse(n,e),S=n=>l&&!l.isLoaded(n)?l.load(n).then(()=>o().setLanguage(n)):o().setLanguage(n),R=()=>o().getLanguage(),T=()=>o().getSupportedLanguages(),I=n=>o().hasKey(n),P=()=>o().isRTL(),E=n=>o().isRTLLanguage(n),$=n=>o().onChange(n),j=(n,e)=>o().loadLanguage(n,e),M=n=>o().loadTranslations(n);function O(n,e,t){return p(n===1?e:t,{count:String(n)})}var U={setupI18n:b,getI18n:o,getLazyLoader:v,I18nManager:u,LazyLoader:d,t:p,tLang:C,tParse:x,setLanguage:S,getLanguage:R,getSupportedLanguages:T,hasKey:I,isRTL:P,isRTLLanguage:E,onChange:$,loadLanguage:j,loadTranslations:M,plural:O};export{u as I18nManager,d as LazyLoader,U as default,o as getI18n,R as getLanguage,v as getLazyLoader,T as getSupportedLanguages,I as hasKey,P as isRTL,E as isRTLLanguage,j as loadLanguage,M as loadTranslations,$ as onChange,O as plural,S as setLanguage,b as setupI18n,p as t,C as tLang,x as tParse};//# sourceMappingURL=index.js.map
1
+ var d=class{constructor(e){this.translations={};this.currentLanguage="en";this.defaultLanguage="en";this.supportedLanguages=new Set(["en"]);this.rtlLanguages=new Set(["ar","he","fa","ur","yi","ji","iw","ku"]);this.listeners=new Set;e&&(this.defaultLanguage=e.defaultLanguage||"en",this.currentLanguage=e.defaultLanguage||"en",this.storage=e.storage,this.onLanguageChange=e.onLanguageChange,e.supportedLanguages&&(this.supportedLanguages=new Set(e.supportedLanguages)));}async init(){if(this.storage){let e=await this.storage.get("i18n-language");e&&this.supportedLanguages.has(e)&&(this.currentLanguage=e);}}loadLanguage(e,t){this.translations[e]||(this.translations[e]={});let a=this.flattenObject(t);this.translations[e]={...this.translations[e],...a},this.supportedLanguages.add(e);}loadTranslations(e){Object.entries(e).forEach(([t,a])=>{this.loadLanguage(t,a);});}flattenObject(e,t=""){let a={};for(let s in e){if(!Object.prototype.hasOwnProperty.call(e,s))continue;let r=e[s],i=t?`${t}.${s}`:s;typeof r=="object"&&r!==null&&!Array.isArray(r)?Object.assign(a,this.flattenObject(r,i)):a[i]=String(r);}return a}t(e,t,a){let s=this.getTranslation(e,a);return t&&Object.entries(t).forEach(([r,i])=>{let g=this.getTranslation(i,i);s=s.replace(new RegExp(`\\{${r}\\}`,"g"),g);}),s}getTranslation(e,t){return this.translations[this.currentLanguage]?.[e]?this.translations[this.currentLanguage][e]:this.defaultLanguage!==this.currentLanguage&&this.translations[this.defaultLanguage]?.[e]?this.translations[this.defaultLanguage][e]:(console.warn(`[i18n] Translation key not found: "${e}" (lang: ${this.currentLanguage})`),t||e)}tLang(e,t,a,s){let r=this.currentLanguage;this.currentLanguage=e;let i=this.t(t,a,s);return this.currentLanguage=r,i}tParse(e,t,a){let s=this.t(e,t,a);s=s.replace(/\\n|\/n/g,"<br>");let r=[],i=/<([a-z]+)>([^<]*)<\/\1>|<([a-z]+)\s*\/?>|([^<]+)/gi,g;for(;(g=i.exec(s))!==null;)g[4]?r.push({type:"text",content:g[4]}):g[1]?r.push({type:"tag",tag:g[1],content:g[2]}):g[3]&&r.push({type:"tag",tag:g[3],content:""});return r.length>0?r:[{type:"text",content:s}]}async setLanguage(e){if(!this.supportedLanguages.has(e)){console.warn(`[i18n] Language "${e}" not supported, aborting setLanguage()`);return}this.currentLanguage=e,this.storage&&await this.storage.set("i18n-language",e),typeof document<"u"&&(L("lang",e,365),document.documentElement.lang=e),this.listeners.forEach(t=>t(e)),this.onLanguageChange&&this.onLanguageChange(e);}getLanguage(){return this.currentLanguage}getSupportedLanguages(){return Array.from(this.supportedLanguages)}isLanguageSupported(e){return this.supportedLanguages.has(e)}hasKey(e){return !!(this.translations[this.currentLanguage]?.[e]||this.translations[this.defaultLanguage]?.[e])}getTranslations(){return this.translations[this.currentLanguage]||{}}isRTL(){return this.rtlLanguages.has(this.currentLanguage.toLowerCase().substring(0,2))}isRTLLanguage(e){return this.rtlLanguages.has(e.toLowerCase().substring(0,2))}onChange(e){return this.listeners.add(e),()=>this.listeners.delete(e)}};function L(n,e,t){let a="";{let s=new Date;s.setTime(s.getTime()+t*24*60*60*1e3),a="; expires="+s.toUTCString();}document.cookie=n+"="+(e||"")+a+"; path=/";}var h=()=>({get:n=>typeof localStorage>"u"?null:localStorage.getItem(n),set:(n,e)=>{typeof localStorage<"u"&&localStorage.setItem(n,e);}}),f=()=>{let n=new Map;return {get:e=>n.get(e)||null,set:(e,t)=>{n.set(e,t);}}},y=()=>typeof localStorage<"u"?h():f(),p=class{constructor(e,t,a="json"){this.loading=new Map;this.loaded=new Set;this.baseUrl=e.endsWith("/")?e:e+"/",this.manager=t,this.fileExtension=a,this.isServerSide=typeof fetch>"u";}async load(e){if(this.loaded.has(e))return;if(this.loading.has(e))return this.loading.get(e);let t=this.doLoad(e);this.loading.set(e,t);try{await t,this.loaded.add(e);}catch(a){console.error(`[LazyLoader] Error loading language "${e}":`,a);}finally{this.loading.delete(e);}}async doLoad(e){try{let t=`${this.baseUrl}${e}.${this.fileExtension}`,a;t.startsWith(".")||t.startsWith("/")||/^[a-zA-Z]:/.test(t)||this.isServerSide?a=await this.loadFromFile(t):a=await this.loadFromUrl(t),a?this.manager.loadLanguage(e,a):console.warn(`[LazyLoader] No data loaded for language: "${e}"`);}catch(t){console.warn(`[i18n] Error loading language: ${e}`,t);}}async loadFromUrl(e){try{let t=await fetch(e);return t.ok?await t.json():(console.warn(`[i18n] Failed to load language from URL: ${e} (${t.status})`),null)}catch(t){return console.warn(`[i18n] Error fetching from URL: ${e}`,t),null}}async loadFromFile(e){try{let t=await import('fs').then(i=>i.promises).catch(()=>null),a=await import('path').then(i=>i).catch(()=>null);if(!t)return console.warn("[i18n] fs module not available. Running in browser?"),null;let s=e;if(a&&!a.isAbsolute(e)){let i=await import('process').then(g=>g).catch(()=>null);i&&(s=a.resolve(i.cwd(),e));}let r=await t.readFile(s,"utf-8");return JSON.parse(r)}catch(t){return console.warn(`[i18n] Error reading file: ${e}`,t),null}}isLoaded(e){return this.loaded.has(e)}},l=null,u=null;function w(){return typeof navigator<"u"&&navigator.language?navigator.language.split("-")[0].toLowerCase():"en"}function m(){return typeof fetch<"u"&&typeof window<"u"}function o(){return l||(l=new d),l}function v(){return u}async function b(n){if(m()&&!n.defaultLanguage){let e=w();n.supportedLanguages?.includes(e)?n.defaultLanguage=e:n.defaultLanguage=n.supportedLanguages?.[0]||"en";}if(n.storage||(n.storage=y()),l=new d(n),await l.init(),n.basePath){let e=n.fileExtension||"json";u=new p(n.basePath,l,e),await u.load(l.getLanguage());}return l}var c=(n,e,t)=>o().t(n,e,t),C=(n,e,t,a)=>o().tLang(n,e,t,a),A=async(n,e,t,a)=>(u&&!u.isLoaded(n)&&await u.load(n),o().tLang(n,e,t,a)),x=(n,e,t)=>o().tParse(n,e,t),S=n=>u&&!u.isLoaded(n)?u.load(n).then(()=>o().setLanguage(n)):o().setLanguage(n),R=()=>o().getLanguage(),T=()=>o().getSupportedLanguages(),I=n=>o().hasKey(n),P=()=>o().isRTL(),$=n=>o().isRTLLanguage(n),E=n=>o().onChange(n),j=(n,e)=>o().loadLanguage(n,e),z=n=>o().loadTranslations(n);function M(n,e,t){return c(n===1?e:t,{count:String(n)})}var U={setupI18n:b,getI18n:o,getLazyLoader:v,I18nManager:d,LazyLoader:p,t:c,tLang:C,tParse:x,setLanguage:S,getLanguage:R,getSupportedLanguages:T,hasKey:I,isRTL:P,isRTLLanguage:$,onChange:E,loadLanguage:j,loadTranslations:z,plural:M};export{d as I18nManager,p as LazyLoader,U as default,o as getI18n,R as getLanguage,v as getLazyLoader,T as getSupportedLanguages,I as hasKey,P as isRTL,$ as isRTLLanguage,j as loadLanguage,z as loadTranslations,E as onChange,M as plural,S as setLanguage,b as setupI18n,c as t,C as tLang,A as tLangAsync,x as tParse};//# sourceMappingURL=index.js.map
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/mod/i18n.ts","../src/index.ts"],"names":["I18nManager","config","stored","lang","translations","flattened","trans","obj","prefix","key","value","newKey","params","translation","param","paramValue","fallback","original","result","tokens","regex","match","setCookie","fn","callback","name","days","expires","date","createBrowserStorage","createMemoryStorage","store","getDefaultStorage","LazyLoader","baseUrl","manager","fileExtension","promise","filePath","data","error","url","response","fs","m","path","resolvedPath","process","content","instance","lazyLoader","detectBrowserLanguage","isBrowser","getI18n","getLazyLoader","setupI18n","detectedLang","t","tLang","tParse","setLanguage","getLanguage","getSupportedLanguages","hasKey","isRTL","isRTLLanguage","onChange","loadLanguage","loadTranslations","plural","count","singleKey","pluralKey","index_default"],"mappings":"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","file":"index.js","sourcesContent":["// src/mod/i18n.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import * as types from '../types';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n export class I18nManager {\r\n\r\n // ┌──────────────────────────────── INIT ──────────────────────────────┐\r\n\r\n private translations : types.TranslationSet = {};\r\n private currentLanguage : types.LanguageCode = 'en';\r\n private defaultLanguage : types.LanguageCode = 'en';\r\n private supportedLanguages = new Set<types.LanguageCode>(['en']);\r\n private rtlLanguages = new Set<string>(['ar', 'he', 'fa', 'ur', 'yi', 'ji', 'iw', 'ku']);\r\n private listeners = new Set<(lang: types.LanguageCode) => void>();\r\n private storage? : types.I18nStorage;\r\n private onLanguageChange? : (lang: types.LanguageCode) => void;\r\n\r\n constructor(config?: types.I18nConfig) {\r\n if (config) {\r\n this.defaultLanguage = config.defaultLanguage || 'en';\r\n this.currentLanguage = config.defaultLanguage || 'en';\r\n this.storage = config.storage;\r\n this.onLanguageChange = config.onLanguageChange;\r\n\r\n if (config.supportedLanguages) {\r\n this.supportedLanguages = new Set(config.supportedLanguages);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Initialize with stored language preference\r\n */\r\n public async init(): Promise<void> {\r\n\r\n if (this.storage) {\r\n const stored = await this.storage.get('lang');\r\n if (stored && this.supportedLanguages.has(stored)) {\r\n this.currentLanguage = stored;\r\n }\r\n }\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── LOAD ──────────────────────────────┐\r\n\r\n /**\r\n * Load translations for a specific language\r\n * @param lang Language code\r\n * @param translations Translation object (can be nested)\r\n */\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n public loadLanguage(lang: types.LanguageCode, translations: Record<string, any>): void {\r\n if (!this.translations[lang]) {\r\n this.translations[lang] = {};\r\n }\r\n\r\n const flattened = this.flattenObject(translations);\r\n this.translations[lang] = { ...this.translations[lang], ...flattened };\r\n this.supportedLanguages.add(lang);\r\n }\r\n\r\n /**\r\n * Load multiple languages at once\r\n * @param translations Object with language codes as keys\r\n */\r\n public loadTranslations(translations: types.TranslationSet): void {\r\n Object.entries(translations).forEach(([lang, trans]) => {\r\n this.loadLanguage(lang, trans);\r\n });\r\n }\r\n\r\n /**\r\n * Flatten nested object into dot notation\r\n * @private\r\n */\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n private flattenObject(obj: Record<string, any>, prefix: string = ''): Record<string, string> {\r\n const flattened: Record<string, string> = {};\r\n\r\n for (const key in obj) {\r\n if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;\r\n\r\n const value = obj[key];\r\n const newKey = prefix ? `${prefix}.${key}` : key;\r\n\r\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\r\n Object.assign(flattened, this.flattenObject(value, newKey));\r\n } else {\r\n flattened[newKey] = String(value);\r\n }\r\n }\r\n\r\n return flattened;\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌───────────────────────────── TRANSLATION ──────────────────────────┐\r\n\r\n /**\r\n * Translate a key with parameter replacement\r\n *\r\n * @example\r\n * t('welcome.message', { name: 'John' })\r\n * // => \"Welcome, John!\"\r\n *\r\n * @param key Translation key (dot notation)\r\n * @param params Optional parameters for replacement\r\n * @returns Translated string\r\n */\r\n public t(key: string, params?: Record<string, string>): string {\r\n\r\n let translation = this.getTranslation(key);\r\n\r\n if (params) {\r\n Object.entries(params).forEach(([param, value]) => {\r\n // Check if parameter value is itself a translation key\r\n const paramValue = this.getTranslation(value, value);\r\n translation = translation.replace(\r\n new RegExp(`\\\\{${param}\\\\}`, 'g'),\r\n paramValue\r\n );\r\n });\r\n }\r\n\r\n return translation;\r\n }\r\n\r\n /**\r\n * Get raw translation without parameter replacement\r\n * @private\r\n */\r\n private getTranslation(key: string, fallback?: string): string {\r\n // Try current language\r\n if (this.translations[this.currentLanguage]?.[key]) {\r\n return this.translations[this.currentLanguage][key];\r\n }\r\n\r\n // Try default language\r\n if (this.defaultLanguage !== this.currentLanguage &&\r\n this.translations[this.defaultLanguage]?.[key]) {\r\n return this.translations[this.defaultLanguage][key];\r\n }\r\n\r\n // Warn and return fallback\r\n console.warn(`[i18n] Translation key not found: \"${key}\" (lang: ${this.currentLanguage})`);\r\n return fallback || key;\r\n }\r\n\r\n /**\r\n * Translate with a specific language temporarily\r\n *\r\n * @param key Translation key\r\n * @param lang Language code\r\n * @param params Optional parameters\r\n */\r\n public tLang(key: string, lang: types.LanguageCode, params?: Record<string, string>): string {\r\n const original = this.currentLanguage;\r\n this.currentLanguage = lang;\r\n const result = this.t(key, params);\r\n this.currentLanguage = original;\r\n return result;\r\n }\r\n\r\n /**\r\n * Translate and parse HTML-like tags into tokens\r\n * Converts \\n or /n to line breaks\r\n *\r\n * @example\r\n * // Translation: \"Hello\\nWorld <strong>here</strong>\"\r\n * tParse('message')\r\n * // => [\r\n * // { type: 'text', content: 'Hello' },\r\n * // { type: 'tag', tag: 'br', content: '' },\r\n * // { type: 'text', content: 'World ' },\r\n * // { type: 'tag', tag: 'strong', content: 'here' }\r\n * // ]\r\n *\r\n * @param key Translation key\r\n * @param params Optional parameters\r\n * @returns Array of tokens\r\n */\r\n public tParse(key: string, params?: Record<string, string>): types.TranslationToken[] {\r\n let translation = this.t(key, params);\r\n\r\n // Convert newlines to <br> tags\r\n translation = translation.replace(/\\\\n|\\/n/g, '<br>');\r\n\r\n const tokens: types.TranslationToken[] = [];\r\n const regex = /<([a-z]+)>([^<]*)<\\/\\1>|<([a-z]+)\\s*\\/?>|([^<]+)/gi;\r\n let match;\r\n\r\n while ((match = regex.exec(translation)) !== null) {\r\n if (match[4]) {\r\n // Plain text\r\n tokens.push({ type: 'text', content: match[4] });\r\n } else if (match[1]) {\r\n // Paired tag: <strong>text</strong>\r\n tokens.push({ type: 'tag', tag: match[1], content: match[2] });\r\n } else if (match[3]) {\r\n // Self-closing: <br> or <br/>\r\n tokens.push({ type: 'tag', tag: match[3], content: '' });\r\n }\r\n }\r\n\r\n return tokens.length > 0 ? tokens : [{ type: 'text', content: translation }];\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌────────────────────────────── LANGUAGE ────────────────────────────┐\r\n\r\n /**\r\n * Set current language\r\n */\r\n public async setLanguage(lang: types.LanguageCode): Promise<void> {\r\n if (!this.supportedLanguages.has(lang)) {\r\n // console.warn(`[i18n] Language \"${lang}\" not supported`);\r\n return;\r\n }\r\n\r\n this.currentLanguage = lang;\r\n\r\n // Persist if storage available\r\n if (this.storage) {\r\n await this.storage.set('lang', lang);\r\n }\r\n\r\n // client\r\n if( typeof document !== 'undefined' ) {\r\n // cookie\r\n setCookie('lang', lang, 365);\r\n\r\n // html lang attribute\r\n document.documentElement.lang = lang;\r\n }\r\n\r\n // Notify listeners\r\n this.listeners.forEach(fn => fn(lang));\r\n\r\n if (this.onLanguageChange) {\r\n this.onLanguageChange(lang);\r\n }\r\n }\r\n\r\n /**\r\n * Get current language\r\n */\r\n public getLanguage(): types.LanguageCode {\r\n return this.currentLanguage;\r\n }\r\n\r\n /**\r\n * Get all supported languages\r\n */\r\n public getSupportedLanguages(): types.LanguageCode[] {\r\n return Array.from(this.supportedLanguages);\r\n }\r\n\r\n /**\r\n * Check if language is supported\r\n */\r\n public isLanguageSupported(lang: types.LanguageCode): boolean {\r\n return this.supportedLanguages.has(lang);\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌─────────────────────────────── HELPERS ────────────────────────────┐\r\n\r\n /**\r\n * Check if a translation key exists\r\n */\r\n public hasKey(key: string): boolean {\r\n return !!(\r\n this.translations[this.currentLanguage]?.[key] ||\r\n this.translations[this.defaultLanguage]?.[key]\r\n );\r\n }\r\n\r\n /**\r\n * Get all translations for current language\r\n */\r\n public getTranslations(): Record<string, string> {\r\n return this.translations[this.currentLanguage] || {};\r\n }\r\n\r\n /**\r\n * Check if current language is RTL\r\n */\r\n public isRTL(): boolean {\r\n return this.rtlLanguages.has(this.currentLanguage.toLowerCase().substring(0, 2));\r\n }\r\n\r\n /**\r\n * Check if specific language is RTL\r\n */\r\n public isRTLLanguage(lang: types.LanguageCode): boolean {\r\n return this.rtlLanguages.has(lang.toLowerCase().substring(0, 2));\r\n }\r\n\r\n /**\r\n * Subscribe to language changes\r\n * @returns Unsubscribe function\r\n */\r\n public onChange(callback: (lang: types.LanguageCode) => void): () => void {\r\n this.listeners.add(callback);\r\n return () => this.listeners.delete(callback);\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ HELP ════════════════════════════════════════╗\r\n\r\n // Function to set a cookie with name, value, and days to expire\r\n function setCookie(name: string, value: string, days: number) {\r\n let expires = \"\";\r\n if (days) {\r\n const date = new Date();\r\n date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));\r\n expires = \"; expires=\" + date.toUTCString();\r\n }\r\n document.cookie = name + \"=\" + (value || \"\") + expires + \"; path=/\";\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","// src/index.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import * as types from './types';\r\n import { I18nManager } from './mod/i18n';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n /**\r\n * Storage adapter for browser (localStorage)\r\n */\r\n const createBrowserStorage = (): types.I18nStorage => ({\r\n get: (key: string) => {\r\n if (typeof localStorage === 'undefined') return null;\r\n return localStorage.getItem(key);\r\n },\r\n set: (key: string, value: string) => {\r\n if (typeof localStorage !== 'undefined') {\r\n localStorage.setItem(key, value);\r\n }\r\n }\r\n });\r\n\r\n /**\r\n * Storage adapter for memory (in-process storage)\r\n */\r\n const createMemoryStorage = (): types.I18nStorage => {\r\n const store = new Map<string, string>();\r\n return {\r\n get: (key: string) => store.get(key) || null,\r\n set: (key: string, value: string) => { store.set(key, value); }\r\n };\r\n };\r\n\r\n /**\r\n * Auto-select appropriate storage based on environment\r\n */\r\n const getDefaultStorage = (): types.I18nStorage => {\r\n return typeof localStorage !== 'undefined' ? createBrowserStorage() : createMemoryStorage();\r\n };\r\n\r\n /**\r\n * Lazy loader: fetch language on-demand\r\n * Supports both URL-based (browser) and file-based (server) loading\r\n */\r\n export class LazyLoader {\r\n private baseUrl: string;\r\n private manager: I18nManager;\r\n private loading = new Map<types.LanguageCode, Promise<void>>();\r\n private loaded = new Set<types.LanguageCode>();\r\n private isServerSide: boolean;\r\n private fileExtension: string;\r\n\r\n constructor(baseUrl: string, manager: I18nManager, fileExtension: string = 'json') {\r\n this.baseUrl = baseUrl.endsWith('/') ? baseUrl : baseUrl + '/';\r\n this.manager = manager;\r\n this.fileExtension = fileExtension;\r\n this.isServerSide = typeof fetch === 'undefined';\r\n }\r\n\r\n /**\r\n * Load a language file on-demand\r\n * Caches the promise to prevent duplicate requests\r\n */\r\n async load(lang: types.LanguageCode): Promise<void> {\r\n // Already loaded\r\n if (this.loaded.has(lang)) {\r\n return;\r\n }\r\n\r\n // Currently loading\r\n if (this.loading.has(lang)) {\r\n return this.loading.get(lang);\r\n }\r\n\r\n // Start loading\r\n const promise = this.doLoad(lang);\r\n this.loading.set(lang, promise);\r\n\r\n try {\r\n await promise;\r\n this.loaded.add(lang);\r\n } finally {\r\n this.loading.delete(lang);\r\n }\r\n }\r\n\r\n private async doLoad(lang: types.LanguageCode): Promise<void> {\r\n try {\r\n const filePath = `${this.baseUrl}${lang}.${this.fileExtension}`;\r\n\r\n let data: Record<string, string> | null;\r\n\r\n // Check if it's a local file path (relative or absolute)\r\n const isLocalPath = filePath.startsWith('.') || filePath.startsWith('/') || /^[a-zA-Z]:/.test(filePath);\r\n\r\n if (isLocalPath || this.isServerSide) {\r\n // Node.js/local: Read from filesystem\r\n data = await this.loadFromFile(filePath);\r\n } else {\r\n // Browser: Fetch from URL\r\n data = await this.loadFromUrl(filePath);\r\n }\r\n\r\n if (data) {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n this.manager.loadLanguage(lang, data as Record<string, any>);\r\n }\r\n } catch (error) {\r\n console.warn(`[i18n] Error loading language: ${lang}`, error);\r\n }\r\n }\r\n\r\n private async loadFromUrl(url: string): Promise<Record<string, string> | null> {\r\n try {\r\n const response = await fetch(url);\r\n\r\n if (response.ok) {\r\n return await response.json();\r\n } else {\r\n console.warn(`[i18n] Failed to load language from URL: ${url} (${response.status})`);\r\n return null;\r\n }\r\n } catch (error) {\r\n console.warn(`[i18n] Error fetching from URL: ${url}`, error);\r\n return null;\r\n }\r\n }\r\n\r\n private async loadFromFile(filePath: string): Promise<Record<string, string> | null> {\r\n try {\r\n // Dynamic import to avoid issues in browsers\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const fs = await import('fs').then(m => m.promises).catch((): any => null);\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const path = await import('path').then(m => m).catch((): any => null);\r\n\r\n if (!fs) {\r\n console.warn('[i18n] fs module not available. Running in browser?');\r\n return null;\r\n }\r\n\r\n // Resolve relative paths to absolute paths\r\n let resolvedPath = filePath;\r\n if (path && !path.isAbsolute(filePath)) {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const process = await import('process').then(m => m).catch((): any => null);\r\n if (process) {\r\n resolvedPath = path.resolve(process.cwd(), filePath);\r\n }\r\n }\r\n\r\n const content = await fs.readFile(resolvedPath, 'utf-8');\r\n return JSON.parse(content);\r\n } catch (error) {\r\n console.warn(`[i18n] Error reading file: ${filePath}`, error);\r\n return null;\r\n }\r\n }\r\n\r\n isLoaded(lang: types.LanguageCode): boolean {\r\n return this.loaded.has(lang);\r\n }\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ ════ ════════════════════════════════════════╗\r\n\r\n let instance: I18nManager | null = null;\r\n let lazyLoader: LazyLoader | null = null;\r\n\r\n /**\r\n * Get browser language preference\r\n * Uses navigator.language if available (browser environment)\r\n * @private\r\n */\r\n function detectBrowserLanguage(): string {\r\n if (typeof navigator !== 'undefined' && navigator.language) {\r\n return navigator.language.split('-')[0].toLowerCase();\r\n }\r\n return 'en';\r\n }\r\n\r\n /**\r\n * Check if running in browser environment\r\n * @private\r\n */\r\n function isBrowser(): boolean {\r\n return typeof fetch !== 'undefined' && typeof window !== 'undefined';\r\n }\r\n\r\n /**\r\n * Get or create the global i18n instance\r\n */\r\n export function getI18n(): I18nManager {\r\n if (!instance) {\r\n instance = new I18nManager();\r\n }\r\n return instance;\r\n }\r\n\r\n /**\r\n * Get the lazy loader instance (only available after setupI18n with basePath)\r\n */\r\n export function getLazyLoader(): LazyLoader | null {\r\n return lazyLoader;\r\n }\r\n\r\n /**\r\n * Main setup function - Single, simple, auto-detecting initialization\r\n *\r\n * Auto-detects environment and handles both browser and server:\r\n * - Browser: Auto-detects language, loads from URL path\r\n * - Server: Uses defaultLanguage, loads from file system\r\n *\r\n * Call this ONCE at app startup.\r\n *\r\n * @example\r\n * // Browser - Auto-detects language, lazy-loads from URL\r\n * await setupI18n({\r\n * supportedLanguages: ['en', 'ar', 'fr'],\r\n * basePath: '/i18n/'\r\n * });\r\n *\r\n * @example\r\n * // Server - Uses default language, lazy-loads from filesystem\r\n * await setupI18n({\r\n * defaultLanguage: 'en',\r\n * supportedLanguages: ['en', 'ar', 'fr'],\r\n * basePath: './locales/'\r\n * });\r\n */\r\n export async function setupI18n(\r\n config: types.I18nConfig & { basePath?: string }\r\n ): Promise<I18nManager> {\r\n\r\n // Auto-detect browser language if in browser and no defaultLanguage specified\r\n if (isBrowser() && !config.defaultLanguage) {\r\n const detectedLang = detectBrowserLanguage();\r\n if (config.supportedLanguages?.includes(detectedLang)) {\r\n config.defaultLanguage = detectedLang;\r\n } else {\r\n config.defaultLanguage = config.supportedLanguages?.[0] || 'en';\r\n }\r\n }\r\n\r\n // Use appropriate storage based on environment\r\n if (!config.storage) {\r\n config.storage = getDefaultStorage();\r\n }\r\n\r\n // Create and initialize manager\r\n instance = new I18nManager(config);\r\n await instance.init();\r\n\r\n // Setup lazy loading if basePath provided\r\n if (config.basePath) {\r\n const fileExtension = config.fileExtension || 'json';\r\n lazyLoader = new LazyLoader(config.basePath, instance, fileExtension);\r\n await lazyLoader.load(instance.getLanguage());\r\n }\r\n\r\n return instance;\r\n }\r\n\r\n\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CONVENIENCE FUNCTIONS ════════════════════════════════════════╗\r\n\r\n /**\r\n * Translate a key with optional parameter replacement\r\n */\r\n export const t = (key: string, params?: Record<string, string>) =>\r\n getI18n().t(key, params);\r\n\r\n /**\r\n * Translate a key with a specific language temporarily\r\n */\r\n export const tLang = (key: string, lang: types.LanguageCode, params?: Record<string, string>) =>\r\n getI18n().tLang(key, lang, params);\r\n\r\n /**\r\n * Parse translation with HTML tags into tokens\r\n */\r\n export const tParse = (key: string, params?: Record<string, string>) =>\r\n getI18n().tParse(key, params);\r\n\r\n /**\r\n * Set current language and trigger listeners\r\n */\r\n export const setLanguage = (lang: types.LanguageCode): Promise<void> => {\r\n // Load language if lazy loader available\r\n if (lazyLoader && !lazyLoader.isLoaded(lang)) {\r\n return lazyLoader.load(lang).then(() => getI18n().setLanguage(lang));\r\n }\r\n return getI18n().setLanguage(lang);\r\n };\r\n\r\n /**\r\n * Get current language code\r\n */\r\n export const getLanguage = () =>\r\n getI18n().getLanguage();\r\n\r\n /**\r\n * Get all supported languages\r\n */\r\n export const getSupportedLanguages = () =>\r\n getI18n().getSupportedLanguages();\r\n\r\n /**\r\n * Check if translation key exists\r\n */\r\n export const hasKey = (key: string) =>\r\n getI18n().hasKey(key);\r\n\r\n /**\r\n * Check if current language is RTL\r\n */\r\n export const isRTL = () =>\r\n getI18n().isRTL();\r\n\r\n /**\r\n * Check if specific language is RTL\r\n */\r\n export const isRTLLanguage = (lang: types.LanguageCode) =>\r\n getI18n().isRTLLanguage(lang);\r\n\r\n /**\r\n * Subscribe to language changes\r\n */\r\n export const onChange = (callback: (lang: types.LanguageCode) => void) =>\r\n getI18n().onChange(callback);\r\n\r\n /**\r\n * Load translations for a specific language\r\n */\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n export const loadLanguage = (lang: types.LanguageCode, translations: Record<string, any>) =>\r\n getI18n().loadLanguage(lang, translations);\r\n\r\n /**\r\n * Load multiple languages at once\r\n */\r\n export const loadTranslations = (translations: types.TranslationSet) =>\r\n getI18n().loadTranslations(translations);\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTILITY FUNCTIONS ════════════════════════════════════════╗\r\n\r\n /**\r\n * Pluralization helper - select translation based on count\r\n *\r\n * @example\r\n * plural(1, 'item.single', 'item.plural') // \"1 item\"\r\n * plural(5, 'item.single', 'item.plural') // \"5 items\"\r\n */\r\n export function plural(count: number, singleKey: string, pluralKey: string): string {\r\n const key = count === 1 ? singleKey : pluralKey;\r\n return t(key, { count: String(count) });\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ EXPORTS ════════════════════════════════════════╗\r\n\r\n export default {\r\n setupI18n,\r\n getI18n,\r\n getLazyLoader,\r\n I18nManager,\r\n LazyLoader,\r\n t,\r\n tLang,\r\n tParse,\r\n setLanguage,\r\n getLanguage,\r\n getSupportedLanguages,\r\n hasKey,\r\n isRTL,\r\n isRTLLanguage,\r\n onChange,\r\n loadLanguage,\r\n loadTranslations,\r\n plural,\r\n };\r\n\r\n export type I18nManagerInstance = InstanceType<typeof I18nManager>;\r\n export type LazyLoaderInstance = InstanceType<typeof LazyLoader>;\r\n\r\n export * from './mod/i18n';\r\n export * from './types';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n"]}
1
+ {"version":3,"sources":["../src/mod/i18n.ts","../src/index.ts"],"names":["I18nManager","config","stored","lang","translations","flattened","trans","obj","prefix","key","value","newKey","params","fallback","translation","param","paramValue","original","result","tokens","regex","match","setCookie","fn","callback","name","days","expires","date","createBrowserStorage","createMemoryStorage","store","getDefaultStorage","LazyLoader","baseUrl","manager","fileExtension","promise","error","filePath","data","url","response","fs","m","path","resolvedPath","process","content","instance","lazyLoader","detectBrowserLanguage","isBrowser","getI18n","getLazyLoader","setupI18n","detectedLang","t","tLang","tLangAsync","tParse","setLanguage","getLanguage","getSupportedLanguages","hasKey","isRTL","isRTLLanguage","onChange","loadLanguage","loadTranslations","plural","count","singleKey","pluralKey","index_default"],"mappings":"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","file":"index.js","sourcesContent":["// src/mod/i18n.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import * as types from '../types';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n export class I18nManager {\r\n\r\n // ┌──────────────────────────────── INIT ──────────────────────────────┐\r\n\r\n private translations : types.TranslationSet = {};\r\n private currentLanguage : types.LanguageCode = 'en';\r\n private defaultLanguage : types.LanguageCode = 'en';\r\n private supportedLanguages = new Set<types.LanguageCode>(['en']);\r\n private rtlLanguages = new Set<string>(['ar', 'he', 'fa', 'ur', 'yi', 'ji', 'iw', 'ku']);\r\n private listeners = new Set<(lang: types.LanguageCode) => void>();\r\n private storage? : types.I18nStorage;\r\n private onLanguageChange? : (lang: types.LanguageCode) => void;\r\n\r\n constructor(config?: types.I18nConfig) {\r\n if (config) {\r\n this.defaultLanguage = config.defaultLanguage || 'en';\r\n this.currentLanguage = config.defaultLanguage || 'en';\r\n this.storage = config.storage;\r\n this.onLanguageChange = config.onLanguageChange;\r\n\r\n if (config.supportedLanguages) {\r\n this.supportedLanguages = new Set(config.supportedLanguages);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Initialize with stored language preference\r\n */\r\n public async init(): Promise<void> {\r\n // console.log('[i18n] Initializing manager...');\r\n // console.log('[i18n] Current language:', this.currentLanguage);\r\n // console.log('[i18n] Default language:', this.defaultLanguage);\r\n\r\n if (this.storage) {\r\n // console.log('[i18n] Storage available, checking for stored language...');\r\n const stored = await this.storage.get('i18n-language');\r\n // console.log('[i18n] Stored language from storage:', stored);\r\n if (stored && this.supportedLanguages.has(stored)) {\r\n // console.log('[i18n] Stored language is supported, setting to:', stored);\r\n this.currentLanguage = stored;\r\n } else {\r\n // console.log('[i18n] Stored language not supported or not found, keeping:', this.currentLanguage);\r\n }\r\n } else {\r\n // console.log('[i18n] No storage available');\r\n }\r\n // console.log('[i18n] Init complete. Current language:', this.currentLanguage);\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── LOAD ──────────────────────────────┐\r\n\r\n /**\r\n * Load translations for a specific language\r\n * @param lang Language code\r\n * @param translations Translation object (can be nested)\r\n */\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n public loadLanguage(lang: types.LanguageCode, translations: Record<string, any>): void {\r\n // console.log(`[i18n] Loading language: \"${lang}\"`);\r\n // console.log(`[i18n] Translation keys count:`, Object.keys(translations).length);\r\n \r\n if (!this.translations[lang]) {\r\n this.translations[lang] = {};\r\n }\r\n\r\n const flattened = this.flattenObject(translations);\r\n // console.log(`[i18n] Flattened translation keys count for \"${lang}\":`, Object.keys(flattened).length);\r\n \r\n this.translations[lang] = { ...this.translations[lang], ...flattened };\r\n this.supportedLanguages.add(lang);\r\n \r\n // console.log(`[i18n] Language \"${lang}\" loaded successfully. Total keys:`, Object.keys(this.translations[lang]).length);\r\n // console.log(`[i18n] Supported languages:`, Array.from(this.supportedLanguages));\r\n }\r\n\r\n /**\r\n * Load multiple languages at once\r\n * @param translations Object with language codes as keys\r\n */\r\n public loadTranslations(translations: types.TranslationSet): void {\r\n Object.entries(translations).forEach(([lang, trans]) => {\r\n this.loadLanguage(lang, trans);\r\n });\r\n }\r\n\r\n /**\r\n * Flatten nested object into dot notation\r\n * @private\r\n */\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n private flattenObject(obj: Record<string, any>, prefix: string = ''): Record<string, string> {\r\n const flattened: Record<string, string> = {};\r\n\r\n for (const key in obj) {\r\n if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;\r\n\r\n const value = obj[key];\r\n const newKey = prefix ? `${prefix}.${key}` : key;\r\n\r\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\r\n Object.assign(flattened, this.flattenObject(value, newKey));\r\n } else {\r\n flattened[newKey] = String(value);\r\n }\r\n }\r\n\r\n return flattened;\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌───────────────────────────── TRANSLATION ──────────────────────────┐\r\n\r\n /**\r\n * Translate a key with parameter replacement\r\n *\r\n * @example\r\n * t('welcome.message', { name: 'John' })\r\n * // => \"Welcome, John!\"\r\n *\r\n * @param key Translation key (dot notation)\r\n * @param params Optional parameters for replacement\r\n * @param fallback Optional fallback string if key not found\r\n * @returns Translated string\r\n */\r\n public t(key: string, params?: Record<string, string>, fallback?: string): string {\r\n\r\n let translation = this.getTranslation(key, fallback);\r\n\r\n if (params) {\r\n Object.entries(params).forEach(([param, value]) => {\r\n // Check if parameter value is itself a translation key\r\n const paramValue = this.getTranslation(value, value);\r\n translation = translation.replace(\r\n new RegExp(`\\\\{${param}\\\\}`, 'g'),\r\n paramValue\r\n );\r\n });\r\n }\r\n\r\n return translation;\r\n }\r\n\r\n /**\r\n * Get raw translation without parameter replacement\r\n * @private\r\n */\r\n private getTranslation(key: string, fallback?: string): string {\r\n // console.log(`[i18n] getTranslation() - Looking up key: \"${key}\" in language: \"${this.currentLanguage}\"`);\r\n // console.log(`[i18n] Translations available for \"${this.currentLanguage}\":`, this.translations[this.currentLanguage] ? 'YES' : 'NO');\r\n \r\n // Try current language\r\n if (this.translations[this.currentLanguage]?.[key]) {\r\n const value = this.translations[this.currentLanguage][key];\r\n // console.log(`[i18n] Found in current language \"${this.currentLanguage}\": \"${value}\"`);\r\n return value;\r\n }\r\n\r\n // console.log(`[i18n] Translation key \"${key}\" not found in language \"${this.currentLanguage}\"`);\r\n\r\n // Try default language\r\n if (this.defaultLanguage !== this.currentLanguage &&\r\n this.translations[this.defaultLanguage]?.[key]) {\r\n const value = this.translations[this.defaultLanguage][key];\r\n // console.log(`[i18n] Found in default language \"${this.defaultLanguage}\": \"${value}\"`);\r\n return value;\r\n }\r\n\r\n // Warn and return fallback\r\n console.warn(`[i18n] Translation key not found: \"${key}\" (lang: ${this.currentLanguage})`);\r\n return fallback || key;\r\n }\r\n\r\n /**\r\n * Translate with a specific language temporarily\r\n *\r\n * @param lang Language code\r\n * @param key Translation key\r\n * @param params Optional parameters\r\n * @param fallback Optional fallback string if key not found\r\n */\r\n public tLang(lang: types.LanguageCode, key: string, params?: Record<string, string>, fallback?: string): string {\r\n // console.log(`[i18n] tLang() called: lang=\"${lang}\", key=\"${key}\"`);\r\n // console.log(`[i18n] Current language before switch: \"${this.currentLanguage}\"`);\r\n // console.log(`[i18n] Available translations for \"${lang}\":`, this.translations[lang] ? Object.keys(this.translations[lang]).slice(0, 5) : 'NONE');\r\n \r\n const original = this.currentLanguage;\r\n this.currentLanguage = lang;\r\n // console.log(`[i18n] Temporarily switched to: \"${lang}\"`);\r\n \r\n const result = this.t(key, params, fallback);\r\n \r\n this.currentLanguage = original;\r\n // console.log(`[i18n] Switched back to original language: \"${original}\"`);\r\n // console.log(`[i18n] tLang() result for key \"${key}\":`, result);\r\n \r\n return result;\r\n }\r\n\r\n /**\r\n * Translate and parse HTML-like tags into tokens\r\n * Converts \\n or /n to line breaks\r\n *\r\n * @example\r\n * // Translation: \"Hello\\nWorld <strong>here</strong>\"\r\n * tParse('message')\r\n * // => [\r\n * // { type: 'text', content: 'Hello' },\r\n * // { type: 'tag', tag: 'br', content: '' },\r\n * // { type: 'text', content: 'World ' },\r\n * // { type: 'tag', tag: 'strong', content: 'here' }\r\n * // ]\r\n *\r\n * @param key Translation key\r\n * @param params Optional parameters\r\n * @param fallback Optional fallback string if key not found\r\n * @returns Array of tokens\r\n */\r\n public tParse(key: string, params?: Record<string, string>, fallback?: string): types.TranslationToken[] {\r\n let translation = this.t(key, params, fallback);\r\n // Convert newlines to <br> tags\r\n translation = translation.replace(/\\\\n|\\/n/g, '<br>');\r\n\r\n const tokens: types.TranslationToken[] = [];\r\n const regex = /<([a-z]+)>([^<]*)<\\/\\1>|<([a-z]+)\\s*\\/?>|([^<]+)/gi;\r\n let match;\r\n\r\n while ((match = regex.exec(translation)) !== null) {\r\n if (match[4]) {\r\n // Plain text\r\n tokens.push({ type: 'text', content: match[4] });\r\n } else if (match[1]) {\r\n // Paired tag: <strong>text</strong>\r\n tokens.push({ type: 'tag', tag: match[1], content: match[2] });\r\n } else if (match[3]) {\r\n // Self-closing: <br> or <br/>\r\n tokens.push({ type: 'tag', tag: match[3], content: '' });\r\n }\r\n }\r\n\r\n return tokens.length > 0 ? tokens : [{ type: 'text', content: translation }];\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌────────────────────────────── LANGUAGE ────────────────────────────┐\r\n\r\n /**\r\n * Set current language\r\n */\r\n public async setLanguage(lang: types.LanguageCode): Promise<void> {\r\n // console.log(`[i18n] setLanguage() called with lang: \"${lang}\"`);\r\n // console.log(`[i18n] Checking if \"${lang}\" is supported...`);\r\n // console.log(`[i18n] Supported languages:`, Array.from(this.supportedLanguages));\r\n \r\n if (!this.supportedLanguages.has(lang)) {\r\n console.warn(`[i18n] Language \"${lang}\" not supported, aborting setLanguage()`);\r\n return;\r\n }\r\n\r\n // console.log(`[i18n] Language \"${lang}\" is supported. Setting current language...`);\r\n this.currentLanguage = lang;\r\n // console.log(`[i18n] Current language set to: \"${lang}\"`);\r\n\r\n // Persist if storage available\r\n if (this.storage) {\r\n // console.log(`[i18n] Persisting language \"${lang}\" to storage...`);\r\n await this.storage.set('i18n-language', lang);\r\n // console.log(`[i18n] Language persisted to storage`);\r\n } else {\r\n // console.log(`[i18n] No storage available for persistence`);\r\n }\r\n\r\n // client\r\n if( typeof document !== 'undefined' ) {\r\n // cookie\r\n setCookie('lang', lang, 365);\r\n\r\n // html lang attribute\r\n document.documentElement.lang = lang;\r\n // console.log(`[i18n] Set document.lang to \"${lang}\"`);\r\n }\r\n\r\n // Notify listeners\r\n // console.log(`[i18n] Notifying ${this.listeners.size} listeners...`);\r\n this.listeners.forEach(fn => fn(lang));\r\n\r\n if (this.onLanguageChange) {\r\n // console.log(`[i18n] Calling onLanguageChange callback...`);\r\n this.onLanguageChange(lang);\r\n }\r\n \r\n // console.log(`[i18n] setLanguage() completed for \"${lang}\"`);\r\n }\r\n\r\n /**\r\n * Get current language\r\n */\r\n public getLanguage(): types.LanguageCode {\r\n return this.currentLanguage;\r\n }\r\n\r\n /**\r\n * Get all supported languages\r\n */\r\n public getSupportedLanguages(): types.LanguageCode[] {\r\n return Array.from(this.supportedLanguages);\r\n }\r\n\r\n /**\r\n * Check if language is supported\r\n */\r\n public isLanguageSupported(lang: types.LanguageCode): boolean {\r\n return this.supportedLanguages.has(lang);\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌─────────────────────────────── HELPERS ────────────────────────────┐\r\n\r\n /**\r\n * Check if a translation key exists\r\n */\r\n public hasKey(key: string): boolean {\r\n return !!(\r\n this.translations[this.currentLanguage]?.[key] ||\r\n this.translations[this.defaultLanguage]?.[key]\r\n );\r\n }\r\n\r\n /**\r\n * Get all translations for current language\r\n */\r\n public getTranslations(): Record<string, string> {\r\n return this.translations[this.currentLanguage] || {};\r\n }\r\n\r\n /**\r\n * Check if current language is RTL\r\n */\r\n public isRTL(): boolean {\r\n return this.rtlLanguages.has(this.currentLanguage.toLowerCase().substring(0, 2));\r\n }\r\n\r\n /**\r\n * Check if specific language is RTL\r\n */\r\n public isRTLLanguage(lang: types.LanguageCode): boolean {\r\n return this.rtlLanguages.has(lang.toLowerCase().substring(0, 2));\r\n }\r\n\r\n /**\r\n * Subscribe to language changes\r\n * @returns Unsubscribe function\r\n */\r\n public onChange(callback: (lang: types.LanguageCode) => void): () => void {\r\n this.listeners.add(callback);\r\n return () => this.listeners.delete(callback);\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ HELP ════════════════════════════════════════╗\r\n\r\n // Function to set a cookie with name, value, and days to expire\r\n function setCookie(name: string, value: string, days: number) {\r\n let expires = \"\";\r\n if (days) {\r\n const date = new Date();\r\n date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));\r\n expires = \"; expires=\" + date.toUTCString();\r\n }\r\n document.cookie = name + \"=\" + (value || \"\") + expires + \"; path=/\";\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","// src/index.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import * as types from './types';\r\n import { I18nManager } from './mod/i18n';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n /**\r\n * Storage adapter for browser (localStorage)\r\n */\r\n const createBrowserStorage = (): types.I18nStorage => ({\r\n get: (key: string) => {\r\n if (typeof localStorage === 'undefined') return null;\r\n return localStorage.getItem(key);\r\n },\r\n set: (key: string, value: string) => {\r\n if (typeof localStorage !== 'undefined') {\r\n localStorage.setItem(key, value);\r\n }\r\n }\r\n });\r\n\r\n /**\r\n * Storage adapter for memory (in-process storage)\r\n */\r\n const createMemoryStorage = (): types.I18nStorage => {\r\n const store = new Map<string, string>();\r\n return {\r\n get: (key: string) => store.get(key) || null,\r\n set: (key: string, value: string) => { store.set(key, value); }\r\n };\r\n };\r\n\r\n /**\r\n * Auto-select appropriate storage based on environment\r\n */\r\n const getDefaultStorage = (): types.I18nStorage => {\r\n return typeof localStorage !== 'undefined' ? createBrowserStorage() : createMemoryStorage();\r\n };\r\n\r\n /**\r\n * Lazy loader: fetch language on-demand\r\n * Supports both URL-based (browser) and file-based (server) loading\r\n */\r\n export class LazyLoader {\r\n private baseUrl: string;\r\n private manager: I18nManager;\r\n private loading = new Map<types.LanguageCode, Promise<void>>();\r\n private loaded = new Set<types.LanguageCode>();\r\n private isServerSide: boolean;\r\n private fileExtension: string;\r\n\r\n constructor(baseUrl: string, manager: I18nManager, fileExtension: string = 'json') {\r\n this.baseUrl = baseUrl.endsWith('/') ? baseUrl : baseUrl + '/';\r\n this.manager = manager;\r\n this.fileExtension = fileExtension;\r\n this.isServerSide = typeof fetch === 'undefined';\r\n }\r\n\r\n /**\r\n * Load a language file on-demand\r\n * Caches the promise to prevent duplicate requests\r\n */\r\n async load(lang: types.LanguageCode): Promise<void> {\r\n // console.log(`[LazyLoader] load() called for language: \"${lang}\"`);\r\n\r\n // Already loaded\r\n if (this.loaded.has(lang)) {\r\n // console.log(`[LazyLoader] Language \"${lang}\" already loaded, returning immediately`);\r\n return;\r\n }\r\n\r\n // Currently loading\r\n if (this.loading.has(lang)) {\r\n // console.log(`[LazyLoader] Language \"${lang}\" is currently loading, returning existing promise`);\r\n return this.loading.get(lang);\r\n }\r\n\r\n // Start loading\r\n // console.log(`[LazyLoader] Starting to load language: \"${lang}\"`);\r\n const promise = this.doLoad(lang);\r\n this.loading.set(lang, promise);\r\n\r\n try {\r\n await promise;\r\n this.loaded.add(lang);\r\n // console.log(`[LazyLoader] Language \"${lang}\" loaded and marked as loaded`);\r\n } catch (error) {\r\n console.error(`[LazyLoader] Error loading language \"${lang}\":`, error);\r\n } finally {\r\n this.loading.delete(lang);\r\n }\r\n }\r\n\r\n private async doLoad(lang: types.LanguageCode): Promise<void> {\r\n try {\r\n const filePath = `${this.baseUrl}${lang}.${this.fileExtension}`;\r\n // console.log(`[LazyLoader] doLoad() - filePath: \"${filePath}\"`);\r\n // console.log(`[LazyLoader] isServerSide: ${this.isServerSide}`);\r\n\r\n let data: Record<string, string> | null;\r\n\r\n // Check if it's a local file path (relative or absolute)\r\n const isLocalPath = filePath.startsWith('.') || filePath.startsWith('/') || /^[a-zA-Z]:/.test(filePath);\r\n // console.log(`[LazyLoader] isLocalPath: ${isLocalPath}`);\r\n\r\n if (isLocalPath || this.isServerSide) {\r\n // Node.js/local: Read from filesystem\r\n // console.log(`[LazyLoader] Loading from file...`);\r\n data = await this.loadFromFile(filePath);\r\n } else {\r\n // Browser: Fetch from URL\r\n // console.log(`[LazyLoader] Loading from URL...`);\r\n data = await this.loadFromUrl(filePath);\r\n }\r\n\r\n if (data) {\r\n // console.log(`[LazyLoader] Data loaded successfully, keys:`, Object.keys(data).length);\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n this.manager.loadLanguage(lang, data as Record<string, any>);\r\n } else {\r\n console.warn(`[LazyLoader] No data loaded for language: \"${lang}\"`);\r\n }\r\n } catch (error) {\r\n console.warn(`[i18n] Error loading language: ${lang}`, error);\r\n }\r\n }\r\n\r\n private async loadFromUrl(url: string): Promise<Record<string, string> | null> {\r\n try {\r\n const response = await fetch(url);\r\n\r\n if (response.ok) {\r\n return await response.json();\r\n } else {\r\n console.warn(`[i18n] Failed to load language from URL: ${url} (${response.status})`);\r\n return null;\r\n }\r\n } catch (error) {\r\n console.warn(`[i18n] Error fetching from URL: ${url}`, error);\r\n return null;\r\n }\r\n }\r\n\r\n private async loadFromFile(filePath: string): Promise<Record<string, string> | null> {\r\n try {\r\n // Dynamic import to avoid issues in browsers\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const fs = await import('fs').then(m => m.promises).catch((): any => null);\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const path = await import('path').then(m => m).catch((): any => null);\r\n\r\n if (!fs) {\r\n console.warn('[i18n] fs module not available. Running in browser?');\r\n return null;\r\n }\r\n\r\n // Resolve relative paths to absolute paths\r\n let resolvedPath = filePath;\r\n if (path && !path.isAbsolute(filePath)) {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const process = await import('process').then(m => m).catch((): any => null);\r\n if (process) {\r\n resolvedPath = path.resolve(process.cwd(), filePath);\r\n }\r\n }\r\n\r\n const content = await fs.readFile(resolvedPath, 'utf-8');\r\n return JSON.parse(content);\r\n } catch (error) {\r\n console.warn(`[i18n] Error reading file: ${filePath}`, error);\r\n return null;\r\n }\r\n }\r\n\r\n isLoaded(lang: types.LanguageCode): boolean {\r\n return this.loaded.has(lang);\r\n }\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ ════ ════════════════════════════════════════╗\r\n\r\n let instance: I18nManager | null = null;\r\n let lazyLoader: LazyLoader | null = null;\r\n\r\n /**\r\n * Get browser language preference\r\n * Uses navigator.language if available (browser environment)\r\n * @private\r\n */\r\n function detectBrowserLanguage(): string {\r\n if (typeof navigator !== 'undefined' && navigator.language) {\r\n return navigator.language.split('-')[0].toLowerCase();\r\n }\r\n return 'en';\r\n }\r\n\r\n /**\r\n * Check if running in browser environment\r\n * @private\r\n */\r\n function isBrowser(): boolean {\r\n return typeof fetch !== 'undefined' && typeof window !== 'undefined';\r\n }\r\n\r\n /**\r\n * Get or create the global i18n instance\r\n */\r\n export function getI18n(): I18nManager {\r\n if (!instance) {\r\n instance = new I18nManager();\r\n }\r\n return instance;\r\n }\r\n\r\n /**\r\n * Get the lazy loader instance (only available after setupI18n with basePath)\r\n */\r\n export function getLazyLoader(): LazyLoader | null {\r\n return lazyLoader;\r\n }\r\n\r\n /**\r\n * Main setup function - Single, simple, auto-detecting initialization\r\n *\r\n * Auto-detects environment and handles both browser and server:\r\n * - Browser: Auto-detects language, loads from URL path\r\n * - Server: Uses defaultLanguage, loads from file system\r\n *\r\n * Call this ONCE at app startup.\r\n *\r\n * @example\r\n * // Browser - Auto-detects language, lazy-loads from URL\r\n * await setupI18n({\r\n * supportedLanguages: ['en', 'ar', 'fr'],\r\n * basePath: '/i18n/'\r\n * });\r\n *\r\n * @example\r\n * // Server - Uses default language, lazy-loads from filesystem\r\n * await setupI18n({\r\n * defaultLanguage: 'en',\r\n * supportedLanguages: ['en', 'ar', 'fr'],\r\n * basePath: './locales/'\r\n * });\r\n */\r\n export async function setupI18n(\r\n config: types.I18nConfig & { basePath?: string }\r\n ): Promise<I18nManager> {\r\n\r\n // Auto-detect browser language if in browser and no defaultLanguage specified\r\n if (isBrowser() && !config.defaultLanguage) {\r\n const detectedLang = detectBrowserLanguage();\r\n if (config.supportedLanguages?.includes(detectedLang)) {\r\n config.defaultLanguage = detectedLang;\r\n } else {\r\n config.defaultLanguage = config.supportedLanguages?.[0] || 'en';\r\n }\r\n }\r\n\r\n // Use appropriate storage based on environment\r\n if (!config.storage) {\r\n config.storage = getDefaultStorage();\r\n }\r\n\r\n // Create and initialize manager\r\n instance = new I18nManager(config);\r\n await instance.init();\r\n\r\n // Setup lazy loading if basePath provided\r\n if (config.basePath) {\r\n const fileExtension = config.fileExtension || 'json';\r\n lazyLoader = new LazyLoader(config.basePath, instance, fileExtension);\r\n await lazyLoader.load(instance.getLanguage());\r\n }\r\n\r\n return instance;\r\n }\r\n\r\n\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CONVENIENCE FUNCTIONS ════════════════════════════════════════╗\r\n\r\n /**\r\n * Translate a key with optional parameter replacement\r\n */\r\n export const t = (key: string, params?: Record<string, string>, fallback?: string) =>\r\n getI18n().t(key, params, fallback);\r\n\r\n /**\r\n * Translate a key with a specific language temporarily\r\n */\r\n export const tLang = (lang: types.LanguageCode, key: string, params?: Record<string, string>, fallback?: string) =>\r\n getI18n().tLang(lang, key, params, fallback);\r\n\r\n /**\r\n * Translate a key with a specific language - async version that loads language if needed\r\n * Use this on server-side with lazy loading to ensure language is loaded first\r\n */\r\n export const tLangAsync = async (lang: types.LanguageCode, key: string, params?: Record<string, string>, fallback?: string): Promise<string> => {\r\n // console.log(`[i18n] tLangAsync() called: lang=\"${lang}\", key=\"${key}\"`);\r\n\r\n // Load language if lazy loader available\r\n if (lazyLoader && !lazyLoader.isLoaded(lang)) {\r\n // console.log(`[i18n] Language \"${lang}\" not loaded, loading now...`);\r\n await lazyLoader.load(lang);\r\n // console.log(`[i18n] Language \"${lang}\" loaded successfully`);\r\n } else if (lazyLoader) {\r\n // console.log(`[i18n] Language \"${lang}\" already loaded`);\r\n } else {\r\n // console.log(`[i18n] No LazyLoader available`);\r\n }\r\n\r\n const result = getI18n().tLang(lang, key, params, fallback);\r\n // console.log(`[i18n] tLangAsync() result: \"${result}\"`);\r\n return result;\r\n };\r\n\r\n /**\r\n * Parse translation with HTML tags into tokens\r\n */\r\n export const tParse = (key: string, params?: Record<string, string>, fallback?: string) =>\r\n getI18n().tParse(key, params, fallback);\r\n\r\n /**\r\n * Set current language and trigger listeners\r\n */\r\n export const setLanguage = (lang: types.LanguageCode): Promise<void> => {\r\n // Load language if lazy loader available\r\n if (lazyLoader && !lazyLoader.isLoaded(lang)) {\r\n return lazyLoader.load(lang).then(() => getI18n().setLanguage(lang));\r\n }\r\n return getI18n().setLanguage(lang);\r\n };\r\n\r\n /**\r\n * Get current language code\r\n */\r\n export const getLanguage = () =>\r\n getI18n().getLanguage();\r\n\r\n /**\r\n * Get all supported languages\r\n */\r\n export const getSupportedLanguages = () =>\r\n getI18n().getSupportedLanguages();\r\n\r\n /**\r\n * Check if translation key exists\r\n */\r\n export const hasKey = (key: string) =>\r\n getI18n().hasKey(key);\r\n\r\n /**\r\n * Check if current language is RTL\r\n */\r\n export const isRTL = () =>\r\n getI18n().isRTL();\r\n\r\n /**\r\n * Check if specific language is RTL\r\n */\r\n export const isRTLLanguage = (lang: types.LanguageCode) =>\r\n getI18n().isRTLLanguage(lang);\r\n\r\n /**\r\n * Subscribe to language changes\r\n */\r\n export const onChange = (callback: (lang: types.LanguageCode) => void) =>\r\n getI18n().onChange(callback);\r\n\r\n /**\r\n * Load translations for a specific language\r\n */\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n export const loadLanguage = (lang: types.LanguageCode, translations: Record<string, any>) =>\r\n getI18n().loadLanguage(lang, translations);\r\n\r\n /**\r\n * Load multiple languages at once\r\n */\r\n export const loadTranslations = (translations: types.TranslationSet) =>\r\n getI18n().loadTranslations(translations);\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTILITY FUNCTIONS ════════════════════════════════════════╗\r\n\r\n /**\r\n * Pluralization helper - select translation based on count\r\n *\r\n * @example\r\n * plural(1, 'item.single', 'item.plural') // \"1 item\"\r\n * plural(5, 'item.single', 'item.plural') // \"5 items\"\r\n */\r\n export function plural(count: number, singleKey: string, pluralKey: string): string {\r\n const key = count === 1 ? singleKey : pluralKey;\r\n return t(key, { count: String(count) });\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ EXPORTS ════════════════════════════════════════╗\r\n\r\n export default {\r\n setupI18n,\r\n getI18n,\r\n getLazyLoader,\r\n I18nManager,\r\n LazyLoader,\r\n t,\r\n tLang,\r\n tParse,\r\n setLanguage,\r\n getLanguage,\r\n getSupportedLanguages,\r\n hasKey,\r\n isRTL,\r\n isRTLLanguage,\r\n onChange,\r\n loadLanguage,\r\n loadTranslations,\r\n plural,\r\n };\r\n\r\n export type I18nManagerInstance = InstanceType<typeof I18nManager>;\r\n export type LazyLoaderInstance = InstanceType<typeof LazyLoader>;\r\n\r\n export * from './mod/i18n';\r\n export * from './types';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minejs/i18n",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "A lightweight, production-ready internationalization (i18n) library with zero dependencies.",
5
5
  "keywords": ["minejs", "i18n"],
6
6
  "license": "MIT",