@fluojs/i18n 1.0.0-beta.1
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/LICENSE +21 -0
- package/README.ko.md +537 -0
- package/README.md +537 -0
- package/dist/adapters.d.ts +180 -0
- package/dist/adapters.d.ts.map +1 -0
- package/dist/adapters.js +266 -0
- package/dist/errors.d.ts +17 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +19 -0
- package/dist/http.d.ts +120 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +179 -0
- package/dist/icu.d.ts +59 -0
- package/dist/icu.d.ts.map +1 -0
- package/dist/icu.js +142 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/loaders/fs.d.ts +43 -0
- package/dist/loaders/fs.d.ts.map +1 -0
- package/dist/loaders/fs.js +79 -0
- package/dist/loaders/remote.d.ts +146 -0
- package/dist/loaders/remote.d.ts.map +1 -0
- package/dist/loaders/remote.js +268 -0
- package/dist/loaders/shared.d.ts +54 -0
- package/dist/loaders/shared.d.ts.map +1 -0
- package/dist/loaders/shared.js +89 -0
- package/dist/locale-resolution.d.ts +86 -0
- package/dist/locale-resolution.d.ts.map +1 -0
- package/dist/locale-resolution.js +201 -0
- package/dist/module.d.ts +22 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +60 -0
- package/dist/options.d.ts +9 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +169 -0
- package/dist/service.d.ts +104 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +348 -0
- package/dist/typegen.d.ts +60 -0
- package/dist/typegen.d.ts.map +1 -0
- package/dist/typegen.js +215 -0
- package/dist/types.d.ts +154 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/validation.d.ts +74 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +123 -0
- package/package.json +97 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalized locale candidate returned by HTTP or non-HTTP locale resolvers.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Parsed `Accept-Language` preference with a validated locale range and q-value.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Input passed to wildcard locale policy callbacks.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Opt-in policy for resolving an `Accept-Language: *` fallback.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Options for locale policy helpers that extend `Accept-Language` matching without changing defaults.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const LOCALE_PATTERN = /^[A-Za-z]{1,8}(?:-[A-Za-z0-9]{1,8})*$/;
|
|
22
|
+
const ACCEPT_LANGUAGE_QVALUE_PATTERN = /^(?:0(?:\.\d{0,3})?|1(?:\.0{0,3})?)$/;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Checks whether a value is a syntactically valid locale identifier for resolver input.
|
|
26
|
+
*
|
|
27
|
+
* @param locale Value to validate as a locale identifier.
|
|
28
|
+
* @returns Whether the value is a string matching the locale grammar accepted by i18n resolvers.
|
|
29
|
+
*/
|
|
30
|
+
export function isValidLocale(locale) {
|
|
31
|
+
return typeof locale === 'string' && LOCALE_PATTERN.test(locale);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Checks whether a valid locale is allowed by an optional supported-locale list.
|
|
36
|
+
*
|
|
37
|
+
* @param locale Locale identifier to check.
|
|
38
|
+
* @param supportedLocales Optional list of supported locale identifiers.
|
|
39
|
+
* @returns Whether the locale is supported, or `true` when no supported-locale list is configured.
|
|
40
|
+
*/
|
|
41
|
+
export function isSupportedLocale(locale, supportedLocales) {
|
|
42
|
+
return supportedLocales === undefined || supportedLocales.length === 0 || supportedLocales.includes(locale);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Normalizes a valid locale candidate to the configured supported locale surface.
|
|
47
|
+
*
|
|
48
|
+
* @param locale Locale candidate from an explicit resolver or language range.
|
|
49
|
+
* @param supportedLocales Optional supported locale allow-list.
|
|
50
|
+
* @param allowBaseLocaleMatch Whether `en-US` may resolve to supported `en`.
|
|
51
|
+
* @returns The supported locale spelling selected by the caller, or `undefined` when unsupported.
|
|
52
|
+
*/
|
|
53
|
+
export function normalizeSupportedLocale(locale, supportedLocales, allowBaseLocaleMatch = true) {
|
|
54
|
+
if (supportedLocales === undefined || supportedLocales.length === 0) {
|
|
55
|
+
return locale;
|
|
56
|
+
}
|
|
57
|
+
const normalizedLocale = locale.toLowerCase();
|
|
58
|
+
const exact = supportedLocales.find(supportedLocale => supportedLocale.toLowerCase() === normalizedLocale);
|
|
59
|
+
if (exact !== undefined) {
|
|
60
|
+
return exact;
|
|
61
|
+
}
|
|
62
|
+
if (!allowBaseLocaleMatch) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
const localeSegments = normalizedLocale.split('-');
|
|
66
|
+
while (localeSegments.length > 1) {
|
|
67
|
+
localeSegments.pop();
|
|
68
|
+
const parentLocale = localeSegments.join('-');
|
|
69
|
+
const supportedParent = supportedLocales.find(supportedLocale => supportedLocale.toLowerCase() === parentLocale);
|
|
70
|
+
if (supportedParent !== undefined) {
|
|
71
|
+
return supportedParent;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Normalizes resolver output into a locale candidate object when the output shape is supported.
|
|
79
|
+
*
|
|
80
|
+
* @param result Resolver output to normalize.
|
|
81
|
+
* @returns A locale candidate, or `undefined` when the resolver output should be ignored.
|
|
82
|
+
*/
|
|
83
|
+
export function normalizeLocaleResolverResult(result) {
|
|
84
|
+
if (result === undefined) {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
if (typeof result === 'string') {
|
|
88
|
+
return {
|
|
89
|
+
locale: result
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (typeof result !== 'object' || result === null || !Object.hasOwn(result, 'locale')) {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
const candidate = result;
|
|
96
|
+
if (candidate.source !== undefined && typeof candidate.source !== 'string') {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
locale: candidate.locale,
|
|
101
|
+
source: candidate.source
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function parseAcceptLanguageQuality(parameters) {
|
|
105
|
+
let quality = 1;
|
|
106
|
+
let hasQuality = false;
|
|
107
|
+
for (const parameter of parameters) {
|
|
108
|
+
const normalizedParameter = parameter.trim();
|
|
109
|
+
const qualityMatch = /^q=(.+)$/i.exec(normalizedParameter);
|
|
110
|
+
if (qualityMatch === null || hasQuality) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
const [, value] = qualityMatch;
|
|
114
|
+
if (value === undefined || !ACCEPT_LANGUAGE_QVALUE_PATTERN.test(value)) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
hasQuality = true;
|
|
118
|
+
quality = Number(value);
|
|
119
|
+
}
|
|
120
|
+
return quality;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Parses one or more `Accept-Language` header values into sorted locale preferences.
|
|
125
|
+
*
|
|
126
|
+
* @param header Header value or values to parse.
|
|
127
|
+
* @returns Valid preferences sorted by descending q-value and original header order.
|
|
128
|
+
*/
|
|
129
|
+
export function parseLocalePreferences(header) {
|
|
130
|
+
const rawHeader = typeof header === 'string' ? header : header?.join(',');
|
|
131
|
+
if (rawHeader === undefined || rawHeader.trim() === '') {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
const preferences = [];
|
|
135
|
+
for (const [index, rawPart] of rawHeader.split(',').entries()) {
|
|
136
|
+
const [rawLocale, ...parameters] = rawPart.split(';');
|
|
137
|
+
const locale = rawLocale?.trim();
|
|
138
|
+
if (locale === undefined || locale === '' || locale !== '*' && !isValidLocale(locale)) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const quality = parseAcceptLanguageQuality(parameters);
|
|
142
|
+
if (quality !== undefined && quality > 0) {
|
|
143
|
+
preferences.push({
|
|
144
|
+
index,
|
|
145
|
+
locale,
|
|
146
|
+
quality
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return [...preferences].sort((left, right) => right.quality - left.quality || left.index - right.index).map(({
|
|
151
|
+
locale,
|
|
152
|
+
quality
|
|
153
|
+
}) => ({
|
|
154
|
+
locale,
|
|
155
|
+
quality
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
function resolveWildcardLocale(policy, defaultLocale, supportedLocales) {
|
|
159
|
+
if (policy === 'defaultLocale') {
|
|
160
|
+
return normalizeSupportedLocale(defaultLocale, supportedLocales, false);
|
|
161
|
+
}
|
|
162
|
+
if (policy === 'firstSupportedLocale') {
|
|
163
|
+
return supportedLocales?.[0] ?? defaultLocale;
|
|
164
|
+
}
|
|
165
|
+
return policy({
|
|
166
|
+
defaultLocale,
|
|
167
|
+
supportedLocales
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Selects a locale from parsed `Accept-Language` preferences using opt-in policy rules.
|
|
173
|
+
*
|
|
174
|
+
* @param preferences Parsed header preferences ordered by q-value.
|
|
175
|
+
* @param defaultLocale Default locale configured for resolver fallback.
|
|
176
|
+
* @param supportedLocales Optional supported locale allow-list.
|
|
177
|
+
* @param options Wildcard and normalization policy options.
|
|
178
|
+
* @returns The selected supported locale, or `undefined` when no policy-selected locale matches.
|
|
179
|
+
*/
|
|
180
|
+
export function selectLocaleFromAcceptLanguagePolicy(preferences, defaultLocale, supportedLocales, options = {}) {
|
|
181
|
+
const allowBaseLocaleMatch = options.normalizeToSupportedLocale ?? true;
|
|
182
|
+
let hasWildcard = false;
|
|
183
|
+
for (const preference of preferences) {
|
|
184
|
+
if (preference.locale === '*') {
|
|
185
|
+
hasWildcard = true;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const normalizedLocale = normalizeSupportedLocale(preference.locale, supportedLocales, allowBaseLocaleMatch);
|
|
189
|
+
if (normalizedLocale !== undefined) {
|
|
190
|
+
return normalizedLocale;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (!hasWildcard || options.wildcardLocale === undefined) {
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
const wildcardLocale = resolveWildcardLocale(options.wildcardLocale, defaultLocale, supportedLocales);
|
|
197
|
+
if (wildcardLocale === undefined || !isValidLocale(wildcardLocale)) {
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
return normalizeSupportedLocale(wildcardLocale, supportedLocales, allowBaseLocaleMatch);
|
|
201
|
+
}
|
package/dist/module.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { I18nModuleOptions } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Module facade that registers the fluo-native core i18n service surface.
|
|
4
|
+
*/
|
|
5
|
+
export declare class I18nModule {
|
|
6
|
+
/**
|
|
7
|
+
* Creates a module class that registers `I18nService` with captured root options.
|
|
8
|
+
*
|
|
9
|
+
* @param options Root i18n module options for catalogs, fallback behavior, and service registration.
|
|
10
|
+
* @returns A module type that can be listed in `imports` during bootstrap.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* @Module({
|
|
15
|
+
* imports: [I18nModule.forRoot({ defaultLocale: 'en' })],
|
|
16
|
+
* })
|
|
17
|
+
* class AppModule {}
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
static forRoot(options?: I18nModuleOptions): new () => I18nModule;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAWpD;;GAEG;AACH,qBAAa,UAAU;IACrB;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,UAAU,UAAU;CAsBlE"}
|
package/dist/module.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
let _initClass;
|
|
2
|
+
function _applyDecs(e, t, n, r, o, i) { var a, c, u, s, f, l, p, d = Symbol.metadata || Symbol.for("Symbol.metadata"), m = Object.defineProperty, h = Object.create, y = [h(null), h(null)], v = t.length; function g(t, n, r) { return function (o, i) { n && (i = o, o = e); for (var a = 0; a < t.length; a++) i = t[a].apply(o, r ? [i] : []); return r ? i : o; }; } function b(e, t, n, r) { if ("function" != typeof e && (r || void 0 !== e)) throw new TypeError(t + " must " + (n || "be") + " a function" + (r ? "" : " or undefined")); return e; } function applyDec(e, t, n, r, o, i, u, s, f, l, p) { function d(e) { if (!p(e)) throw new TypeError("Attempted to access private element on non-instance"); } var h = [].concat(t[0]), v = t[3], w = !u, D = 1 === o, S = 3 === o, j = 4 === o, E = 2 === o; function I(t, n, r) { return function (o, i) { return n && (i = o, o = e), r && r(o), P[t].call(o, i); }; } if (!w) { var P = {}, k = [], F = S ? "get" : j || D ? "set" : "value"; if (f ? (l || D ? P = { get: _setFunctionName(function () { return v(this); }, r, "get"), set: function (e) { t[4](this, e); } } : P[F] = v, l || _setFunctionName(P[F], r, E ? "" : F)) : l || (P = Object.getOwnPropertyDescriptor(e, r)), !l && !f) { if ((c = y[+s][r]) && 7 !== (c ^ o)) throw Error("Decorating two elements with the same name (" + P[F].name + ") is not supported yet"); y[+s][r] = o < 3 ? 1 : o; } } for (var N = e, O = h.length - 1; O >= 0; O -= n ? 2 : 1) { var T = b(h[O], "A decorator", "be", !0), z = n ? h[O - 1] : void 0, A = {}, H = { kind: ["field", "accessor", "method", "getter", "setter", "class"][o], name: r, metadata: a, addInitializer: function (e, t) { if (e.v) throw new TypeError("attempted to call addInitializer after decoration was finished"); b(t, "An initializer", "be", !0), i.push(t); }.bind(null, A) }; if (w) c = T.call(z, N, H), A.v = 1, b(c, "class decorators", "return") && (N = c);else if (H.static = s, H.private = f, c = H.access = { has: f ? p.bind() : function (e) { return r in e; } }, j || (c.get = f ? E ? function (e) { return d(e), P.value; } : I("get", 0, d) : function (e) { return e[r]; }), E || S || (c.set = f ? I("set", 0, d) : function (e, t) { e[r] = t; }), N = T.call(z, D ? { get: P.get, set: P.set } : P[F], H), A.v = 1, D) { if ("object" == typeof N && N) (c = b(N.get, "accessor.get")) && (P.get = c), (c = b(N.set, "accessor.set")) && (P.set = c), (c = b(N.init, "accessor.init")) && k.unshift(c);else if (void 0 !== N) throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined"); } else b(N, (l ? "field" : "method") + " decorators", "return") && (l ? k.unshift(N) : P[F] = N); } return o < 2 && u.push(g(k, s, 1), g(i, s, 0)), l || w || (f ? D ? u.splice(-1, 0, I("get", s), I("set", s)) : u.push(E ? P[F] : b.call.bind(P[F])) : m(e, r, P)), N; } function w(e) { return m(e, d, { configurable: !0, enumerable: !0, value: a }); } return void 0 !== i && (a = i[d]), a = h(null == a ? null : a), f = [], l = function (e) { e && f.push(g(e)); }, p = function (t, r) { for (var i = 0; i < n.length; i++) { var a = n[i], c = a[1], l = 7 & c; if ((8 & c) == t && !l == r) { var p = a[2], d = !!a[3], m = 16 & c; applyDec(t ? e : e.prototype, a, m, d ? "#" + p : _toPropertyKey(p), l, l < 2 ? [] : t ? s = s || [] : u = u || [], f, !!t, d, r, t && d ? function (t) { return _checkInRHS(t) === e; } : o); } } }, p(8, 0), p(0, 0), p(8, 1), p(0, 1), l(u), l(s), c = f, v || w(e), { e: c, get c() { var n = []; return v && [w(e = applyDec(e, [t], r, e.name, 5, n)), g(n, 1)]; } }; }
|
|
3
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
4
|
+
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
5
|
+
function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
|
|
6
|
+
function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
|
|
7
|
+
import { Inject } from '@fluojs/core';
|
|
8
|
+
import { defineModuleMetadata } from '@fluojs/core/internal';
|
|
9
|
+
import { snapshotI18nModuleOptions } from './options.js';
|
|
10
|
+
import { I18nService } from './service.js';
|
|
11
|
+
const I18N_MODULE_OPTIONS = Symbol('fluo.i18n.module-options');
|
|
12
|
+
let _I18nServiceFactory;
|
|
13
|
+
class I18nServiceFactory extends I18nService {
|
|
14
|
+
static {
|
|
15
|
+
[_I18nServiceFactory, _initClass] = _applyDecs(this, [Inject(I18N_MODULE_OPTIONS)], [], 0, void 0, I18nService).c;
|
|
16
|
+
}
|
|
17
|
+
constructor(options) {
|
|
18
|
+
super(options);
|
|
19
|
+
}
|
|
20
|
+
static {
|
|
21
|
+
_initClass();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Module facade that registers the fluo-native core i18n service surface.
|
|
27
|
+
*/
|
|
28
|
+
export class I18nModule {
|
|
29
|
+
/**
|
|
30
|
+
* Creates a module class that registers `I18nService` with captured root options.
|
|
31
|
+
*
|
|
32
|
+
* @param options Root i18n module options for catalogs, fallback behavior, and service registration.
|
|
33
|
+
* @returns A module type that can be listed in `imports` during bootstrap.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* @Module({
|
|
38
|
+
* imports: [I18nModule.forRoot({ defaultLocale: 'en' })],
|
|
39
|
+
* })
|
|
40
|
+
* class AppModule {}
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
static forRoot(options) {
|
|
44
|
+
const moduleOptions = snapshotI18nModuleOptions(options);
|
|
45
|
+
class I18nModuleImpl extends I18nModule {}
|
|
46
|
+
const providers = [{
|
|
47
|
+
provide: I18N_MODULE_OPTIONS,
|
|
48
|
+
useValue: moduleOptions
|
|
49
|
+
}, {
|
|
50
|
+
provide: I18nService,
|
|
51
|
+
useClass: _I18nServiceFactory
|
|
52
|
+
}];
|
|
53
|
+
defineModuleMetadata(I18nModuleImpl, {
|
|
54
|
+
global: moduleOptions.global ?? true,
|
|
55
|
+
exports: [I18nService],
|
|
56
|
+
providers
|
|
57
|
+
});
|
|
58
|
+
return I18nModuleImpl;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { I18nModuleOptions } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a detached copy of root i18n module options.
|
|
4
|
+
*
|
|
5
|
+
* @param options Root i18n module options provided by the caller.
|
|
6
|
+
* @returns A detached options snapshot for module registration or standalone service creation.
|
|
7
|
+
*/
|
|
8
|
+
export declare function snapshotI18nModuleOptions(options?: I18nModuleOptions): I18nModuleOptions;
|
|
9
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAgF,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAwMlI;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,GAAE,iBAAsB,GAAG,iBAAiB,CAU5F"}
|
package/dist/options.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { I18nError } from './errors.js';
|
|
2
|
+
function isPlainObject(value) {
|
|
3
|
+
if (typeof value !== 'object' || value === null) {
|
|
4
|
+
return false;
|
|
5
|
+
}
|
|
6
|
+
const prototype = Object.getPrototypeOf(value);
|
|
7
|
+
return prototype === Object.prototype || prototype === null;
|
|
8
|
+
}
|
|
9
|
+
function assertLocale(value, label) {
|
|
10
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
11
|
+
throw new I18nError(`${label} must be a non-empty string.`, 'I18N_INVALID_LOCALE_CONFIG');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function assertLocaleList(value, label) {
|
|
15
|
+
if (!Array.isArray(value)) {
|
|
16
|
+
throw new I18nError(`${label} must be an array of locale strings.`, 'I18N_INVALID_LOCALE_CONFIG');
|
|
17
|
+
}
|
|
18
|
+
for (const locale of value) {
|
|
19
|
+
assertLocale(locale, label);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function assertSupportedLocale(locale, supportedLocales, label) {
|
|
23
|
+
if (supportedLocales !== undefined && !supportedLocales.includes(locale)) {
|
|
24
|
+
throw new I18nError(`${label} must be listed in supportedLocales.`, 'I18N_INVALID_LOCALE_CONFIG');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function snapshotIntlOptions(value, path) {
|
|
28
|
+
if (!isPlainObject(value)) {
|
|
29
|
+
throw new I18nError(`${path} must be a plain object option bag.`, 'I18N_INVALID_OPTIONS');
|
|
30
|
+
}
|
|
31
|
+
return Object.freeze({
|
|
32
|
+
...value
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function snapshotNamedIntlFormats(value, path) {
|
|
36
|
+
if (value === undefined) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
if (!isPlainObject(value)) {
|
|
40
|
+
throw new I18nError(`${path} must be a named format map.`, 'I18N_INVALID_OPTIONS');
|
|
41
|
+
}
|
|
42
|
+
const snapshot = {};
|
|
43
|
+
for (const [name, options] of Object.entries(value)) {
|
|
44
|
+
if (name.trim() === '') {
|
|
45
|
+
throw new I18nError(`${path} contains an empty format name.`, 'I18N_INVALID_OPTIONS');
|
|
46
|
+
}
|
|
47
|
+
snapshot[name] = snapshotIntlOptions(options, `${path}.${name}`);
|
|
48
|
+
}
|
|
49
|
+
return Object.freeze(snapshot);
|
|
50
|
+
}
|
|
51
|
+
function snapshotFormats(formats) {
|
|
52
|
+
if (formats === undefined) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
if (!isPlainObject(formats)) {
|
|
56
|
+
throw new I18nError('formats must be a plain object when provided.', 'I18N_INVALID_OPTIONS');
|
|
57
|
+
}
|
|
58
|
+
return Object.freeze({
|
|
59
|
+
dateTime: snapshotNamedIntlFormats(formats.dateTime, 'formats.dateTime'),
|
|
60
|
+
list: snapshotNamedIntlFormats(formats.list, 'formats.list'),
|
|
61
|
+
number: snapshotNamedIntlFormats(formats.number, 'formats.number'),
|
|
62
|
+
relativeTime: snapshotNamedIntlFormats(formats.relativeTime, 'formats.relativeTime')
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function snapshotMessageTree(value, path) {
|
|
66
|
+
if (!isPlainObject(value)) {
|
|
67
|
+
throw new I18nError(`${path} must be a plain object message tree.`, 'I18N_INVALID_CATALOG');
|
|
68
|
+
}
|
|
69
|
+
const snapshot = {};
|
|
70
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
71
|
+
if (key.trim() === '') {
|
|
72
|
+
throw new I18nError(`${path} contains an empty message key segment.`, 'I18N_INVALID_CATALOG');
|
|
73
|
+
}
|
|
74
|
+
if (typeof entry === 'string') {
|
|
75
|
+
snapshot[key] = entry;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (isPlainObject(entry)) {
|
|
79
|
+
snapshot[key] = snapshotMessageTree(entry, `${path}.${key}`);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
throw new I18nError(`${path}.${key} must be a string or nested message tree.`, 'I18N_INVALID_CATALOG');
|
|
83
|
+
}
|
|
84
|
+
return Object.freeze(snapshot);
|
|
85
|
+
}
|
|
86
|
+
function snapshotCatalogs(catalogs, supportedLocales) {
|
|
87
|
+
if (catalogs === undefined) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
if (!isPlainObject(catalogs)) {
|
|
91
|
+
throw new I18nError('catalogs must be a locale-scoped object.', 'I18N_INVALID_CATALOG');
|
|
92
|
+
}
|
|
93
|
+
const snapshot = {};
|
|
94
|
+
for (const [locale, tree] of Object.entries(catalogs)) {
|
|
95
|
+
assertLocale(locale, 'catalog locale');
|
|
96
|
+
assertSupportedLocale(locale, supportedLocales, 'catalog locale');
|
|
97
|
+
snapshot[locale] = snapshotMessageTree(tree, `catalogs.${locale}`);
|
|
98
|
+
}
|
|
99
|
+
return Object.freeze(snapshot);
|
|
100
|
+
}
|
|
101
|
+
function snapshotFallbackLocales(fallbackLocales, supportedLocales) {
|
|
102
|
+
if (fallbackLocales === undefined) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
if (Array.isArray(fallbackLocales)) {
|
|
106
|
+
assertLocaleList(fallbackLocales, 'fallbackLocales');
|
|
107
|
+
for (const fallbackLocale of fallbackLocales) {
|
|
108
|
+
assertSupportedLocale(fallbackLocale, supportedLocales, 'fallbackLocales locale');
|
|
109
|
+
}
|
|
110
|
+
return Object.freeze([...fallbackLocales]);
|
|
111
|
+
}
|
|
112
|
+
if (!isPlainObject(fallbackLocales)) {
|
|
113
|
+
throw new I18nError('fallbackLocales must be an array or locale map.', 'I18N_INVALID_LOCALE_CONFIG');
|
|
114
|
+
}
|
|
115
|
+
const snapshot = {};
|
|
116
|
+
for (const [locale, chain] of Object.entries(fallbackLocales)) {
|
|
117
|
+
assertLocale(locale, 'fallbackLocales locale');
|
|
118
|
+
assertSupportedLocale(locale, supportedLocales, 'fallbackLocales locale');
|
|
119
|
+
assertLocaleList(chain, `fallbackLocales.${locale}`);
|
|
120
|
+
for (const fallbackLocale of chain) {
|
|
121
|
+
assertSupportedLocale(fallbackLocale, supportedLocales, `fallbackLocales.${locale} locale`);
|
|
122
|
+
}
|
|
123
|
+
snapshot[locale] = Object.freeze([...chain]);
|
|
124
|
+
}
|
|
125
|
+
return Object.freeze(snapshot);
|
|
126
|
+
}
|
|
127
|
+
function validateModuleOptions(options) {
|
|
128
|
+
if (!isPlainObject(options)) {
|
|
129
|
+
throw new I18nError('i18n module options must be a plain object.', 'I18N_INVALID_OPTIONS');
|
|
130
|
+
}
|
|
131
|
+
if (options.defaultLocale !== undefined) {
|
|
132
|
+
assertLocale(options.defaultLocale, 'defaultLocale');
|
|
133
|
+
}
|
|
134
|
+
if (options.global !== undefined && typeof options.global !== 'boolean') {
|
|
135
|
+
throw new I18nError('global must be a boolean when provided.', 'I18N_INVALID_OPTIONS');
|
|
136
|
+
}
|
|
137
|
+
if (options.missingMessage !== undefined && typeof options.missingMessage !== 'function') {
|
|
138
|
+
throw new I18nError('missingMessage must be a function when provided.', 'I18N_INVALID_OPTIONS');
|
|
139
|
+
}
|
|
140
|
+
if (options.formats !== undefined && !isPlainObject(options.formats)) {
|
|
141
|
+
throw new I18nError('formats must be a plain object when provided.', 'I18N_INVALID_OPTIONS');
|
|
142
|
+
}
|
|
143
|
+
if (options.supportedLocales !== undefined) {
|
|
144
|
+
assertLocaleList(options.supportedLocales, 'supportedLocales');
|
|
145
|
+
if (options.defaultLocale !== undefined) {
|
|
146
|
+
assertSupportedLocale(options.defaultLocale, options.supportedLocales, 'defaultLocale');
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (options.catalogs !== undefined && options.defaultLocale === undefined) {
|
|
150
|
+
throw new I18nError('defaultLocale is required when catalogs are configured.', 'I18N_INVALID_OPTIONS');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Creates a detached copy of root i18n module options.
|
|
156
|
+
*
|
|
157
|
+
* @param options Root i18n module options provided by the caller.
|
|
158
|
+
* @returns A detached options snapshot for module registration or standalone service creation.
|
|
159
|
+
*/
|
|
160
|
+
export function snapshotI18nModuleOptions(options = {}) {
|
|
161
|
+
validateModuleOptions(options);
|
|
162
|
+
return Object.freeze({
|
|
163
|
+
...options,
|
|
164
|
+
catalogs: snapshotCatalogs(options.catalogs, options.supportedLocales),
|
|
165
|
+
fallbackLocales: snapshotFallbackLocales(options.fallbackLocales, options.supportedLocales),
|
|
166
|
+
formats: snapshotFormats(options.formats),
|
|
167
|
+
supportedLocales: options.supportedLocales ? Object.freeze([...options.supportedLocales]) : undefined
|
|
168
|
+
});
|
|
169
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { I18nCurrencyFormatOptions, I18nDateTimeFormatOptions, I18nListFormatOptions, I18nLocale, I18nModuleOptions, I18nNumberFormatOptions, I18nRelativeTimeFormatOptions, I18nTranslateOptions } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Framework-agnostic core translation service backed by locale-scoped message catalogs.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Locale selection is explicit per call. The core service performs deterministic catalog lookup and string
|
|
7
|
+
* interpolation only; request locale detection, loaders, ICU/messageformat, and framework adapters remain out of scope.
|
|
8
|
+
*/
|
|
9
|
+
export declare class I18nService {
|
|
10
|
+
private readonly options;
|
|
11
|
+
/**
|
|
12
|
+
* Creates a service with a detached options snapshot.
|
|
13
|
+
*
|
|
14
|
+
* @param options Root i18n options captured at the application boundary.
|
|
15
|
+
*/
|
|
16
|
+
constructor(options?: I18nModuleOptions);
|
|
17
|
+
/**
|
|
18
|
+
* Returns a detached copy of the root i18n options snapshot.
|
|
19
|
+
*
|
|
20
|
+
* @returns The captured i18n module options without exposing mutable service internals.
|
|
21
|
+
*/
|
|
22
|
+
snapshotOptions(): I18nModuleOptions;
|
|
23
|
+
/**
|
|
24
|
+
* Returns the deterministic locale chain used for a translation request.
|
|
25
|
+
*
|
|
26
|
+
* @param locale Explicit caller locale to resolve first.
|
|
27
|
+
* @returns Locale chain ordered as requested locale, configured fallback chain, then default locale.
|
|
28
|
+
* @throws {I18nError} When the locale is invalid or outside `supportedLocales`.
|
|
29
|
+
*/
|
|
30
|
+
resolveLocales(locale: I18nLocale): readonly I18nLocale[];
|
|
31
|
+
private resolveNamedOptions;
|
|
32
|
+
/**
|
|
33
|
+
* Formats a date or timestamp for an explicit locale using `Intl.DateTimeFormat`.
|
|
34
|
+
*
|
|
35
|
+
* @param value Date instance or epoch timestamp accepted by `Intl.DateTimeFormat`.
|
|
36
|
+
* @param options Explicit locale, optional named format, and inline date/time options.
|
|
37
|
+
* @returns Locale-formatted date/time text from the host standard `Intl` implementation.
|
|
38
|
+
* @throws {I18nError} When options are invalid or a named date/time format is missing.
|
|
39
|
+
*/
|
|
40
|
+
formatDateTime(value: Date | number, options: I18nDateTimeFormatOptions): string;
|
|
41
|
+
/**
|
|
42
|
+
* Formats a number for an explicit locale using `Intl.NumberFormat`.
|
|
43
|
+
*
|
|
44
|
+
* @param value Number value passed to `Intl.NumberFormat`.
|
|
45
|
+
* @param options Explicit locale, optional named format, and inline number options.
|
|
46
|
+
* @returns Locale-formatted number text from the host standard `Intl` implementation.
|
|
47
|
+
* @throws {I18nError} When options are invalid or a named number format is missing.
|
|
48
|
+
*/
|
|
49
|
+
formatNumber(value: number, options: I18nNumberFormatOptions): string;
|
|
50
|
+
/**
|
|
51
|
+
* Formats a currency amount for an explicit locale using `Intl.NumberFormat`.
|
|
52
|
+
*
|
|
53
|
+
* @param value Currency amount passed to `Intl.NumberFormat`.
|
|
54
|
+
* @param options Explicit locale, ISO 4217 currency code, optional named format, and inline number options.
|
|
55
|
+
* @returns Locale-formatted currency text from the host standard `Intl` implementation.
|
|
56
|
+
* @throws {I18nError} When options are invalid or a named number format is missing.
|
|
57
|
+
*/
|
|
58
|
+
formatCurrency(value: number, options: I18nCurrencyFormatOptions): string;
|
|
59
|
+
/**
|
|
60
|
+
* Formats a ratio as a percent for an explicit locale using `Intl.NumberFormat`.
|
|
61
|
+
*
|
|
62
|
+
* @param value Ratio value passed to `Intl.NumberFormat` with `style: 'percent'`.
|
|
63
|
+
* @param options Explicit locale, optional named format, and inline number options.
|
|
64
|
+
* @returns Locale-formatted percent text from the host standard `Intl` implementation.
|
|
65
|
+
* @throws {I18nError} When options are invalid or a named number format is missing.
|
|
66
|
+
*/
|
|
67
|
+
formatPercent(value: number, options: I18nNumberFormatOptions): string;
|
|
68
|
+
/**
|
|
69
|
+
* Formats a string list for an explicit locale using `Intl.ListFormat`.
|
|
70
|
+
*
|
|
71
|
+
* @param values List item strings passed to `Intl.ListFormat`.
|
|
72
|
+
* @param options Explicit locale, optional named format, and inline list options.
|
|
73
|
+
* @returns Locale-formatted list text from the host standard `Intl` implementation.
|
|
74
|
+
* @throws {I18nError} When options are invalid or a named list format is missing.
|
|
75
|
+
*/
|
|
76
|
+
formatList(values: readonly string[], options: I18nListFormatOptions): string;
|
|
77
|
+
/**
|
|
78
|
+
* Formats a relative time value for an explicit locale using `Intl.RelativeTimeFormat`.
|
|
79
|
+
*
|
|
80
|
+
* @param value Numeric offset passed to `Intl.RelativeTimeFormat`.
|
|
81
|
+
* @param unit Relative time unit such as `day`, `hour`, or `minute`.
|
|
82
|
+
* @param options Explicit locale, optional named format, and inline relative time options.
|
|
83
|
+
* @returns Locale-formatted relative time text from the host standard `Intl` implementation.
|
|
84
|
+
* @throws {I18nError} When options are invalid or a named relative time format is missing.
|
|
85
|
+
*/
|
|
86
|
+
formatRelativeTime(value: number, unit: Intl.RelativeTimeFormatUnit, options: I18nRelativeTimeFormatOptions): string;
|
|
87
|
+
/**
|
|
88
|
+
* Resolves and interpolates a catalog message for an explicit locale.
|
|
89
|
+
*
|
|
90
|
+
* @param key Dot-path catalog key, optionally prefixed by `options.namespace`.
|
|
91
|
+
* @param options Per-call locale, interpolation values, and default value.
|
|
92
|
+
* @returns The resolved catalog message, default value, or missing-message hook result.
|
|
93
|
+
* @throws {I18nError} When options are invalid or the message cannot be resolved.
|
|
94
|
+
*/
|
|
95
|
+
translate(key: string, options: I18nTranslateOptions): string;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Creates a standalone i18n service without registering a fluo module.
|
|
99
|
+
*
|
|
100
|
+
* @param options Root i18n options for the standalone service instance.
|
|
101
|
+
* @returns An `I18nService` configured with a detached options snapshot.
|
|
102
|
+
*/
|
|
103
|
+
export declare function createI18n(options?: I18nModuleOptions): I18nService;
|
|
104
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,yBAAyB,EACzB,yBAAyB,EAEzB,qBAAqB,EACrB,UAAU,EAEV,iBAAiB,EACjB,uBAAuB,EACvB,6BAA6B,EAC7B,oBAAoB,EACrB,MAAM,YAAY,CAAC;AA+IpB;;;;;;GAMG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAE5C;;;;OAIG;gBACS,OAAO,GAAE,iBAAsB;IAI3C;;;;OAIG;IACH,eAAe,IAAI,iBAAiB;IAIpC;;;;;;OAMG;IACH,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,SAAS,UAAU,EAAE;IA2BzD,OAAO,CAAC,mBAAmB;IAkB3B;;;;;;;OAOG;IACH,cAAc,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,EAAE,OAAO,EAAE,yBAAyB,GAAG,MAAM;IAWhF;;;;;;;OAOG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,uBAAuB,GAAG,MAAM;IAWrE;;;;;;;OAOG;IACH,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,yBAAyB,GAAG,MAAM;IAiBzE;;;;;;;OAOG;IACH,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,uBAAuB,GAAG,MAAM;IAWtE;;;;;;;OAOG;IACH,UAAU,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,qBAAqB,GAAG,MAAM;IAY7E;;;;;;;;OAQG;IACH,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,sBAAsB,EAAE,OAAO,EAAE,6BAA6B,GAAG,MAAM;IAWpH;;;;;;;OAOG;IACH,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,GAAG,MAAM;CAoC9D;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,OAAO,GAAE,iBAAsB,GAAG,WAAW,CAEvE"}
|