intor-translator 1.4.15 → 1.5.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 +11 -41
- package/dist/chunk-B5RTLRCS.js +173 -0
- package/dist/index.cjs +235 -223
- package/dist/index.d.ts +734 -572
- package/dist/index.js +180 -339
- package/dist/internal/index.cjs +77 -0
- package/dist/internal/index.js +1 -0
- package/package.json +17 -7
- package/dist/index.d.cts +0 -572
package/README.md
CHANGED
|
@@ -7,17 +7,11 @@ The <a href="https://github.com/yiming-liao/intor">Intor</a> translation engine
|
|
|
7
7
|
<div align="center">
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/intor-translator)
|
|
10
|
-
[](https://coveralls.io/github/yiming-liao/intor-translator?branch=main)
|
|
11
10
|
[](https://www.typescriptlang.org/)
|
|
12
11
|
[](LICENSE)
|
|
13
12
|
|
|
14
13
|
</div>
|
|
15
14
|
|
|
16
|
-
> [!NOTE]
|
|
17
|
-
> intor-translator is the execution core of the Intor ecosystem.
|
|
18
|
-
> It provides deterministic translation resolution and rendering,
|
|
19
|
-
> without coupling to routing, configuration systems, or frameworks.
|
|
20
|
-
|
|
21
15
|
## Features
|
|
22
16
|
|
|
23
17
|
- **Modular Pipeline** – A pluggable, hook-driven flow for any translation logic.
|
|
@@ -37,12 +31,6 @@ yarn add intor-translator
|
|
|
37
31
|
pnpm add intor-translator
|
|
38
32
|
```
|
|
39
33
|
|
|
40
|
-
Or load it directly from a CDN:
|
|
41
|
-
|
|
42
|
-
```js
|
|
43
|
-
import { Translator } from "https://cdn.jsdelivr.net/npm/intor-translator/+esm";
|
|
44
|
-
```
|
|
45
|
-
|
|
46
34
|
## Quick Start
|
|
47
35
|
|
|
48
36
|
```typescript
|
|
@@ -67,42 +55,24 @@ translator.t("greeting", { name: "John doe" }); // -> Hello, John doe!
|
|
|
67
55
|
|
|
68
56
|
## Handlers & Hooks
|
|
69
57
|
|
|
70
|
-
Intor Translator
|
|
71
|
-
|
|
72
|
-
### Handlers — format the final output
|
|
73
|
-
|
|
74
|
-
<sup>_changing how translations look_.</sup>
|
|
58
|
+
Intor Translator runs on an explicit, hook-driven pipeline.
|
|
75
59
|
|
|
76
|
-
|
|
60
|
+
**Ordered pipeline:**
|
|
61
|
+
resolveLocales → findMessage → **_loading_** → **_missing_** → **_format_** → interpolate
|
|
77
62
|
|
|
78
|
-
|
|
79
|
-
- apply custom plural logic
|
|
80
|
-
- post-process output
|
|
81
|
-
- style or transform the final string
|
|
63
|
+
### Handlers
|
|
82
64
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
<sup>_changing how translations work_.</sup>
|
|
86
|
-
|
|
87
|
-
Hooks run through the pipeline and can intercept any stage, use them to:
|
|
88
|
-
|
|
89
|
-
- transform keys or messages
|
|
90
|
-
- adjust fallback behavior
|
|
91
|
-
- implement loading or missing logic
|
|
92
|
-
- attach metadata or analytics
|
|
93
|
-
|
|
94
|
-
> Together, they form a customizable translation pipeline — structured, predictable, beautifully simple.
|
|
95
|
-
|
|
96
|
-
---
|
|
65
|
+
Handlers override specific pipeline stages:
|
|
97
66
|
|
|
98
|
-
|
|
67
|
+
- loading
|
|
68
|
+
- missing
|
|
69
|
+
- formatting
|
|
99
70
|
|
|
100
|
-
|
|
71
|
+
### Hooks
|
|
101
72
|
|
|
102
|
-
|
|
103
|
-
- Environment-agnostic by design
|
|
73
|
+
Hooks participate in the ordered pipeline and control how the translation process executes.
|
|
104
74
|
|
|
105
|
-
|
|
75
|
+
They allow external logic to extend or adjust the pipeline behavior.
|
|
106
76
|
|
|
107
77
|
---
|
|
108
78
|
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// src/message/tokenize/utils/extract-attributes.ts
|
|
2
|
+
var ATTR_REGEX = /\s+([a-zA-Z_][a-zA-Z0-9_]*)="([^"]*)"/g;
|
|
3
|
+
var extractAttributes = (input) => {
|
|
4
|
+
const attributes = {};
|
|
5
|
+
let match;
|
|
6
|
+
let consumed = "";
|
|
7
|
+
while (match = ATTR_REGEX.exec(input)) {
|
|
8
|
+
const key = match[1];
|
|
9
|
+
const value = match[2];
|
|
10
|
+
attributes[key] = value;
|
|
11
|
+
consumed += match[0];
|
|
12
|
+
}
|
|
13
|
+
if (consumed.length !== input.length) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
return attributes;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// src/message/tokenize/tokenize.ts
|
|
20
|
+
var OPEN_TAG_REGEX = /^<([a-zA-Z0-9_]+)([^>]*)>/;
|
|
21
|
+
var CLOSE_TAG_REGEX = /^<\/([a-zA-Z0-9_]+)>/;
|
|
22
|
+
var tokenize = (message) => {
|
|
23
|
+
const tokens = [];
|
|
24
|
+
let pos = 0;
|
|
25
|
+
let buffer = "";
|
|
26
|
+
const flushText = () => {
|
|
27
|
+
if (!buffer) return;
|
|
28
|
+
tokens.push({
|
|
29
|
+
type: "text",
|
|
30
|
+
value: buffer,
|
|
31
|
+
position: pos - buffer.length
|
|
32
|
+
});
|
|
33
|
+
buffer = "";
|
|
34
|
+
};
|
|
35
|
+
while (pos < message.length) {
|
|
36
|
+
const char = message[pos];
|
|
37
|
+
if (char === "<") {
|
|
38
|
+
const openMatch = message.slice(pos).match(OPEN_TAG_REGEX);
|
|
39
|
+
if (openMatch) {
|
|
40
|
+
const name = openMatch[1];
|
|
41
|
+
const rawAttributes = openMatch[2];
|
|
42
|
+
const attributes = extractAttributes(rawAttributes);
|
|
43
|
+
if (attributes) {
|
|
44
|
+
flushText();
|
|
45
|
+
tokens.push({
|
|
46
|
+
type: "tag-open",
|
|
47
|
+
name,
|
|
48
|
+
attributes,
|
|
49
|
+
position: pos
|
|
50
|
+
});
|
|
51
|
+
pos += openMatch[0].length;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const closeMatch = message.slice(pos).match(CLOSE_TAG_REGEX);
|
|
56
|
+
if (closeMatch) {
|
|
57
|
+
const name = closeMatch[1];
|
|
58
|
+
flushText();
|
|
59
|
+
tokens.push({
|
|
60
|
+
type: "tag-close",
|
|
61
|
+
name,
|
|
62
|
+
position: pos
|
|
63
|
+
});
|
|
64
|
+
pos += closeMatch[0].length;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
buffer += char;
|
|
69
|
+
pos += 1;
|
|
70
|
+
}
|
|
71
|
+
flushText();
|
|
72
|
+
return tokens;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// src/message/ast/build-ast.ts
|
|
76
|
+
function buildAST(tokens) {
|
|
77
|
+
const root = [];
|
|
78
|
+
const stack = [];
|
|
79
|
+
const pushNode = (node) => {
|
|
80
|
+
const parent = stack.at(-1);
|
|
81
|
+
if (parent) {
|
|
82
|
+
parent.children.push(node);
|
|
83
|
+
} else {
|
|
84
|
+
root.push(node);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
for (const token of tokens) {
|
|
88
|
+
switch (token.type) {
|
|
89
|
+
case "text": {
|
|
90
|
+
pushNode({
|
|
91
|
+
type: "text",
|
|
92
|
+
value: token.value
|
|
93
|
+
});
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case "tag-open": {
|
|
97
|
+
const node = {
|
|
98
|
+
type: "tag",
|
|
99
|
+
name: token.name,
|
|
100
|
+
attributes: token.attributes,
|
|
101
|
+
children: []
|
|
102
|
+
};
|
|
103
|
+
pushNode(node);
|
|
104
|
+
stack.push(node);
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case "tag-close": {
|
|
108
|
+
const last = stack.pop();
|
|
109
|
+
if (!last || last.name !== token.name) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Unmatched closing tag </${token.name}> at position ${token.position}`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (stack.length > 0) {
|
|
119
|
+
throw new Error(`Unclosed tag detected: <${stack.at(-1)?.name}>`);
|
|
120
|
+
}
|
|
121
|
+
return root;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/message/parse-rich-message.ts
|
|
125
|
+
function parseRichMessage(message) {
|
|
126
|
+
if (message == null) return [];
|
|
127
|
+
if (typeof message === "string") {
|
|
128
|
+
const tokens = tokenize(message);
|
|
129
|
+
return buildAST(tokens);
|
|
130
|
+
}
|
|
131
|
+
if (typeof message === "number" || typeof message === "boolean") {
|
|
132
|
+
const tokens = tokenize(String(message));
|
|
133
|
+
return buildAST(tokens);
|
|
134
|
+
}
|
|
135
|
+
if (Array.isArray(message)) {
|
|
136
|
+
return message.flatMap((m) => parseRichMessage(m));
|
|
137
|
+
}
|
|
138
|
+
return [
|
|
139
|
+
{
|
|
140
|
+
type: "raw",
|
|
141
|
+
value: message
|
|
142
|
+
}
|
|
143
|
+
];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/message/render/render.ts
|
|
147
|
+
function render(nodes, renderer) {
|
|
148
|
+
return nodes.map((node) => {
|
|
149
|
+
switch (node.type) {
|
|
150
|
+
// Plain text node
|
|
151
|
+
case "text": {
|
|
152
|
+
return renderer.text(node.value);
|
|
153
|
+
}
|
|
154
|
+
// Semantic tag node
|
|
155
|
+
case "tag": {
|
|
156
|
+
const children = render(node.children, renderer);
|
|
157
|
+
return renderer.tag(node.name, node.attributes, children);
|
|
158
|
+
}
|
|
159
|
+
// Raw message value
|
|
160
|
+
case "raw": {
|
|
161
|
+
return renderer.raw(node.value);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/message/render-rich-message.ts
|
|
168
|
+
function renderRichMessage(message, renderer) {
|
|
169
|
+
const nodes = parseRichMessage(message);
|
|
170
|
+
return render(nodes, renderer);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export { renderRichMessage, tokenize };
|