@i18n-micro/core 1.1.3 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -93,10 +93,6 @@ console.log(localizedRoute) // Localized route object
93
93
  Sets the cache for a specific route and locale.
94
94
  - **`mergeTranslation(locale: string, routeName: string, newTranslations: Translations, force = false): void`**:
95
95
  Merges new translations into the cache for a specific route and locale.
96
- - **`mergeGlobalTranslation(locale: string, newTranslations: Translations, force = false): void`**:
97
- Merges new translations into the global cache for a specific locale.
98
- - **`hasGeneralTranslation(locale: string): boolean`**:
99
- Checks if global translations are loaded for the specified locale.
100
96
  - **`hasPageTranslation(locale: string, routeName: string): boolean`**:
101
97
  Checks if translations for a specific route and locale are loaded.
102
98
  - **`hasTranslation(locale: string, key: string): boolean`**:
package/dist/base.d.ts CHANGED
@@ -6,12 +6,7 @@ export interface BaseI18nOptions {
6
6
  plural?: PluralFunc;
7
7
  missingWarn?: boolean;
8
8
  missingHandler?: (locale: string, key: string, routeName: string) => void;
9
- getPreviousPageInfo?: () => {
10
- locale: string;
11
- routeName: string;
12
- } | null;
13
9
  getCustomMissingHandler?: () => MissingHandler | null;
14
- enablePreviousPageFallback?: boolean;
15
10
  }
16
11
  /**
17
12
  * Abstract base class for i18n adapters
@@ -25,12 +20,7 @@ export declare abstract class BaseI18n {
25
20
  pluralFunc: PluralFunc;
26
21
  missingWarn: boolean;
27
22
  missingHandler?: (locale: string, key: string, routeName: string) => void;
28
- getPreviousPageInfo?: () => {
29
- locale: string;
30
- routeName: string;
31
- } | null;
32
23
  getCustomMissingHandler?: () => MissingHandler | null;
33
- enablePreviousPageFallback: boolean;
34
24
  constructor(options?: BaseI18nOptions);
35
25
  /**
36
26
  * Get current locale
@@ -82,7 +72,7 @@ export declare abstract class BaseI18n {
82
72
  * Core translation loading logic (without reactivity)
83
73
  * Subclasses can override addTranslations/addRouteTranslations to add reactivity
84
74
  */
