@frame-kit/utils 0.2.9 → 0.2.10
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/CHANGELOG.md +4 -0
- package/package.json +1 -1
- package/src/index.d.ts +3 -0
- package/src/index.js +3 -0
- package/src/index.js.map +1 -1
- package/src/lib/i18n/i18n.d.ts +55 -0
- package/src/lib/i18n/i18n.js +175 -0
- package/src/lib/i18n/i18n.js.map +1 -0
- package/src/lib/i18n/i18n.locale.d.ts +34 -0
- package/src/lib/i18n/i18n.locale.js +62 -0
- package/src/lib/i18n/i18n.locale.js.map +1 -0
- package/src/lib/i18n/i18n.types.d.ts +17 -0
- package/src/lib/i18n/i18n.types.js +2 -0
- package/src/lib/i18n/i18n.types.js.map +1 -0
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -2,3 +2,6 @@ export * from './lib/color/color.js';
|
|
|
2
2
|
export * from './lib/color/color.types.js';
|
|
3
3
|
export * from './lib/date/date.js';
|
|
4
4
|
export * from './lib/date/date.types.js';
|
|
5
|
+
export * from './lib/i18n/i18n.js';
|
|
6
|
+
export * from './lib/i18n/i18n.locale.js';
|
|
7
|
+
export * from './lib/i18n/i18n.types.js';
|
package/src/index.js
CHANGED
|
@@ -2,4 +2,7 @@ export * from './lib/color/color.js';
|
|
|
2
2
|
export * from './lib/color/color.types.js';
|
|
3
3
|
export * from './lib/date/date.js';
|
|
4
4
|
export * from './lib/date/date.types.js';
|
|
5
|
+
export * from './lib/i18n/i18n.js';
|
|
6
|
+
export * from './lib/i18n/i18n.locale.js';
|
|
7
|
+
export * from './lib/i18n/i18n.types.js';
|
|
5
8
|
//# sourceMappingURL=index.js.map
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/utils/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/utils/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { LocaleMap, MessageKey, Messages } from './i18n.types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Dot-path message lookup with `{{var}}` interpolation, a minimal ICU plural
|
|
4
|
+
* subset, and deep merge across several sources.
|
|
5
|
+
*
|
|
6
|
+
* Deliberately free of any framework or DOM dependency, so the same instance
|
|
7
|
+
* works in a browser app, in server-side rendering, and in a backend that
|
|
8
|
+
* renders emails or hosted pages — the three places a product's copy has to
|
|
9
|
+
* agree. The Angular wrapper that adds signal reactivity and browser locale
|
|
10
|
+
* detection lives in `@frame-kit/ui-ng/services/i18n`.
|
|
11
|
+
*/
|
|
12
|
+
export declare class I18nService {
|
|
13
|
+
protected localeValue: string;
|
|
14
|
+
protected messages: Messages;
|
|
15
|
+
protected readonly localeMaps: LocaleMap[];
|
|
16
|
+
protected readonly staticSources: Messages[];
|
|
17
|
+
protected readonly supportedLocaleList: string[];
|
|
18
|
+
constructor(config: {
|
|
19
|
+
locale?: string;
|
|
20
|
+
supportedLocales?: string[];
|
|
21
|
+
localeMaps?: LocaleMap[];
|
|
22
|
+
sources?: Messages[];
|
|
23
|
+
});
|
|
24
|
+
getLocale(): string;
|
|
25
|
+
getSupportedLocales(): string[];
|
|
26
|
+
/**
|
|
27
|
+
* A message by dot-path, with optional interpolation.
|
|
28
|
+
*
|
|
29
|
+
* Returns the key itself when nothing is found. That is deliberate: a missing
|
|
30
|
+
* message renders as `auth.signIn.title` rather than as an empty space, so it
|
|
31
|
+
* is visible in review instead of silently disappearing.
|
|
32
|
+
*/
|
|
33
|
+
get(key: MessageKey, vars?: Record<string, string | number>): string;
|
|
34
|
+
/**
|
|
35
|
+
* A non-string value by dot-path — a list of steps, a table of rows.
|
|
36
|
+
*
|
|
37
|
+
* With `vars`, every string reachable inside is interpolated and the shape is
|
|
38
|
+
* preserved. Content of this kind mixes strings, arrays and objects, and a
|
|
39
|
+
* placeholder left in any one of them renders literally, so substitution has
|
|
40
|
+
* to follow the data rather than assume a shape.
|
|
41
|
+
*/
|
|
42
|
+
getRaw<T = unknown>(path: string, vars?: Record<string, string | number>): T | undefined;
|
|
43
|
+
/** No-ops on an unsupported locale rather than rendering keys. */
|
|
44
|
+
setLocale(locale: string): void;
|
|
45
|
+
/**
|
|
46
|
+
* Add a locale map after construction — for a lazily loaded feature that
|
|
47
|
+
* ships its own copy and should not be paid for at boot.
|
|
48
|
+
*/
|
|
49
|
+
registerLocaleMap(map: LocaleMap): void;
|
|
50
|
+
/** Overridden by the reactive subclass to read from a signal. */
|
|
51
|
+
protected currentMessages(): Messages;
|
|
52
|
+
/** Fires after messages are rebuilt; the reactive subclass syncs signals. */
|
|
53
|
+
protected onLocaleChanged(): void;
|
|
54
|
+
private buildMessages;
|
|
55
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dot-path message lookup with `{{var}}` interpolation, a minimal ICU plural
|
|
3
|
+
* subset, and deep merge across several sources.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately free of any framework or DOM dependency, so the same instance
|
|
6
|
+
* works in a browser app, in server-side rendering, and in a backend that
|
|
7
|
+
* renders emails or hosted pages — the three places a product's copy has to
|
|
8
|
+
* agree. The Angular wrapper that adds signal reactivity and browser locale
|
|
9
|
+
* detection lives in `@frame-kit/ui-ng/services/i18n`.
|
|
10
|
+
*/
|
|
11
|
+
export class I18nService {
|
|
12
|
+
constructor(config) {
|
|
13
|
+
var _a, _b, _c, _d, _e;
|
|
14
|
+
this.supportedLocaleList = (_a = config.supportedLocales) !== null && _a !== void 0 ? _a : ['en'];
|
|
15
|
+
this.localeMaps = (_b = config.localeMaps) !== null && _b !== void 0 ? _b : [];
|
|
16
|
+
this.staticSources = (_c = config.sources) !== null && _c !== void 0 ? _c : [];
|
|
17
|
+
// No detection here: the caller passes an explicit locale. A browser app
|
|
18
|
+
// resolves it from storage or `navigator`, a backend from `Accept-Language`
|
|
19
|
+
// — neither of which this base should know about.
|
|
20
|
+
this.localeValue = (_e = (_d = config.locale) !== null && _d !== void 0 ? _d : this.supportedLocaleList[0]) !== null && _e !== void 0 ? _e : 'en';
|
|
21
|
+
this.messages = this.buildMessages(this.localeValue);
|
|
22
|
+
}
|
|
23
|
+
getLocale() {
|
|
24
|
+
return this.localeValue;
|
|
25
|
+
}
|
|
26
|
+
getSupportedLocales() {
|
|
27
|
+
return [...this.supportedLocaleList];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A message by dot-path, with optional interpolation.
|
|
31
|
+
*
|
|
32
|
+
* Returns the key itself when nothing is found. That is deliberate: a missing
|
|
33
|
+
* message renders as `auth.signIn.title` rather than as an empty space, so it
|
|
34
|
+
* is visible in review instead of silently disappearing.
|
|
35
|
+
*/
|
|
36
|
+
get(key, vars) {
|
|
37
|
+
const value = getByPath(this.currentMessages(), key);
|
|
38
|
+
if (value === undefined) {
|
|
39
|
+
return key;
|
|
40
|
+
}
|
|
41
|
+
const str = String(value);
|
|
42
|
+
if (!vars || Object.keys(vars).length === 0) {
|
|
43
|
+
return str;
|
|
44
|
+
}
|
|
45
|
+
return interpolate(str, vars);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A non-string value by dot-path — a list of steps, a table of rows.
|
|
49
|
+
*
|
|
50
|
+
* With `vars`, every string reachable inside is interpolated and the shape is
|
|
51
|
+
* preserved. Content of this kind mixes strings, arrays and objects, and a
|
|
52
|
+
* placeholder left in any one of them renders literally, so substitution has
|
|
53
|
+
* to follow the data rather than assume a shape.
|
|
54
|
+
*/
|
|
55
|
+
getRaw(path, vars) {
|
|
56
|
+
const value = getByPath(this.currentMessages(), path);
|
|
57
|
+
if (value === undefined || !vars || Object.keys(vars).length === 0) {
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
return interpolateDeep(value, vars);
|
|
61
|
+
}
|
|
62
|
+
/** No-ops on an unsupported locale rather than rendering keys. */
|
|
63
|
+
setLocale(locale) {
|
|
64
|
+
if (!this.supportedLocaleList.includes(locale)) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
this.localeValue = locale;
|
|
68
|
+
this.messages = this.buildMessages(locale);
|
|
69
|
+
this.onLocaleChanged();
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Add a locale map after construction — for a lazily loaded feature that
|
|
73
|
+
* ships its own copy and should not be paid for at boot.
|
|
74
|
+
*/
|
|
75
|
+
registerLocaleMap(map) {
|
|
76
|
+
this.localeMaps.push(map);
|
|
77
|
+
this.messages = this.buildMessages(this.localeValue);
|
|
78
|
+
this.onLocaleChanged();
|
|
79
|
+
}
|
|
80
|
+
/** Overridden by the reactive subclass to read from a signal. */
|
|
81
|
+
currentMessages() {
|
|
82
|
+
return this.messages;
|
|
83
|
+
}
|
|
84
|
+
/** Fires after messages are rebuilt; the reactive subclass syncs signals. */
|
|
85
|
+
onLocaleChanged() {
|
|
86
|
+
// no-op in the framework-agnostic base
|
|
87
|
+
}
|
|
88
|
+
buildMessages(locale) {
|
|
89
|
+
const sources = [];
|
|
90
|
+
for (const map of this.localeMaps) {
|
|
91
|
+
if (map[locale]) {
|
|
92
|
+
sources.push(map[locale]);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
for (const source of this.staticSources) {
|
|
96
|
+
sources.push(source);
|
|
97
|
+
}
|
|
98
|
+
return mergeMessages(...sources);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// ── internals ────────────────────────────────────────────────────────────
|
|
102
|
+
function getByPath(obj, path) {
|
|
103
|
+
let current = obj;
|
|
104
|
+
for (const key of path.split('.')) {
|
|
105
|
+
if (current === null ||
|
|
106
|
+
current === undefined ||
|
|
107
|
+
typeof current !== 'object') {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
current = current[key];
|
|
111
|
+
}
|
|
112
|
+
return current;
|
|
113
|
+
}
|
|
114
|
+
function interpolateDeep(value, vars) {
|
|
115
|
+
if (typeof value === 'string') {
|
|
116
|
+
return interpolate(value, vars);
|
|
117
|
+
}
|
|
118
|
+
if (Array.isArray(value)) {
|
|
119
|
+
return value.map((entry) => interpolateDeep(entry, vars));
|
|
120
|
+
}
|
|
121
|
+
if (value !== null && typeof value === 'object') {
|
|
122
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
|
|
123
|
+
key,
|
|
124
|
+
interpolateDeep(entry, vars),
|
|
125
|
+
]));
|
|
126
|
+
}
|
|
127
|
+
return value;
|
|
128
|
+
}
|
|
129
|
+
function interpolate(str, vars) {
|
|
130
|
+
// An unknown placeholder is left as written rather than blanked: `{{name}}`
|
|
131
|
+
// on the page says which variable was not passed.
|
|
132
|
+
return applyPlurals(str, vars).replace(/\{\{(\w+)}}/g, (match, key) => (key in vars ? String(vars[key]) : match));
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* A minimal ICU plural subset: `{count, plural, =0 {none} other {# items}}`.
|
|
136
|
+
*
|
|
137
|
+
* Exact selectors plus `other`, with no nested braces in branch bodies. Enough
|
|
138
|
+
* for the counts copy actually contains, without pulling in a full ICU parser.
|
|
139
|
+
*/
|
|
140
|
+
function applyPlurals(str, vars) {
|
|
141
|
+
const PLURAL = /\{(\w+),\s*plural,\s*((?:[^{}]+|\{[^{}]*\})*)\}/g;
|
|
142
|
+
return str.replace(PLURAL, (match, name, cases) => {
|
|
143
|
+
if (!(name in vars)) {
|
|
144
|
+
return match;
|
|
145
|
+
}
|
|
146
|
+
const count = Number(vars[name]);
|
|
147
|
+
const exact = new RegExp(`=${count}\\s*\\{([^{}]*)\\}`).exec(cases);
|
|
148
|
+
if (exact) {
|
|
149
|
+
return exact[1].replace(/#/g, String(count));
|
|
150
|
+
}
|
|
151
|
+
const other = /other\s*\{([^{}]*)\}/.exec(cases);
|
|
152
|
+
return other ? other[1].replace(/#/g, String(count)) : match;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
function isObject(value) {
|
|
156
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
157
|
+
}
|
|
158
|
+
function deepMerge(target, source) {
|
|
159
|
+
const result = Object.assign({}, target);
|
|
160
|
+
for (const key of Object.keys(source)) {
|
|
161
|
+
result[key] =
|
|
162
|
+
isObject(result[key]) && isObject(source[key])
|
|
163
|
+
? deepMerge(result[key], source[key])
|
|
164
|
+
: source[key];
|
|
165
|
+
}
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
function mergeMessages(...sources) {
|
|
169
|
+
let merged = {};
|
|
170
|
+
for (const source of sources) {
|
|
171
|
+
merged = deepMerge(merged, source);
|
|
172
|
+
}
|
|
173
|
+
return merged;
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=i18n.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.js","sourceRoot":"","sources":["../../../../../../packages/utils/src/lib/i18n/i18n.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locale detection and persistence.
|
|
3
|
+
*
|
|
4
|
+
* Browser concerns, but no framework: the same functions serve an Angular SPA
|
|
5
|
+
* and any other client. Every access is guarded, so importing this on a server
|
|
6
|
+
* (SSR, Node) is safe — detection simply falls through to the default.
|
|
7
|
+
*/
|
|
8
|
+
/** How to resolve the locale a visitor should see. */
|
|
9
|
+
export interface LocaleDetectionOptions {
|
|
10
|
+
/** Locale codes the app ships messages for, e.g. `['en', 'es']`. */
|
|
11
|
+
supportedLocales: string[];
|
|
12
|
+
/**
|
|
13
|
+
* `localStorage` key holding the visitor's chosen locale. Omit to skip
|
|
14
|
+
* persistence entirely — detection then reads the browser language only.
|
|
15
|
+
*
|
|
16
|
+
* Name it per product (`'acme-locale'`); two apps on one origin sharing a
|
|
17
|
+
* key would overwrite each other's preference.
|
|
18
|
+
*/
|
|
19
|
+
storageKey?: string;
|
|
20
|
+
/** Used when nothing else matches. Defaults to the first supported locale. */
|
|
21
|
+
defaultLocale?: string;
|
|
22
|
+
}
|
|
23
|
+
/** Reduce a locale tag to its language subtag: `en-US` → `en`. */
|
|
24
|
+
export declare function normalizeLocale(locale: string): string;
|
|
25
|
+
/** The persisted locale, or `null` when there is none or storage is unavailable. */
|
|
26
|
+
export declare function readStoredLocale(storageKey: string): string | null;
|
|
27
|
+
/** Persist the visitor's locale. Silently no-ops when storage is unavailable. */
|
|
28
|
+
export declare function saveLocale(storageKey: string, locale: string): void;
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the active locale: stored preference, then browser language, then
|
|
31
|
+
* the default. Each candidate is normalized and must be in `supportedLocales`
|
|
32
|
+
* to win, so an unsupported language falls through rather than rendering keys.
|
|
33
|
+
*/
|
|
34
|
+
export declare function detectLocale(options: LocaleDetectionOptions): string;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locale detection and persistence.
|
|
3
|
+
*
|
|
4
|
+
* Browser concerns, but no framework: the same functions serve an Angular SPA
|
|
5
|
+
* and any other client. Every access is guarded, so importing this on a server
|
|
6
|
+
* (SSR, Node) is safe — detection simply falls through to the default.
|
|
7
|
+
*/
|
|
8
|
+
/** Reduce a locale tag to its language subtag: `en-US` → `en`. */
|
|
9
|
+
export function normalizeLocale(locale) {
|
|
10
|
+
return locale.split('-')[0].toLowerCase();
|
|
11
|
+
}
|
|
12
|
+
/** The persisted locale, or `null` when there is none or storage is unavailable. */
|
|
13
|
+
export function readStoredLocale(storageKey) {
|
|
14
|
+
if (typeof localStorage === 'undefined') {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
return localStorage.getItem(storageKey);
|
|
19
|
+
}
|
|
20
|
+
catch (_a) {
|
|
21
|
+
// Private-mode and blocked-cookie browsers throw on access rather than
|
|
22
|
+
// returning null. A remembered locale is a convenience, never a hard
|
|
23
|
+
// requirement, so failing to read one is not an error.
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Persist the visitor's locale. Silently no-ops when storage is unavailable. */
|
|
28
|
+
export function saveLocale(storageKey, locale) {
|
|
29
|
+
if (typeof localStorage === 'undefined') {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
localStorage.setItem(storageKey, locale);
|
|
34
|
+
}
|
|
35
|
+
catch (_a) {
|
|
36
|
+
// See readStoredLocale: storage is best-effort.
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolve the active locale: stored preference, then browser language, then
|
|
41
|
+
* the default. Each candidate is normalized and must be in `supportedLocales`
|
|
42
|
+
* to win, so an unsupported language falls through rather than rendering keys.
|
|
43
|
+
*/
|
|
44
|
+
export function detectLocale(options) {
|
|
45
|
+
var _a, _b;
|
|
46
|
+
const { supportedLocales, storageKey } = options;
|
|
47
|
+
const fallback = (_b = (_a = options.defaultLocale) !== null && _a !== void 0 ? _a : supportedLocales[0]) !== null && _b !== void 0 ? _b : 'en';
|
|
48
|
+
if (storageKey) {
|
|
49
|
+
const stored = readStoredLocale(storageKey);
|
|
50
|
+
if (stored && supportedLocales.includes(normalizeLocale(stored))) {
|
|
51
|
+
return normalizeLocale(stored);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (typeof navigator !== 'undefined' && navigator.language) {
|
|
55
|
+
const fromBrowser = normalizeLocale(navigator.language);
|
|
56
|
+
if (supportedLocales.includes(fromBrowser)) {
|
|
57
|
+
return fromBrowser;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return fallback;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=i18n.locale.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.locale.js","sourceRoot":"","sources":["../../../../../../packages/utils/src/lib/i18n/i18n.locale.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAoBH,kEAAkE;AAClE,MAAM,UAAU,eAAe,CAAC,MAAc;IAC5C,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;AAC5C,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,gBAAgB,CAAC,UAAkB;IACjD,IAAI,OAAO,YAAY,KAAK,WAAW,EAAE,CAAC;QACxC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC1C,CAAC;IAAC,WAAM,CAAC;QACP,uEAAuE;QACvE,qEAAqE;QACrE,uDAAuD;QACvD,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,UAAU,CAAC,UAAkB,EAAE,MAAc;IAC3D,IAAI,OAAO,YAAY,KAAK,WAAW,EAAE,CAAC;QACxC,OAAO;IACT,CAAC;IAED,IAAI,CAAC;QACH,YAAY,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC3C,CAAC;IAAC,WAAM,CAAC;QACP,gDAAgD;IAClD,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,OAA+B;;IAC1D,MAAM,EAAE,gBAAgB,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IACjD,MAAM,QAAQ,GAAG,MAAA,MAAA,OAAO,CAAC,aAAa,mCAAI,gBAAgB,CAAC,CAAC,CAAC,mCAAI,IAAI,CAAC;IAEtE,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;QAE5C,IAAI,MAAM,IAAI,gBAAgB,CAAC,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;YACjE,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED,IAAI,OAAO,SAAS,KAAK,WAAW,IAAI,SAAS,CAAC,QAAQ,EAAE,CAAC;QAC3D,MAAM,WAAW,GAAG,eAAe,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAExD,IAAI,gBAAgB,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YAC3C,OAAO,WAAW,CAAC;QACrB,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Message bundles are plain nested objects, usually loaded from JSON. */
|
|
2
|
+
export type Messages = Record<string, unknown>;
|
|
3
|
+
/** One message bundle per locale code. */
|
|
4
|
+
export type LocaleMap = Record<string, Messages>;
|
|
5
|
+
export type SupportedLocale = string;
|
|
6
|
+
/**
|
|
7
|
+
* Every dot-path reachable in a message bundle.
|
|
8
|
+
*
|
|
9
|
+
* Pass a bundle's type as `T` and key lookups are checked against it, so a
|
|
10
|
+
* renamed or deleted message is a compile error rather than a key rendered
|
|
11
|
+
* literally on the page.
|
|
12
|
+
*/
|
|
13
|
+
export type Paths<T> = T extends object ? {
|
|
14
|
+
[K in keyof T]: K extends string ? T[K] extends object ? `${K}.${Paths<T[K]>}` : K : never;
|
|
15
|
+
}[keyof T] : never;
|
|
16
|
+
/** A dot-path key. Falls back to `string` when no bundle type is supplied. */
|
|
17
|
+
export type MessageKey<T = Record<string, unknown>> = Paths<T> | string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.types.js","sourceRoot":"","sources":["../../../../../../packages/utils/src/lib/i18n/i18n.types.ts"],"names":[],"mappings":""}
|