@i18n-micro/core 1.1.4 → 1.3.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 +0 -4
- package/dist/base.d.ts +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +40 -47
- package/dist/translation.d.ts +3 -5
- package/package.json +2 -2
- package/src/base.ts +4 -5
- package/src/translation.ts +22 -31
- package/tests/base.test.ts +57 -56
- package/tests/core.test.ts +3 -3
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
|
@@ -72,7 +72,7 @@ export declare abstract class BaseI18n {
|
|
|
72
72
|
* Core translation loading logic (without reactivity)
|
|
73
73
|
* Subclasses can override addTranslations/addRouteTranslations to add reactivity
|
|
74
74
|
*/
|
|
75
|
-
loadTranslationsCore(locale: string, translations: Translations, merge: boolean): void;
|
|
75
|
+
loadTranslationsCore(locale: string, translations: Translations, merge: boolean, routeName?: string): void;
|
|
76
76
|
/**
|
|
77
77
|
* Core route translation loading logic (without reactivity)
|
|
78
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
|
|
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,4 +1,4 @@
|
|
|
1
|
-
class
|
|
1
|
+
class f {
|
|
2
2
|
formatNumber(t, e, n) {
|
|
3
3
|
return new Intl.NumberFormat(e, n).format(t);
|
|
4
4
|
}
|
|
@@ -18,10 +18,10 @@ class h {
|
|
|
18
18
|
{ unit: "minute", seconds: 60 },
|
|
19
19
|
{ unit: "second", seconds: 1 }
|
|
20
20
|
];
|
|
21
|
-
for (const { unit: l, seconds:
|
|
22
|
-
const
|
|
23
|
-
if (
|
|
24
|
-
return new Intl.RelativeTimeFormat(e, n).format(-
|
|
21
|
+
for (const { unit: l, seconds: u } of o) {
|
|
22
|
+
const c = Math.floor(i / u);
|
|
23
|
+
if (c >= 1)
|
|
24
|
+
return new Intl.RelativeTimeFormat(e, n).format(-c, l);
|
|
25
25
|
}
|
|
26
26
|
return new Intl.RelativeTimeFormat(e, n).format(0, "second");
|
|
27
27
|
}
|
|
@@ -33,19 +33,19 @@ function m(s, t) {
|
|
|
33
33
|
return r !== void 0 ? String(r) : `{${n}}`;
|
|
34
34
|
}) : s;
|
|
35
35
|
}
|
|
36
|
-
function
|
|
36
|
+
function x(s) {
|
|
37
37
|
return s === "prefix" || s === "prefix_and_default";
|
|
38
38
|
}
|
|
39
|
-
function
|
|
39
|
+
function T(s) {
|
|
40
40
|
return s === "no_prefix";
|
|
41
41
|
}
|
|
42
|
-
function
|
|
42
|
+
function $(s) {
|
|
43
43
|
return s === "prefix";
|
|
44
44
|
}
|
|
45
|
-
function
|
|
45
|
+
function v(s) {
|
|
46
46
|
return s === "prefix_except_default";
|
|
47
47
|
}
|
|
48
|
-
function
|
|
48
|
+
function w(s) {
|
|
49
49
|
return s === "prefix_and_default";
|
|
50
50
|
}
|
|
51
51
|
const d = (s, t, e, n, r) => {
|
|
@@ -57,7 +57,7 @@ const d = (s, t, e, n, r) => {
|
|
|
57
57
|
const o = t < i.length ? i[t] : i[i.length - 1];
|
|
58
58
|
return o ? o.trim().replace("{count}", t.toString()) : null;
|
|
59
59
|
};
|
|
60
|
-
function
|
|
60
|
+
function h(s, t) {
|
|
61
61
|
if (!s || typeof t != "string") return null;
|
|
62
62
|
if (t in s) {
|
|
63
63
|
const r = s[t];
|
|
@@ -76,51 +76,44 @@ function p(s) {
|
|
|
76
76
|
const t = s?.translations ?? /* @__PURE__ */ new Map();
|
|
77
77
|
return {
|
|
78
78
|
hasCache(e, n) {
|
|
79
|
-
const r =
|
|
80
|
-
return t.has(r)
|
|
79
|
+
const r = n || "index";
|
|
80
|
+
return t.has(`${e}:${r}`);
|
|
81
81
|
},
|
|
82
82
|
getCache(e, n) {
|
|
83
|
-
const r =
|
|
84
|
-
return t.get(r);
|
|
83
|
+
const r = n || "index";
|
|
84
|
+
return t.get(`${e}:${r}`);
|
|
85
85
|
},
|
|
86
86
|
setCache(e, n, r) {
|
|
87
87
|
},
|
|
88
88
|
hasTranslation(e, n) {
|
|
89
89
|
for (const [r, a] of t)
|
|
90
|
-
if (
|
|
90
|
+
if (r.startsWith(`${e}:`) && h(a, n) !== null)
|
|
91
91
|
return !0;
|
|
92
92
|
return !1;
|
|
93
93
|
},
|
|
94
|
-
hasGeneralTranslation(e) {
|
|
95
|
-
return t.has(e);
|
|
96
|
-
},
|
|
97
94
|
hasPageTranslation(e, n) {
|
|
98
|
-
|
|
95
|
+
const r = n || "index";
|
|
96
|
+
return t.has(`${e}:${r}`);
|
|
99
97
|
},
|
|
100
98
|
getTranslation(e, n, r) {
|
|
101
|
-
const a =
|
|
102
|
-
|
|
103
|
-
const l = t.get(e);
|
|
104
|
-
return f(l, r);
|
|
99
|
+
const a = n || "index";
|
|
100
|
+
return h(t.get(`${e}:${a}`), r);
|
|
105
101
|
},
|
|
106
|
-
loadTranslations(e, n) {
|
|
107
|
-
const r = t.get(
|
|
108
|
-
t.set(
|
|
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(e, n) {
|
|
111
|
-
|
|
106
|
+
setTranslations(e, n, r = "index") {
|
|
107
|
+
const a = r || "index";
|
|
108
|
+
t.set(`${e}:${a}`, n);
|
|
112
109
|
},
|
|
113
110
|
loadPageTranslations(e, n, r) {
|
|
114
|
-
const
|
|
115
|
-
!
|
|
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
114
|
mergeTranslation(e, n, r, a = !1) {
|
|
118
|
-
const
|
|
119
|
-
t.set(
|
|
120
|
-
},
|
|
121
|
-
mergeGlobalTranslation(e, n, r = !1) {
|
|
122
|
-
const a = t.get(e) ?? {};
|
|
123
|
-
t.set(e, { ...a, ...n });
|
|
115
|
+
const o = `${e}:${n || "index"}`, l = t.get(o) ?? {};
|
|
116
|
+
t.set(o, { ...l, ...r });
|
|
124
117
|
},
|
|
125
118
|
clearCache() {
|
|
126
119
|
t.clear();
|
|
@@ -129,7 +122,7 @@ function p(s) {
|
|
|
129
122
|
}
|
|
130
123
|
class _ {
|
|
131
124
|
constructor(t = {}) {
|
|
132
|
-
this.formatter = new
|
|
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
|
/**
|
|
@@ -163,7 +156,7 @@ class _ {
|
|
|
163
156
|
const { count: r, ...a } = typeof e == "number" ? { count: e } : e;
|
|
164
157
|
if (r === void 0)
|
|
165
158
|
return n ?? t;
|
|
166
|
-
const i = (l,
|
|
159
|
+
const i = (l, u, c) => this.t(l, u, c);
|
|
167
160
|
return this.pluralFunc(t, Number.parseInt(r.toString(), 10), a, this.getLocale(), i) ?? n ?? t;
|
|
168
161
|
}
|
|
169
162
|
/**
|
|
@@ -203,8 +196,8 @@ class _ {
|
|
|
203
196
|
* Core translation loading logic (without reactivity)
|
|
204
197
|
* Subclasses can override addTranslations/addRouteTranslations to add reactivity
|
|
205
198
|
*/
|
|
206
|
-
loadTranslationsCore(t, e, n) {
|
|
207
|
-
n ? this.helper.
|
|
199
|
+
loadTranslationsCore(t, e, n, r = "index") {
|
|
200
|
+
n ? this.helper.mergeTranslation(t, r, e, !0) : this.helper.setTranslations(t, e, r);
|
|
208
201
|
}
|
|
209
202
|
/**
|
|
210
203
|
* Core route translation loading logic (without reactivity)
|
|
@@ -216,13 +209,13 @@ class _ {
|
|
|
216
209
|
}
|
|
217
210
|
export {
|
|
218
211
|
_ as BaseI18n,
|
|
219
|
-
|
|
212
|
+
f as FormatService,
|
|
220
213
|
d as defaultPlural,
|
|
221
214
|
m as interpolate,
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
215
|
+
T as isNoPrefixStrategy,
|
|
216
|
+
w as isPrefixAndDefaultStrategy,
|
|
217
|
+
v as isPrefixExceptDefaultStrategy,
|
|
218
|
+
$ as isPrefixStrategy,
|
|
226
219
|
p as useTranslationHelper,
|
|
227
|
-
|
|
220
|
+
x as withPrefixStrategy
|
|
228
221
|
};
|
package/dist/translation.d.ts
CHANGED
|
@@ -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
|
|
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.
|
|
3
|
+
"version": "1.3.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.
|
|
25
|
+
"@i18n-micro/types": "1.2.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"vite": "^7.3.1",
|
package/src/base.ts
CHANGED
|
@@ -66,10 +66,9 @@ export abstract class BaseI18n {
|
|
|
66
66
|
const route = routeName || this.getRoute()
|
|
67
67
|
|
|
68
68
|
// 1. Try to find translation in current locale
|
|
69
|
-
// Note: In Nuxt runtime, server already merges global translations, so we don't need explicit fallback
|
|
70
69
|
let value = this.helper.getTranslation<string>(locale, route, key)
|
|
71
70
|
|
|
72
|
-
// 2. Fallback to fallbackLocale if not found and different
|
|
71
|
+
// 2. Fallback to fallbackLocale if not found and different
|
|
73
72
|
if (!value) {
|
|
74
73
|
const fallbackLocale = this.getFallbackLocale()
|
|
75
74
|
if (locale !== fallbackLocale) {
|
|
@@ -173,11 +172,11 @@ export abstract class BaseI18n {
|
|
|
173
172
|
* Core translation loading logic (without reactivity)
|
|
174
173
|
* Subclasses can override addTranslations/addRouteTranslations to add reactivity
|
|
175
174
|
*/
|
|
176
|
-
public loadTranslationsCore(locale: string, translations: Translations, merge: boolean): void {
|
|
175
|
+
public loadTranslationsCore(locale: string, translations: Translations, merge: boolean, routeName = 'index'): void {
|
|
177
176
|
if (merge) {
|
|
178
|
-
this.helper.
|
|
177
|
+
this.helper.mergeTranslation(locale, routeName, translations, true)
|
|
179
178
|
} else {
|
|
180
|
-
this.helper.setTranslations(locale, translations)
|
|
179
|
+
this.helper.setTranslations(locale, translations, routeName)
|
|
181
180
|
}
|
|
182
181
|
}
|
|
183
182
|
|
package/src/translation.ts
CHANGED
|
@@ -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
|
|
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
|
|
40
|
-
return translations.has(
|
|
39
|
+
const p = page || 'index'
|
|
40
|
+
return translations.has(`${locale}:${p}`)
|
|
41
41
|
},
|
|
42
42
|
getCache(locale: string, routeName: string) {
|
|
43
|
-
const
|
|
44
|
-
return translations.get(
|
|
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 (
|
|
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
|
-
|
|
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
|
|
65
|
-
|
|
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
|
-
|
|
74
|
-
const
|
|
75
|
-
translations.
|
|
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
|
-
|
|
79
|
-
translations.set(locale
|
|
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
|
|
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
|
|
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
|
},
|
package/tests/base.test.ts
CHANGED
|
@@ -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', '
|
|
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('
|
|
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', '
|
|
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', '
|
|
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', '
|
|
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', '
|
|
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', '
|
|
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', '
|
|
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', '
|
|
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', '
|
|
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', '
|
|
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', '
|
|
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,7 +111,7 @@ 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', '
|
|
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
|
|
|
@@ -120,27 +120,27 @@ describe('BaseI18n', () => {
|
|
|
120
120
|
|
|
121
121
|
test('should call missingHandler when translation is missing', () => {
|
|
122
122
|
const handler = jest.fn()
|
|
123
|
-
const i18n = new TestI18n('en', 'en', '
|
|
123
|
+
const i18n = new TestI18n('en', 'en', 'index', { missingHandler: handler })
|
|
124
124
|
|
|
125
125
|
i18n.t('missing.key')
|
|
126
126
|
|
|
127
|
-
expect(handler).toHaveBeenCalledWith('en', 'missing.key', '
|
|
127
|
+
expect(handler).toHaveBeenCalledWith('en', 'missing.key', 'index')
|
|
128
128
|
})
|
|
129
129
|
|
|
130
130
|
test('should call customMissingHandler when set (Nuxt runtime)', () => {
|
|
131
131
|
const customHandler = jest.fn()
|
|
132
|
-
const i18n = new TestI18n('en', 'en', '
|
|
132
|
+
const i18n = new TestI18n('en', 'en', 'index', {
|
|
133
133
|
getCustomMissingHandler: () => customHandler,
|
|
134
134
|
})
|
|
135
135
|
|
|
136
136
|
i18n.t('missing.key')
|
|
137
137
|
|
|
138
|
-
expect(customHandler).toHaveBeenCalledWith('en', 'missing.key', '
|
|
138
|
+
expect(customHandler).toHaveBeenCalledWith('en', 'missing.key', 'index')
|
|
139
139
|
})
|
|
140
140
|
|
|
141
141
|
test('should not warn when missingWarn is false', () => {
|
|
142
142
|
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation()
|
|
143
|
-
const i18n = new TestI18n('en', 'en', '
|
|
143
|
+
const i18n = new TestI18n('en', 'en', 'index', { missingWarn: false })
|
|
144
144
|
|
|
145
145
|
i18n.t('missing.key')
|
|
146
146
|
|
|
@@ -151,7 +151,7 @@ describe('BaseI18n', () => {
|
|
|
151
151
|
|
|
152
152
|
describe('ts() method', () => {
|
|
153
153
|
test('should return translation as string', async () => {
|
|
154
|
-
const i18n = new TestI18n('en', 'en', '
|
|
154
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
155
155
|
const translations: Translations = { greeting: 'Hello' }
|
|
156
156
|
i18n['helper'].loadTranslations('en', translations)
|
|
157
157
|
|
|
@@ -159,17 +159,17 @@ describe('BaseI18n', () => {
|
|
|
159
159
|
})
|
|
160
160
|
|
|
161
161
|
test('should return defaultValue when translation is missing', () => {
|
|
162
|
-
const i18n = new TestI18n('en', 'en', '
|
|
162
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
163
163
|
expect(i18n.ts('missing.key', undefined, 'Default')).toBe('Default')
|
|
164
164
|
})
|
|
165
165
|
|
|
166
166
|
test('should return key when translation is missing and no defaultValue', () => {
|
|
167
|
-
const i18n = new TestI18n('en', 'en', '
|
|
167
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
168
168
|
expect(i18n.ts('missing.key')).toBe('missing.key')
|
|
169
169
|
})
|
|
170
170
|
|
|
171
171
|
test('should convert non-string values to string', async () => {
|
|
172
|
-
const i18n = new TestI18n('en', 'en', '
|
|
172
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
173
173
|
const translations: Translations = { count: 42 }
|
|
174
174
|
i18n['helper'].loadTranslations('en', translations)
|
|
175
175
|
|
|
@@ -179,12 +179,12 @@ describe('BaseI18n', () => {
|
|
|
179
179
|
|
|
180
180
|
describe('tc() method', () => {
|
|
181
181
|
test('should return defaultValue when count is undefined', () => {
|
|
182
|
-
const i18n = new TestI18n('en', 'en', '
|
|
182
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
183
183
|
expect(i18n.tc('apples', { other: 'params' }, 'No count')).toBe('No count')
|
|
184
184
|
})
|
|
185
185
|
|
|
186
186
|
test('should use plural function with count', async () => {
|
|
187
|
-
const i18n = new TestI18n('en', 'en', '
|
|
187
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
188
188
|
const translations: Translations = { apples: 'apple|apples' }
|
|
189
189
|
await i18n['helper'].loadTranslations('en', translations)
|
|
190
190
|
|
|
@@ -196,7 +196,7 @@ describe('BaseI18n', () => {
|
|
|
196
196
|
})
|
|
197
197
|
|
|
198
198
|
test('should handle count as number', async () => {
|
|
199
|
-
const i18n = new TestI18n('en', 'en', '
|
|
199
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
200
200
|
const translations: Translations = { apples: 'apple|apples' }
|
|
201
201
|
i18n['helper'].loadTranslations('en', translations)
|
|
202
202
|
|
|
@@ -204,7 +204,7 @@ describe('BaseI18n', () => {
|
|
|
204
204
|
})
|
|
205
205
|
|
|
206
206
|
test('should handle count as Params object', async () => {
|
|
207
|
-
const i18n = new TestI18n('en', 'en', '
|
|
207
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
208
208
|
const translations: Translations = { apples: 'apple|apples' }
|
|
209
209
|
i18n['helper'].loadTranslations('en', translations)
|
|
210
210
|
|
|
@@ -212,7 +212,7 @@ describe('BaseI18n', () => {
|
|
|
212
212
|
})
|
|
213
213
|
|
|
214
214
|
test('should return defaultValue when plural function returns null', () => {
|
|
215
|
-
const i18n = new TestI18n('en', 'en', '
|
|
215
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
216
216
|
// When translation is missing, t() returns key, which is passed to pluralFunc
|
|
217
217
|
// pluralFunc tries to process 'missing.key' as translation, but since it doesn't contain '|',
|
|
218
218
|
// it returns the key itself. So tc returns the key, not defaultValue.
|
|
@@ -224,19 +224,19 @@ describe('BaseI18n', () => {
|
|
|
224
224
|
|
|
225
225
|
describe('tn() method', () => {
|
|
226
226
|
test('should format number with default locale', () => {
|
|
227
|
-
const i18n = new TestI18n('en', 'en', '
|
|
227
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
228
228
|
const result = i18n.tn(1234.56)
|
|
229
229
|
expect(result).toMatch(/1[,.]234[.,]56/)
|
|
230
230
|
})
|
|
231
231
|
|
|
232
232
|
test('should format number with custom options', () => {
|
|
233
|
-
const i18n = new TestI18n('en', 'en', '
|
|
233
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
234
234
|
const result = i18n.tn(1234.56, { style: 'currency', currency: 'USD' })
|
|
235
235
|
expect(result).toContain('1,234.56')
|
|
236
236
|
})
|
|
237
237
|
|
|
238
238
|
test('should use current locale for formatting', () => {
|
|
239
|
-
const i18n = new TestI18n('ru', 'en', '
|
|
239
|
+
const i18n = new TestI18n('ru', 'en', 'index')
|
|
240
240
|
const result = i18n.tn(1234.56)
|
|
241
241
|
// Russian locale uses different number formatting
|
|
242
242
|
expect(result).toBeDefined()
|
|
@@ -245,7 +245,7 @@ describe('BaseI18n', () => {
|
|
|
245
245
|
|
|
246
246
|
describe('td() method', () => {
|
|
247
247
|
test('should format date with default locale', () => {
|
|
248
|
-
const i18n = new TestI18n('en', 'en', '
|
|
248
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
249
249
|
const date = new Date('2024-01-15')
|
|
250
250
|
const result = i18n.td(date)
|
|
251
251
|
expect(result).toBeDefined()
|
|
@@ -253,7 +253,7 @@ describe('BaseI18n', () => {
|
|
|
253
253
|
})
|
|
254
254
|
|
|
255
255
|
test('should format date with custom options', () => {
|
|
256
|
-
const i18n = new TestI18n('en', 'en', '
|
|
256
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
257
257
|
const date = new Date('2024-01-15')
|
|
258
258
|
const result = i18n.td(date, { year: 'numeric', month: 'long', day: 'numeric' })
|
|
259
259
|
expect(result).toContain('2024')
|
|
@@ -261,7 +261,7 @@ describe('BaseI18n', () => {
|
|
|
261
261
|
})
|
|
262
262
|
|
|
263
263
|
test('should handle date as number (timestamp)', () => {
|
|
264
|
-
const i18n = new TestI18n('en', 'en', '
|
|
264
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
265
265
|
const timestamp = new Date('2024-01-15').getTime()
|
|
266
266
|
const result = i18n.td(timestamp)
|
|
267
267
|
expect(result).toBeDefined()
|
|
@@ -269,7 +269,7 @@ describe('BaseI18n', () => {
|
|
|
269
269
|
})
|
|
270
270
|
|
|
271
271
|
test('should handle date as string', () => {
|
|
272
|
-
const i18n = new TestI18n('en', 'en', '
|
|
272
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
273
273
|
const result = i18n.td('2024-01-15')
|
|
274
274
|
expect(result).toBeDefined()
|
|
275
275
|
expect(result).not.toBe('Invalid Date')
|
|
@@ -278,7 +278,7 @@ describe('BaseI18n', () => {
|
|
|
278
278
|
|
|
279
279
|
describe('tdr() method', () => {
|
|
280
280
|
test('should format relative time', () => {
|
|
281
|
-
const i18n = new TestI18n('en', 'en', '
|
|
281
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
282
282
|
const yesterday = new Date(Date.now() - 86400000)
|
|
283
283
|
const result = i18n.tdr(yesterday)
|
|
284
284
|
expect(result).toBeDefined()
|
|
@@ -286,14 +286,14 @@ describe('BaseI18n', () => {
|
|
|
286
286
|
})
|
|
287
287
|
|
|
288
288
|
test('should format relative time with custom options', () => {
|
|
289
|
-
const i18n = new TestI18n('en', 'en', '
|
|
289
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
290
290
|
const yesterday = new Date(Date.now() - 86400000)
|
|
291
291
|
const result = i18n.tdr(yesterday, { numeric: 'always' })
|
|
292
292
|
expect(result).toBeDefined()
|
|
293
293
|
})
|
|
294
294
|
|
|
295
295
|
test('should handle invalid date gracefully', () => {
|
|
296
|
-
const i18n = new TestI18n('en', 'en', '
|
|
296
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
297
297
|
const invalidDate = new Date('invalid')
|
|
298
298
|
const result = i18n.tdr(invalidDate)
|
|
299
299
|
expect(result).toBeDefined()
|
|
@@ -302,7 +302,7 @@ describe('BaseI18n', () => {
|
|
|
302
302
|
|
|
303
303
|
describe('has() method', () => {
|
|
304
304
|
test('should return true when translation exists', async () => {
|
|
305
|
-
const i18n = new TestI18n('en', 'en', '
|
|
305
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
306
306
|
const translations: Translations = { greeting: 'Hello' }
|
|
307
307
|
i18n['helper'].loadTranslations('en', translations)
|
|
308
308
|
|
|
@@ -310,17 +310,17 @@ describe('BaseI18n', () => {
|
|
|
310
310
|
})
|
|
311
311
|
|
|
312
312
|
test('should return false when translation does not exist', () => {
|
|
313
|
-
const i18n = new TestI18n('en', 'en', '
|
|
313
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
314
314
|
expect(i18n.has('missing.key')).toBe(false)
|
|
315
315
|
})
|
|
316
316
|
|
|
317
317
|
test('should check route-specific translation when routeName is provided', async () => {
|
|
318
|
-
const i18n = new TestI18n('en', 'en', '
|
|
318
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
319
319
|
const routeTranslations: Translations = { title: 'Route Title' }
|
|
320
320
|
await i18n['helper'].loadPageTranslations('en', 'about', routeTranslations)
|
|
321
321
|
|
|
322
322
|
expect(i18n.has('title', 'about')).toBe(true)
|
|
323
|
-
expect(i18n.has('title', '
|
|
323
|
+
expect(i18n.has('title', 'index')).toBe(false)
|
|
324
324
|
})
|
|
325
325
|
|
|
326
326
|
test('should use current route when routeName is not provided', async () => {
|
|
@@ -334,7 +334,7 @@ describe('BaseI18n', () => {
|
|
|
334
334
|
|
|
335
335
|
describe('clearCache() method', () => {
|
|
336
336
|
test('should clear all translations from cache', async () => {
|
|
337
|
-
const i18n = new TestI18n('en', 'en', '
|
|
337
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
338
338
|
const translations: Translations = { greeting: 'Hello' }
|
|
339
339
|
i18n['helper'].loadTranslations('en', translations)
|
|
340
340
|
|
|
@@ -348,7 +348,7 @@ describe('BaseI18n', () => {
|
|
|
348
348
|
|
|
349
349
|
describe('loadTranslationsCore() method', () => {
|
|
350
350
|
test('should load translations when merge is false', async () => {
|
|
351
|
-
const i18n = new TestI18n('en', 'en', '
|
|
351
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
352
352
|
const translations: Translations = { greeting: 'Hello' }
|
|
353
353
|
|
|
354
354
|
i18n['loadTranslationsCore']('en', translations, false)
|
|
@@ -359,7 +359,7 @@ describe('BaseI18n', () => {
|
|
|
359
359
|
})
|
|
360
360
|
|
|
361
361
|
test('should merge translations when merge is true', async () => {
|
|
362
|
-
const i18n = new TestI18n('en', 'en', '
|
|
362
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
363
363
|
const initial: Translations = { greeting: 'Hello' }
|
|
364
364
|
const additional: Translations = { farewell: 'Goodbye' }
|
|
365
365
|
|
|
@@ -375,7 +375,7 @@ describe('BaseI18n', () => {
|
|
|
375
375
|
|
|
376
376
|
describe('loadRouteTranslationsCore() method', () => {
|
|
377
377
|
test('should load route translations when merge is false', async () => {
|
|
378
|
-
const i18n = new TestI18n('en', 'en', '
|
|
378
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
379
379
|
const translations: Translations = { title: 'Page Title' }
|
|
380
380
|
|
|
381
381
|
i18n['loadRouteTranslationsCore']('en', 'about', translations, false)
|
|
@@ -386,7 +386,7 @@ describe('BaseI18n', () => {
|
|
|
386
386
|
})
|
|
387
387
|
|
|
388
388
|
test('should merge route translations when merge is true', async () => {
|
|
389
|
-
const i18n = new TestI18n('en', 'en', '
|
|
389
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
390
390
|
const initial: Translations = { title: 'Page Title' }
|
|
391
391
|
const additional: Translations = { description: 'Page Description' }
|
|
392
392
|
|
|
@@ -402,7 +402,7 @@ describe('BaseI18n', () => {
|
|
|
402
402
|
|
|
403
403
|
describe('Edge cases', () => {
|
|
404
404
|
test('should handle nested translation keys', async () => {
|
|
405
|
-
const i18n = new TestI18n('en', 'en', '
|
|
405
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
406
406
|
const translations: Translations = {
|
|
407
407
|
nested: {
|
|
408
408
|
deep: {
|
|
@@ -416,7 +416,7 @@ describe('BaseI18n', () => {
|
|
|
416
416
|
})
|
|
417
417
|
|
|
418
418
|
test('should handle multiple params in interpolation', async () => {
|
|
419
|
-
const i18n = new TestI18n('en', 'en', '
|
|
419
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
420
420
|
const translations: Translations = {
|
|
421
421
|
message: 'Hello, {name}! You are {age} years old.',
|
|
422
422
|
}
|
|
@@ -426,23 +426,25 @@ describe('BaseI18n', () => {
|
|
|
426
426
|
})
|
|
427
427
|
|
|
428
428
|
test('should handle null defaultValue', () => {
|
|
429
|
-
const i18n = new TestI18n('en', 'en', '
|
|
429
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
430
430
|
expect(i18n.t('missing.key', undefined, null)).toBe('missing.key')
|
|
431
431
|
})
|
|
432
432
|
|
|
433
433
|
test('should handle empty string defaultValue', () => {
|
|
434
|
-
const i18n = new TestI18n('en', 'en', '
|
|
434
|
+
const i18n = new TestI18n('en', 'en', 'index')
|
|
435
435
|
// Empty string is falsy, so it will fall back to key (as per defaultValue || key logic)
|
|
436
436
|
expect(i18n.t('missing.key', undefined, '')).toBe('missing.key')
|
|
437
437
|
})
|
|
438
438
|
|
|
439
439
|
test('should handle route change', async () => {
|
|
440
440
|
const storage = { translations: new Map<string, Translations>() }
|
|
441
|
-
const i18n = new TestI18n('en', 'en', '
|
|
442
|
-
const
|
|
443
|
-
|
|
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' }
|
|
444
446
|
|
|
445
|
-
await i18n['helper'].loadTranslations('en',
|
|
447
|
+
await i18n['helper'].loadTranslations('en', rootTranslations)
|
|
446
448
|
await i18n['helper'].loadPageTranslations('en', 'about', routeTranslations)
|
|
447
449
|
|
|
448
450
|
// Verify translations are loaded
|
|
@@ -450,14 +452,13 @@ describe('BaseI18n', () => {
|
|
|
450
452
|
expect(i18n.has('title', 'about')).toBe(true)
|
|
451
453
|
|
|
452
454
|
// Check route-specific translation with explicit routeName
|
|
453
|
-
// Note: getTranslation uses routeName parameter, so this should work
|
|
454
455
|
const titleValue = i18n.t('title', undefined, undefined, 'about')
|
|
455
456
|
expect(titleValue).toBe('About Page')
|
|
456
457
|
|
|
457
458
|
// Change route and check
|
|
458
459
|
i18n.setRoute('about')
|
|
459
460
|
expect(i18n.t('title')).toBe('About Page')
|
|
460
|
-
expect(i18n.t('greeting')).toBe('Hello')
|
|
461
|
+
expect(i18n.t('greeting')).toBe('Hello')
|
|
461
462
|
})
|
|
462
463
|
})
|
|
463
464
|
})
|
package/tests/core.test.ts
CHANGED
|
@@ -63,12 +63,12 @@ describe('Translation Helper', () => {
|
|
|
63
63
|
expect(helper.getTranslation('en', 'home', 'newKey')).toBe('New value')
|
|
64
64
|
})
|
|
65
65
|
|
|
66
|
-
test('
|
|
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.
|
|
71
|
-
expect(helper.getTranslation('en', 'index', '
|
|
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', () => {
|