85
- loadTranslationsCore(locale: string, translations: Translations, merge: boolean): void;
75
+ loadTranslationsCore(locale: string, translations: Translations, merge: boolean, routeName?: string): void;
86
76
  /**
87
77
  * Core route translation loading logic (without reactivity)
88
78
  * Subclasses can override addRouteTranslations to add reactivity
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class g{formatNumber(e,t,r){return new Intl.NumberFormat(t,r).format(e)}formatDate(e,t,r){const n=new Date(e);return Number.isNaN(n.getTime())?"Invalid Date":new Intl.DateTimeFormat(t,r).format(n)}formatRelativeTime(e,t,r){const n=new Date(e);if(Number.isNaN(n.getTime()))return new Intl.RelativeTimeFormat(t,r).format(0,"second");const i=Math.floor((new Date().getTime()-n.getTime())/1e3),o=[{unit:"year",seconds:31536e3},{unit:"month",seconds:2592e3},{unit:"day",seconds:86400},{unit:"hour",seconds:3600},{unit:"minute",seconds:60},{unit:"second",seconds:1}];for(const{unit:l,seconds:u}of o){const c=Math.floor(i/u);if(c>=1)return new Intl.RelativeTimeFormat(t,r).format(-c,l)}return new Intl.RelativeTimeFormat(t,r).format(0,"second")}}const p=/\{(\w+)\}/g;function h(s,e){return e?s.replace(p,(t,r)=>{const n=e[r];return n!==void 0?String(n):`{${r}}`}):s}function v(s){return s==="prefix"||s==="prefix_and_default"}function T(s){return s==="no_prefix"}function P(s){return s==="prefix"}function b(s){return s==="prefix_except_default"}function x(s){return s==="prefix_and_default"}const m=(s,e,t,r,n)=>{const a=n(s,t);if(!a)return null;const i=a.toString().split("|");if(i.length===0)return null;const o=e<i.length?i[e]:i[i.length-1];return o?o.trim().replace("{count}",e.toString()):null};function f(s,e){if(!s||typeof e!="string")return null;if(e in s){const n=s[e];return n}const t=e.split(".");let r=s;for(const n of t)if(r&&typeof r=="object"&&n in r)r=r[n];else return null;return r??null}function d(s){const e=s?.translations??new Map;return{hasCache(t,r){const n=`${t}:${r}`;return e.has(n)||e.has(t)},getCache(t,r){const n=`${t}:${r}`;return e.get(n)},setCache(t,r,n){},hasTranslation(t,r){for(const[n,a]of e)if((n===t||n.startsWith(`${t}:`))&&f(a,r)!==null)return!0;return!1},hasGeneralTranslation(t){return e.has(t)},hasPageTranslation(t,r){return e.has(`${t}:${r}`)},getTranslation(t,r,n){const a=`${t}:${r}`,i=e.get(a),o=f(i,n);if(o!==null)return o;const l=e.get(t);return f(l,n)},loadTranslations(t,r){const n=e.get(t)??{};e.set(t,{...n,...r})},setTranslations(t,r){e.set(t,r)},loadPageTranslations(t,r,n){const a=`${t}:${r}`,i=e.get(a);!i||Object.keys(i).length===0?e.set(a,n):e.set(a,{...i,...n})},mergeTranslation(t,r,n,a=!1){const i=`${t}:${r}`,o=e.get(i)??{};e.set(i,{...o,...n})},mergeGlobalTranslation(t,r,n=!1){const a=e.get(t)??{};e.set(t,{...a,...r})},clearCache(){e.clear()}}}class N{constructor(e={}){this.formatter=new g,this.helper=d(e.storage),this.formatter=new g,this.pluralFunc=e.plural||m,this.missingWarn=e.missingWarn??!0,this.missingHandler=e.missingHandler,this.getPreviousPageInfo=e.getPreviousPageInfo,this.getCustomMissingHandler=e.getCustomMissingHandler,this.enablePreviousPageFallback=e.enablePreviousPageFallback??!1}t(e,t,r,n){if(!e)return"";const a=this.getLocale(),i=n||this.getRoute();let o=this.helper.getTranslation(a,i,e);if(!o&&this.enablePreviousPageFallback&&this.getPreviousPageInfo){const l=this.getPreviousPageInfo();if(l){const u=this.helper.getTranslation(l.locale,l.routeName,e);u&&(o=u,process.env.NODE_ENV!=="production"&&console.log(`Using fallback translation from previous route: ${l.routeName} -> ${e}`))}}if(!o){const l=this.getFallbackLocale();a!==l&&(o=this.helper.getTranslation(l,i,e))}if(!o){const l=this.getCustomMissingHandler?.();l?l(a,e,i):this.missingHandler?this.missingHandler(a,e,i):this.missingWarn&&process.env.NODE_ENV!=="production"&&typeof window<"u"&&console.warn(`Not found '${e}' key in '${a}' locale messages for route '${i}'.`),o=r===void 0?e:r||e}return typeof o=="string"&&t?h(o,t):o}ts(e,t,r,n){return this.t(e,t,r,n)?.toString()??r??e}tc(e,t,r){const{count:n,...a}=typeof t=="number"?{count:t}:t;if(n===void 0)return r??e;const i=(l,u,c)=>this.t(l,u,c);return this.pluralFunc(e,Number.parseInt(n.toString(),10),a,this.getLocale(),i)??r??e}tn(e,t){return this.formatter.formatNumber(e,this.getLocale(),t)}td(e,t){return this.formatter.formatDate(e,this.getLocale(),t)}tdr(e,t){return this.formatter.formatRelativeTime(e,this.getLocale(),t)}has(e,t){const r=t||this.getRoute(),n=this.getLocale();return!!this.helper.getTranslation(n,r,e)}clearCache(){this.helper.clearCache()}loadTranslationsCore(e,t,r){r?this.helper.mergeGlobalTranslation(e,t,!0):this.helper.setTranslations(e,t)}loadRouteTranslationsCore(e,t,r,n){n?this.helper.mergeTranslation(e,t,r,!0):this.helper.loadPageTranslations(e,t,r)}}exports.BaseI18n=N;exports.FormatService=g;exports.defaultPlural=m;exports.interpolate=h;exports.isNoPrefixStrategy=T;exports.isPrefixAndDefaultStrategy=x;exports.isPrefixExceptDefaultStrategy=b;exports.isPrefixStrategy=P;exports.useTranslationHelper=d;exports.withPrefixStrategy=v;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class f{formatNumber(t,e,n){return new Intl.NumberFormat(e,n).format(t)}formatDate(t,e,n){const r=new Date(t);return Number.isNaN(r.getTime())?"Invalid Date":new Intl.DateTimeFormat(e,n).format(r)}formatRelativeTime(t,e,n){const r=new Date(t);if(Number.isNaN(r.getTime()))return new Intl.RelativeTimeFormat(e,n).format(0,"second");const i=Math.floor((new Date().getTime()-r.getTime())/1e3),o=[{unit:"year",seconds:31536e3},{unit:"month",seconds:2592e3},{unit:"day",seconds:86400},{unit:"hour",seconds:3600},{unit:"minute",seconds:60},{unit:"second",seconds:1}];for(const{unit:l,seconds:c}of o){const u=Math.floor(i/c);if(u>=1)return new Intl.RelativeTimeFormat(e,n).format(-u,l)}return new Intl.RelativeTimeFormat(e,n).format(0,"second")}}const p=/\{(\w+)\}/g;function h(s,t){return t?s.replace(p,(e,n)=>{const r=t[n];return r!==void 0?String(r):`{${n}}`}):s}function x(s){return s==="prefix"||s==="prefix_and_default"}function T(s){return s==="no_prefix"}function v(s){return s==="prefix"}function $(s){return s==="prefix_except_default"}function S(s){return s==="prefix_and_default"}const m=(s,t,e,n,r)=>{const a=r(s,e);if(!a)return null;const i=a.toString().split("|");if(i.length===0)return null;const o=t<i.length?i[t]:i[i.length-1];return o?o.trim().replace("{count}",t.toString()):null};function g(s,t){if(!s||typeof t!="string")return null;if(t in s){const r=s[t];return r}const e=t.split(".");let n=s;for(const r of e)if(n&&typeof n=="object"&&r in n)n=n[r];else return null;return n??null}function d(s){const t=s?.translations??new Map;return{hasCache(e,n){const r=n||"index";return t.has(`${e}:${r}`)},getCache(e,n){const r=n||"index";return t.get(`${e}:${r}`)},setCache(e,n,r){},hasTranslation(e,n){for(const[r,a]of t)if(r.startsWith(`${e}:`)&&g(a,n)!==null)return!0;return!1},hasPageTranslation(e,n){const r=n||"index";return t.has(`${e}:${r}`)},getTranslation(e,n,r){const a=n||"index";return g(t.get(`${e}:${a}`),r)},loadTranslations(e,n,r="index"){const i=`${e}:${r||"index"}`,o=t.get(i)??{};t.set(i,{...o,...n})},setTranslations(e,n,r="index"){const a=r||"index";t.set(`${e}:${a}`,n)},loadPageTranslations(e,n,r){const i=`${e}:${n||"index"}`,o=t.get(i);!o||Object.keys(o).length===0?t.set(i,r):t.set(i,{...o,...r})},mergeTranslation(e,n,r,a=!1){const o=`${e}:${n||"index"}`,l=t.get(o)??{};t.set(o,{...l,...r})},clearCache(){t.clear()}}}class w{constructor(t={}){this.formatter=new f,this.helper=d(t.storage),this.formatter=new f,this.pluralFunc=t.plural||m,this.missingWarn=t.missingWarn??!0,this.missingHandler=t.missingHandler,this.getCustomMissingHandler=t.getCustomMissingHandler}t(t,e,n,r){if(!t)return"";const a=this.getLocale(),i=r||this.getRoute();let o=this.helper.getTranslation(a,i,t);if(!o){const l=this.getFallbackLocale();a!==l&&(o=this.helper.getTranslation(l,i,t))}if(!o){const l=this.getCustomMissingHandler?.();l?l(a,t,i):this.missingHandler?this.missingHandler(a,t,i):this.missingWarn&&process.env.NODE_ENV!=="production"&&typeof window<"u"&&console.warn(`Not found '${t}' key in '${a}' locale messages for route '${i}'.`),o=n===void 0?t:n||t}return typeof o=="string"&&e?h(o,e):o}ts(t,e,n,r){return this.t(t,e,n,r)?.toString()??n??t}tc(t,e,n){const{count:r,...a}=typeof e=="number"?{count:e}:e;if(r===void 0)return n??t;const i=(l,c,u)=>this.t(l,c,u);return this.pluralFunc(t,Number.parseInt(r.toString(),10),a,this.getLocale(),i)??n??t}tn(t,e){return this.formatter.formatNumber(t,this.getLocale(),e)}td(t,e){return this.formatter.formatDate(t,this.getLocale(),e)}tdr(t,e){return this.formatter.formatRelativeTime(t,this.getLocale(),e)}has(t,e){const n=e||this.getRoute(),r=this.getLocale();return!!this.helper.getTranslation(r,n,t)}clearCache(){this.helper.clearCache()}loadTranslationsCore(t,e,n,r="index"){n?this.helper.mergeTranslation(t,r,e,!0):this.helper.setTranslations(t,e,r)}loadRouteTranslationsCore(t,e,n,r){r?this.helper.mergeTranslation(t,e,n,!0):this.helper.loadPageTranslations(t,e,n)}}exports.BaseI18n=w;exports.FormatService=f;exports.defaultPlural=m;exports.interpolate=h;exports.isNoPrefixStrategy=T;exports.isPrefixAndDefaultStrategy=S;exports.isPrefixExceptDefaultStrategy=$;exports.isPrefixStrategy=v;exports.useTranslationHelper=d;exports.withPrefixStrategy=x;
package/dist/index.mjs CHANGED
@@ -1,15 +1,15 @@
1
- class g {
2
- formatNumber(e, t, n) {
3
- return new Intl.NumberFormat(t, n).format(e);
1
+ class f {
2
+ formatNumber(t, e, n) {
3
+ return new Intl.NumberFormat(e, n).format(t);
4
4
  }
5
- formatDate(e, t, n) {
6
- const r = new Date(e);
7
- return Number.isNaN(r.getTime()) ? "Invalid Date" : new Intl.DateTimeFormat(t, n).format(r);
5
+ formatDate(t, e, n) {
6
+ const r = new Date(t);
7
+ return Number.isNaN(r.getTime()) ? "Invalid Date" : new Intl.DateTimeFormat(e, n).format(r);
8
8
  }
9
- formatRelativeTime(e, t, n) {
10
- const r = new Date(e);
9
+ formatRelativeTime(t, e, n) {
10
+ const r = new Date(t);
11
11
  if (Number.isNaN(r.getTime()))
12
- return new Intl.RelativeTimeFormat(t, n).format(0, "second");
12
+ return new Intl.RelativeTimeFormat(e, n).format(0, "second");
13
13
  const i = Math.floor(((/* @__PURE__ */ new Date()).getTime() - r.getTime()) / 1e3), o = [
14
14
  { unit: "year", seconds: 31536e3 },
15
15
  { unit: "month", seconds: 2592e3 },
@@ -21,51 +21,51 @@ class g {
21
21
  for (const { unit: l, seconds: u } of o) {
22
22
  const c = Math.floor(i / u);
23
23
  if (c >= 1)
24
- return new Intl.RelativeTimeFormat(t, n).format(-c, l);
24
+ return new Intl.RelativeTimeFormat(e, n).format(-c, l);
25
25
  }
26
- return new Intl.RelativeTimeFormat(t, n).format(0, "second");
26
+ return new Intl.RelativeTimeFormat(e, n).format(0, "second");
27
27
  }
28
28
  }
