intor-translator 1.1.5 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -142
- package/dist/index.cjs +215 -83
- package/dist/index.d.cts +110 -81
- package/dist/index.d.ts +110 -81
- package/dist/index.js +216 -83
- package/package.json +11 -21
package/README.md
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
|
-
A
|
|
5
|
+
A modern **i18n engine** powered by a customizable, type-safe translation pipeline.
|
|
6
|
+
Easy to adopt, modular at its core, and fully extensible.
|
|
6
7
|
|
|
7
8
|
</div>
|
|
8
9
|
|
|
@@ -10,44 +11,32 @@ A type safe translator that knows what to say and how to handle the rest.
|
|
|
10
11
|
|
|
11
12
|
[](https://www.npmjs.com/package/intor-translator)
|
|
12
13
|
[](https://bundlephobia.com/package/intor-translator)
|
|
14
|
+
[](https://coveralls.io/github/yiming-liao/intor-translator?branch=main)
|
|
13
15
|
[](LICENSE)
|
|
14
16
|
[](https://www.typescriptlang.org/)
|
|
15
17
|
|
|
16
18
|
</div>
|
|
17
19
|
|
|
18
|
-
>
|
|
19
|
-
> A type-safe i18n engine with fallback, scoped namespaces, and graceful loading.
|
|
20
|
-
|
|
21
|
-
---
|
|
20
|
+
> Structured • Predictable • Beautifully simple
|
|
22
21
|
|
|
23
22
|
## Features
|
|
24
23
|
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
- 🔁 Flexible replacement and interpolation support
|
|
29
|
-
- 🎨 Rich formatting for complex replacement content
|
|
30
|
-
- 🌀 Graceful handling of loading and async states
|
|
31
|
-
- 🔧 Configurable handlers for fallback, loading, and missing keys
|
|
32
|
-
- 🧩 Scoped translators for modules and namespaces
|
|
33
|
-
|
|
34
|
-
---
|
|
24
|
+
- 🔧 **Modular Pipeline** – A pluggable, hook-driven flow for any translation logic.
|
|
25
|
+
- ✨ **Typed Autocomplete** – Inferred keys and locales with precise, reliable completion.
|
|
26
|
+
- 🌐 **Framework-Agnostic** – A lightweight engine that runs anywhere in JavaScript.
|
|
35
27
|
|
|
36
|
-
##
|
|
28
|
+
## Installation
|
|
37
29
|
|
|
38
30
|
```bash
|
|
31
|
+
# npm
|
|
39
32
|
npm install intor-translator
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
or use **yarn**
|
|
43
|
-
|
|
44
|
-
```bash
|
|
33
|
+
# yarn
|
|
45
34
|
yarn add intor-translator
|
|
35
|
+
# pnpm
|
|
36
|
+
pnpm add intor-translator
|
|
46
37
|
```
|
|
47
38
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
## <img src="https://raw.githubusercontent.com/Tarikul-Islam-Anik/Animated-Fluent-Emojis/master/Emojis/Travel%20and%20places/Rocket.png" alt="Rocket" width="25" height="25" /> Quick Start
|
|
39
|
+
## Quick Start
|
|
51
40
|
|
|
52
41
|
```typescript
|
|
53
42
|
import { Translator } from "intor-translator";
|
|
@@ -67,134 +56,35 @@ translator.t("hello"); // -> Hello World
|
|
|
67
56
|
translator.t("greeting", { name: "John doe" }); // -> Hello, John doe!
|
|
68
57
|
```
|
|
69
58
|
|
|
70
|
-
|
|
59
|
+
## Handlers & Hooks
|
|
71
60
|
|
|
72
|
-
|
|
61
|
+
Intor Translator is powered by **a flexible pipeline** that lets you control how translations behave and how they are rendered.
|
|
73
62
|
|
|
74
|
-
|
|
63
|
+
### Handlers — format the final output
|
|
75
64
|
|
|
76
|
-
|
|
77
|
-
const translator = new Translator({
|
|
78
|
-
locale: "en",
|
|
79
|
-
messages: {
|
|
80
|
-
en: {
|
|
81
|
-
welcome: "Welcome back, {name}",
|
|
82
|
-
},
|
|
83
|
-
zh: {
|
|
84
|
-
welcome: "歡迎回來,{name}",
|
|
85
|
-
notification: "你有 {count} 則新通知",
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
fallbackLocales: { en: ["zh"] }, // Use zh if message not found in en
|
|
89
|
-
placeholder: "Content unavailable", // Shown if key is missing in all locales
|
|
90
|
-
handlers: {
|
|
91
|
-
formatHandler: ({ locale, message }) =>
|
|
92
|
-
locale === "zh" ? `${message}。` : `${message}.`, // Auto punctuation per locale
|
|
93
|
-
},
|
|
94
|
-
});
|
|
65
|
+
<sup>_changing how translations look_.</sup>
|
|
95
66
|
|
|
96
|
-
|
|
97
|
-
console.log(translator.t("welcome", { name: "John" })); // -> Welcome back, John.
|
|
67
|
+
Handlers operate on the resolved message, use them to:
|
|
98
68
|
|
|
99
|
-
|
|
100
|
-
|
|
69
|
+
- format ICU messages
|
|
70
|
+
- apply custom plural logic
|
|
71
|
+
- post-process output
|
|
72
|
+
- style or transform the final string
|
|
101
73
|
|
|
102
|
-
|
|
103
|
-
console.log(translator.t("unknown.key")); // -> Content unavailable
|
|
104
|
-
```
|
|
74
|
+
### Hooks — shape the translation flow
|
|
105
75
|
|
|
106
|
-
|
|
76
|
+
<sup>_changing how translations work_.</sup>
|
|
107
77
|
|
|
108
|
-
|
|
109
|
-
import { Translator, FormatMessage } from "intor-translator";
|
|
110
|
-
import { IntlMessageFormat } from "intl-messageformat";
|
|
78
|
+
Hooks run through the pipeline and can intercept any stage, use them to:
|
|
111
79
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
};
|
|
80
|
+
- transform keys or messages
|
|
81
|
+
- adjust fallback behavior
|
|
82
|
+
- implement loading or missing logic
|
|
83
|
+
- attach metadata or analytics
|
|
117
84
|
|
|
118
|
-
|
|
119
|
-
en: {
|
|
120
|
-
notification:
|
|
121
|
-
"{name} has {count, plural, =0 {no messages} one {1 message} other {# messages}}.",
|
|
122
|
-
},
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
// Create a translator instance
|
|
126
|
-
const translator = new Translator({
|
|
127
|
-
locale: "en",
|
|
128
|
-
messages,
|
|
129
|
-
handlers: { formatHandler },
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
translator.t("notification", { name: "John", count: 0 }); // -> John has no messages.
|
|
133
|
-
translator.t("notification", { name: "John", count: 5 }); // -> John has 5 messages.
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
---
|
|
137
|
-
|
|
138
|
-
## API Reference
|
|
139
|
-
|
|
140
|
-
### Translator Parameters
|
|
141
|
-
|
|
142
|
-
| Option | Type | Description |
|
|
143
|
-
| ----------------- | ------------------------------------- | ------------------------------------------------------------------------ |
|
|
144
|
-
| `messages` | `Readonly<LocaleMessages>` | Translation messages grouped by locale and namespace |
|
|
145
|
-
| `locale` | `string` | Active locale key |
|
|
146
|
-
| `fallbackLocales` | `Record<Locale, Locale[]>` (optional) | Locales to fallback to when a key is missing |
|
|
147
|
-
| `placeholder` | `string` (optional) | Message to display when a key is missing in all locales |
|
|
148
|
-
| `loadingMessage` | `string` (optional) | Message to display during loading or async state |
|
|
149
|
-
| `handlers` | `TranslateHandlers` (optional) | Custom functions for formatting, loading state, and missing key handling |
|
|
150
|
-
|
|
151
|
-
**TranslateHandlers :**
|
|
152
|
-
|
|
153
|
-
```ts
|
|
154
|
-
type TranslateHandlers = {
|
|
155
|
-
formatHandler?: (
|
|
156
|
-
ctx: TranslateHandlerContext & { message: string },
|
|
157
|
-
) => unknown;
|
|
158
|
-
LoadingHandler?: (ctx: TranslateHandlerContext) => unknown;
|
|
159
|
-
MissingHandler?: (ctx: TranslateHandlerContext) => unknown;
|
|
160
|
-
};
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
> Use handlers to control how messages are formatted, what to show during loading, and how to respond to missing keys.
|
|
164
|
-
> Each handler receives a full translation context, including the current locale, key, and replacement values.
|
|
85
|
+
> Together, they form a customizable translation pipeline — structured, predictable, beautifully simple.
|
|
165
86
|
|
|
166
87
|
---
|
|
167
88
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
| Property | Type | Description |
|
|
171
|
-
| ----------- | ----------- | ------------------------------------------ |
|
|
172
|
-
| `messages` | `M` | Current messages object |
|
|
173
|
-
| `locale` | `Locale<M>` | Currently active locale |
|
|
174
|
-
| `isLoading` | `boolean` | Whether the translator is in loading state |
|
|
175
|
-
|
|
176
|
-
---
|
|
177
|
-
|
|
178
|
-
### Instance Methods
|
|
179
|
-
|
|
180
|
-
| Method | Signature | Description |
|
|
181
|
-
| ------------- | ------------------------------------------------- | ------------------------------------------------------------------- |
|
|
182
|
-
| `setMessages` | `(messages: M) => void` | Replaces the current message set |
|
|
183
|
-
| `setLocale` | `(locale: Locale<M>) => boolean` | Sets a new locale and returns whether it changed |
|
|
184
|
-
| `setLoading` | `(state: boolean) => void` | Sets the loading state manually |
|
|
185
|
-
| `hasKey` | `(key, targetLocale?) => boolean` | Checks whether the given key exists in the target or current locale |
|
|
186
|
-
| `t` | `<Result = string>(key, replacements?) => Result` | Translates a key with optional replacements |
|
|
187
|
-
| `scoped` | `(preKey: string) => { hasKey(), t() }` | Creates a scoped translator with a namespace prefix |
|
|
188
|
-
|
|
189
|
-
**translator.t(key, replacements?)**
|
|
190
|
-
|
|
191
|
-
- Fully type-safe key access
|
|
192
|
-
- Supports nested keys
|
|
193
|
-
- Supports both string and rich replacements
|
|
194
|
-
|
|
195
|
-
**translator.scoped(preKey)**
|
|
196
|
-
|
|
197
|
-
- Returns a scoped translator instance based on a message subtree
|
|
198
|
-
- Useful for organizing large sets of translations with shared prefixes
|
|
199
|
-
|
|
200
|
-
---
|
|
89
|
+
**_For more advanced usage, see the full examples._**
|
|
90
|
+
[View examples ↗](https://github.com/yiming-liao/intor-translator/tree/main/examples)
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// src/utils/find-message-in-locales.ts
|
|
3
|
+
// src/translators/shared/utils/find-message-in-locales.ts
|
|
4
4
|
var findMessageInLocales = ({
|
|
5
5
|
messages,
|
|
6
6
|
candidateLocales,
|
|
@@ -23,28 +23,50 @@ var findMessageInLocales = ({
|
|
|
23
23
|
}
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
// src/
|
|
27
|
-
var
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
// src/pipeline/hooks/find-message.hook.ts
|
|
27
|
+
var findMessageHook = {
|
|
28
|
+
name: "findMessage",
|
|
29
|
+
order: 200,
|
|
30
|
+
run(ctx) {
|
|
31
|
+
ctx.rawMessage = findMessageInLocales({
|
|
32
|
+
messages: ctx.messages,
|
|
33
|
+
candidateLocales: ctx.candidateLocales,
|
|
34
|
+
key: ctx.key
|
|
35
|
+
});
|
|
36
|
+
}
|
|
31
37
|
};
|
|
32
38
|
|
|
33
|
-
// src/
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
// src/pipeline/utils/make-handler-context.ts
|
|
40
|
+
function makeHandlerContext(ctx) {
|
|
41
|
+
return Object.freeze({
|
|
42
|
+
locale: ctx.locale,
|
|
43
|
+
key: ctx.key,
|
|
44
|
+
replacements: ctx.replacements,
|
|
45
|
+
messages: ctx.messages,
|
|
46
|
+
candidateLocales: ctx.candidateLocales,
|
|
47
|
+
config: ctx.config,
|
|
48
|
+
isLoading: ctx.isLoading,
|
|
49
|
+
rawMessage: ctx.rawMessage,
|
|
50
|
+
formattedMessage: ctx.formattedMessage,
|
|
51
|
+
meta: ctx.meta
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/pipeline/hooks/format.hook.ts
|
|
56
|
+
var formatHook = {
|
|
57
|
+
name: "format",
|
|
58
|
+
order: 500,
|
|
59
|
+
run(ctx) {
|
|
60
|
+
const { config, rawMessage } = ctx;
|
|
61
|
+
const { formatHandler } = config.handlers || {};
|
|
62
|
+
if (!formatHandler || rawMessage === void 0) return;
|
|
63
|
+
ctx.formattedMessage = formatHandler(
|
|
64
|
+
makeHandlerContext(ctx)
|
|
65
|
+
);
|
|
66
|
+
}
|
|
45
67
|
};
|
|
46
68
|
|
|
47
|
-
// src/utils/replace-values.ts
|
|
69
|
+
// src/translators/shared/utils/replace-values.ts
|
|
48
70
|
var replaceValues = (message, params) => {
|
|
49
71
|
if (!params || typeof params !== "object" || Object.keys(params).length === 0) {
|
|
50
72
|
return message;
|
|
@@ -63,63 +85,117 @@ var replaceValues = (message, params) => {
|
|
|
63
85
|
return replaced;
|
|
64
86
|
};
|
|
65
87
|
|
|
66
|
-
// src/
|
|
67
|
-
var
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const { formatHandler, loadingHandler, missingHandler } = handlers || {};
|
|
80
|
-
const candidateLocales = resolveCandidateLocales(locale, fallbackLocales);
|
|
81
|
-
const message = findMessageInLocales({ messages, candidateLocales, key });
|
|
82
|
-
if (isLoading && (loadingHandler || loadingMessage)) {
|
|
83
|
-
if (loadingHandler)
|
|
84
|
-
return loadingHandler({ key, locale, replacements });
|
|
85
|
-
if (loadingMessage) return loadingMessage;
|
|
86
|
-
}
|
|
87
|
-
if (message === void 0) {
|
|
88
|
-
if (missingHandler)
|
|
89
|
-
return missingHandler({ key, locale, replacements });
|
|
90
|
-
if (placeholder) return placeholder;
|
|
91
|
-
return key;
|
|
92
|
-
}
|
|
93
|
-
if (formatHandler) {
|
|
94
|
-
return formatHandler({ message, key, locale, replacements });
|
|
95
|
-
}
|
|
96
|
-
return replacements ? replaceValues(message, replacements) : message;
|
|
88
|
+
// src/pipeline/hooks/interpolate.hook.ts
|
|
89
|
+
var interpolateHook = {
|
|
90
|
+
name: "interpolate",
|
|
91
|
+
order: 600,
|
|
92
|
+
run(ctx) {
|
|
93
|
+
const { rawMessage, formattedMessage, replacements } = ctx;
|
|
94
|
+
const message = formattedMessage ?? rawMessage;
|
|
95
|
+
if (typeof message !== "string" || !replacements) {
|
|
96
|
+
ctx.finalMessage = message;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
ctx.finalMessage = replaceValues(message, replacements);
|
|
100
|
+
}
|
|
97
101
|
};
|
|
98
102
|
|
|
103
|
+
// src/pipeline/hooks/loading.hook.ts
|
|
104
|
+
var loadingHook = {
|
|
105
|
+
name: "loading",
|
|
106
|
+
order: 300,
|
|
107
|
+
run(ctx) {
|
|
108
|
+
const { config, isLoading } = ctx;
|
|
109
|
+
if (!isLoading) return;
|
|
110
|
+
const { loadingHandler } = config.handlers || {};
|
|
111
|
+
if (loadingHandler) {
|
|
112
|
+
return {
|
|
113
|
+
done: true,
|
|
114
|
+
value: loadingHandler(makeHandlerContext(ctx))
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const { loadingMessage } = config;
|
|
118
|
+
if (loadingMessage) {
|
|
119
|
+
return { done: true, value: loadingMessage };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// src/pipeline/hooks/missing.hook.ts
|
|
125
|
+
var missingHook = {
|
|
126
|
+
name: "missing",
|
|
127
|
+
order: 400,
|
|
128
|
+
run(ctx) {
|
|
129
|
+
const { config, key, rawMessage } = ctx;
|
|
130
|
+
if (rawMessage !== void 0) return;
|
|
131
|
+
const { missingHandler } = config.handlers || {};
|
|
132
|
+
if (missingHandler) {
|
|
133
|
+
return {
|
|
134
|
+
done: true,
|
|
135
|
+
value: missingHandler(makeHandlerContext(ctx))
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const { placeholder } = config;
|
|
139
|
+
if (placeholder) {
|
|
140
|
+
return { done: true, value: placeholder };
|
|
141
|
+
}
|
|
142
|
+
return { done: true, value: key };
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// src/translators/shared/utils/resolve-candidate-locales.ts
|
|
147
|
+
var resolveCandidateLocales = (locale, fallbackLocalesMap) => {
|
|
148
|
+
const fallbacks = fallbackLocalesMap?.[locale] || [];
|
|
149
|
+
const filteredFallbacks = fallbacks.filter((l) => l !== locale);
|
|
150
|
+
return [locale, ...filteredFallbacks];
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// src/pipeline/hooks/resolve-locales.hook.ts
|
|
154
|
+
var resolveLocalesHook = {
|
|
155
|
+
name: "resolveLocales",
|
|
156
|
+
order: 100,
|
|
157
|
+
run(ctx) {
|
|
158
|
+
ctx.candidateLocales = resolveCandidateLocales(
|
|
159
|
+
ctx.locale,
|
|
160
|
+
ctx.config.fallbackLocales
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// src/pipeline/hooks/index.ts
|
|
166
|
+
var DEFAULT_HOOKS = [
|
|
167
|
+
resolveLocalesHook,
|
|
168
|
+
findMessageHook,
|
|
169
|
+
loadingHook,
|
|
170
|
+
missingHook,
|
|
171
|
+
formatHook,
|
|
172
|
+
interpolateHook
|
|
173
|
+
];
|
|
174
|
+
|
|
99
175
|
// src/translators/base-translator/base-translator.ts
|
|
100
176
|
var BaseTranslator = class {
|
|
101
177
|
/** Current messages for translation */
|
|
102
|
-
|
|
178
|
+
_messages;
|
|
103
179
|
/** Current active locale */
|
|
104
|
-
|
|
180
|
+
_locale;
|
|
105
181
|
/** Current loading state */
|
|
106
|
-
|
|
182
|
+
_isLoading;
|
|
107
183
|
constructor(options) {
|
|
108
|
-
this.
|
|
109
|
-
this.
|
|
110
|
-
this.
|
|
184
|
+
this._messages = options.messages ?? {};
|
|
185
|
+
this._locale = options.locale;
|
|
186
|
+
this._isLoading = options.isLoading ?? false;
|
|
111
187
|
}
|
|
112
188
|
/** Get messages. */
|
|
113
189
|
get messages() {
|
|
114
|
-
return this.
|
|
190
|
+
return this._messages;
|
|
115
191
|
}
|
|
116
192
|
/** Get the current active locale. */
|
|
117
193
|
get locale() {
|
|
118
|
-
return this.
|
|
194
|
+
return this._locale;
|
|
119
195
|
}
|
|
120
196
|
/** Get the current loading state. */
|
|
121
197
|
get isLoading() {
|
|
122
|
-
return this.
|
|
198
|
+
return this._isLoading;
|
|
123
199
|
}
|
|
124
200
|
/**
|
|
125
201
|
* Replace messages with new ones.
|
|
@@ -128,7 +204,7 @@ var BaseTranslator = class {
|
|
|
128
204
|
* The type cast bypasses TypeScript restrictions on dynamic messages.
|
|
129
205
|
*/
|
|
130
206
|
setMessages(messages) {
|
|
131
|
-
this.
|
|
207
|
+
this._messages = messages;
|
|
132
208
|
}
|
|
133
209
|
/**
|
|
134
210
|
* Set the active locale.
|
|
@@ -136,30 +212,85 @@ var BaseTranslator = class {
|
|
|
136
212
|
* - Note: Unlike `setMessages`, the locale structure cannot be changed at runtime.
|
|
137
213
|
*/
|
|
138
214
|
setLocale(newLocale) {
|
|
139
|
-
this.
|
|
215
|
+
this._locale = newLocale;
|
|
140
216
|
}
|
|
141
217
|
/** Set the loading state. */
|
|
142
218
|
setLoading(state) {
|
|
143
|
-
this.
|
|
219
|
+
this._isLoading = state;
|
|
144
220
|
}
|
|
145
221
|
};
|
|
146
222
|
|
|
223
|
+
// src/translators/shared/has-key.ts
|
|
224
|
+
var hasKey = ({
|
|
225
|
+
messages,
|
|
226
|
+
locale,
|
|
227
|
+
key,
|
|
228
|
+
targetLocale
|
|
229
|
+
}) => {
|
|
230
|
+
const candidateLocales = resolveCandidateLocales(targetLocale || locale);
|
|
231
|
+
const message = findMessageInLocales({
|
|
232
|
+
messages,
|
|
233
|
+
candidateLocales,
|
|
234
|
+
key
|
|
235
|
+
});
|
|
236
|
+
return !!message;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
// src/pipeline/run-pipeline.ts
|
|
240
|
+
function runPipeline(ctx, hooks) {
|
|
241
|
+
for (const hook of hooks) {
|
|
242
|
+
const result = hook.run(ctx);
|
|
243
|
+
if (result?.done) {
|
|
244
|
+
return result.value;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return ctx.finalMessage ?? ctx.rawMessage;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// src/translators/shared/translate.ts
|
|
251
|
+
function translate(options) {
|
|
252
|
+
const ctx = {
|
|
253
|
+
...options,
|
|
254
|
+
config: options.translateConfig,
|
|
255
|
+
candidateLocales: [],
|
|
256
|
+
meta: {}
|
|
257
|
+
};
|
|
258
|
+
return runPipeline(ctx, options.hooks);
|
|
259
|
+
}
|
|
260
|
+
|
|
147
261
|
// src/translators/core-translator/core-translator.ts
|
|
148
262
|
var CoreTranslator = class extends BaseTranslator {
|
|
149
|
-
options
|
|
263
|
+
/** User-provided options including messages, locale, and config. */
|
|
264
|
+
translateConfig;
|
|
265
|
+
/** Active pipeline hooks applied during translation. */
|
|
266
|
+
hooks = [...DEFAULT_HOOKS];
|
|
150
267
|
constructor(options) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
268
|
+
const { locale, messages, isLoading, plugins, ...translateConfig } = options;
|
|
269
|
+
super({ locale, messages, isLoading });
|
|
270
|
+
this.translateConfig = translateConfig;
|
|
271
|
+
if (plugins) {
|
|
272
|
+
for (const plugin of plugins) this.use(plugin);
|
|
273
|
+
}
|
|
274
|
+
this.sortHooks();
|
|
275
|
+
}
|
|
276
|
+
/** Sort hooks by order value (lower runs earlier). */
|
|
277
|
+
sortHooks() {
|
|
278
|
+
this.hooks.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
279
|
+
}
|
|
280
|
+
/** Register a plugin or a raw pipeline hook. */
|
|
281
|
+
use(plugin) {
|
|
282
|
+
if ("run" in plugin) this.hooks.push(plugin);
|
|
283
|
+
else if ("hook" in plugin && plugin.hook) {
|
|
284
|
+
const hooks = Array.isArray(plugin.hook) ? plugin.hook : [plugin.hook];
|
|
285
|
+
this.hooks.push(...hooks);
|
|
286
|
+
}
|
|
287
|
+
this.sortHooks();
|
|
157
288
|
}
|
|
158
289
|
/** Check if a key exists in the specified locale or current locale. */
|
|
159
290
|
hasKey = (key, targetLocale) => {
|
|
160
291
|
return hasKey({
|
|
161
|
-
|
|
162
|
-
|
|
292
|
+
messages: this._messages,
|
|
293
|
+
locale: this._locale,
|
|
163
294
|
key,
|
|
164
295
|
targetLocale
|
|
165
296
|
});
|
|
@@ -167,17 +298,18 @@ var CoreTranslator = class extends BaseTranslator {
|
|
|
167
298
|
/** Get the translated message for a key, with optional replacements. */
|
|
168
299
|
t = (key, replacements) => {
|
|
169
300
|
return translate({
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
301
|
+
hooks: this.hooks,
|
|
302
|
+
messages: this._messages,
|
|
303
|
+
locale: this._locale,
|
|
304
|
+
isLoading: this._isLoading,
|
|
305
|
+
translateConfig: this.translateConfig,
|
|
174
306
|
key,
|
|
175
307
|
replacements
|
|
176
308
|
});
|
|
177
309
|
};
|
|
178
310
|
};
|
|
179
311
|
|
|
180
|
-
// src/utils/get-full-key.ts
|
|
312
|
+
// src/translators/scope-translator/utils/get-full-key.ts
|
|
181
313
|
var getFullKey = (preKey = "", key = "") => {
|
|
182
314
|
if (!preKey) return key;
|
|
183
315
|
if (!key) return preKey;
|
|
@@ -195,8 +327,8 @@ var ScopeTranslator = class extends CoreTranslator {
|
|
|
195
327
|
hasKey: (key, targetLocale) => {
|
|
196
328
|
const fullKey = getFullKey(preKey, key);
|
|
197
329
|
return hasKey({
|
|
198
|
-
|
|
199
|
-
|
|
330
|
+
messages: this._messages,
|
|
331
|
+
locale: this._locale,
|
|
200
332
|
key: fullKey,
|
|
201
333
|
targetLocale
|
|
202
334
|
});
|
|
@@ -204,10 +336,11 @@ var ScopeTranslator = class extends CoreTranslator {
|
|
|
204
336
|
t: (key, replacements) => {
|
|
205
337
|
const fullKey = getFullKey(preKey, key);
|
|
206
338
|
return translate({
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
339
|
+
hooks: this.hooks,
|
|
340
|
+
messages: this._messages,
|
|
341
|
+
locale: this._locale,
|
|
342
|
+
isLoading: this._isLoading,
|
|
343
|
+
translateConfig: this.translateConfig,
|
|
211
344
|
key: fullKey,
|
|
212
345
|
replacements
|
|
213
346
|
});
|
|
@@ -216,5 +349,4 @@ var ScopeTranslator = class extends CoreTranslator {
|
|
|
216
349
|
}
|
|
217
350
|
};
|
|
218
351
|
|
|
219
|
-
exports.ScopeTranslator = ScopeTranslator;
|
|
220
352
|
exports.Translator = ScopeTranslator;
|