@i18n-micro/core 1.1.1 → 1.1.3
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/dist/base.d.ts +2 -2
- package/dist/helpers.d.ts +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.mjs +66 -72
- package/dist/translation.d.ts +2 -2
- package/package.json +2 -2
- package/src/base.ts +15 -48
- package/src/format-service.ts +1 -1
- package/src/helpers.ts +1 -1
- package/src/index.ts +11 -3
- package/src/translation.ts +5 -7
- package/tests/base.test.ts +6 -11
- package/tests/core.test.ts +1 -1
- package/tests/helpers.test.ts +3 -3
- package/vite.config.mts +2 -5
package/dist/base.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CleanTranslation, MissingHandler, Params, PluralFunc, TranslationKey, Translations } from '@i18n-micro/types';
|
|
2
2
|
import { FormatService } from './format-service';
|
|
3
|
-
import {
|
|
3
|
+
import { TranslationStorage, useTranslationHelper } from './translation';
|
|
4
4
|
export interface BaseI18nOptions {
|
|
5
5
|
storage?: TranslationStorage;
|
|
6
6
|
plural?: PluralFunc;
|
package/dist/helpers.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Params,
|
|
1
|
+
import { Params, PluralFunc, Strategies } from '@i18n-micro/types';
|
|
2
2
|
export declare function interpolate(template: string, params: Params): string;
|
|
3
3
|
export declare function withPrefixStrategy(strategy: Strategies): strategy is "prefix" | "prefix_and_default";
|
|
4
4
|
export declare function isNoPrefixStrategy(strategy: Strategies): strategy is "no_prefix";
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});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
|
|
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;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useTranslationHelper, TranslationStorage } from './translation';
|
|
2
|
-
import { FormatService } from './format-service';
|
|
3
|
-
import { interpolate, withPrefixStrategy, isNoPrefixStrategy, isPrefixStrategy, isPrefixExceptDefaultStrategy, isPrefixAndDefaultStrategy, defaultPlural } from './helpers';
|
|
4
1
|
import { BaseI18n, BaseI18nOptions } from './base';
|
|
2
|
+
import { FormatService } from './format-service';
|
|
3
|
+
import { defaultPlural, interpolate, isNoPrefixStrategy, isPrefixAndDefaultStrategy, isPrefixExceptDefaultStrategy, isPrefixStrategy, withPrefixStrategy } from './helpers';
|
|
4
|
+
import { TranslationStorage, useTranslationHelper } from './translation';
|
|
5
5
|
export { useTranslationHelper, interpolate, withPrefixStrategy, isNoPrefixStrategy, isPrefixStrategy, isPrefixExceptDefaultStrategy, isPrefixAndDefaultStrategy, defaultPlural, FormatService, BaseI18n, type TranslationStorage, type BaseI18nOptions, };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,62 @@
|
|
|
1
|
+
class g {
|
|
2
|
+
formatNumber(e, t, n) {
|
|
3
|
+
return new Intl.NumberFormat(t, n).format(e);
|
|
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);
|
|
8
|
+
}
|
|
9
|
+
formatRelativeTime(e, t, n) {
|
|
10
|
+
const r = new Date(e);
|
|
11
|
+
if (Number.isNaN(r.getTime()))
|
|
12
|
+
return new Intl.RelativeTimeFormat(t, n).format(0, "second");
|
|
13
|
+
const i = Math.floor(((/* @__PURE__ */ new Date()).getTime() - r.getTime()) / 1e3), o = [
|
|
14
|
+
{ unit: "year", seconds: 31536e3 },
|
|
15
|
+
{ unit: "month", seconds: 2592e3 },
|
|
16
|
+
{ unit: "day", seconds: 86400 },
|
|
17
|
+
{ unit: "hour", seconds: 3600 },
|
|
18
|
+
{ unit: "minute", seconds: 60 },
|
|
19
|
+
{ unit: "second", seconds: 1 }
|
|
20
|
+
];
|
|
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(t, n).format(-c, l);
|
|
25
|
+
}
|
|
26
|
+
return new Intl.RelativeTimeFormat(t, n).format(0, "second");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const h = /\{(\w+)\}/g;
|
|
30
|
+
function m(s, e) {
|
|
31
|
+
return e ? s.replace(h, (t, n) => {
|
|
32
|
+
const r = e[n];
|
|
33
|
+
return r !== void 0 ? String(r) : `{${n}}`;
|
|
34
|
+
}) : s;
|
|
35
|
+
}
|
|
36
|
+
function v(s) {
|
|
37
|
+
return s === "prefix" || s === "prefix_and_default";
|
|
38
|
+
}
|
|
39
|
+
function T(s) {
|
|
40
|
+
return s === "no_prefix";
|
|
41
|
+
}
|
|
42
|
+
function P(s) {
|
|
43
|
+
return s === "prefix";
|
|
44
|
+
}
|
|
45
|
+
function b(s) {
|
|
46
|
+
return s === "prefix_except_default";
|
|
47
|
+
}
|
|
48
|
+
function N(s) {
|
|
49
|
+
return s === "prefix_and_default";
|
|
50
|
+
}
|
|
51
|
+
const d = (s, e, t, n, r) => {
|
|
52
|
+
const a = r(s, t);
|
|
53
|
+
if (!a)
|
|
54
|
+
return null;
|
|
55
|
+
const i = a.toString().split("|");
|
|
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;
|
|
59
|
+
};
|
|
1
60
|
function f(s, e) {
|
|
2
61
|
if (!s || typeof e != "string") return null;
|
|
3
62
|
if (e in s) {
|
|
@@ -13,7 +72,7 @@ function f(s, e) {
|
|
|
13
72
|
return null;
|
|
14
73
|
return n ?? null;
|
|
15
74
|
}
|
|
16
|
-
function
|
|
75
|
+
function p(s) {
|
|
17
76
|
const e = s?.translations ?? /* @__PURE__ */ new Map();
|
|
18
77
|
return {
|
|
19
78
|
hasCache(t, n) {
|
|
@@ -68,68 +127,9 @@ function h(s) {
|
|
|
68
127
|
}
|
|
69
128
|
};
|
|
70
129
|
}
|
|
71
|
-
class g {
|
|
72
|
-
formatNumber(e, t, n) {
|
|
73
|
-
return new Intl.NumberFormat(t, n).format(e);
|
|
74
|
-
}
|
|
75
|
-
formatDate(e, t, n) {
|
|
76
|
-
const r = new Date(e);
|
|
77
|
-
return Number.isNaN(r.getTime()) ? "Invalid Date" : new Intl.DateTimeFormat(t, n).format(r);
|
|
78
|
-
}
|
|
79
|
-
formatRelativeTime(e, t, n) {
|
|
80
|
-
const r = new Date(e);
|
|
81
|
-
if (Number.isNaN(r.getTime()))
|
|
82
|
-
return new Intl.RelativeTimeFormat(t, n).format(0, "second");
|
|
83
|
-
const i = Math.floor(((/* @__PURE__ */ new Date()).getTime() - r.getTime()) / 1e3), o = [
|
|
84
|
-
{ unit: "year", seconds: 31536e3 },
|
|
85
|
-
{ unit: "month", seconds: 2592e3 },
|
|
86
|
-
{ unit: "day", seconds: 86400 },
|
|
87
|
-
{ unit: "hour", seconds: 3600 },
|
|
88
|
-
{ unit: "minute", seconds: 60 },
|
|
89
|
-
{ unit: "second", seconds: 1 }
|
|
90
|
-
];
|
|
91
|
-
for (const { unit: l, seconds: u } of o) {
|
|
92
|
-
const c = Math.floor(i / u);
|
|
93
|
-
if (c >= 1)
|
|
94
|
-
return new Intl.RelativeTimeFormat(t, n).format(-c, l);
|
|
95
|
-
}
|
|
96
|
-
return new Intl.RelativeTimeFormat(t, n).format(0, "second");
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
const m = /\{(\w+)\}/g;
|
|
100
|
-
function d(s, e) {
|
|
101
|
-
return e ? s.replace(m, (t, n) => {
|
|
102
|
-
const r = e[n];
|
|
103
|
-
return r !== void 0 ? String(r) : `{${n}}`;
|
|
104
|
-
}) : s;
|
|
105
|
-
}
|
|
106
|
-
function v(s) {
|
|
107
|
-
return s === "prefix" || s === "prefix_and_default";
|
|
108
|
-
}
|
|
109
|
-
function T(s) {
|
|
110
|
-
return s === "no_prefix";
|
|
111
|
-
}
|
|
112
|
-
function P(s) {
|
|
113
|
-
return s === "prefix";
|
|
114
|
-
}
|
|
115
|
-
function b(s) {
|
|
116
|
-
return s === "prefix_except_default";
|
|
117
|
-
}
|
|
118
|
-
function N(s) {
|
|
119
|
-
return s === "prefix_and_default";
|
|
120
|
-
}
|
|
121
|
-
const p = (s, e, t, n, r) => {
|
|
122
|
-
const a = r(s, t);
|
|
123
|
-
if (!a)
|
|
124
|
-
return null;
|
|
125
|
-
const i = a.toString().split("|");
|
|
126
|
-
if (i.length === 0) return null;
|
|
127
|
-
const o = e < i.length ? i[e] : i[i.length - 1];
|
|
128
|
-
return o ? o.trim().replace("{count}", e.toString()) : null;
|
|
129
|
-
};
|
|
130
130
|
class $ {
|
|
131
131
|
constructor(e = {}) {
|
|
132
|
-
this.formatter = new g(), this.helper =
|
|
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;
|
|
133
133
|
}
|
|
134
134
|
// --- Public methods (implemented in base class) ---
|
|
135
135
|
/**
|
|
@@ -155,7 +155,7 @@ class $ {
|
|
|
155
155
|
const l = this.getCustomMissingHandler?.();
|
|
156
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;
|
|
157
157
|
}
|
|
158
|
-
return typeof o == "string" && t ?
|
|
158
|
+
return typeof o == "string" && t ? m(o, t) : o;
|
|
159
159
|
}
|
|
160
160
|
/**
|
|
161
161
|
* Get translation as string
|
|
@@ -171,13 +171,7 @@ class $ {
|
|
|
171
171
|
if (r === void 0)
|
|
172
172
|
return n ?? e;
|
|
173
173
|
const i = (l, u, c) => this.t(l, u, c);
|
|
174
|
-
return this.pluralFunc(
|
|
175
|
-
e,
|
|
176
|
-
Number.parseInt(r.toString()),
|
|
177
|
-
a,
|
|
178
|
-
this.getLocale(),
|
|
179
|
-
i
|
|
180
|
-
) ?? n ?? e;
|
|
174
|
+
return this.pluralFunc(e, Number.parseInt(r.toString(), 10), a, this.getLocale(), i) ?? n ?? e;
|
|
181
175
|
}
|
|
182
176
|
/**
|
|
183
177
|
* Format number
|
|
@@ -230,12 +224,12 @@ class $ {
|
|
|
230
224
|
export {
|
|
231
225
|
$ as BaseI18n,
|
|
232
226
|
g as FormatService,
|
|
233
|
-
|
|
234
|
-
|
|
227
|
+
d as defaultPlural,
|
|
228
|
+
m as interpolate,
|
|
235
229
|
T as isNoPrefixStrategy,
|
|
236
230
|
N as isPrefixAndDefaultStrategy,
|
|
237
231
|
b as isPrefixExceptDefaultStrategy,
|
|
238
232
|
P as isPrefixStrategy,
|
|
239
|
-
|
|
233
|
+
p as useTranslationHelper,
|
|
240
234
|
v as withPrefixStrategy
|
|
241
235
|
};
|
package/dist/translation.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Translations } from '@i18n-micro/types';
|
|
2
2
|
/**
|
|
3
|
-
* Bare Metal:
|
|
4
|
-
*
|
|
3
|
+
* Bare Metal: Simple translation storage without Ref, useState, devalue.
|
|
4
|
+
* Map key: locale (general) or locale:routeName (page-specific).
|
|
5
5
|
*/
|
|
6
6
|
export interface TranslationStorage {
|
|
7
7
|
translations: Map<string, Translations>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@i18n-micro/core",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
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.
|
|
25
|
+
"@i18n-micro/types": "1.1.3"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"vite": "^7.3.1",
|
package/src/base.ts
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { CleanTranslation, Getter, MissingHandler, Params, PluralFunc, TranslationKey, Translations } from '@i18n-micro/types'
|
|
2
2
|
import { FormatService } from './format-service'
|
|
3
|
-
import {
|
|
4
|
-
import type
|
|
5
|
-
Translations,
|
|
6
|
-
Params,
|
|
7
|
-
PluralFunc,
|
|
8
|
-
Getter,
|
|
9
|
-
CleanTranslation,
|
|
10
|
-
TranslationKey,
|
|
11
|
-
MissingHandler,
|
|
12
|
-
} from '@i18n-micro/types'
|
|
3
|
+
import { defaultPlural, interpolate } from './helpers'
|
|
4
|
+
import { type TranslationStorage, useTranslationHelper } from './translation'
|
|
13
5
|
|
|
14
6
|
export interface BaseI18nOptions {
|
|
15
7
|
storage?: TranslationStorage
|
|
@@ -17,7 +9,7 @@ export interface BaseI18nOptions {
|
|
|
17
9
|
missingWarn?: boolean
|
|
18
10
|
missingHandler?: (locale: string, key: string, routeName: string) => void
|
|
19
11
|
// Optional hooks for Nuxt runtime specific features
|
|
20
|
-
getPreviousPageInfo?: () => { locale: string
|
|
12
|
+
getPreviousPageInfo?: () => { locale: string; routeName: string } | null
|
|
21
13
|
getCustomMissingHandler?: () => MissingHandler | null
|
|
22
14
|
enablePreviousPageFallback?: boolean
|
|
23
15
|
}
|
|
@@ -36,7 +28,7 @@ export abstract class BaseI18n {
|
|
|
36
28
|
public missingWarn: boolean
|
|
37
29
|
public missingHandler?: (locale: string, key: string, routeName: string) => void
|
|
38
30
|
// Optional hooks for Nuxt runtime specific features
|
|
39
|
-
public getPreviousPageInfo?: () => { locale: string
|
|
31
|
+
public getPreviousPageInfo?: () => { locale: string; routeName: string } | null
|
|
40
32
|
public getCustomMissingHandler?: () => MissingHandler | null
|
|
41
33
|
public enablePreviousPageFallback: boolean
|
|
42
34
|
|
|
@@ -74,12 +66,7 @@ export abstract class BaseI18n {
|
|
|
74
66
|
* Get translation for a key
|
|
75
67
|
* Based on logic from src/runtime/plugins/01.plugin.ts
|
|
76
68
|
*/
|
|
77
|
-
public t(
|
|
78
|
-
key: TranslationKey,
|
|
79
|
-
params?: Params,
|
|
80
|
-
defaultValue?: string | null,
|
|
81
|
-
routeName?: string,
|
|
82
|
-
): CleanTranslation {
|
|
69
|
+
public t(key: TranslationKey, params?: Params, defaultValue?: string | null, routeName?: string): CleanTranslation {
|
|
83
70
|
if (!key) return ''
|
|
84
71
|
|
|
85
72
|
// Use abstract getters to get current state
|
|
@@ -118,33 +105,26 @@ export abstract class BaseI18n {
|
|
|
118
105
|
const customHandler = this.getCustomMissingHandler?.()
|
|
119
106
|
if (customHandler) {
|
|
120
107
|
customHandler(locale, key, route)
|
|
121
|
-
}
|
|
122
|
-
else if (this.missingHandler) {
|
|
108
|
+
} else if (this.missingHandler) {
|
|
123
109
|
this.missingHandler(locale, key as string, route)
|
|
124
|
-
}
|
|
125
|
-
else if (this.missingWarn) {
|
|
110
|
+
} else if (this.missingWarn) {
|
|
126
111
|
const isDev = process.env.NODE_ENV !== 'production'
|
|
127
112
|
const isClient = typeof window !== 'undefined'
|
|
128
113
|
if (isDev && isClient) {
|
|
129
114
|
console.warn(`Not found '${key}' key in '${locale}' locale messages for route '${route}'.`)
|
|
130
115
|
}
|
|
131
116
|
}
|
|
132
|
-
value = defaultValue === undefined ? key :
|
|
117
|
+
value = defaultValue === undefined ? key : defaultValue || key
|
|
133
118
|
}
|
|
134
119
|
|
|
135
120
|
// 5. Interpolate
|
|
136
|
-
return typeof value === 'string' && params ? interpolate(value, params) : value as CleanTranslation
|
|
121
|
+
return typeof value === 'string' && params ? interpolate(value, params) : (value as CleanTranslation)
|
|
137
122
|
}
|
|
138
123
|
|
|
139
124
|
/**
|
|
140
125
|
* Get translation as string
|
|
141
126
|
*/
|
|
142
|
-
public ts(
|
|
143
|
-
key: TranslationKey,
|
|
144
|
-
params?: Params,
|
|
145
|
-
defaultValue?: string,
|
|
146
|
-
routeName?: string,
|
|
147
|
-
): string {
|
|
127
|
+
public ts(key: TranslationKey, params?: Params, defaultValue?: string, routeName?: string): string {
|
|
148
128
|
const value = this.t(key, params, defaultValue, routeName)
|
|
149
129
|
return value?.toString() ?? defaultValue ?? key
|
|
150
130
|
}
|
|
@@ -164,13 +144,7 @@ export abstract class BaseI18n {
|
|
|
164
144
|
return this.t(k, p, dv)
|
|
165
145
|
}
|
|
166
146
|
|
|
167
|
-
const result = this.pluralFunc(
|
|
168
|
-
key,
|
|
169
|
-
Number.parseInt(countValue.toString()),
|
|
170
|
-
params,
|
|
171
|
-
this.getLocale(),
|
|
172
|
-
getter,
|
|
173
|
-
)
|
|
147
|
+
const result = this.pluralFunc(key, Number.parseInt(countValue.toString(), 10), params, this.getLocale(), getter)
|
|
174
148
|
|
|
175
149
|
return result ?? defaultValue ?? key
|
|
176
150
|
}
|
|
@@ -224,8 +198,7 @@ export abstract class BaseI18n {
|
|
|
224
198
|
public loadTranslationsCore(locale: string, translations: Translations, merge: boolean): void {
|
|
225
199
|
if (merge) {
|
|
226
200
|
this.helper.mergeGlobalTranslation(locale, translations, true)
|
|
227
|
-
}
|
|
228
|
-
else {
|
|
201
|
+
} else {
|
|
229
202
|
this.helper.setTranslations(locale, translations)
|
|
230
203
|
}
|
|
231
204
|
}
|
|
@@ -234,16 +207,10 @@ export abstract class BaseI18n {
|
|
|
234
207
|
* Core route translation loading logic (without reactivity)
|
|
235
208
|
* Subclasses can override addRouteTranslations to add reactivity
|
|
236
209
|
*/
|
|
237
|
-
public loadRouteTranslationsCore(
|
|
238
|
-
locale: string,
|
|
239
|
-
routeName: string,
|
|
240
|
-
translations: Translations,
|
|
241
|
-
merge: boolean,
|
|
242
|
-
): void {
|
|
210
|
+
public loadRouteTranslationsCore(locale: string, routeName: string, translations: Translations, merge: boolean): void {
|
|
243
211
|
if (merge) {
|
|
244
212
|
this.helper.mergeTranslation(locale, routeName, translations, true)
|
|
245
|
-
}
|
|
246
|
-
else {
|
|
213
|
+
} else {
|
|
247
214
|
this.helper.loadPageTranslations(locale, routeName, translations)
|
|
248
215
|
}
|
|
249
216
|
}
|
package/src/format-service.ts
CHANGED
|
@@ -20,7 +20,7 @@ export class FormatService {
|
|
|
20
20
|
const now = new Date()
|
|
21
21
|
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000)
|
|
22
22
|
|
|
23
|
-
const units: { unit: Intl.RelativeTimeFormatUnit
|
|
23
|
+
const units: { unit: Intl.RelativeTimeFormatUnit; seconds: number }[] = [
|
|
24
24
|
{ unit: 'year', seconds: 31536000 },
|
|
25
25
|
{ unit: 'month', seconds: 2592000 },
|
|
26
26
|
{ unit: 'day', seconds: 86400 },
|
package/src/helpers.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import { useTranslationHelper, type TranslationStorage } from './translation'
|
|
2
|
-
import { FormatService } from './format-service'
|
|
3
|
-
import { interpolate, withPrefixStrategy, isNoPrefixStrategy, isPrefixStrategy, isPrefixExceptDefaultStrategy, isPrefixAndDefaultStrategy, defaultPlural } from './helpers'
|
|
4
1
|
import { BaseI18n, type BaseI18nOptions } from './base'
|
|
2
|
+
import { FormatService } from './format-service'
|
|
3
|
+
import {
|
|
4
|
+
defaultPlural,
|
|
5
|
+
interpolate,
|
|
6
|
+
isNoPrefixStrategy,
|
|
7
|
+
isPrefixAndDefaultStrategy,
|
|
8
|
+
isPrefixExceptDefaultStrategy,
|
|
9
|
+
isPrefixStrategy,
|
|
10
|
+
withPrefixStrategy,
|
|
11
|
+
} from './helpers'
|
|
12
|
+
import { type TranslationStorage, useTranslationHelper } from './translation'
|
|
5
13
|
|
|
6
14
|
export {
|
|
7
15
|
useTranslationHelper,
|
package/src/translation.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Translations } from '@i18n-micro/types'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Bare Metal:
|
|
5
|
-
*
|
|
4
|
+
* Bare Metal: Simple translation storage without Ref, useState, devalue.
|
|
5
|
+
* Map key: locale (general) or locale:routeName (page-specific).
|
|
6
6
|
*/
|
|
7
7
|
export interface TranslationStorage {
|
|
8
8
|
translations: Map<string, Translations>
|
|
@@ -24,8 +24,7 @@ function findValue<T = unknown>(data: Translations | null | undefined, key: stri
|
|
|
24
24
|
for (const part of parts) {
|
|
25
25
|
if (value && typeof value === 'object' && part in (value as object)) {
|
|
26
26
|
value = (value as Translations)[part]
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
27
|
+
} else {
|
|
29
28
|
return null
|
|
30
29
|
}
|
|
31
30
|
}
|
|
@@ -82,11 +81,10 @@ export function useTranslationHelper(storage?: TranslationStorage) {
|
|
|
82
81
|
loadPageTranslations(locale: string, routeName: string, data: Translations): void {
|
|
83
82
|
const key = `${locale}:${routeName}`
|
|
84
83
|
const existing = translations.get(key)
|
|
85
|
-
// Perf:
|
|
84
|
+
// Perf: when existing is empty — keep reference, avoid O(n) copying of large objects
|
|
86
85
|
if (!existing || Object.keys(existing).length === 0) {
|
|
87
86
|
translations.set(key, data)
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
87
|
+
} else {
|
|
90
88
|
translations.set(key, { ...existing, ...data })
|
|
91
89
|
}
|
|
92
90
|
},
|
package/tests/base.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import type { PluralFunc, Translations } from '@i18n-micro/types'
|
|
1
2
|
import { BaseI18n, type BaseI18nOptions } from '../src/base'
|
|
2
|
-
import type { Translations, PluralFunc } from '@i18n-micro/types'
|
|
3
3
|
|
|
4
4
|
// Test implementation of BaseI18n
|
|
5
5
|
class TestI18n extends BaseI18n {
|
|
@@ -7,12 +7,7 @@ class TestI18n extends BaseI18n {
|
|
|
7
7
|
private _fallbackLocale: string
|
|
8
8
|
private _route: string
|
|
9
9
|
|
|
10
|
-
constructor(
|
|
11
|
-
locale: string,
|
|
12
|
-
fallbackLocale: string,
|
|
13
|
-
route: string,
|
|
14
|
-
options?: BaseI18nOptions,
|
|
15
|
-
) {
|
|
10
|
+
constructor(locale: string, fallbackLocale: string, route: string, options?: BaseI18nOptions) {
|
|
16
11
|
super(options)
|
|
17
12
|
this._locale = locale
|
|
18
13
|
this._fallbackLocale = fallbackLocale
|
|
@@ -375,7 +370,7 @@ describe('BaseI18n', () => {
|
|
|
375
370
|
|
|
376
371
|
i18n['loadTranslationsCore']('en', translations, false)
|
|
377
372
|
// Wait for async operation to complete
|
|
378
|
-
await new Promise(resolve => setTimeout(resolve, 0))
|
|
373
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
379
374
|
|
|
380
375
|
expect(i18n.has('greeting')).toBe(true)
|
|
381
376
|
})
|
|
@@ -388,7 +383,7 @@ describe('BaseI18n', () => {
|
|
|
388
383
|
await i18n['helper'].loadTranslations('en', initial)
|
|
389
384
|
i18n['loadTranslationsCore']('en', additional, true)
|
|
390
385
|
// Wait for async operation to complete
|
|
391
|
-
await new Promise(resolve => setTimeout(resolve, 0))
|
|
386
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
392
387
|
|
|
393
388
|
expect(i18n.has('greeting')).toBe(true)
|
|
394
389
|
expect(i18n.has('farewell')).toBe(true)
|
|
@@ -402,7 +397,7 @@ describe('BaseI18n', () => {
|
|
|
402
397
|
|
|
403
398
|
i18n['loadRouteTranslationsCore']('en', 'about', translations, false)
|
|
404
399
|
// Wait for async operation to complete
|
|
405
|
-
await new Promise(resolve => setTimeout(resolve, 0))
|
|
400
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
406
401
|
|
|
407
402
|
expect(i18n.has('title', 'about')).toBe(true)
|
|
408
403
|
})
|
|
@@ -415,7 +410,7 @@ describe('BaseI18n', () => {
|
|
|
415
410
|
await i18n['helper'].loadPageTranslations('en', 'about', initial)
|
|
416
411
|
i18n['loadRouteTranslationsCore']('en', 'about', additional, true)
|
|
417
412
|
// Wait for async operation to complete
|
|
418
|
-
await new Promise(resolve => setTimeout(resolve, 0))
|
|
413
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
419
414
|
|
|
420
415
|
expect(i18n.has('title', 'about')).toBe(true)
|
|
421
416
|
expect(i18n.has('description', 'about')).toBe(true)
|
package/tests/core.test.ts
CHANGED
package/tests/helpers.test.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
interpolate,
|
|
3
|
-
withPrefixStrategy,
|
|
4
3
|
isNoPrefixStrategy,
|
|
5
|
-
isPrefixStrategy,
|
|
6
|
-
isPrefixExceptDefaultStrategy,
|
|
7
4
|
isPrefixAndDefaultStrategy,
|
|
5
|
+
isPrefixExceptDefaultStrategy,
|
|
6
|
+
isPrefixStrategy,
|
|
7
|
+
withPrefixStrategy,
|
|
8
8
|
} from '../src/helpers'
|
|
9
9
|
|
|
10
10
|
describe('Helpers', () => {
|
package/vite.config.mts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
2
1
|
// @ts-nocheck
|
|
3
2
|
import { resolve } from 'node:path'
|
|
4
3
|
import { defineConfig } from 'vite'
|
|
@@ -10,7 +9,7 @@ export default defineConfig({
|
|
|
10
9
|
entry: resolve(__dirname, 'src/index.ts'),
|
|
11
10
|
name: '@i18n-micro/core',
|
|
12
11
|
formats: ['cjs', 'es'],
|
|
13
|
-
fileName: format => `index.${format === 'cjs' ? 'cjs' : 'mjs'}`,
|
|
12
|
+
fileName: (format) => `index.${format === 'cjs' ? 'cjs' : 'mjs'}`,
|
|
14
13
|
},
|
|
15
14
|
rollupOptions: {
|
|
16
15
|
external: [],
|
|
@@ -19,7 +18,5 @@ export default defineConfig({
|
|
|
19
18
|
},
|
|
20
19
|
},
|
|
21
20
|
},
|
|
22
|
-
plugins: [
|
|
23
|
-
dts(),
|
|
24
|
-
],
|
|
21
|
+
plugins: [dts()],
|
|
25
22
|
})
|