29
- const h = /\{(\w+)\}/g;
30
- function m(s, e) {
31
- return e ? s.replace(h, (t, n) => {
32
- const r = e[n];
29
+ const g = /\{(\w+)\}/g;
30
+ function m(s, t) {
31
+ return t ? s.replace(g, (e, n) => {
32
+ const r = t[n];
33
33
  return r !== void 0 ? String(r) : `{${n}}`;
34
34
  }) : s;
35
35
  }
36
- function v(s) {
36
+ function x(s) {
37
37
  return s === "prefix" || s === "prefix_and_default";
38
38
  }
39
39
  function T(s) {
40
40
  return s === "no_prefix";
41
41
  }
42
- function P(s) {
42
+ function $(s) {
43
43
  return s === "prefix";
44
44
  }
45
- function b(s) {
45
+ function v(s) {
46
46
  return s === "prefix_except_default";
47
47
  }
48
- function N(s) {
48
+ function w(s) {
49
49
  return s === "prefix_and_default";
50
50
  }
51
- const d = (s, e, t, n, r) => {
52
- const a = r(s, t);
51
+ const d = (s, t, e, n, r) => {
52
+ const a = r(s, e);
53
53
  if (!a)
54
54
  return null;
55
55
  const i = a.toString().split("|");
56
56
  if (i.length === 0) return null;
57
- const o = e < i.length ? i[e] : i[i.length - 1];
58
- return o ? o.trim().replace("{count}", e.toString()) : null;
57
+ const o = t < i.length ? i[t] : i[i.length - 1];
58
+ return o ? o.trim().replace("{count}", t.toString()) : null;
59
59
  };
60
- function f(s, e) {
61
- if (!s || typeof e != "string") return null;
62
- if (e in s) {
63
- const r = s[e];
60
+ function h(s, t) {
61
+ if (!s || typeof t != "string") return null;
62
+ if (t in s) {
63
+ const r = s[t];
64
64
  return r;
65
65
  }
66
- const t = e.split(".");
66
+ const e = t.split(".");
67
67
  let n = s;
68
- for (const r of t)
68
+ for (const r of e)
69
69
  if (n && typeof n == "object" && r in n)
70
70
  n = n[r];
71
71
  else
@@ -73,131 +73,117 @@ function f(s, e) {
73
73
  return n ?? null;
74
74
  }
75
75
  function p(s) {
76
- const e = s?.translations ?? /* @__PURE__ */ new Map();
76
+ const t = s?.translations ?? /* @__PURE__ */ new Map();
77
77
  return {
78
- hasCache(t, n) {
79
- const r = `${t}:${n}`;
80
- return e.has(r) || e.has(t);
78
+ hasCache(e, n) {
79
+ const r = n || "index";
80
+ return t.has(`${e}:${r}`);
81
81
  },
82
- getCache(t, n) {
83
- const r = `${t}:${n}`;
84
- return e.get(r);
82
+ getCache(e, n) {
83
+ const r = n || "index";
84
+ return t.get(`${e}:${r}`);
85
85
  },
86
- setCache(t, n, r) {
86
+ setCache(e, n, r) {
87
87
  },
88
- hasTranslation(t, n) {
89
- for (const [r, a] of e)
90
- if ((r === t || r.startsWith(`${t}:`)) && f(a, n) !== null)
88
+ hasTranslation(e, n) {
89
+ for (const [r, a] of t)
90
+ if (r.startsWith(`${e}:`) && h(a, n) !== null)
91
91
  return !0;
92
92
  return !1;
93
93
  },
94
- hasGeneralTranslation(t) {
95
- return e.has(t);
96
- },
97
- hasPageTranslation(t, n) {
98
- return e.has(`${t}:${n}`);
94
+ hasPageTranslation(e, n) {
95
+ const r = n || "index";
96
+ return t.has(`${e}:${r}`);
99
97
  },
100
- getTranslation(t, n, r) {
101
- const a = `${t}:${n}`, i = e.get(a), o = f(i, r);
102
- if (o !== null) return o;
103
- const l = e.get(t);
104
- return f(l, r);
98
+ getTranslation(e, n, r) {
99
+ const a = n || "index";
100
+ return h(t.get(`${e}:${a}`), r);
105
101
  },
106
- loadTranslations(t, n) {
107
- const r = e.get(t) ?? {};
108
- e.set(t, { ...r, ...n });
102
+ loadTranslations(e, n, r = "index") {
103
+ const i = `${e}:${r || "index"}`, o = t.get(i) ?? {};
104
+ t.set(i, { ...o, ...n });
109
105
  },
110
- setTranslations(t, n) {
111
- e.set(t, n);
106
+ setTranslations(e, n, r = "index") {
107
+ const a = r || "index";
108
+ t.set(`${e}:${a}`, n);
112
109
  },
113
- loadPageTranslations(t, n, r) {
114
- const a = `${t}:${n}`, i = e.get(a);
115
- !i || Object.keys(i).length === 0 ? e.set(a, r) : e.set(a, { ...i, ...r });
110
+ loadPageTranslations(e, n, r) {
111
+ const i = `${e}:${n || "index"}`, o = t.get(i);
112
+ !o || Object.keys(o).length === 0 ? t.set(i, r) : t.set(i, { ...o, ...r });
116
113
  },
117
- mergeTranslation(t, n, r, a = !1) {
118
- const i = `${t}:${n}`, o = e.get(i) ?? {};
119
- e.set(i, { ...o, ...r });
120
- },
121
- mergeGlobalTranslation(t, n, r = !1) {
122
- const a = e.get(t) ?? {};
123
- e.set(t, { ...a, ...n });
114
+ mergeTranslation(e, n, r, a = !1) {
115
+ const o = `${e}:${n || "index"}`, l = t.get(o) ?? {};
116
+ t.set(o, { ...l, ...r });
124
117
  },
125
118
  clearCache() {
126
- e.clear();
119
+ t.clear();
127
120
  }
128
121
  };
129
122
  }
130
- class $ {
131
- constructor(e = {}) {
132
- this.formatter = new g(), this.helper = p(e.storage), this.formatter = new g(), this.pluralFunc = e.plural || d, this.missingWarn = e.missingWarn ?? !0, this.missingHandler = e.missingHandler, this.getPreviousPageInfo = e.getPreviousPageInfo, this.getCustomMissingHandler = e.getCustomMissingHandler, this.enablePreviousPageFallback = e.enablePreviousPageFallback ?? !1;
123
+ class _ {
124
+ constructor(t = {}) {
125
+ this.formatter = new f(), this.helper = p(t.storage), this.formatter = new f(), this.pluralFunc = t.plural || d, this.missingWarn = t.missingWarn ?? !0, this.missingHandler = t.missingHandler, this.getCustomMissingHandler = t.getCustomMissingHandler;
133
126
  }
134
127
  // --- Public methods (implemented in base class) ---
135
128
  /**
136
129
  * Get translation for a key
137
130
  * Based on logic from src/runtime/plugins/01.plugin.ts
138
131
  */
139
- t(e, t, n, r) {
140
- if (!e) return "";
132
+ t(t, e, n, r) {
133
+ if (!t) return "";
141
134
  const a = this.getLocale(), i = r || this.getRoute();
142
- let o = this.helper.getTranslation(a, i, e);
143
- if (!o && this.enablePreviousPageFallback && this.getPreviousPageInfo) {
144
- const l = this.getPreviousPageInfo();
145
- if (l) {
146
- const u = this.helper.getTranslation(l.locale, l.routeName, e);
147
- u && (o = u, process.env.NODE_ENV !== "production" && console.log(`Using fallback translation from previous route: ${l.routeName} -> ${e}`));
148
- }
149
- }
135
+ let o = this.helper.getTranslation(a, i, t);
150
136
  if (!o) {
151
137
  const l = this.getFallbackLocale();
152
- a !== l && (o = this.helper.getTranslation(l, i, e));
138
+ a !== l && (o = this.helper.getTranslation(l, i, t));
153
139
  }
154
140
  if (!o) {
155
141
  const l = this.getCustomMissingHandler?.();
156
- l ? l(a, e, i) : this.missingHandler ? this.missingHandler(a, e, i) : this.missingWarn && process.env.NODE_ENV !== "production" && typeof window < "u" && console.warn(`Not found '${e}' key in '${a}' locale messages for route '${i}'.`), o = n === void 0 ? e : n || e;
142
+ l ? l(a, t, i) : this.missingHandler ? this.missingHandler(a, t, i) : this.missingWarn && process.env.NODE_ENV !== "production" && typeof window < "u" && console.warn(`Not found '${t}' key in '${a}' locale messages for route '${i}'.`), o = n === void 0 ? t : n || t;
157
143
  }
158
- return typeof o == "string" && t ? m(o, t) : o;
144
+ return typeof o == "string" && e ? m(o, e) : o;
159
145
  }
160
146
  /**
161
147
  * Get translation as string
162
148
  */
163
- ts(e, t, n, r) {
164
- return this.t(e, t, n, r)?.toString() ?? n ?? e;
149
+ ts(t, e, n, r) {
150
+ return this.t(t, e, n, r)?.toString() ?? n ?? t;
165
151
  }
166
152
  /**
167
153
  * Plural translation
168
154
  */
169
- tc(e, t, n) {
170
- const { count: r, ...a } = typeof t == "number" ? { count: t } : t;
155
+ tc(t, e, n) {
156
+ const { count: r, ...a } = typeof e == "number" ? { count: e } : e;
171
157
  if (r === void 0)
172
- return n ?? e;
158
+ return n ?? t;
173
159
  const i = (l, u, c) => this.t(l, u, c);
174
- return this.pluralFunc(e, Number.parseInt(r.toString(), 10), a, this.getLocale(), i) ?? n ?? e;
160
+ return this.pluralFunc(t, Number.parseInt(r.toString(), 10), a, this.getLocale(), i) ?? n ?? t;
175
161
  }
176
162
  /**
177
163
  * Format number
178
164
  */
179
- tn(e, t) {
180
- return this.formatter.formatNumber(e, this.getLocale(), t);
165
+ tn(t, e) {
166
+ return this.formatter.formatNumber(t, this.getLocale(), e);
181
167
  }
182
168
  /**
183
169
  * Format date
184
170
  */
185
- td(e, t) {
186
- return this.formatter.formatDate(e, this.getLocale(), t);
171
+ td(t, e) {
172
+ return this.formatter.formatDate(t, this.getLocale(), e);
187
173
  }
188
174
  /**
189
175
  * Format relative time
190
176
  */
191
- tdr(e, t) {
192
- return this.formatter.formatRelativeTime(e, this.getLocale(), t);
177
+ tdr(t, e) {
178
+ return this.formatter.formatRelativeTime(t, this.getLocale(), e);
193
179
  }
194
180
  /**
195
181
  * Check if translation exists
196
182
  * Based on logic from src/runtime/plugins/01.plugin.ts
197
183
  */
198
- has(e, t) {
199
- const n = t || this.getRoute(), r = this.getLocale();
200
- return !!this.helper.getTranslation(r, n, e);
184
+ has(t, e) {
185
+ const n = e || this.getRoute(), r = this.getLocale();
186
+ return !!this.helper.getTranslation(r, n, t);
201
187
  }
202
188
  /**
203
189
  * Clear cache
@@ -210,26 +196,26 @@ class $ {
210
196
  * Core translation loading logic (without reactivity)
211
197
  * Subclasses can override addTranslations/addRouteTranslations to add reactivity
212
198
  */
213
- loadTranslationsCore(e, t, n) {
214
- n ? this.helper.mergeGlobalTranslation(e, t, !0) : this.helper.setTranslations(e, t);
199
+ loadTranslationsCore(t, e, n, r = "index") {
200
+ n ? this.helper.mergeTranslation(t, r, e, !0) : this.helper.setTranslations(t, e, r);
215
201
  }
216
202
  /**
217
203
  * Core route translation loading logic (without reactivity)
218
204
  * Subclasses can override addRouteTranslations to add reactivity
219
205
  */
220
- loadRouteTranslationsCore(e, t, n, r) {
221
- r ? this.helper.mergeTranslation(e, t, n, !0) : this.helper.loadPageTranslations(e, t, n);
206
+ loadRouteTranslationsCore(t, e, n, r) {
207
+ r ? this.helper.mergeTranslation(t, e, n, !0) : this.helper.loadPageTranslations(t, e, n);
222
208
  }
223
209
  }
224
210
  export {
225
- $ as BaseI18n,
226
- g as FormatService,
211
+ _ as BaseI18n,
212
+ f as FormatService,
227
213
  d as defaultPlural,
228
214
  m as interpolate,
229
215
  T as isNoPrefixStrategy,
230
- N as isPrefixAndDefaultStrategy,
231
- b as isPrefixExceptDefaultStrategy,
232
- P as isPrefixStrategy,
216
+ w as isPrefixAndDefaultStrategy,
217
+ v as isPrefixExceptDefaultStrategy,
218
+ $ as isPrefixStrategy,
233
219
  p as useTranslationHelper,
234
- v as withPrefixStrategy
220
+ x as withPrefixStrategy
235
221
  };
@@ -1,7 +1,7 @@
1
1
  import { Translations } from '@i18n-micro/types';
2
2
  /**
3
3
  * Bare Metal: Simple translation storage without Ref, useState, devalue.
4
- * Map key: locale (general) or locale:routeName (page-specific).
4
+ * Map key: `${locale}:${routeName}` (page-specific).
5
5
  */
6
6
  export interface TranslationStorage {
7
7
  translations: Map<string, Translations>;
@@ -11,13 +11,11 @@ export declare function useTranslationHelper(storage?: TranslationStorage): {
11
11
  getCache(locale: string, routeName: string): Translations | undefined;
12
12
  setCache(_locale: string, _routeName: string, _cache: Map<string, unknown>): void;
13
13
  hasTranslation(locale: string, key: string): boolean;
14
- hasGeneralTranslation(locale: string): boolean;
15
14
  hasPageTranslation(locale: string, routeName: string): boolean;
16
15
  getTranslation<T = unknown>(locale: string, routeName: string, key: string): T | null;
17
- loadTranslations(locale: string, data: Translations): void;
18
- setTranslations(locale: string, data: Translations): void;
16
+ loadTranslations(locale: string, data: Translations, routeName?: string): void;
17
+ setTranslations(locale: string, data: Translations, routeName?: string): void;
19
18
  loadPageTranslations(locale: string, routeName: string, data: Translations): void;
20
19
  mergeTranslation(locale: string, routeName: string, newTranslations: Translations, _force?: boolean): void;
21
- mergeGlobalTranslation(locale: string, newTranslations: Translations, _force?: boolean): void;
22
20
  clearCache(): void;
23
21
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@i18n-micro/core",
3
- "version": "1.1.3",
3
+ "version": "1.2.0",
4
4
  "description": "",
5
5
  "repository": "s00d/nuxt-i18n-micro",
6
6
  "license": "MIT",
@@ -22,7 +22,7 @@
22
22
  },
23
23
  "keywords": [],
24
24
  "dependencies": {
25
- "@i18n-micro/types": "1.1.3"
25
+ "@i18n-micro/types": "1.1.5"
26
26
  },
27
27
  "devDependencies": {
28
28
  "vite": "^7.3.1",
package/src/base.ts CHANGED
@@ -8,10 +8,7 @@ export interface BaseI18nOptions {
8
8
  plural?: PluralFunc
9
9
  missingWarn?: boolean
10
10
  missingHandler?: (locale: string, key: string, routeName: string) => void
11
- // Optional hooks for Nuxt runtime specific features
12
- getPreviousPageInfo?: () => { locale: string; routeName: string } | null
13
11
  getCustomMissingHandler?: () => MissingHandler | null
14
- enablePreviousPageFallback?: boolean
15
12
  }
16
13
 
17
14
  /**
@@ -27,10 +24,7 @@ export abstract class BaseI18n {
27
24
  public pluralFunc: PluralFunc
28
25
  public missingWarn: boolean
29
26
  public missingHandler?: (locale: string, key: string, routeName: string) => void
30
- // Optional hooks for Nuxt runtime specific features
31
- public getPreviousPageInfo?: () => { locale: string; routeName: string } | null
32
27
  public getCustomMissingHandler?: () => MissingHandler | null
33
- public enablePreviousPageFallback: boolean
34
28
 
35
29
  constructor(options: BaseI18nOptions = {}) {
36
30
  this.helper = useTranslationHelper(options.storage)
@@ -38,9 +32,7 @@ export abstract class BaseI18n {
38
32
  this.pluralFunc = options.plural || defaultPlural
39
33
  this.missingWarn = options.missingWarn ?? true
40
34
  this.missingHandler = options.missingHandler
41
- this.getPreviousPageInfo = options.getPreviousPageInfo
42
35
  this.getCustomMissingHandler = options.getCustomMissingHandler
43
- this.enablePreviousPageFallback = options.enablePreviousPageFallback ?? false
44
36
  }
45
37
 
46
38
  // --- Abstract methods (must be implemented by subclasses) ---
@@ -74,24 +66,9 @@ export abstract class BaseI18n {
74
66
  const route = routeName || this.getRoute()
75
67
 
76
68
  // 1. Try to find translation in current locale
77
- // Note: In Nuxt runtime, server already merges global translations, so we don't need explicit fallback
78
69
  let value = this.helper.getTranslation<string>(locale, route, key)
79
70
 
80
- // 2. If translation not found and there are saved previous translations, use them (only if enabled)
81
- if (!value && this.enablePreviousPageFallback && this.getPreviousPageInfo) {
82
- const prev = this.getPreviousPageInfo()
83
- if (prev) {
84
- const prevValue = this.helper.getTranslation<string>(prev.locale, prev.routeName, key)
85
- if (prevValue) {
86
- value = prevValue
87
- if (process.env.NODE_ENV !== 'production') {
88
- console.log(`Using fallback translation from previous route: ${prev.routeName} -> ${key}`)
89
- }
90
- }
91
- }
92
- }
93
-
94
- // 3. Fallback to fallbackLocale if not found and different (for non-Nuxt adapters)
71
+ // 2. Fallback to fallbackLocale if not found and different
95
72
  if (!value) {
96
73
  const fallbackLocale = this.getFallbackLocale()
97
74
  if (locale !== fallbackLocale) {
@@ -99,7 +76,7 @@ export abstract class BaseI18n {
99
76
  }
100
77
  }
101
78
 
102
- // 4. Handle missing
79
+ // 3. Handle missing
103
80
  if (!value) {
104
81
  // Call custom handler if set (Nuxt runtime), otherwise use instance handler
105
82
  const customHandler = this.getCustomMissingHandler?.()
@@ -117,7 +94,7 @@ export abstract class BaseI18n {
117
94
  value = defaultValue === undefined ? key : defaultValue || key
118
95
  }
119
96
 
120
- // 5. Interpolate
97
+ // 4. Interpolate
121
98
  return typeof value === 'string' && params ? interpolate(value, params) : (value as CleanTranslation)
122
99
  }
123
100
 
@@ -195,11 +172,11 @@ export abstract class BaseI18n {
195
172
  * Core translation loading logic (without reactivity)
196
173
  * Subclasses can override addTranslations/addRouteTranslations to add reactivity
197
174
  */
198
- public loadTranslationsCore(locale: string, translations: Translations, merge: boolean): void {
175
+ public loadTranslationsCore(locale: string, translations: Translations, merge: boolean, routeName = 'index'): void {
199
176
  if (merge) {
200
- this.helper.mergeGlobalTranslation(locale, translations, true)
177
+ this.helper.mergeTranslation(locale, routeName, translations, true)
201
178
  } else {
202
- this.helper.setTranslations(locale, translations)
179
+ this.helper.setTranslations(locale, translations, routeName)
203
180
  }
204
181
  }
205
182
 
@@ -2,7 +2,7 @@ import type { Translations } from '@i18n-micro/types'
2
2
 
3
3
  /**
4
4
  * Bare Metal: Simple translation storage without Ref, useState, devalue.
5
- * Map key: locale (general) or locale:routeName (page-specific).
5
+ * Map key: `${locale}:${routeName}` (page-specific).
6
6
  */
7
7
  export interface TranslationStorage {
8
8
  translations: Map<string, Translations>
@@ -36,52 +36,46 @@ export function useTranslationHelper(storage?: TranslationStorage) {
36
36
 
37
37
  return {
38
38
  hasCache(locale: string, page: string) {
39
- const cacheKey = `${locale}:${page}`
40
- return translations.has(cacheKey) || translations.has(locale)
39
+ const p = page || 'index'
40
+ return translations.has(`${locale}:${p}`)
41
41
  },
42
42
  getCache(locale: string, routeName: string) {
43
- const cacheKey = `${locale}:${routeName}`
44
- return translations.get(cacheKey)
43
+ const rn = routeName || 'index'
44
+ return translations.get(`${locale}:${rn}`)
45
45
  },
46
46
  setCache(_locale: string, _routeName: string, _cache: Map<string, unknown>) {
47
47
  // No-op for bare metal
48
48
  },
49
49
  hasTranslation(locale: string, key: string): boolean {
50
50
  for (const [k, v] of translations) {
51
- if ((k === locale || k.startsWith(`${locale}:`)) && findValue(v, key) !== null) {
51
+ if (k.startsWith(`${locale}:`) && findValue(v, key) !== null) {
52
52
  return true
53
53
  }
54
54
  }
55
55
  return false
56
56
  },
57
- hasGeneralTranslation(locale: string): boolean {
58
- return translations.has(locale)
59
- },
60
57
  hasPageTranslation(locale: string, routeName: string): boolean {
61
- return translations.has(`${locale}:${routeName}`)
58
+ const rn = routeName || 'index'
59
+ return translations.has(`${locale}:${rn}`)
62
60
  },
63
61
  getTranslation<T = unknown>(locale: string, routeName: string, key: string): T | null {
64
- const routeKey = `${locale}:${routeName}`
65
- const routeData = translations.get(routeKey)
66
- const val = findValue<T>(routeData, key)
67
- if (val !== null) return val
68
-
69
- const generalData = translations.get(locale)
70
- return findValue<T>(generalData, key)
62
+ const rn = routeName || 'index'
63
+ return findValue<T>(translations.get(`${locale}:${rn}`), key)
71
64
  },
72
- loadTranslations(locale: string, data: Translations): void {
73
- // Merge with existing, replacing duplicate keys
74
- const existing = translations.get(locale) ?? {}
75
- translations.set(locale, { ...existing, ...data })
65
+ loadTranslations(locale: string, data: Translations, routeName = 'index'): void {
66
+ const rn = routeName || 'index'
67
+ const key = `${locale}:${rn}`
68
+ const existing = translations.get(key) ?? {}
69
+ translations.set(key, { ...existing, ...data })
76
70
  },
77
- setTranslations(locale: string, data: Translations): void {
78
- // Replace all translations for locale (no merge)
79
- translations.set(locale, data)
71
+ setTranslations(locale: string, data: Translations, routeName = 'index'): void {
72
+ const rn = routeName || 'index'
73
+ translations.set(`${locale}:${rn}`, data)
80
74
  },
81
75
  loadPageTranslations(locale: string, routeName: string, data: Translations): void {
82
- const key = `${locale}:${routeName}`
76
+ const rn = routeName || 'index'
77
+ const key = `${locale}:${rn}`
83
78
  const existing = translations.get(key)
84
- // Perf: when existing is empty — keep reference, avoid O(n) copying of large objects
85
79
  if (!existing || Object.keys(existing).length === 0) {
86
80
  translations.set(key, data)
87
81
  } else {
@@ -89,14 +83,11 @@ export function useTranslationHelper(storage?: TranslationStorage) {
89
83
  }
90
84
  },
91
85
  mergeTranslation(locale: string, routeName: string, newTranslations: Translations, _force = false): void {
92
- const key = `${locale}:${routeName}`
86
+ const rn = routeName || 'index'
87
+ const key = `${locale}:${rn}`
93
88
  const existing = translations.get(key) ?? {}
94
89
  translations.set(key, { ...existing, ...newTranslations })
95
90
  },
96
- mergeGlobalTranslation(locale: string, newTranslations: Translations, _force = false): void {
97
- const existing = translations.get(locale) ?? {}
98
- translations.set(locale, { ...existing, ...newTranslations })
99
- },
100
91
  clearCache(): void {
101
92
  translations.clear()
102
93
  },
@@ -38,44 +38,44 @@ class TestI18n extends BaseI18n {
38
38
  describe('BaseI18n', () => {
39
39
  describe('Constructor', () => {
40
40
  test('should initialize with default options', () => {
41
- const i18n = new TestI18n('en', 'en', 'general')
41
+ const i18n = new TestI18n('en', 'en', 'index')
42
42
  expect(i18n.getLocale()).toBe('en')
43
43
  expect(i18n.getFallbackLocale()).toBe('en')
44
- expect(i18n.getRoute()).toBe('general')
44
+ expect(i18n.getRoute()).toBe('index')
45
45
  })
46
46
 
47
47
  test('should initialize with custom storage', () => {
48
48
  const storage = { translations: new Map<string, Translations>() }
49
- const i18n = new TestI18n('en', 'en', 'general', { storage })
49
+ const i18n = new TestI18n('en', 'en', 'index', { storage })
50
50
  expect(i18n).toBeDefined()
51
51
  })
52
52
 
53
53
  test('should initialize with custom plural function', () => {
54
54
  const customPlural: PluralFunc = () => 'custom'
55
- const i18n = new TestI18n('en', 'en', 'general', { plural: customPlural })
55
+ const i18n = new TestI18n('en', 'en', 'index', { plural: customPlural })
56
56
  expect(i18n).toBeDefined()
57
57
  })
58
58
 
59
59
  test('should initialize with missingWarn option', () => {
60
- const i18n = new TestI18n('en', 'en', 'general', { missingWarn: false })
60
+ const i18n = new TestI18n('en', 'en', 'index', { missingWarn: false })
61
61
  expect(i18n).toBeDefined()
62
62
  })
63
63
 
64
64
  test('should initialize with missingHandler', () => {
65
65
  const handler = jest.fn()
66
- const i18n = new TestI18n('en', 'en', 'general', { missingHandler: handler })
66
+ const i18n = new TestI18n('en', 'en', 'index', { missingHandler: handler })
67
67
  expect(i18n).toBeDefined()
68
68
  })
69
69
  })
70
70
 
71
71
  describe('t() method', () => {
72
72
  test('should return empty string for empty key', () => {
73
- const i18n = new TestI18n('en', 'en', 'general')
73
+ const i18n = new TestI18n('en', 'en', 'index')
74
74
  expect(i18n.t('')).toBe('')
75
75
  })
76
76
 
77
77
  test('should return translation for existing key', async () => {
78
- const i18n = new TestI18n('en', 'en', 'general')
78
+ const i18n = new TestI18n('en', 'en', 'index')
79
79
  const translations: Translations = { greeting: 'Hello' }
80
80
  i18n['helper'].loadTranslations('en', translations)
81
81
 
@@ -84,7 +84,7 @@ describe('BaseI18n', () => {
84
84
 
85
85
  test('should interpolate params in translation', async () => {
86
86
  const storage = { translations: new Map<string, Translations>() }
87
- const i18n = new TestI18n('en', 'en', 'general', { storage })
87
+ const i18n = new TestI18n('en', 'en', 'index', { storage })
88
88
  const translations: Translations = { greeting: 'Hello, {name}!' }
89
89
  await i18n['helper'].loadTranslations('en', translations)
90
90
 
@@ -92,18 +92,18 @@ describe('BaseI18n', () => {
92
92
  })
93
93
 
94
94
  test('should use defaultValue when translation is missing', () => {
95
- const i18n = new TestI18n('en', 'en', 'general')
95
+ const i18n = new TestI18n('en', 'en', 'index')
96
96
  expect(i18n.t('missing.key', undefined, 'Default value')).toBe('Default value')
97
97
  })
98
98
 
99
99
  test('should return key when translation is missing and no defaultValue', () => {
100
- const i18n = new TestI18n('en', 'en', 'general')
100
+ const i18n = new TestI18n('en', 'en', 'index')
101
101
  expect(i18n.t('missing.key')).toBe('missing.key')
102
102
  })
103
103
 
104
104
  test('should fallback to fallbackLocale when translation is missing', async () => {
105
105
  const storage = { translations: new Map<string, Translations>() }
106
- const i18n = new TestI18n('en', 'fr', 'general', { storage })
106
+ const i18n = new TestI18n('en', 'fr', 'index', { storage })
107
107
  const translations: Translations = { greeting: 'Bonjour' }
108
108
  await i18n['helper'].loadTranslations('fr', translations)
109
109
 
@@ -111,53 +111,36 @@ describe('BaseI18n', () => {
111
111
  })
112
112
 
113
113
  test('should use route-specific translation when routeName is provided', async () => {
114
- const i18n = new TestI18n('en', 'en', 'general')
114
+ const i18n = new TestI18n('en', 'en', 'index')
115
115
  const routeTranslations: Translations = { title: 'Route Title' }
116
116
  await i18n['helper'].loadPageTranslations('en', 'about', routeTranslations)
117
117
 
118
118
  expect(i18n.t('title', undefined, undefined, 'about')).toBe('Route Title')
119
119
  })
120
120
 
121
- test('should use previousPageInfo fallback when enabled', async () => {
122
- const storage = { translations: new Map<string, Translations>() }
123
- const prevInfo = { locale: 'fr', routeName: 'previous' }
124
- const i18n = new TestI18n('en', 'en', 'general', {
125
- storage,
126
- getPreviousPageInfo: () => prevInfo,
127
- enablePreviousPageFallback: true,
128
- })
129
-
130
- const translations: Translations = { greeting: 'Bonjour' }
131
- await i18n['helper'].loadPageTranslations('fr', 'previous', translations)
132
-
133
- // Current locale is 'en', route is 'general', translation not found
134
- // Should fallback to previous page info: locale 'fr', route 'previous'
135
- expect(i18n.t('greeting')).toBe('Bonjour')
136
- })
137
-
138
121
  test('should call missingHandler when translation is missing', () => {
139
122
  const handler = jest.fn()
140
- const i18n = new TestI18n('en', 'en', 'general', { missingHandler: handler })
123
+ const i18n = new TestI18n('en', 'en', 'index', { missingHandler: handler })
141
124
 
142
125
  i18n.t('missing.key')
143
126
 
144
- expect(handler).toHaveBeenCalledWith('en', 'missing.key', 'general')
127
+ expect(handler).toHaveBeenCalledWith('en', 'missing.key', 'index')
145
128
  })
146
129
 
147
130
  test('should call customMissingHandler when set (Nuxt runtime)', () => {
148
131
  const customHandler = jest.fn()
149
- const i18n = new TestI18n('en', 'en', 'general', {
132
+ const i18n = new TestI18n('en', 'en', 'index', {
150
133
  getCustomMissingHandler: () => customHandler,
151
134
  })
152
135
 
153
136
  i18n.t('missing.key')
154
137
 
155
- expect(customHandler).toHaveBeenCalledWith('en', 'missing.key', 'general')
138
+ expect(customHandler).toHaveBeenCalledWith('en', 'missing.key', 'index')
156
139
  })
157
140
 
158
141
  test('should not warn when missingWarn is false', () => {
159
142
  const consoleSpy = jest.spyOn(console, 'warn').mockImplementation()
160
- const i18n = new TestI18n('en', 'en', 'general', { missingWarn: false })
143
+ const i18n = new TestI18n('en', 'en', 'index', { missingWarn: false })
161
144
 
162
145
  i18n.t('missing.key')
163
146
 
@@ -168,7 +151,7 @@ describe('BaseI18n', () => {
168
151
 
169
152
  describe('ts() method', () => {
170
153
  test('should return translation as string', async () => {
171
- const i18n = new TestI18n('en', 'en', 'general')
154
+ const i18n = new TestI18n('en', 'en', 'index')
172
155
  const translations: Translations = { greeting: 'Hello' }
173
156
  i18n['helper'].loadTranslations('en', translations)
174
157
 
@@ -176,17 +159,17 @@ describe('BaseI18n', () => {
176
159
  })
177
160
 
178
161
  test('should return defaultValue when translation is missing', () => {
179
- const i18n = new TestI18n('en', 'en', 'general')
162
+ const i18n = new TestI18n('en', 'en', 'index')
180
163
  expect(i18n.ts('missing.key', undefined, 'Default')).toBe('Default')
181
164
  })
182
165
 
183
166
  test('should return key when translation is missing and no defaultValue', () => {
184
- const i18n = new TestI18n('en', 'en', 'general')
167
+ const i18n = new TestI18n('en', 'en', 'index')
185
168
  expect(i18n.ts('missing.key')).toBe('missing.key')
186
169
  })
187
170
 
188
171
  test('should convert non-string values to string', async () => {
189
- const i18n = new TestI18n('en', 'en', 'general')
172
+ const i18n = new TestI18n('en', 'en', 'index')
190
173
  const translations: Translations = { count: 42 }
191
174
  i18n['helper'].loadTranslations('en', translations)
192
175
 
@@ -196,12 +179,12 @@ describe('BaseI18n', () => {
196
179
 
197
180
  describe('tc() method', () => {
198
181
  test('should return defaultValue when count is undefined', () => {
199
- const i18n = new TestI18n('en', 'en', 'general')
182
+ const i18n = new TestI18n('en', 'en', 'index')
200
183
  expect(i18n.tc('apples', { other: 'params' }, 'No count')).toBe('No count')
201
184
  })
202
185
 
203
186
  test('should use plural function with count', async () => {
204
- const i18n = new TestI18n('en', 'en', 'general')
187
+ const i18n = new TestI18n('en', 'en', 'index')
205
188
  const translations: Translations = { apples: 'apple|apples' }
206
189
  await i18n['helper'].loadTranslations('en', translations)
207
190
 
@@ -213,7 +196,7 @@ describe('BaseI18n', () => {
213
196
  })
214
197
 
215
198
  test('should handle count as number', async () => {
216
- const i18n = new TestI18n('en', 'en', 'general')
199
+ const i18n = new TestI18n('en', 'en', 'index')
217
200
  const translations: Translations = { apples: 'apple|apples' }
218
201
  i18n['helper'].loadTranslations('en', translations)
219
202
 
@@ -221,7 +204,7 @@ describe('BaseI18n', () => {
221
204
  })
222
205
 
223
206
  test('should handle count as Params object', async () => {
224
- const i18n = new TestI18n('en', 'en', 'general')
207
+ const i18n = new TestI18n('en', 'en', 'index')
225
208
  const translations: Translations = { apples: 'apple|apples' }
226
209
  i18n['helper'].loadTranslations('en', translations)
227
210
 
@@ -229,7 +212,7 @@ describe('BaseI18n', () => {
229
212
  })
230
213
 
231
214
  test('should return defaultValue when plural function returns null', () => {
232
- const i18n = new TestI18n('en', 'en', 'general')
215
+ const i18n = new TestI18n('en', 'en', 'index')
233
216
  // When translation is missing, t() returns key, which is passed to pluralFunc
234
217
  // pluralFunc tries to process 'missing.key' as translation, but since it doesn't contain '|',
235
218
  // it returns the key itself. So tc returns the key, not defaultValue.
@@ -241,19 +224,19 @@ describe('BaseI18n', () => {
241
224
 
242
225
  describe('tn() method', () => {
243
226
  test('should format number with default locale', () => {
244
- const i18n = new TestI18n('en', 'en', 'general')
227
+ const i18n = new TestI18n('en', 'en', 'index')
245
228
  const result = i18n.tn(1234.56)
246
229
  expect(result).toMatch(/1[,.]234[.,]56/)
247
230
  })
248
231
 
249
232
  test('should format number with custom options', () => {
250
- const i18n = new TestI18n('en', 'en', 'general')
233
+ const i18n = new TestI18n('en', 'en', 'index')
251
234
  const result = i18n.tn(1234.56, { style: 'currency', currency: 'USD' })
252
235
  expect(result).toContain('1,234.56')
253
236
  })
254
237
 
255
238
  test('should use current locale for formatting', () => {
256
- const i18n = new TestI18n('ru', 'en', 'general')
239
+ const i18n = new TestI18n('ru', 'en', 'index')
257
240
  const result = i18n.tn(1234.56)
258
241
  // Russian locale uses different number formatting
259
242
  expect(result).toBeDefined()
@@ -262,7 +245,7 @@ describe('BaseI18n', () => {
262
245
 
263
246
  describe('td() method', () => {
264
247
  test('should format date with default locale', () => {
265
- const i18n = new TestI18n('en', 'en', 'general')
248
+ const i18n = new TestI18n('en', 'en', 'index')
266
249
  const date = new Date('2024-01-15')
267
250
  const result = i18n.td(date)
268
251
  expect(result).toBeDefined()
@@ -270,7 +253,7 @@ describe('BaseI18n', () => {
270
253
  })
271
254
 
272
255
  test('should format date with custom options', () => {
273
- const i18n = new TestI18n('en', 'en', 'general')
256
+ const i18n = new TestI18n('en', 'en', 'index')
274
257
  const date = new Date('2024-01-15')
275
258
  const result = i18n.td(date, { year: 'numeric', month: 'long', day: 'numeric' })
276
259
  expect(result).toContain('2024')
@@ -278,7 +261,7 @@ describe('BaseI18n', () => {
278
261
  })
279
262
 
280
263
  test('should handle date as number (timestamp)', () => {
281
- const i18n = new TestI18n('en', 'en', 'general')
264
+ const i18n = new TestI18n('en', 'en', 'index')
282
265
  const timestamp = new Date('2024-01-15').getTime()
283
266
  const result = i18n.td(timestamp)
284
267
  expect(result).toBeDefined()
@@ -286,7 +269,7 @@ describe('BaseI18n', () => {
286
269
  })
287
270
 
288
271
  test('should handle date as string', () => {
289
- const i18n = new TestI18n('en', 'en', 'general')
272
+ const i18n = new TestI18n('en', 'en', 'index')
290
273
  const result = i18n.td('2024-01-15')
291
274
  expect(result).toBeDefined()
292
275
  expect(result).not.toBe('Invalid Date')
@@ -295,7 +278,7 @@ describe('BaseI18n', () => {
295
278
 
296
279
  describe('tdr() method', () => {
297
280
  test('should format relative time', () => {
298
- const i18n = new TestI18n('en', 'en', 'general')
281
+ const i18n = new TestI18n('en', 'en', 'index')
299
282
  const yesterday = new Date(Date.now() - 86400000)
300
283
  const result = i18n.tdr(yesterday)
301
284
  expect(result).toBeDefined()
@@ -303,14 +286,14 @@ describe('BaseI18n', () => {
303
286
  })
304
287
 
305
288
  test('should format relative time with custom options', () => {
306
- const i18n = new TestI18n('en', 'en', 'general')
289
+ const i18n = new TestI18n('en', 'en', 'index')
307
290
  const yesterday = new Date(Date.now() - 86400000)
308
291
  const result = i18n.tdr(yesterday, { numeric: 'always' })
309
292
  expect(result).toBeDefined()
310
293
  })
311
294
 
312
295
  test('should handle invalid date gracefully', () => {
313
- const i18n = new TestI18n('en', 'en', 'general')
296
+ const i18n = new TestI18n('en', 'en', 'index')
314
297
  const invalidDate = new Date('invalid')
315
298
  const result = i18n.tdr(invalidDate)
316
299
  expect(result).toBeDefined()
@@ -319,7 +302,7 @@ describe('BaseI18n', () => {
319
302
 
320
303
  describe('has() method', () => {
321
304
  test('should return true when translation exists', async () => {
322
- const i18n = new TestI18n('en', 'en', 'general')
305
+ const i18n = new TestI18n('en', 'en', 'index')
323
306
  const translations: Translations = { greeting: 'Hello' }
324
307
  i18n['helper'].loadTranslations('en', translations)
325
308
 
@@ -327,17 +310,17 @@ describe('BaseI18n', () => {
327
310
  })
328
311
 
329
312
  test('should return false when translation does not exist', () => {
330
- const i18n = new TestI18n('en', 'en', 'general')
313
+ const i18n = new TestI18n('en', 'en', 'index')
331
314
  expect(i18n.has('missing.key')).toBe(false)
332
315
  })
333
316
 
334
317
  test('should check route-specific translation when routeName is provided', async () => {
335
- const i18n = new TestI18n('en', 'en', 'general')
318
+ const i18n = new TestI18n('en', 'en', 'index')
336
319
  const routeTranslations: Translations = { title: 'Route Title' }
337
320
  await i18n['helper'].loadPageTranslations('en', 'about', routeTranslations)
338
321
 
339
322
  expect(i18n.has('title', 'about')).toBe(true)
340
- expect(i18n.has('title', 'general')).toBe(false)
323
+ expect(i18n.has('title', 'index')).toBe(false)
341
324
  })
342
325
 
343
326
  test('should use current route when routeName is not provided', async () => {
@@ -351,7 +334,7 @@ describe('BaseI18n', () => {
351
334
 
352
335
  describe('clearCache() method', () => {
353
336
  test('should clear all translations from cache', async () => {
354
- const i18n = new TestI18n('en', 'en', 'general')
337
+ const i18n = new TestI18n('en', 'en', 'index')
355
338
  const translations: Translations = { greeting: 'Hello' }
356
339
  i18n['helper'].loadTranslations('en', translations)
357
340
 
@@ -365,7 +348,7 @@ describe('BaseI18n', () => {
365
348
 
366
349
  describe('loadTranslationsCore() method', () => {
367
350
  test('should load translations when merge is false', async () => {
368
- const i18n = new TestI18n('en', 'en', 'general')
351
+ const i18n = new TestI18n('en', 'en', 'index')
369
352
  const translations: Translations = { greeting: 'Hello' }
370
353
 
371
354
  i18n['loadTranslationsCore']('en', translations, false)
@@ -376,7 +359,7 @@ describe('BaseI18n', () => {
376
359
  })
377
360
 
378
361
  test('should merge translations when merge is true', async () => {
379
- const i18n = new TestI18n('en', 'en', 'general')
362
+ const i18n = new TestI18n('en', 'en', 'index')
380
363
  const initial: Translations = { greeting: 'Hello' }
381
364
  const additional: Translations = { farewell: 'Goodbye' }
382
365
 
@@ -392,7 +375,7 @@ describe('BaseI18n', () => {
392
375
 
393
376
  describe('loadRouteTranslationsCore() method', () => {
394
377
  test('should load route translations when merge is false', async () => {
395
- const i18n = new TestI18n('en', 'en', 'general')
378
+ const i18n = new TestI18n('en', 'en', 'index')
396
379
  const translations: Translations = { title: 'Page Title' }
397
380
 
398
381
  i18n['loadRouteTranslationsCore']('en', 'about', translations, false)
@@ -403,7 +386,7 @@ describe('BaseI18n', () => {
403
386
  })
404
387
 
405
388
  test('should merge route translations when merge is true', async () => {
406
- const i18n = new TestI18n('en', 'en', 'general')
389
+ const i18n = new TestI18n('en', 'en', 'index')
407
390
  const initial: Translations = { title: 'Page Title' }
408
391
  const additional: Translations = { description: 'Page Description' }
409
392
 
@@ -419,7 +402,7 @@ describe('BaseI18n', () => {
419
402
 
420
403
  describe('Edge cases', () => {
421
404
  test('should handle nested translation keys', async () => {
422
- const i18n = new TestI18n('en', 'en', 'general')
405
+ const i18n = new TestI18n('en', 'en', 'index')
423
406
  const translations: Translations = {
424
407
  nested: {
425
408
  deep: {
@@ -433,7 +416,7 @@ describe('BaseI18n', () => {
433
416
  })
434
417
 
435
418
  test('should handle multiple params in interpolation', async () => {
436
- const i18n = new TestI18n('en', 'en', 'general')
419
+ const i18n = new TestI18n('en', 'en', 'index')
437
420
  const translations: Translations = {
438
421
  message: 'Hello, {name}! You are {age} years old.',
439
422
  }
@@ -443,23 +426,25 @@ describe('BaseI18n', () => {
443
426
  })
444
427
 
445
428
  test('should handle null defaultValue', () => {
446
- const i18n = new TestI18n('en', 'en', 'general')
429
+ const i18n = new TestI18n('en', 'en', 'index')
447
430
  expect(i18n.t('missing.key', undefined, null)).toBe('missing.key')
448
431
  })
449
432
 
450
433
  test('should handle empty string defaultValue', () => {
451
- const i18n = new TestI18n('en', 'en', 'general')
434
+ const i18n = new TestI18n('en', 'en', 'index')
452
435
  // Empty string is falsy, so it will fall back to key (as per defaultValue || key logic)
453
436
  expect(i18n.t('missing.key', undefined, '')).toBe('missing.key')
454
437
  })
455
438
 
456
439
  test('should handle route change', async () => {
457
440
  const storage = { translations: new Map<string, Translations>() }
458
- const i18n = new TestI18n('en', 'en', 'general', { storage })
459
- const generalTranslations: Translations = { greeting: 'Hello' }
460
- const routeTranslations: Translations = { title: 'About Page' }
441
+ const i18n = new TestI18n('en', 'en', 'index', { storage })
442
+ const rootTranslations: Translations = { greeting: 'Hello' }
443
+ // Packages (vue, node, etc.) merge root into pages automatically.
444
+ // Core does not — so we simulate pre-merged data here.
445
+ const routeTranslations: Translations = { greeting: 'Hello', title: 'About Page' }
461
446
 
462
- await i18n['helper'].loadTranslations('en', generalTranslations)
447
+ await i18n['helper'].loadTranslations('en', rootTranslations)
463
448
  await i18n['helper'].loadPageTranslations('en', 'about', routeTranslations)
464
449
 
465
450
  // Verify translations are loaded
@@ -467,14 +452,13 @@ describe('BaseI18n', () => {
467
452
  expect(i18n.has('title', 'about')).toBe(true)
468
453
 
469
454
  // Check route-specific translation with explicit routeName
470
- // Note: getTranslation uses routeName parameter, so this should work
471
455
  const titleValue = i18n.t('title', undefined, undefined, 'about')
472
456
  expect(titleValue).toBe('About Page')
473
457
 
474
458
  // Change route and check
475
459
  i18n.setRoute('about')
476
460
  expect(i18n.t('title')).toBe('About Page')
477
- expect(i18n.t('greeting')).toBe('Hello') // Should still work from general
461
+ expect(i18n.t('greeting')).toBe('Hello')
478
462
  })
479
463
  })
480
464
  })
@@ -63,12 +63,12 @@ describe('Translation Helper', () => {
63
63
  expect(helper.getTranslation('en', 'home', 'newKey')).toBe('New value')
64
64
  })
65
65
 
66
- test('mergeGlobalTranslation updates general translations', () => {
66
+ test('mergeTranslation with index routeName updates index translations', () => {
67
67
  const helper = useTranslationHelper()
68
68
  helper.loadTranslations('en', translations.en)
69
69
 
70
- helper.mergeGlobalTranslation('en', { newGlobalKey: 'Global value' })
71
- expect(helper.getTranslation('en', 'index', 'newGlobalKey')).toBe('Global value')
70
+ helper.mergeTranslation('en', 'index', { newKey: 'New value' })
71
+ expect(helper.getTranslation('en', 'index', 'newKey')).toBe('New value')
72
72
  })
73
73
 
74
74
  test('deepClone creates a deep copy of objects', () => {