@minejs/i18n 0.0.6 → 0.0.7

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.0.6-black"/>
11
+ <img src="https://img.shields.io/badge/v-0.0.7-black"/>
12
12
  <a href="https://img.shields.io/github/stars/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.40%25-brightgreen" alt="Test Coverage" />
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- 'use strict';Object.defineProperty(exports,'__esModule',{value:true});var u=class{constructor(t){this.translations={};this.currentLanguage="en";this.defaultLanguage="en";this.fallbackLanguage="en";this.supportedLanguages=new Set(["en"]);this.rtlLanguages=new Set(["ar","he","fa","ur","yi","ji","iw","ku"]);this.listeners=new Set;t&&(this.defaultLanguage=t.defaultLanguage||"en",this.fallbackLanguage=t.fallbackLanguage||t.defaultLanguage||"en",this.currentLanguage=t.defaultLanguage||"en",this.storage=t.storage,this.onLanguageChange=t.onLanguageChange,t.supportedLanguages&&(this.supportedLanguages=new Set(t.supportedLanguages)));}async init(){if(this.storage){let t=await this.storage.get("i18n-language");t&&this.supportedLanguages.has(t)&&(this.currentLanguage=t);}}loadLanguage(t,e){this.translations[t]||(this.translations[t]={});let n=this.flattenObject(e);this.translations[t]={...this.translations[t],...n},this.supportedLanguages.add(t);}loadTranslations(t){Object.entries(t).forEach(([e,n])=>{this.loadLanguage(e,n);});}flattenObject(t,e=""){let n={};for(let s in t){if(!Object.prototype.hasOwnProperty.call(t,s))continue;let i=t[s],r=e?`${e}.${s}`:s;typeof i=="object"&&i!==null&&!Array.isArray(i)?Object.assign(n,this.flattenObject(i,r)):n[r]=String(i);}return n}t(t,e){let n=this.getTranslation(t);return e&&Object.entries(e).forEach(([s,i])=>{let r=this.getTranslation(i,i);n=n.replace(new RegExp(`\\{${s}\\}`,"g"),r);}),n}getTranslation(t,e){return this.translations[this.currentLanguage]?.[t]?this.translations[this.currentLanguage][t]:this.fallbackLanguage!==this.currentLanguage&&this.translations[this.fallbackLanguage]?.[t]?this.translations[this.fallbackLanguage][t]:this.defaultLanguage!==this.currentLanguage&&this.defaultLanguage!==this.fallbackLanguage&&this.translations[this.defaultLanguage]?.[t]?this.translations[this.defaultLanguage][t]:(console.warn(`[i18n] Translation key not found: "${t}" (lang: ${this.currentLanguage})`),e||t)}tLang(t,e,n){let s=this.currentLanguage;this.currentLanguage=e;let i=this.t(t,n);return this.currentLanguage=s,i}tParse(t,e){let n=this.t(t,e);n=n.replace(/\\n|\/n/g,"<br>");let s=[],i=/<([a-z]+)>([^<]*)<\/\1>|<([a-z]+)\s*\/?>|([^<]+)/gi,r;for(;(r=i.exec(n))!==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:n}]}async setLanguage(t){if(!this.supportedLanguages.has(t)){console.warn(`[i18n] Language "${t}" not supported`);return}this.currentLanguage=t,this.storage&&await this.storage.set("i18n-language",t),this.listeners.forEach(e=>e(t)),this.onLanguageChange&&this.onLanguageChange(t);}getLanguage(){return this.currentLanguage}getSupportedLanguages(){return Array.from(this.supportedLanguages)}isLanguageSupported(t){return this.supportedLanguages.has(t)}hasKey(t){return !!(this.translations[this.currentLanguage]?.[t]||this.translations[this.fallbackLanguage]?.[t]||this.translations[this.defaultLanguage]?.[t])}getTranslations(){return this.translations[this.currentLanguage]||{}}isRTL(){return this.rtlLanguages.has(this.currentLanguage.toLowerCase().substring(0,2))}isRTLLanguage(t){return this.rtlLanguages.has(t.toLowerCase().substring(0,2))}onChange(t){return this.listeners.add(t),()=>this.listeners.delete(t)}},h={get:a=>typeof localStorage>"u"?null:localStorage.getItem(a),set:(a,t)=>{typeof localStorage<"u"&&localStorage.setItem(a,t);}},L=(()=>{let a=new Map;return {get:t=>a.get(t)||null,set:(t,e)=>{a.set(t,e);}}})();async function f(a,t){let e=Array.isArray(a)?a:[a],n={};for(let s of e)try{let i=await fetch(s);if(i.ok){let r=await i.json(),c=s.match(/([a-z]{2,3})\.json$/i),d=c?c[1].toLowerCase():"en";n[d]=r;}}catch(i){console.warn(`[i18n] Failed to fetch: ${s}`,i);}Object.keys(n).length>0&&t.loadTranslations(n);}var l=class{constructor(t,e,n="json"){this.loading=new Map;this.loaded=new Set;this.baseUrl=t.endsWith("/")?t:t+"/",this.manager=e,this.fileExtension=n,this.isServerSide=typeof fetch>"u";}async load(t){if(this.loaded.has(t))return;if(this.loading.has(t))return this.loading.get(t);let e=this.doLoad(t);this.loading.set(t,e);try{await e,this.loaded.add(t);}finally{this.loading.delete(t);}}async doLoad(t){try{let e=`${this.baseUrl}${t}.${this.fileExtension}`,n;e.startsWith(".")||e.startsWith("/")||/^[a-zA-Z]:/.test(e)||this.isServerSide?n=await this.loadFromFile(e):n=await this.loadFromUrl(e),n&&this.manager.loadLanguage(t,n);}catch(e){console.warn(`[i18n] Error loading language: ${t}`,e);}}async loadFromUrl(t){try{let e=await fetch(t);return e.ok?await e.json():(console.warn(`[i18n] Failed to load language from URL: ${t} (${e.status})`),null)}catch(e){return console.warn(`[i18n] Error fetching from URL: ${t}`,e),null}}async loadFromFile(t){try{let e=await import('fs').then(r=>r.promises).catch(()=>null),n=await import('path').then(r=>r).catch(()=>null);if(!e)return console.warn("[i18n] fs module not available. Running in browser?"),null;let s=t;if(n&&!n.isAbsolute(t)){let r=await import('process').then(c=>c).catch(()=>null);r&&(s=n.resolve(r.cwd(),t));}let i=await e.readFile(s,"utf-8");return JSON.parse(i)}catch(e){return console.warn(`[i18n] Error reading file: ${t}`,e),null}}isLoaded(t){return this.loaded.has(t)}},g=null;function o(){return g||(g=new u),g}async function y(a){return g=new u(a),await g.init(),g}function b(a,t="json"){return new l(a,o(),t)}async function w(a){let t=new u(a);await t.init(),g=t;let e=a.basePath||a.baseUrl||"./locales/",n=a.fileExtension||"json",s=new l(e,t,n);return await s.load(t.getLanguage()),s}async function m(a){let t=new u(a);await t.init(),g=t;let e=a.fileExtension||"json",n=new l(a.basePath,t,e);return await n.load(t.getLanguage()),n}var p=(a,t)=>o().t(a,t),S=(a,t,e)=>o().tLang(a,t,e),T=(a,t)=>o().tParse(a,t),R=a=>o().setLanguage(a),P=()=>o().getLanguage(),I=()=>o().getSupportedLanguages(),$=a=>o().hasKey(a),v=()=>o().isRTL(),j=a=>o().isRTLLanguage(a),E=a=>o().onChange(a),z=(a,t)=>o().loadLanguage(a,t),M=a=>o().loadTranslations(a);function x(a,t="page."){let e=p("app.name"),n=p(t+a);return v()?`${e} - ${n}`:`${n} - ${e}`}function C(a,t,e){return p(a===1?t:e,{count:String(a)})}var O={I18nManager:u,getI18n:o,setupI18n:y,createLazyLoader:b,setupLazy:w,setupAuto:m,browserStorage:h,memoryStorage:L,fetchTranslations:f,genPageTitle:x,plural:C};exports.I18nManager=u;exports.LazyLoader=l;exports.browserStorage=h;exports.createLazyLoader=b;exports.default=O;exports.fetchTranslations=f;exports.genPageTitle=x;exports.getI18n=o;exports.getLanguage=P;exports.getSupportedLanguages=I;exports.hasKey=$;exports.isRTL=v;exports.isRTLLanguage=j;exports.loadLanguage=z;exports.loadTranslations=M;exports.memoryStorage=L;exports.onChange=E;exports.plural=C;exports.setLanguage=R;exports.setupAuto=m;exports.setupI18n=y;exports.setupLazy=w;exports.t=p;exports.tLang=S;exports.tParse=T;//# sourceMappingURL=index.cjs.map
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("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 r in e){if(!Object.prototype.hasOwnProperty.call(e,r))continue;let i=e[r],s=t?`${t}.${r}`:r;typeof i=="object"&&i!==null&&!Array.isArray(i)?Object.assign(a,this.flattenObject(i,s)):a[s]=String(i);}return a}t(e,t){let a=this.getTranslation(e);return t&&Object.entries(t).forEach(([r,i])=>{let s=this.getTranslation(i,i);a=a.replace(new RegExp(`\\{${r}\\}`,"g"),s);}),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 r=this.currentLanguage;this.currentLanguage=t;let i=this.t(e,a);return this.currentLanguage=r,i}tParse(e,t){let a=this.t(e,t);a=a.replace(/\\n|\/n/g,"<br>");let r=[],i=/<([a-z]+)>([^<]*)<\/\1>|<([a-z]+)\s*\/?>|([^<]+)/gi,s;for(;(s=i.exec(a))!==null;)s[4]?r.push({type:"text",content:s[4]}):s[1]?r.push({type:"tag",tag:s[1],content:s[2]}):s[3]&&r.push({type:"tag",tag:s[3],content:""});return r.length>0?r:[{type:"text",content:a}]}async setLanguage(e){if(!this.supportedLanguages.has(e)){console.warn(`[i18n] Language "${e}" not supported`);return}this.currentLanguage=e,this.storage&&await this.storage.set("i18n-language",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)}};var L=()=>({get:n=>typeof localStorage>"u"?null:localStorage.getItem(n),set:(n,e)=>{typeof localStorage<"u"&&localStorage.setItem(n,e);}}),y=()=>{let n=new Map;return {get:e=>n.get(e)||null,set:(e,t)=>{n.set(e,t);}}},f=()=>typeof localStorage<"u"?L():y(),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);}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(s=>s.promises).catch(()=>null),a=await import('path').then(s=>s).catch(()=>null);if(!t)return console.warn("[i18n] fs module not available. Running in browser?"),null;let r=e;if(a&&!a.isAbsolute(e)){let s=await import('process').then(h=>h).catch(()=>null);s&&(r=a.resolve(s.cwd(),e));}let i=await t.readFile(r,"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 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 g||(g=new u),g}function v(){return l}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=f()),g=new u(n),await g.init(),n.basePath){let e=n.fileExtension||"json";l=new p(n.basePath,g,e),await l.load(g.getLanguage());}return g}var d=(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),c=()=>o().isRTL(),P=n=>o().isRTLLanguage(n),$=n=>o().onChange(n),E=(n,e)=>o().loadLanguage(n,e),j=n=>o().loadTranslations(n);function M(n,e="page."){let t=d("app.name"),a=d(e+n);return c()?`${t} - ${a}`:`${a} - ${t}`}function O(n,e,t){return d(n===1?e:t,{count:String(n)})}var A={setupI18n:b,getI18n:o,getLazyLoader:v,I18nManager:u,LazyLoader:p,t:d,tLang:C,tParse:x,setLanguage:S,getLanguage:R,getSupportedLanguages:T,hasKey:I,isRTL:c,isRTLLanguage:P,onChange:$,loadLanguage:E,loadTranslations:j,genPageTitle:M,plural:O};exports.I18nManager=u;exports.LazyLoader=p;exports.default=A;exports.genPageTitle=M;exports.getI18n=o;exports.getLanguage=R;exports.getLazyLoader=v;exports.getSupportedLanguages=T;exports.hasKey=I;exports.isRTL=c;exports.isRTLLanguage=P;exports.loadLanguage=E;exports.loadTranslations=j;exports.onChange=$;exports.plural=O;exports.setLanguage=S;exports.setupI18n=b;exports.t=d;exports.tLang=C;exports.tParse=x;//# sourceMappingURL=index.cjs.map
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../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","fn","callback","browserStorage","memoryStorage","store","fetchTranslations","urls","manager","urlList","url","response","data","langMatch","error","LazyLoader","baseUrl","fileExtension","promise","filePath","fs","m","path","resolvedPath","process","content","instance","getI18n","setupI18n","createLazyLoader","setupLazy","basePath","loader","setupAuto","t","tLang","tParse","setLanguage","getLanguage","getSupportedLanguages","hasKey","isRTL","isRTLLanguage","onChange","loadLanguage","loadTranslations","genPageTitle","appName","pageName","plural","count","singleKey","pluralKey","index_default"],"mappings":"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","file":"index.cjs","sourcesContent":["// 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\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n export class I18nManager {\r\n\r\n // ┌──────────────────────────────── STATE ─────────────────────────────┐\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 fallbackLanguage : 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 // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── INIT ──────────────────────────────┐\r\n\r\n constructor(config?: types.I18nConfig) {\r\n if (config) {\r\n this.defaultLanguage = config.defaultLanguage || 'en';\r\n this.fallbackLanguage = config.fallbackLanguage || 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 if (this.storage) {\r\n const stored = await this.storage.get('i18n-language');\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 // ┌─────────────────────────────── LOADING ────────────────────────────┐\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 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 fallback language\r\n if (this.fallbackLanguage !== this.currentLanguage &&\r\n this.translations[this.fallbackLanguage]?.[key]) {\r\n return this.translations[this.fallbackLanguage][key];\r\n }\r\n\r\n // Try default language\r\n if (this.defaultLanguage !== this.currentLanguage &&\r\n this.defaultLanguage !== this.fallbackLanguage &&\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('i18n-language', 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.fallbackLanguage]?.[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// ╔════════════════════════════════════════ ════ ════════════════════════════════════════╗\r\n\r\n /**\r\n * Browser storage adapter using localStorage\r\n */\r\n export const browserStorage: 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 * Memory storage adapter (for server/testing)\r\n */\r\n export const memoryStorage = (() => {\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 * Fetch translations from URLs\r\n * Works in both browser and Node.js (with node-fetch)\r\n */\r\n export async function fetchTranslations(\r\n urls: string | string[],\r\n manager: I18nManager\r\n ): Promise<void> {\r\n const urlList = Array.isArray(urls) ? urls : [urls];\r\n const translations: types.TranslationSet = {};\r\n\r\n for (const url of urlList) {\r\n try {\r\n const response = await fetch(url);\r\n if (response.ok) {\r\n const data = await response.json();\r\n // Extract language from filename: /path/en.json -> en\r\n const langMatch = url.match(/([a-z]{2,3})\\.json$/i);\r\n const lang = langMatch ? langMatch[1].toLowerCase() : 'en';\r\n translations[lang] = data;\r\n }\r\n } catch (error) {\r\n console.warn(`[i18n] Failed to fetch: ${url}`, error);\r\n }\r\n }\r\n\r\n if (Object.keys(translations).length > 0) {\r\n manager.loadTranslations(translations);\r\n }\r\n }\r\n\r\n /**\r\n * Lazy loader: fetch language only when needed\r\n * Prevents loading all languages at startup\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\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 * Initialize i18n with config\r\n * Call this once at app startup\r\n *\r\n * @example\r\n * // Load only default language at startup\r\n * await setupI18n({\r\n * defaultLanguage: 'en',\r\n * supportedLanguages: ['en', 'ar', 'fr', 'de']\r\n * });\r\n */\r\n export async function setupI18n(config: types.I18nConfig): Promise<I18nManager> {\r\n instance = new I18nManager(config);\r\n await instance.init();\r\n return instance;\r\n }\r\n\r\n /**\r\n * Create a lazy loader for translations\r\n * Only loads languages when needed\r\n *\r\n * @example\r\n * const loader = createLazyLoader('https://mycdn.com/i18n/');\r\n *\r\n * // Later, when user switches language:\r\n * await loader.load('ar');\r\n * await setLanguage('ar');\r\n */\r\n export function createLazyLoader(baseUrl: string, fileExtension: string = 'json'): LazyLoader {\r\n return new LazyLoader(baseUrl, getI18n(), fileExtension);\r\n }\r\n\r\n /**\r\n * Setup i18n with lazy loading\r\n * Only loads the default language at startup\r\n *\r\n * @example\r\n * // Browser\r\n * const loader = await setupLazy({\r\n * defaultLanguage: 'en',\r\n * supportedLanguages: ['en', 'ar', 'fr', 'de', 'zh'],\r\n * basePath: '/i18n/'\r\n * });\r\n *\r\n * // Later when user switches:\r\n * await loader.load('ar');\r\n * await setLanguage('ar');\r\n */\r\n export async function setupLazy(config: types.I18nConfig & { basePath?: string; baseUrl?: string }): Promise<LazyLoader> {\r\n const manager = new I18nManager(config);\r\n await manager.init();\r\n instance = manager;\r\n\r\n const basePath = config.basePath || config.baseUrl || './locales/';\r\n const fileExtension = config.fileExtension || 'json';\r\n const loader = new LazyLoader(basePath, manager, fileExtension);\r\n\r\n // Load only the current language at startup\r\n await loader.load(manager.getLanguage());\r\n\r\n return loader;\r\n }\r\n\r\n /**\r\n * Auto-setup: Smart initialization based on environment and config\r\n * Automatically handles browser vs server and file vs URL loading\r\n *\r\n * @example\r\n * // Browser: Auto-fetches from server\r\n * const loader = await setupAuto({\r\n * defaultLanguage: 'en',\r\n * supportedLanguages: ['en', 'ar', 'fr'],\r\n * basePath: 'http://localhost:3000/static/i18n/'\r\n * });\r\n *\r\n * // Server (Node.js): Auto-reads from filesystem\r\n * const loader = await setupAuto({\r\n * defaultLanguage: 'en',\r\n * supportedLanguages: ['en', 'ar', 'fr'],\r\n * basePath: './public/locales/'\r\n * });\r\n */\r\n export async function setupAuto(config: types.I18nConfig & { basePath: string }): Promise<LazyLoader> {\r\n const manager = new I18nManager(config);\r\n await manager.init();\r\n instance = manager;\r\n\r\n const fileExtension = config.fileExtension || 'json';\r\n const loader = new LazyLoader(config.basePath, manager, fileExtension);\r\n\r\n // Auto-load default language\r\n await loader.load(manager.getLanguage());\r\n\r\n return loader;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ ════ ════════════════════════════════════════╗\r\n\r\n export const t = (key: string, params?: Record<string, string>) =>\r\n getI18n().t(key, params);\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 export const tParse = (key: string, params?: Record<string, string>) =>\r\n getI18n().tParse(key, params);\r\n\r\n export const setLanguage = (lang: types.LanguageCode) =>\r\n getI18n().setLanguage(lang);\r\n\r\n export const getLanguage = () =>\r\n getI18n().getLanguage();\r\n\r\n export const getSupportedLanguages = () =>\r\n getI18n().getSupportedLanguages();\r\n\r\n export const hasKey = (key: string) =>\r\n getI18n().hasKey(key);\r\n\r\n export const isRTL = () =>\r\n getI18n().isRTL();\r\n\r\n export const isRTLLanguage = (lang: types.LanguageCode) =>\r\n getI18n().isRTLLanguage(lang);\r\n\r\n export const onChange = (callback: (lang: types.LanguageCode) => void) =>\r\n getI18n().onChange(callback);\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 export const loadTranslations = (translations: types.TranslationSet) =>\r\n getI18n().loadTranslations(translations);\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ ════ ════════════════════════════════════════╗\r\n\r\n /**\r\n * Generate page title with proper RTL handling\r\n *\r\n * @example\r\n * // English: \"Profile - MyApp\"\r\n * // Arabic: \"MyApp - الملف الشخصي\"\r\n * genPageTitle('profile', 'page.')\r\n */\r\n export function genPageTitle(key: string, prefix: string = 'page.'): string {\r\n const appName = t('app.name');\r\n const pageName = t(prefix + key);\r\n return isRTL() ? `${appName} - ${pageName}` : `${pageName} - ${appName}`;\r\n }\r\n\r\n /**\r\n * Pluralization helper\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// ╔════════════════════════════════════════ ════ ════════════════════════════════════════╗\r\n\r\n export default {\r\n I18nManager,\r\n getI18n,\r\n setupI18n,\r\n createLazyLoader,\r\n setupLazy,\r\n setupAuto,\r\n browserStorage,\r\n memoryStorage,\r\n fetchTranslations,\r\n genPageTitle,\r\n plural,\r\n };\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 './types';\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","translation","param","paramValue","fallback","original","result","tokens","regex","match","fn","callback","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","genPageTitle","appName","pageName","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 if (this.storage) {\r\n const stored = await this.storage.get('i18n-language');\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 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('i18n-language', 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","// 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 // 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 * Generate page title with proper RTL handling\r\n *\r\n * @example\r\n * // English: \"Profile - MyApp\"\r\n * // Arabic: \"MyApp - الملف الشخصي\"\r\n */\r\n export function genPageTitle(key: string, prefix: string = 'page.'): string {\r\n const appName = t('app.name');\r\n const pageName = t(prefix + key);\r\n return isRTL() ? `${appName} - ${pageName}` : `${pageName} - ${appName}`;\r\n }\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 genPageTitle,\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
@@ -23,7 +23,6 @@ declare class I18nManager {
23
23
  private translations;
24
24
  private currentLanguage;
25
25
  private defaultLanguage;
26
- private fallbackLanguage;
27
26
  private supportedLanguages;
28
27
  private rtlLanguages;
29
28
  private listeners;
@@ -132,25 +131,10 @@ declare class I18nManager {
132
131
  */
133
132
  onChange(callback: (lang: LanguageCode) => void): () => void;
134
133
  }
134
+
135
135
  /**
136
- * Browser storage adapter using localStorage
137
- */
138
- declare const browserStorage: I18nStorage;
139
- /**
140
- * Memory storage adapter (for server/testing)
141
- */
142
- declare const memoryStorage: {
143
- get: (key: string) => string | null;
144
- set: (key: string, value: string) => void;
145
- };
146
- /**
147
- * Fetch translations from URLs
148
- * Works in both browser and Node.js (with node-fetch)
149
- */
150
- declare function fetchTranslations(urls: string | string[], manager: I18nManager): Promise<void>;
151
- /**
152
- * Lazy loader: fetch language only when needed
153
- * Prevents loading all languages at startup
136
+ * Lazy loader: fetch language on-demand
137
+ * Supports both URL-based (browser) and file-based (server) loading
154
138
  */
155
139
  declare class LazyLoader {
156
140
  private baseUrl;
@@ -175,82 +159,83 @@ declare class LazyLoader {
175
159
  */
176
160
  declare function getI18n(): I18nManager;
177
161
  /**
178
- * Initialize i18n with config
179
- * Call this once at app startup
180
- *
181
- * @example
182
- * // Load only default language at startup
183
- * await setupI18n({
184
- * defaultLanguage: 'en',
185
- * supportedLanguages: ['en', 'ar', 'fr', 'de']
186
- * });
162
+ * Get the lazy loader instance (only available after setupI18n with basePath)
187
163
  */
188
- declare function setupI18n(config: I18nConfig): Promise<I18nManager>;
164
+ declare function getLazyLoader(): LazyLoader | null;
189
165
  /**
190
- * Create a lazy loader for translations
191
- * Only loads languages when needed
166
+ * Main setup function - Single, simple, auto-detecting initialization
192
167
  *
193
- * @example
194
- * const loader = createLazyLoader('https://mycdn.com/i18n/');
168
+ * Auto-detects environment and handles both browser and server:
169
+ * - Browser: Auto-detects language, loads from URL path
170
+ * - Server: Uses defaultLanguage, loads from file system
195
171
  *
196
- * // Later, when user switches language:
197
- * await loader.load('ar');
198
- * await setLanguage('ar');
199
- */
200
- declare function createLazyLoader(baseUrl: string, fileExtension?: string): LazyLoader;
201
- /**
202
- * Setup i18n with lazy loading
203
- * Only loads the default language at startup
172
+ * Call this ONCE at app startup.
204
173
  *
205
174
  * @example
206
- * // Browser
207
- * const loader = await setupLazy({
208
- * defaultLanguage: 'en',
209
- * supportedLanguages: ['en', 'ar', 'fr', 'de', 'zh'],
175
+ * // Browser - Auto-detects language, lazy-loads from URL
176
+ * await setupI18n({
177
+ * supportedLanguages: ['en', 'ar', 'fr'],
210
178
  * basePath: '/i18n/'
211
179
  * });
212
180
  *
213
- * // Later when user switches:
214
- * await loader.load('ar');
215
- * await setLanguage('ar');
216
- */
217
- declare function setupLazy(config: I18nConfig & {
218
- basePath?: string;
219
- baseUrl?: string;
220
- }): Promise<LazyLoader>;
221
- /**
222
- * Auto-setup: Smart initialization based on environment and config
223
- * Automatically handles browser vs server and file vs URL loading
224
- *
225
181
  * @example
226
- * // Browser: Auto-fetches from server
227
- * const loader = await setupAuto({
228
- * defaultLanguage: 'en',
229
- * supportedLanguages: ['en', 'ar', 'fr'],
230
- * basePath: 'http://localhost:3000/static/i18n/'
231
- * });
232
- *
233
- * // Server (Node.js): Auto-reads from filesystem
234
- * const loader = await setupAuto({
182
+ * // Server - Uses default language, lazy-loads from filesystem
183
+ * await setupI18n({
235
184
  * defaultLanguage: 'en',
236
185
  * supportedLanguages: ['en', 'ar', 'fr'],
237
- * basePath: './public/locales/'
186
+ * basePath: './locales/'
238
187
  * });
239
188
  */
240
- declare function setupAuto(config: I18nConfig & {
241
- basePath: string;
242
- }): Promise<LazyLoader>;
189
+ declare function setupI18n(config: I18nConfig & {
190
+ basePath?: string;
191
+ }): Promise<I18nManager>;
192
+ /**
193
+ * Translate a key with optional parameter replacement
194
+ */
243
195
  declare const t: (key: string, params?: Record<string, string>) => string;
196
+ /**
197
+ * Translate a key with a specific language temporarily
198
+ */
244
199
  declare const tLang: (key: string, lang: LanguageCode, params?: Record<string, string>) => string;
200
+ /**
201
+ * Parse translation with HTML tags into tokens
202
+ */
245
203
  declare const tParse: (key: string, params?: Record<string, string>) => TranslationToken[];
204
+ /**
205
+ * Set current language and trigger listeners
206
+ */
246
207
  declare const setLanguage: (lang: LanguageCode) => Promise<void>;
208
+ /**
209
+ * Get current language code
210
+ */
247
211
  declare const getLanguage: () => string;
212
+ /**
213
+ * Get all supported languages
214
+ */
248
215
  declare const getSupportedLanguages: () => string[];
216
+ /**
217
+ * Check if translation key exists
218
+ */
249
219
  declare const hasKey: (key: string) => boolean;
220
+ /**
221
+ * Check if current language is RTL
222
+ */
250
223
  declare const isRTL: () => boolean;
224
+ /**
225
+ * Check if specific language is RTL
226
+ */
251
227
  declare const isRTLLanguage: (lang: LanguageCode) => boolean;
228
+ /**
229
+ * Subscribe to language changes
230
+ */
252
231
  declare const onChange: (callback: (lang: LanguageCode) => void) => () => void;
232
+ /**
233
+ * Load translations for a specific language
234
+ */
253
235
  declare const loadLanguage: (lang: LanguageCode, translations: Record<string, any>) => void;
236
+ /**
237
+ * Load multiple languages at once
238
+ */
254
239
  declare const loadTranslations: (translations: TranslationSet) => void;
255
240
  /**
256
241
  * Generate page title with proper RTL handling
@@ -258,11 +243,10 @@ declare const loadTranslations: (translations: TranslationSet) => void;
258
243
  * @example
259
244
  * // English: "Profile - MyApp"
260
245
  * // Arabic: "MyApp - الملف الشخصي"
261
- * genPageTitle('profile', 'page.')
262
246
  */
263
247
  declare function genPageTitle(key: string, prefix?: string): string;
264
248
  /**
265
- * Pluralization helper
249
+ * Pluralization helper - select translation based on count
266
250
  *
267
251
  * @example
268
252
  * plural(1, 'item.single', 'item.plural') // "1 item"
@@ -270,18 +254,23 @@ declare function genPageTitle(key: string, prefix?: string): string;
270
254
  */
271
255
  declare function plural(count: number, singleKey: string, pluralKey: string): string;
272
256
  declare const _default: {
273
- I18nManager: typeof I18nManager;
274
- getI18n: typeof getI18n;
275
257
  setupI18n: typeof setupI18n;
276
- createLazyLoader: typeof createLazyLoader;
277
- setupLazy: typeof setupLazy;
278
- setupAuto: typeof setupAuto;
279
- browserStorage: I18nStorage;
280
- memoryStorage: {
281
- get: (key: string) => string | null;
282
- set: (key: string, value: string) => void;
283
- };
284
- fetchTranslations: typeof fetchTranslations;
258
+ getI18n: typeof getI18n;
259
+ getLazyLoader: typeof getLazyLoader;
260
+ I18nManager: typeof I18nManager;
261
+ LazyLoader: typeof LazyLoader;
262
+ t: (key: string, params?: Record<string, string>) => string;
263
+ tLang: (key: string, lang: LanguageCode, params?: Record<string, string>) => string;
264
+ tParse: (key: string, params?: Record<string, string>) => TranslationToken[];
265
+ setLanguage: (lang: LanguageCode) => Promise<void>;
266
+ getLanguage: () => string;
267
+ getSupportedLanguages: () => string[];
268
+ hasKey: (key: string) => boolean;
269
+ isRTL: () => boolean;
270
+ isRTLLanguage: (lang: LanguageCode) => boolean;
271
+ onChange: (callback: (lang: LanguageCode) => void) => () => void;
272
+ loadLanguage: (lang: LanguageCode, translations: Record<string, any>) => void;
273
+ loadTranslations: (translations: TranslationSet) => void;
285
274
  genPageTitle: typeof genPageTitle;
286
275
  plural: typeof plural;
287
276
  };
@@ -289,4 +278,4 @@ declare const _default: {
289
278
  type I18nManagerInstance = InstanceType<typeof I18nManager>;
290
279
  type LazyLoaderInstance = InstanceType<typeof LazyLoader>;
291
280
 
292
- export { type I18nConfig, I18nManager, type I18nManagerInstance, type I18nStorage, type LanguageCode, LazyLoader, type LazyLoaderInstance, type TranslationSet, type TranslationToken, browserStorage, createLazyLoader, _default as default, fetchTranslations, genPageTitle, getI18n, getLanguage, getSupportedLanguages, hasKey, isRTL, isRTLLanguage, loadLanguage, loadTranslations, memoryStorage, onChange, plural, setLanguage, setupAuto, setupI18n, setupLazy, t, tLang, tParse };
281
+ export { type I18nConfig, I18nManager, type I18nManagerInstance, type I18nStorage, type LanguageCode, LazyLoader, type LazyLoaderInstance, type TranslationSet, type TranslationToken, _default as default, genPageTitle, getI18n, getLanguage, getLazyLoader, getSupportedLanguages, hasKey, isRTL, isRTLLanguage, loadLanguage, loadTranslations, onChange, plural, setLanguage, setupI18n, t, tLang, tParse };
package/dist/index.d.ts CHANGED
@@ -23,7 +23,6 @@ declare class I18nManager {
23
23
  private translations;
24
24
  private currentLanguage;
25
25
  private defaultLanguage;
26
- private fallbackLanguage;
27
26
  private supportedLanguages;
28
27
  private rtlLanguages;
29
28
  private listeners;
@@ -132,25 +131,10 @@ declare class I18nManager {
132
131
  */
133
132
  onChange(callback: (lang: LanguageCode) => void): () => void;
134
133
  }
134
+
135
135
  /**
136
- * Browser storage adapter using localStorage
137
- */
138
- declare const browserStorage: I18nStorage;
139
- /**
140
- * Memory storage adapter (for server/testing)
141
- */
142
- declare const memoryStorage: {
143
- get: (key: string) => string | null;
144
- set: (key: string, value: string) => void;
145
- };
146
- /**
147
- * Fetch translations from URLs
148
- * Works in both browser and Node.js (with node-fetch)
149
- */
150
- declare function fetchTranslations(urls: string | string[], manager: I18nManager): Promise<void>;
151
- /**
152
- * Lazy loader: fetch language only when needed
153
- * Prevents loading all languages at startup
136
+ * Lazy loader: fetch language on-demand
137
+ * Supports both URL-based (browser) and file-based (server) loading
154
138
  */
155
139
  declare class LazyLoader {
156
140
  private baseUrl;
@@ -175,82 +159,83 @@ declare class LazyLoader {
175
159
  */
176
160
  declare function getI18n(): I18nManager;
177
161
  /**
178
- * Initialize i18n with config
179
- * Call this once at app startup
180
- *
181
- * @example
182
- * // Load only default language at startup
183
- * await setupI18n({
184
- * defaultLanguage: 'en',
185
- * supportedLanguages: ['en', 'ar', 'fr', 'de']
186
- * });
162
+ * Get the lazy loader instance (only available after setupI18n with basePath)
187
163
  */
188
- declare function setupI18n(config: I18nConfig): Promise<I18nManager>;
164
+ declare function getLazyLoader(): LazyLoader | null;
189
165
  /**
190
- * Create a lazy loader for translations
191
- * Only loads languages when needed
166
+ * Main setup function - Single, simple, auto-detecting initialization
192
167
  *
193
- * @example
194
- * const loader = createLazyLoader('https://mycdn.com/i18n/');
168
+ * Auto-detects environment and handles both browser and server:
169
+ * - Browser: Auto-detects language, loads from URL path
170
+ * - Server: Uses defaultLanguage, loads from file system
195
171
  *
196
- * // Later, when user switches language:
197
- * await loader.load('ar');
198
- * await setLanguage('ar');
199
- */
200
- declare function createLazyLoader(baseUrl: string, fileExtension?: string): LazyLoader;
201
- /**
202
- * Setup i18n with lazy loading
203
- * Only loads the default language at startup
172
+ * Call this ONCE at app startup.
204
173
  *
205
174
  * @example
206
- * // Browser
207
- * const loader = await setupLazy({
208
- * defaultLanguage: 'en',
209
- * supportedLanguages: ['en', 'ar', 'fr', 'de', 'zh'],
175
+ * // Browser - Auto-detects language, lazy-loads from URL
176
+ * await setupI18n({
177
+ * supportedLanguages: ['en', 'ar', 'fr'],
210
178
  * basePath: '/i18n/'
211
179
  * });
212
180
  *
213
- * // Later when user switches:
214
- * await loader.load('ar');
215
- * await setLanguage('ar');
216
- */
217
- declare function setupLazy(config: I18nConfig & {
218
- basePath?: string;
219
- baseUrl?: string;
220
- }): Promise<LazyLoader>;
221
- /**
222
- * Auto-setup: Smart initialization based on environment and config
223
- * Automatically handles browser vs server and file vs URL loading
224
- *
225
181
  * @example
226
- * // Browser: Auto-fetches from server
227
- * const loader = await setupAuto({
228
- * defaultLanguage: 'en',
229
- * supportedLanguages: ['en', 'ar', 'fr'],
230
- * basePath: 'http://localhost:3000/static/i18n/'
231
- * });
232
- *
233
- * // Server (Node.js): Auto-reads from filesystem
234
- * const loader = await setupAuto({
182
+ * // Server - Uses default language, lazy-loads from filesystem
183
+ * await setupI18n({
235
184
  * defaultLanguage: 'en',
236
185
  * supportedLanguages: ['en', 'ar', 'fr'],
237
- * basePath: './public/locales/'
186
+ * basePath: './locales/'
238
187
  * });
239
188
  */
240
- declare function setupAuto(config: I18nConfig & {
241
- basePath: string;
242
- }): Promise<LazyLoader>;
189
+ declare function setupI18n(config: I18nConfig & {
190
+ basePath?: string;
191
+ }): Promise<I18nManager>;
192
+ /**
193
+ * Translate a key with optional parameter replacement
194
+ */
243
195
  declare const t: (key: string, params?: Record<string, string>) => string;
196
+ /**
197
+ * Translate a key with a specific language temporarily
198
+ */
244
199
  declare const tLang: (key: string, lang: LanguageCode, params?: Record<string, string>) => string;
200
+ /**
201
+ * Parse translation with HTML tags into tokens
202
+ */
245
203
  declare const tParse: (key: string, params?: Record<string, string>) => TranslationToken[];
204
+ /**
205
+ * Set current language and trigger listeners
206
+ */
246
207
  declare const setLanguage: (lang: LanguageCode) => Promise<void>;
208
+ /**
209
+ * Get current language code
210
+ */
247
211
  declare const getLanguage: () => string;
212
+ /**
213
+ * Get all supported languages
214
+ */
248
215
  declare const getSupportedLanguages: () => string[];
216
+ /**
217
+ * Check if translation key exists
218
+ */
249
219
  declare const hasKey: (key: string) => boolean;
220
+ /**
221
+ * Check if current language is RTL
222
+ */
250
223
  declare const isRTL: () => boolean;
224
+ /**
225
+ * Check if specific language is RTL
226
+ */
251
227
  declare const isRTLLanguage: (lang: LanguageCode) => boolean;
228
+ /**
229
+ * Subscribe to language changes
230
+ */
252
231
  declare const onChange: (callback: (lang: LanguageCode) => void) => () => void;
232
+ /**
233
+ * Load translations for a specific language
234
+ */
253
235
  declare const loadLanguage: (lang: LanguageCode, translations: Record<string, any>) => void;
236
+ /**
237
+ * Load multiple languages at once
238
+ */
254
239
  declare const loadTranslations: (translations: TranslationSet) => void;
255
240
  /**
256
241
  * Generate page title with proper RTL handling
@@ -258,11 +243,10 @@ declare const loadTranslations: (translations: TranslationSet) => void;
258
243
  * @example
259
244
  * // English: "Profile - MyApp"
260
245
  * // Arabic: "MyApp - الملف الشخصي"
261
- * genPageTitle('profile', 'page.')
262
246
  */
263
247
  declare function genPageTitle(key: string, prefix?: string): string;
264
248
  /**
265
- * Pluralization helper
249
+ * Pluralization helper - select translation based on count
266
250
  *
267
251
  * @example
268
252
  * plural(1, 'item.single', 'item.plural') // "1 item"
@@ -270,18 +254,23 @@ declare function genPageTitle(key: string, prefix?: string): string;
270
254
  */
271
255
  declare function plural(count: number, singleKey: string, pluralKey: string): string;
272
256
  declare const _default: {
273
- I18nManager: typeof I18nManager;
274
- getI18n: typeof getI18n;
275
257
  setupI18n: typeof setupI18n;
276
- createLazyLoader: typeof createLazyLoader;
277
- setupLazy: typeof setupLazy;
278
- setupAuto: typeof setupAuto;
279
- browserStorage: I18nStorage;
280
- memoryStorage: {
281
- get: (key: string) => string | null;
282
- set: (key: string, value: string) => void;
283
- };
284
- fetchTranslations: typeof fetchTranslations;
258
+ getI18n: typeof getI18n;
259
+ getLazyLoader: typeof getLazyLoader;
260
+ I18nManager: typeof I18nManager;
261
+ LazyLoader: typeof LazyLoader;
262
+ t: (key: string, params?: Record<string, string>) => string;
263
+ tLang: (key: string, lang: LanguageCode, params?: Record<string, string>) => string;
264
+ tParse: (key: string, params?: Record<string, string>) => TranslationToken[];
265
+ setLanguage: (lang: LanguageCode) => Promise<void>;
266
+ getLanguage: () => string;
267
+ getSupportedLanguages: () => string[];
268
+ hasKey: (key: string) => boolean;
269
+ isRTL: () => boolean;
270
+ isRTLLanguage: (lang: LanguageCode) => boolean;
271
+ onChange: (callback: (lang: LanguageCode) => void) => () => void;
272
+ loadLanguage: (lang: LanguageCode, translations: Record<string, any>) => void;
273
+ loadTranslations: (translations: TranslationSet) => void;
285
274
  genPageTitle: typeof genPageTitle;
286
275
  plural: typeof plural;
287
276
  };
@@ -289,4 +278,4 @@ declare const _default: {
289
278
  type I18nManagerInstance = InstanceType<typeof I18nManager>;
290
279
  type LazyLoaderInstance = InstanceType<typeof LazyLoader>;
291
280
 
292
- export { type I18nConfig, I18nManager, type I18nManagerInstance, type I18nStorage, type LanguageCode, LazyLoader, type LazyLoaderInstance, type TranslationSet, type TranslationToken, browserStorage, createLazyLoader, _default as default, fetchTranslations, genPageTitle, getI18n, getLanguage, getSupportedLanguages, hasKey, isRTL, isRTLLanguage, loadLanguage, loadTranslations, memoryStorage, onChange, plural, setLanguage, setupAuto, setupI18n, setupLazy, t, tLang, tParse };
281
+ export { type I18nConfig, I18nManager, type I18nManagerInstance, type I18nStorage, type LanguageCode, LazyLoader, type LazyLoaderInstance, type TranslationSet, type TranslationToken, _default as default, genPageTitle, getI18n, getLanguage, getLazyLoader, getSupportedLanguages, hasKey, isRTL, isRTLLanguage, loadLanguage, loadTranslations, onChange, plural, setLanguage, setupI18n, t, tLang, tParse };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- var u=class{constructor(t){this.translations={};this.currentLanguage="en";this.defaultLanguage="en";this.fallbackLanguage="en";this.supportedLanguages=new Set(["en"]);this.rtlLanguages=new Set(["ar","he","fa","ur","yi","ji","iw","ku"]);this.listeners=new Set;t&&(this.defaultLanguage=t.defaultLanguage||"en",this.fallbackLanguage=t.fallbackLanguage||t.defaultLanguage||"en",this.currentLanguage=t.defaultLanguage||"en",this.storage=t.storage,this.onLanguageChange=t.onLanguageChange,t.supportedLanguages&&(this.supportedLanguages=new Set(t.supportedLanguages)));}async init(){if(this.storage){let t=await this.storage.get("i18n-language");t&&this.supportedLanguages.has(t)&&(this.currentLanguage=t);}}loadLanguage(t,e){this.translations[t]||(this.translations[t]={});let n=this.flattenObject(e);this.translations[t]={...this.translations[t],...n},this.supportedLanguages.add(t);}loadTranslations(t){Object.entries(t).forEach(([e,n])=>{this.loadLanguage(e,n);});}flattenObject(t,e=""){let n={};for(let s in t){if(!Object.prototype.hasOwnProperty.call(t,s))continue;let i=t[s],r=e?`${e}.${s}`:s;typeof i=="object"&&i!==null&&!Array.isArray(i)?Object.assign(n,this.flattenObject(i,r)):n[r]=String(i);}return n}t(t,e){let n=this.getTranslation(t);return e&&Object.entries(e).forEach(([s,i])=>{let r=this.getTranslation(i,i);n=n.replace(new RegExp(`\\{${s}\\}`,"g"),r);}),n}getTranslation(t,e){return this.translations[this.currentLanguage]?.[t]?this.translations[this.currentLanguage][t]:this.fallbackLanguage!==this.currentLanguage&&this.translations[this.fallbackLanguage]?.[t]?this.translations[this.fallbackLanguage][t]:this.defaultLanguage!==this.currentLanguage&&this.defaultLanguage!==this.fallbackLanguage&&this.translations[this.defaultLanguage]?.[t]?this.translations[this.defaultLanguage][t]:(console.warn(`[i18n] Translation key not found: "${t}" (lang: ${this.currentLanguage})`),e||t)}tLang(t,e,n){let s=this.currentLanguage;this.currentLanguage=e;let i=this.t(t,n);return this.currentLanguage=s,i}tParse(t,e){let n=this.t(t,e);n=n.replace(/\\n|\/n/g,"<br>");let s=[],i=/<([a-z]+)>([^<]*)<\/\1>|<([a-z]+)\s*\/?>|([^<]+)/gi,r;for(;(r=i.exec(n))!==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:n}]}async setLanguage(t){if(!this.supportedLanguages.has(t)){console.warn(`[i18n] Language "${t}" not supported`);return}this.currentLanguage=t,this.storage&&await this.storage.set("i18n-language",t),this.listeners.forEach(e=>e(t)),this.onLanguageChange&&this.onLanguageChange(t);}getLanguage(){return this.currentLanguage}getSupportedLanguages(){return Array.from(this.supportedLanguages)}isLanguageSupported(t){return this.supportedLanguages.has(t)}hasKey(t){return !!(this.translations[this.currentLanguage]?.[t]||this.translations[this.fallbackLanguage]?.[t]||this.translations[this.defaultLanguage]?.[t])}getTranslations(){return this.translations[this.currentLanguage]||{}}isRTL(){return this.rtlLanguages.has(this.currentLanguage.toLowerCase().substring(0,2))}isRTLLanguage(t){return this.rtlLanguages.has(t.toLowerCase().substring(0,2))}onChange(t){return this.listeners.add(t),()=>this.listeners.delete(t)}},h={get:a=>typeof localStorage>"u"?null:localStorage.getItem(a),set:(a,t)=>{typeof localStorage<"u"&&localStorage.setItem(a,t);}},L=(()=>{let a=new Map;return {get:t=>a.get(t)||null,set:(t,e)=>{a.set(t,e);}}})();async function f(a,t){let e=Array.isArray(a)?a:[a],n={};for(let s of e)try{let i=await fetch(s);if(i.ok){let r=await i.json(),c=s.match(/([a-z]{2,3})\.json$/i),d=c?c[1].toLowerCase():"en";n[d]=r;}}catch(i){console.warn(`[i18n] Failed to fetch: ${s}`,i);}Object.keys(n).length>0&&t.loadTranslations(n);}var l=class{constructor(t,e,n="json"){this.loading=new Map;this.loaded=new Set;this.baseUrl=t.endsWith("/")?t:t+"/",this.manager=e,this.fileExtension=n,this.isServerSide=typeof fetch>"u";}async load(t){if(this.loaded.has(t))return;if(this.loading.has(t))return this.loading.get(t);let e=this.doLoad(t);this.loading.set(t,e);try{await e,this.loaded.add(t);}finally{this.loading.delete(t);}}async doLoad(t){try{let e=`${this.baseUrl}${t}.${this.fileExtension}`,n;e.startsWith(".")||e.startsWith("/")||/^[a-zA-Z]:/.test(e)||this.isServerSide?n=await this.loadFromFile(e):n=await this.loadFromUrl(e),n&&this.manager.loadLanguage(t,n);}catch(e){console.warn(`[i18n] Error loading language: ${t}`,e);}}async loadFromUrl(t){try{let e=await fetch(t);return e.ok?await e.json():(console.warn(`[i18n] Failed to load language from URL: ${t} (${e.status})`),null)}catch(e){return console.warn(`[i18n] Error fetching from URL: ${t}`,e),null}}async loadFromFile(t){try{let e=await import('fs').then(r=>r.promises).catch(()=>null),n=await import('path').then(r=>r).catch(()=>null);if(!e)return console.warn("[i18n] fs module not available. Running in browser?"),null;let s=t;if(n&&!n.isAbsolute(t)){let r=await import('process').then(c=>c).catch(()=>null);r&&(s=n.resolve(r.cwd(),t));}let i=await e.readFile(s,"utf-8");return JSON.parse(i)}catch(e){return console.warn(`[i18n] Error reading file: ${t}`,e),null}}isLoaded(t){return this.loaded.has(t)}},g=null;function o(){return g||(g=new u),g}async function y(a){return g=new u(a),await g.init(),g}function b(a,t="json"){return new l(a,o(),t)}async function w(a){let t=new u(a);await t.init(),g=t;let e=a.basePath||a.baseUrl||"./locales/",n=a.fileExtension||"json",s=new l(e,t,n);return await s.load(t.getLanguage()),s}async function m(a){let t=new u(a);await t.init(),g=t;let e=a.fileExtension||"json",n=new l(a.basePath,t,e);return await n.load(t.getLanguage()),n}var p=(a,t)=>o().t(a,t),S=(a,t,e)=>o().tLang(a,t,e),T=(a,t)=>o().tParse(a,t),R=a=>o().setLanguage(a),P=()=>o().getLanguage(),I=()=>o().getSupportedLanguages(),$=a=>o().hasKey(a),v=()=>o().isRTL(),j=a=>o().isRTLLanguage(a),E=a=>o().onChange(a),z=(a,t)=>o().loadLanguage(a,t),M=a=>o().loadTranslations(a);function x(a,t="page."){let e=p("app.name"),n=p(t+a);return v()?`${e} - ${n}`:`${n} - ${e}`}function C(a,t,e){return p(a===1?t:e,{count:String(a)})}var O={I18nManager:u,getI18n:o,setupI18n:y,createLazyLoader:b,setupLazy:w,setupAuto:m,browserStorage:h,memoryStorage:L,fetchTranslations:f,genPageTitle:x,plural:C};export{u as I18nManager,l as LazyLoader,h as browserStorage,b as createLazyLoader,O as default,f as fetchTranslations,x as genPageTitle,o as getI18n,P as getLanguage,I as getSupportedLanguages,$ as hasKey,v as isRTL,j as isRTLLanguage,z as loadLanguage,M as loadTranslations,L as memoryStorage,E as onChange,C as plural,R as setLanguage,m as setupAuto,y as setupI18n,w as setupLazy,p as t,S as tLang,T as tParse};//# sourceMappingURL=index.js.map
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("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 r in e){if(!Object.prototype.hasOwnProperty.call(e,r))continue;let i=e[r],s=t?`${t}.${r}`:r;typeof i=="object"&&i!==null&&!Array.isArray(i)?Object.assign(a,this.flattenObject(i,s)):a[s]=String(i);}return a}t(e,t){let a=this.getTranslation(e);return t&&Object.entries(t).forEach(([r,i])=>{let s=this.getTranslation(i,i);a=a.replace(new RegExp(`\\{${r}\\}`,"g"),s);}),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 r=this.currentLanguage;this.currentLanguage=t;let i=this.t(e,a);return this.currentLanguage=r,i}tParse(e,t){let a=this.t(e,t);a=a.replace(/\\n|\/n/g,"<br>");let r=[],i=/<([a-z]+)>([^<]*)<\/\1>|<([a-z]+)\s*\/?>|([^<]+)/gi,s;for(;(s=i.exec(a))!==null;)s[4]?r.push({type:"text",content:s[4]}):s[1]?r.push({type:"tag",tag:s[1],content:s[2]}):s[3]&&r.push({type:"tag",tag:s[3],content:""});return r.length>0?r:[{type:"text",content:a}]}async setLanguage(e){if(!this.supportedLanguages.has(e)){console.warn(`[i18n] Language "${e}" not supported`);return}this.currentLanguage=e,this.storage&&await this.storage.set("i18n-language",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)}};var L=()=>({get:n=>typeof localStorage>"u"?null:localStorage.getItem(n),set:(n,e)=>{typeof localStorage<"u"&&localStorage.setItem(n,e);}}),y=()=>{let n=new Map;return {get:e=>n.get(e)||null,set:(e,t)=>{n.set(e,t);}}},f=()=>typeof localStorage<"u"?L():y(),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);}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(s=>s.promises).catch(()=>null),a=await import('path').then(s=>s).catch(()=>null);if(!t)return console.warn("[i18n] fs module not available. Running in browser?"),null;let r=e;if(a&&!a.isAbsolute(e)){let s=await import('process').then(h=>h).catch(()=>null);s&&(r=a.resolve(s.cwd(),e));}let i=await t.readFile(r,"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 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 g||(g=new u),g}function v(){return l}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=f()),g=new u(n),await g.init(),n.basePath){let e=n.fileExtension||"json";l=new p(n.basePath,g,e),await l.load(g.getLanguage());}return g}var d=(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),c=()=>o().isRTL(),P=n=>o().isRTLLanguage(n),$=n=>o().onChange(n),E=(n,e)=>o().loadLanguage(n,e),j=n=>o().loadTranslations(n);function M(n,e="page."){let t=d("app.name"),a=d(e+n);return c()?`${t} - ${a}`:`${a} - ${t}`}function O(n,e,t){return d(n===1?e:t,{count:String(n)})}var A={setupI18n:b,getI18n:o,getLazyLoader:v,I18nManager:u,LazyLoader:p,t:d,tLang:C,tParse:x,setLanguage:S,getLanguage:R,getSupportedLanguages:T,hasKey:I,isRTL:c,isRTLLanguage:P,onChange:$,loadLanguage:E,loadTranslations:j,genPageTitle:M,plural:O};export{u as I18nManager,p as LazyLoader,A as default,M as genPageTitle,o as getI18n,R as getLanguage,v as getLazyLoader,T as getSupportedLanguages,I as hasKey,c as isRTL,P as isRTLLanguage,E as loadLanguage,j as loadTranslations,$ as onChange,O as plural,S as setLanguage,b as setupI18n,d as t,C as tLang,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/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","fn","callback","browserStorage","memoryStorage","store","fetchTranslations","urls","manager","urlList","url","response","data","langMatch","error","LazyLoader","baseUrl","fileExtension","promise","filePath","fs","m","path","resolvedPath","process","content","instance","getI18n","setupI18n","createLazyLoader","setupLazy","basePath","loader","setupAuto","t","tLang","tParse","setLanguage","getLanguage","getSupportedLanguages","hasKey","isRTL","isRTLLanguage","onChange","loadLanguage","loadTranslations","genPageTitle","appName","pageName","plural","count","singleKey","pluralKey","index_default"],"mappings":"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","file":"index.js","sourcesContent":["// 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\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n export class I18nManager {\r\n\r\n // ┌──────────────────────────────── STATE ─────────────────────────────┐\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 fallbackLanguage : 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 // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── INIT ──────────────────────────────┐\r\n\r\n constructor(config?: types.I18nConfig) {\r\n if (config) {\r\n this.defaultLanguage = config.defaultLanguage || 'en';\r\n this.fallbackLanguage = config.fallbackLanguage || 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 if (this.storage) {\r\n const stored = await this.storage.get('i18n-language');\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 // ┌─────────────────────────────── LOADING ────────────────────────────┐\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 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 fallback language\r\n if (this.fallbackLanguage !== this.currentLanguage &&\r\n this.translations[this.fallbackLanguage]?.[key]) {\r\n return this.translations[this.fallbackLanguage][key];\r\n }\r\n\r\n // Try default language\r\n if (this.defaultLanguage !== this.currentLanguage &&\r\n this.defaultLanguage !== this.fallbackLanguage &&\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('i18n-language', 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.fallbackLanguage]?.[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// ╔════════════════════════════════════════ ════ ════════════════════════════════════════╗\r\n\r\n /**\r\n * Browser storage adapter using localStorage\r\n */\r\n export const browserStorage: 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 * Memory storage adapter (for server/testing)\r\n */\r\n export const memoryStorage = (() => {\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 * Fetch translations from URLs\r\n * Works in both browser and Node.js (with node-fetch)\r\n */\r\n export async function fetchTranslations(\r\n urls: string | string[],\r\n manager: I18nManager\r\n ): Promise<void> {\r\n const urlList = Array.isArray(urls) ? urls : [urls];\r\n const translations: types.TranslationSet = {};\r\n\r\n for (const url of urlList) {\r\n try {\r\n const response = await fetch(url);\r\n if (response.ok) {\r\n const data = await response.json();\r\n // Extract language from filename: /path/en.json -> en\r\n const langMatch = url.match(/([a-z]{2,3})\\.json$/i);\r\n const lang = langMatch ? langMatch[1].toLowerCase() : 'en';\r\n translations[lang] = data;\r\n }\r\n } catch (error) {\r\n console.warn(`[i18n] Failed to fetch: ${url}`, error);\r\n }\r\n }\r\n\r\n if (Object.keys(translations).length > 0) {\r\n manager.loadTranslations(translations);\r\n }\r\n }\r\n\r\n /**\r\n * Lazy loader: fetch language only when needed\r\n * Prevents loading all languages at startup\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\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 * Initialize i18n with config\r\n * Call this once at app startup\r\n *\r\n * @example\r\n * // Load only default language at startup\r\n * await setupI18n({\r\n * defaultLanguage: 'en',\r\n * supportedLanguages: ['en', 'ar', 'fr', 'de']\r\n * });\r\n */\r\n export async function setupI18n(config: types.I18nConfig): Promise<I18nManager> {\r\n instance = new I18nManager(config);\r\n await instance.init();\r\n return instance;\r\n }\r\n\r\n /**\r\n * Create a lazy loader for translations\r\n * Only loads languages when needed\r\n *\r\n * @example\r\n * const loader = createLazyLoader('https://mycdn.com/i18n/');\r\n *\r\n * // Later, when user switches language:\r\n * await loader.load('ar');\r\n * await setLanguage('ar');\r\n */\r\n export function createLazyLoader(baseUrl: string, fileExtension: string = 'json'): LazyLoader {\r\n return new LazyLoader(baseUrl, getI18n(), fileExtension);\r\n }\r\n\r\n /**\r\n * Setup i18n with lazy loading\r\n * Only loads the default language at startup\r\n *\r\n * @example\r\n * // Browser\r\n * const loader = await setupLazy({\r\n * defaultLanguage: 'en',\r\n * supportedLanguages: ['en', 'ar', 'fr', 'de', 'zh'],\r\n * basePath: '/i18n/'\r\n * });\r\n *\r\n * // Later when user switches:\r\n * await loader.load('ar');\r\n * await setLanguage('ar');\r\n */\r\n export async function setupLazy(config: types.I18nConfig & { basePath?: string; baseUrl?: string }): Promise<LazyLoader> {\r\n const manager = new I18nManager(config);\r\n await manager.init();\r\n instance = manager;\r\n\r\n const basePath = config.basePath || config.baseUrl || './locales/';\r\n const fileExtension = config.fileExtension || 'json';\r\n const loader = new LazyLoader(basePath, manager, fileExtension);\r\n\r\n // Load only the current language at startup\r\n await loader.load(manager.getLanguage());\r\n\r\n return loader;\r\n }\r\n\r\n /**\r\n * Auto-setup: Smart initialization based on environment and config\r\n * Automatically handles browser vs server and file vs URL loading\r\n *\r\n * @example\r\n * // Browser: Auto-fetches from server\r\n * const loader = await setupAuto({\r\n * defaultLanguage: 'en',\r\n * supportedLanguages: ['en', 'ar', 'fr'],\r\n * basePath: 'http://localhost:3000/static/i18n/'\r\n * });\r\n *\r\n * // Server (Node.js): Auto-reads from filesystem\r\n * const loader = await setupAuto({\r\n * defaultLanguage: 'en',\r\n * supportedLanguages: ['en', 'ar', 'fr'],\r\n * basePath: './public/locales/'\r\n * });\r\n */\r\n export async function setupAuto(config: types.I18nConfig & { basePath: string }): Promise<LazyLoader> {\r\n const manager = new I18nManager(config);\r\n await manager.init();\r\n instance = manager;\r\n\r\n const fileExtension = config.fileExtension || 'json';\r\n const loader = new LazyLoader(config.basePath, manager, fileExtension);\r\n\r\n // Auto-load default language\r\n await loader.load(manager.getLanguage());\r\n\r\n return loader;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ ════ ════════════════════════════════════════╗\r\n\r\n export const t = (key: string, params?: Record<string, string>) =>\r\n getI18n().t(key, params);\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 export const tParse = (key: string, params?: Record<string, string>) =>\r\n getI18n().tParse(key, params);\r\n\r\n export const setLanguage = (lang: types.LanguageCode) =>\r\n getI18n().setLanguage(lang);\r\n\r\n export const getLanguage = () =>\r\n getI18n().getLanguage();\r\n\r\n export const getSupportedLanguages = () =>\r\n getI18n().getSupportedLanguages();\r\n\r\n export const hasKey = (key: string) =>\r\n getI18n().hasKey(key);\r\n\r\n export const isRTL = () =>\r\n getI18n().isRTL();\r\n\r\n export const isRTLLanguage = (lang: types.LanguageCode) =>\r\n getI18n().isRTLLanguage(lang);\r\n\r\n export const onChange = (callback: (lang: types.LanguageCode) => void) =>\r\n getI18n().onChange(callback);\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 export const loadTranslations = (translations: types.TranslationSet) =>\r\n getI18n().loadTranslations(translations);\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ ════ ════════════════════════════════════════╗\r\n\r\n /**\r\n * Generate page title with proper RTL handling\r\n *\r\n * @example\r\n * // English: \"Profile - MyApp\"\r\n * // Arabic: \"MyApp - الملف الشخصي\"\r\n * genPageTitle('profile', 'page.')\r\n */\r\n export function genPageTitle(key: string, prefix: string = 'page.'): string {\r\n const appName = t('app.name');\r\n const pageName = t(prefix + key);\r\n return isRTL() ? `${appName} - ${pageName}` : `${pageName} - ${appName}`;\r\n }\r\n\r\n /**\r\n * Pluralization helper\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// ╔════════════════════════════════════════ ════ ════════════════════════════════════════╗\r\n\r\n export default {\r\n I18nManager,\r\n getI18n,\r\n setupI18n,\r\n createLazyLoader,\r\n setupLazy,\r\n setupAuto,\r\n browserStorage,\r\n memoryStorage,\r\n fetchTranslations,\r\n genPageTitle,\r\n plural,\r\n };\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 './types';\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","translation","param","paramValue","fallback","original","result","tokens","regex","match","fn","callback","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","genPageTitle","appName","pageName","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 if (this.storage) {\r\n const stored = await this.storage.get('i18n-language');\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 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('i18n-language', 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","// 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 // 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 * Generate page title with proper RTL handling\r\n *\r\n * @example\r\n * // English: \"Profile - MyApp\"\r\n * // Arabic: \"MyApp - الملف الشخصي\"\r\n */\r\n export function genPageTitle(key: string, prefix: string = 'page.'): string {\r\n const appName = t('app.name');\r\n const pageName = t(prefix + key);\r\n return isRTL() ? `${appName} - ${pageName}` : `${pageName} - ${appName}`;\r\n }\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 genPageTitle,\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.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "A lightweight, production-ready internationalization (i18n) library with zero dependencies.",
5
5
  "keywords": ["minejs", "i18n"],
6
6
  "license": "MIT",