intl-template 1.0.6 → 1.0.7
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 +138 -58
- package/intl.cjs +91 -56
- package/intl.d.ts +5 -5
- package/intl.js +106 -64
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,19 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# intl-template
|
|
2
2
|
|
|
3
|
-
A tiny i18n/l10n
|
|
3
|
+
A tiny i18n/l10n helper built on JavaScript Tagged Template Literals.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- [intel-template](#intel-template)
|
|
8
|
-
- [Table of Contents](#table-of-contents)
|
|
9
|
-
- [Installation](#installation)
|
|
10
|
-
- [Usage](#usage)
|
|
11
|
-
- [Use browser specifies locale](#use-browser-specifies-locale)
|
|
12
|
-
- [Use with React](#use-with-react)
|
|
13
|
-
- [Specify slot order](#specify-slot-order)
|
|
14
|
-
- [Nested](#nested)
|
|
15
|
-
- [Function slot](#function-slot)
|
|
16
|
-
- [Call as function](#call-as-function)
|
|
5
|
+
`intl-template` lets source strings stay close to your UI code while translations live in a simple locale-indexed template map.
|
|
17
6
|
|
|
18
7
|
## Installation
|
|
19
8
|
|
|
@@ -21,96 +10,187 @@ A tiny i18n/l10n TTL(Tagged Template Literals) function
|
|
|
21
10
|
npm install intl-template
|
|
22
11
|
```
|
|
23
12
|
|
|
24
|
-
##
|
|
13
|
+
## Quick Start
|
|
25
14
|
|
|
26
|
-
```
|
|
27
|
-
import translation from "intl-template"
|
|
15
|
+
```javascript
|
|
16
|
+
import translation, { l10n } from "intl-template"
|
|
28
17
|
|
|
18
|
+
translation.locale = "es-ES"
|
|
29
19
|
translation.templates["es-ES"] = {
|
|
30
|
-
"hello {}": "hola {}"
|
|
20
|
+
"hello {}": "hola {}",
|
|
31
21
|
}
|
|
32
22
|
|
|
33
|
-
const
|
|
23
|
+
const name = "Willow"
|
|
34
24
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
console.log(l10n`hello ${name}`)
|
|
38
|
-
// => hola willow
|
|
25
|
+
console.log(l10n`hello ${name}`.toString())
|
|
26
|
+
// => hola Willow
|
|
39
27
|
```
|
|
40
28
|
|
|
41
|
-
|
|
29
|
+
Every interpolation in a tagged template becomes `{}` in the translation key. For example, `l10n`hello ${name}`` looks up the key `"hello {}"`.
|
|
30
|
+
|
|
31
|
+
By default, translations return a `Runes` array so React nodes and other non-string values can pass through unchanged. Call `.toString()` when you need plain text, or create a `Translation` instance in `"string"` mode.
|
|
32
|
+
|
|
33
|
+
## Templates
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
translation.templates["en-US"] = {
|
|
37
|
+
"hello {}": "hello {}",
|
|
38
|
+
"{} invited {}": "{} invited {}",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
translation.templates["de-DE"] = {
|
|
42
|
+
"hello {}": "hallo {}",
|
|
43
|
+
"{} invited {}": "{1} wurde von {0} eingeladen",
|
|
44
|
+
}
|
|
42
45
|
```
|
|
46
|
+
|
|
47
|
+
Use `{}` to keep the original slot order. Use `{0}`, `{1}`, and later indexes when a locale needs a different order.
|
|
48
|
+
|
|
49
|
+
## Locale
|
|
50
|
+
|
|
51
|
+
The shared `translation` instance uses `navigator.language` when it is available, then falls back to `"en"`. You can also set the locale explicitly.
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
43
54
|
import translation, { l10n } from "intl-template"
|
|
44
55
|
|
|
45
|
-
|
|
46
|
-
|
|
56
|
+
const browserLocale = navigator.language
|
|
57
|
+
|
|
58
|
+
translation.locale = browserLocale
|
|
59
|
+
translation.templates[browserLocale] = {
|
|
60
|
+
"hello {}": "hola {}",
|
|
47
61
|
}
|
|
48
62
|
|
|
49
|
-
|
|
63
|
+
console.log(l10n`hello ${"Willow"}`.toString())
|
|
64
|
+
// => hola Willow
|
|
65
|
+
```
|
|
50
66
|
|
|
51
|
-
|
|
67
|
+
## Examples
|
|
52
68
|
|
|
53
|
-
|
|
54
|
-
// => hola willow
|
|
55
|
-
```
|
|
69
|
+
### React
|
|
56
70
|
|
|
57
|
-
|
|
71
|
+
The default `"react"` mode keeps interpolated values as values, so JSX can be used inside a translation.
|
|
58
72
|
|
|
59
|
-
```
|
|
73
|
+
```jsx
|
|
74
|
+
import translation, { l10n } from "intl-template"
|
|
60
75
|
|
|
61
|
-
|
|
76
|
+
translation.locale = "es-ES"
|
|
77
|
+
translation.templates["es-ES"] = {
|
|
78
|
+
"hello {}": "hola {}",
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function Greeting({ name }) {
|
|
62
82
|
return (
|
|
63
|
-
<
|
|
64
|
-
{l10n`hello ${<
|
|
65
|
-
</
|
|
83
|
+
<p>
|
|
84
|
+
{l10n`hello ${<strong key="name">{name}</strong>}`}
|
|
85
|
+
</p>
|
|
66
86
|
)
|
|
67
87
|
}
|
|
68
88
|
```
|
|
69
89
|
|
|
70
|
-
###
|
|
90
|
+
### Slot Order
|
|
71
91
|
|
|
72
|
-
```
|
|
92
|
+
```javascript
|
|
93
|
+
translation.locale = "de-DE"
|
|
73
94
|
translation.templates["de-DE"] = {
|
|
74
|
-
"
|
|
95
|
+
"{} invited {}": "{1} wurde von {0} eingeladen",
|
|
75
96
|
}
|
|
76
97
|
|
|
77
|
-
const
|
|
98
|
+
const inviter = "Willow"
|
|
99
|
+
const guest = "Jack"
|
|
100
|
+
|
|
101
|
+
console.log(l10n`${inviter} invited ${guest}`.toString())
|
|
102
|
+
// => Jack wurde von Willow eingeladen
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Nested Translations
|
|
78
106
|
|
|
79
|
-
|
|
80
|
-
|
|
107
|
+
```javascript
|
|
108
|
+
translation.locale = "de-DE"
|
|
109
|
+
translation.templates["de-DE"] = {
|
|
110
|
+
"Bill": "Schmidt",
|
|
111
|
+
"hello {}": "hallo {}",
|
|
112
|
+
}
|
|
81
113
|
|
|
82
|
-
console.log(l10n`hello ${
|
|
83
|
-
// =>
|
|
114
|
+
console.log(l10n`hello ${l10n`Bill`}`.toString())
|
|
115
|
+
// => hallo Schmidt
|
|
84
116
|
```
|
|
85
117
|
|
|
86
|
-
###
|
|
118
|
+
### Function Slots
|
|
119
|
+
|
|
120
|
+
When a slot is a function, it receives the active locale.
|
|
87
121
|
|
|
88
122
|
```javascript
|
|
123
|
+
translation.locale = "de-DE"
|
|
89
124
|
translation.templates["de-DE"] = {
|
|
90
|
-
"
|
|
91
|
-
"hello {}": "hallo {1}"
|
|
125
|
+
"current locale: {}": "aktuelle Sprache: {}",
|
|
92
126
|
}
|
|
93
127
|
|
|
94
|
-
|
|
128
|
+
console.log(l10n`current locale: ${locale => locale}`.toString())
|
|
129
|
+
// => aktuelle Sprache: de-DE
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Call as a Function
|
|
133
|
+
|
|
134
|
+
Tagged templates and function calls use the same placeholder format.
|
|
95
135
|
|
|
96
|
-
|
|
136
|
+
```javascript
|
|
137
|
+
translation.locale = "es-ES"
|
|
138
|
+
translation.templates["es-ES"] = {
|
|
139
|
+
"hello {}": "hola {}",
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
console.log(l10n("hello {}", "Willow").toString())
|
|
143
|
+
// => hola Willow
|
|
97
144
|
```
|
|
98
145
|
|
|
99
|
-
###
|
|
146
|
+
### String Mode
|
|
100
147
|
|
|
101
148
|
```javascript
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
149
|
+
import { Translation } from "intl-template"
|
|
150
|
+
|
|
151
|
+
const translation = new Translation("es-ES", "string")
|
|
152
|
+
|
|
153
|
+
translation.templates["es-ES"] = {
|
|
154
|
+
"hello {}": "hola {}",
|
|
105
155
|
}
|
|
106
156
|
|
|
107
|
-
const
|
|
157
|
+
const t = translation.translate
|
|
108
158
|
|
|
109
|
-
|
|
159
|
+
console.log(t`hello ${"Willow"}`)
|
|
160
|
+
// => hola Willow
|
|
110
161
|
```
|
|
111
162
|
|
|
112
|
-
|
|
163
|
+
## API
|
|
164
|
+
|
|
165
|
+
### `translation`
|
|
166
|
+
|
|
167
|
+
The default shared `Translation` instance.
|
|
168
|
+
|
|
169
|
+
### `l10n`
|
|
170
|
+
|
|
171
|
+
A shorthand for `translation.translate`.
|
|
172
|
+
|
|
173
|
+
### `new Translation(defaultLocale, mode)`
|
|
174
|
+
|
|
175
|
+
Creates an isolated translation instance.
|
|
176
|
+
|
|
177
|
+
- `defaultLocale`: locale used by this instance.
|
|
178
|
+
- `mode`: `"react"` by default, or `"string"` for plain string output.
|
|
179
|
+
|
|
180
|
+
### `translation.locale`
|
|
181
|
+
|
|
182
|
+
The active locale used by `translate`.
|
|
183
|
+
|
|
184
|
+
### `translation.templates`
|
|
185
|
+
|
|
186
|
+
Locale-indexed translation templates.
|
|
113
187
|
|
|
114
188
|
```javascript
|
|
115
|
-
|
|
189
|
+
translation.templates["es-ES"] = {
|
|
190
|
+
"source {}": "translated {}",
|
|
191
|
+
}
|
|
116
192
|
```
|
|
193
|
+
|
|
194
|
+
### `translation.translate(strings, ...parts)`
|
|
195
|
+
|
|
196
|
+
Translates a tagged template or a string with `{}` placeholders.
|
package/intl.cjs
CHANGED
|
@@ -43,23 +43,43 @@ class Runes extends Array {
|
|
|
43
43
|
return this.join("");
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
+
var SLOT_RE = /\{(\d*)\}/g;
|
|
47
|
+
var TEMPLATE_KEY_CACHE = new WeakMap;
|
|
46
48
|
function parseTemplate(templateString) {
|
|
47
|
-
const order = [];
|
|
48
49
|
const template = [];
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
});
|
|
50
|
+
const order = [];
|
|
51
|
+
let lastIndex = 0;
|
|
52
|
+
let match;
|
|
53
|
+
SLOT_RE.lastIndex = 0;
|
|
54
|
+
while ((match = SLOT_RE.exec(templateString)) !== null) {
|
|
55
|
+
template.push(templateString.slice(lastIndex, match.index));
|
|
56
|
+
order.push(match[1] === "" ? order.length : +match[1]);
|
|
57
|
+
lastIndex = SLOT_RE.lastIndex;
|
|
58
|
+
}
|
|
59
|
+
template.push(templateString.slice(lastIndex));
|
|
61
60
|
return { template, order };
|
|
62
61
|
}
|
|
62
|
+
function compileRegionTemplates(regionTemplates) {
|
|
63
|
+
const region = {};
|
|
64
|
+
for (const key in regionTemplates) {
|
|
65
|
+
region[key] = parseTemplate(regionTemplates[key]);
|
|
66
|
+
}
|
|
67
|
+
return region;
|
|
68
|
+
}
|
|
69
|
+
function getTemplateKey(strings) {
|
|
70
|
+
if (!strings.raw) {
|
|
71
|
+
return strings.join("{}");
|
|
72
|
+
}
|
|
73
|
+
let key = TEMPLATE_KEY_CACHE.get(strings);
|
|
74
|
+
if (key === undefined) {
|
|
75
|
+
key = strings.join("{}");
|
|
76
|
+
TEMPLATE_KEY_CACHE.set(strings, key);
|
|
77
|
+
}
|
|
78
|
+
return key;
|
|
79
|
+
}
|
|
80
|
+
function toTemplateString(value) {
|
|
81
|
+
return value == null ? "" : value;
|
|
82
|
+
}
|
|
63
83
|
|
|
64
84
|
class Translation {
|
|
65
85
|
mode = "react";
|
|
@@ -68,23 +88,25 @@ class Translation {
|
|
|
68
88
|
this.mode = mode;
|
|
69
89
|
this.locale = defaultLocale || globalThis?.navigator?.language || "en";
|
|
70
90
|
}
|
|
71
|
-
#
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
91
|
+
#regions = {};
|
|
92
|
+
#regionProxies = {};
|
|
93
|
+
#templates = new Proxy(this.#regions, {
|
|
94
|
+
get: (regions, locale) => {
|
|
95
|
+
const region = regions[locale];
|
|
96
|
+
if (!region) {
|
|
97
|
+
return new Proxy({}, REGION_HANDLER);
|
|
98
|
+
}
|
|
99
|
+
let proxy = this.#regionProxies[locale];
|
|
100
|
+
if (!proxy) {
|
|
101
|
+
proxy = new Proxy(region, REGION_HANDLER);
|
|
102
|
+
this.#regionProxies[locale] = proxy;
|
|
103
|
+
}
|
|
104
|
+
return proxy;
|
|
82
105
|
},
|
|
83
|
-
set(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}, {});
|
|
106
|
+
set: (regions, locale, regionTemplates) => {
|
|
107
|
+
const region = compileRegionTemplates(regionTemplates);
|
|
108
|
+
regions[locale] = region;
|
|
109
|
+
this.#regionProxies[locale] = new Proxy(region, REGION_HANDLER);
|
|
88
110
|
return true;
|
|
89
111
|
}
|
|
90
112
|
});
|
|
@@ -92,46 +114,59 @@ class Translation {
|
|
|
92
114
|
return this.#templates;
|
|
93
115
|
}
|
|
94
116
|
set templates(value) {
|
|
95
|
-
|
|
96
|
-
this.#templates[locale] =
|
|
97
|
-
}
|
|
98
|
-
return true;
|
|
117
|
+
for (const locale in value) {
|
|
118
|
+
this.#templates[locale] = value[locale];
|
|
119
|
+
}
|
|
99
120
|
}
|
|
100
121
|
translate = (strings, ...parts) => {
|
|
101
122
|
const locale = this.locale;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
123
|
+
const isStringInput = typeof strings === "string";
|
|
124
|
+
const key = isStringInput ? strings : getTemplateKey(strings);
|
|
125
|
+
const compiled = this.#regions[locale]?.[key];
|
|
126
|
+
let template;
|
|
127
|
+
let order;
|
|
128
|
+
if (compiled) {
|
|
129
|
+
template = compiled.template;
|
|
130
|
+
order = compiled.order;
|
|
131
|
+
} else {
|
|
132
|
+
template = isStringInput ? strings.split("{}") : strings.slice();
|
|
112
133
|
}
|
|
113
134
|
if (parts.length !== template.length - 1) {
|
|
114
135
|
throw new Error(`translate template parts length does not match. locale: ${locale}, key: ${key}`);
|
|
115
136
|
}
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
runes2.push(part);
|
|
137
|
+
const len = template.length;
|
|
138
|
+
if (this.mode !== "react") {
|
|
139
|
+
let result = "";
|
|
140
|
+
for (let idx = 0;idx < len; idx++) {
|
|
141
|
+
result += template[idx];
|
|
142
|
+
if (idx < parts.length) {
|
|
143
|
+
const part = parts[order ? order[idx] : idx];
|
|
144
|
+
result += toTemplateString(typeof part === "function" ? part(locale) : part);
|
|
125
145
|
}
|
|
126
146
|
}
|
|
127
|
-
return
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
const runes = new Runes(len + parts.length);
|
|
150
|
+
let runeIdx = 0;
|
|
151
|
+
for (let idx = 0;idx < len; idx++) {
|
|
152
|
+
runes[runeIdx++] = template[idx];
|
|
153
|
+
if (idx < parts.length) {
|
|
154
|
+
const part = parts[order ? order[idx] : idx];
|
|
155
|
+
runes[runeIdx++] = typeof part === "function" ? part(locale) : part;
|
|
156
|
+
}
|
|
131
157
|
}
|
|
132
158
|
return runes;
|
|
133
159
|
};
|
|
134
160
|
}
|
|
161
|
+
var REGION_HANDLER = {
|
|
162
|
+
set(region, key, value) {
|
|
163
|
+
if (typeof value !== "string") {
|
|
164
|
+
throw new Error("Template must be a string.");
|
|
165
|
+
}
|
|
166
|
+
region[key] = parseTemplate(value);
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
};
|
|
135
170
|
var translation = new Translation;
|
|
136
171
|
var intl_default = translation;
|
|
137
172
|
var l10n = translation.translate;
|
package/intl.d.ts
CHANGED
|
@@ -28,19 +28,19 @@ export class Translation {
|
|
|
28
28
|
* @type {string}
|
|
29
29
|
**/
|
|
30
30
|
locale: string;
|
|
31
|
-
set templates(value:
|
|
32
|
-
get templates():
|
|
31
|
+
set templates(value: any);
|
|
32
|
+
get templates(): any;
|
|
33
33
|
/**
|
|
34
34
|
* Translates a string based on the provided locale and strings.
|
|
35
35
|
*
|
|
36
36
|
* @param {TemplateStringsArray | string} strings - The string or array of strings to be translated.
|
|
37
37
|
* @param {...any} parts - The dynamic parts to be inserted into the translated string.
|
|
38
|
-
* @returns {Runes} - The translated string with dynamic parts inserted.
|
|
38
|
+
* @returns {Runes | string} - The translated string with dynamic parts inserted.
|
|
39
39
|
* @throws {Error} - If the length of the template parts does not match the length of the template.
|
|
40
40
|
*/
|
|
41
|
-
translate: (strings: TemplateStringsArray | string, ...parts: any[]) => Runes;
|
|
41
|
+
translate: (strings: TemplateStringsArray | string, ...parts: any[]) => Runes | string;
|
|
42
42
|
#private;
|
|
43
43
|
}
|
|
44
44
|
export const translation: Translation;
|
|
45
45
|
export default translation;
|
|
46
|
-
export function l10n(strings: TemplateStringsArray | string, ...parts: any[]): Runes;
|
|
46
|
+
export function l10n(strings: TemplateStringsArray | string, ...parts: any[]): Runes | string;
|
package/intl.js
CHANGED
|
@@ -4,31 +4,59 @@ export class Runes extends Array {
|
|
|
4
4
|
}
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
const SLOT_RE = /\{(\d*)\}/g
|
|
8
|
+
const TEMPLATE_KEY_CACHE = new WeakMap()
|
|
9
|
+
|
|
7
10
|
/**
|
|
8
11
|
* Parses a template string and extracts the template parts and order of slots.
|
|
9
12
|
* @param {string} templateString - The template string to parse.
|
|
10
13
|
* @returns {{ template: string[], order: number[] }}
|
|
11
14
|
*/
|
|
12
15
|
export function parseTemplate(templateString) {
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
})
|
|
16
|
+
const template = []
|
|
17
|
+
const order = []
|
|
18
|
+
let lastIndex = 0
|
|
19
|
+
let match
|
|
20
|
+
|
|
21
|
+
SLOT_RE.lastIndex = 0
|
|
22
|
+
while ((match = SLOT_RE.exec(templateString)) !== null) {
|
|
23
|
+
template.push(templateString.slice(lastIndex, match.index))
|
|
24
|
+
// `{}` keeps original auto-numbering: index = current order length
|
|
25
|
+
order.push(match[1] === "" ? order.length : +match[1])
|
|
26
|
+
lastIndex = SLOT_RE.lastIndex
|
|
27
|
+
}
|
|
28
|
+
template.push(templateString.slice(lastIndex))
|
|
28
29
|
|
|
29
30
|
return { template, order }
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
function compileRegionTemplates(regionTemplates) {
|
|
34
|
+
const region = {}
|
|
35
|
+
for (const key in regionTemplates) {
|
|
36
|
+
region[key] = parseTemplate(regionTemplates[key])
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return region
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getTemplateKey(strings) {
|
|
43
|
+
if (!strings.raw) {
|
|
44
|
+
return strings.join("{}")
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let key = TEMPLATE_KEY_CACHE.get(strings)
|
|
48
|
+
if (key === undefined) {
|
|
49
|
+
key = strings.join("{}")
|
|
50
|
+
TEMPLATE_KEY_CACHE.set(strings, key)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return key
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function toTemplateString(value) {
|
|
57
|
+
return value == null ? "" : value
|
|
58
|
+
}
|
|
59
|
+
|
|
32
60
|
/**
|
|
33
61
|
* Represents a Translation object that handles string translation based on locale and templates.
|
|
34
62
|
*/
|
|
@@ -55,26 +83,29 @@ export class Translation {
|
|
|
55
83
|
* Templates object that stores the translation templates for each locale.
|
|
56
84
|
* @type {Proxy}
|
|
57
85
|
*/
|
|
58
|
-
#
|
|
59
|
-
get(templates, locale) {
|
|
60
|
-
return new Proxy(templates[locale] || {}, {
|
|
61
|
-
set(region, key, value) {
|
|
62
|
-
if (typeof value !== "string") {
|
|
63
|
-
throw new Error("Template must be a string.")
|
|
64
|
-
}
|
|
86
|
+
#regions = {}
|
|
65
87
|
|
|
66
|
-
|
|
88
|
+
#regionProxies = {}
|
|
67
89
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
region[key] = parseTemplate(value)
|
|
90
|
+
#templates = new Proxy(this.#regions, {
|
|
91
|
+
get: (regions, locale) => {
|
|
92
|
+
const region = regions[locale]
|
|
93
|
+
if (!region) {
|
|
94
|
+
return new Proxy({}, REGION_HANDLER)
|
|
95
|
+
}
|
|
75
96
|
|
|
76
|
-
|
|
77
|
-
|
|
97
|
+
let proxy = this.#regionProxies[locale]
|
|
98
|
+
if (!proxy) {
|
|
99
|
+
proxy = new Proxy(region, REGION_HANDLER)
|
|
100
|
+
this.#regionProxies[locale] = proxy
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return proxy
|
|
104
|
+
},
|
|
105
|
+
set: (regions, locale, regionTemplates) => {
|
|
106
|
+
const region = compileRegionTemplates(regionTemplates)
|
|
107
|
+
regions[locale] = region
|
|
108
|
+
this.#regionProxies[locale] = new Proxy(region, REGION_HANDLER)
|
|
78
109
|
|
|
79
110
|
return true
|
|
80
111
|
}
|
|
@@ -85,11 +116,9 @@ export class Translation {
|
|
|
85
116
|
}
|
|
86
117
|
|
|
87
118
|
set templates(value) {
|
|
88
|
-
|
|
89
|
-
this.#templates[locale] =
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return true
|
|
119
|
+
for (const locale in value) {
|
|
120
|
+
this.#templates[locale] = value[locale]
|
|
121
|
+
}
|
|
93
122
|
}
|
|
94
123
|
|
|
95
124
|
/**
|
|
@@ -97,53 +126,66 @@ export class Translation {
|
|
|
97
126
|
*
|
|
98
127
|
* @param {TemplateStringsArray | string} strings - The string or array of strings to be translated.
|
|
99
128
|
* @param {...any} parts - The dynamic parts to be inserted into the translated string.
|
|
100
|
-
* @returns {Runes} - The translated string with dynamic parts inserted.
|
|
129
|
+
* @returns {Runes | string} - The translated string with dynamic parts inserted.
|
|
101
130
|
* @throws {Error} - If the length of the template parts does not match the length of the template.
|
|
102
131
|
*/
|
|
103
132
|
translate = (strings, ...parts) => {
|
|
104
133
|
const locale = this.locale
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
template = strings.slice()
|
|
116
|
-
order = parts.map((_, i) => i)
|
|
134
|
+
const isStringInput = typeof strings === "string"
|
|
135
|
+
const key = isStringInput ? strings : getTemplateKey(strings)
|
|
136
|
+
|
|
137
|
+
const compiled = this.#regions[locale]?.[key]
|
|
138
|
+
let template
|
|
139
|
+
let order
|
|
140
|
+
if (compiled) {
|
|
141
|
+
template = compiled.template
|
|
142
|
+
order = compiled.order
|
|
143
|
+
} else {
|
|
144
|
+
template = isStringInput ? strings.split("{}") : strings.slice()
|
|
117
145
|
}
|
|
118
146
|
|
|
119
147
|
if (parts.length !== template.length - 1) {
|
|
120
148
|
throw new Error(`translate template parts length does not match. locale: ${locale}, key: ${key}`)
|
|
121
149
|
}
|
|
122
150
|
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
} else {
|
|
132
|
-
runes.push(part)
|
|
151
|
+
const len = template.length
|
|
152
|
+
if (this.mode !== "react") {
|
|
153
|
+
let result = ""
|
|
154
|
+
for (let idx = 0; idx < len; idx++) {
|
|
155
|
+
result += template[idx]
|
|
156
|
+
if (idx < parts.length) {
|
|
157
|
+
const part = parts[order ? order[idx] : idx]
|
|
158
|
+
result += toTemplateString(typeof part === "function" ? part(locale) : part)
|
|
133
159
|
}
|
|
134
160
|
}
|
|
135
161
|
|
|
136
|
-
return
|
|
137
|
-
}
|
|
162
|
+
return result
|
|
163
|
+
}
|
|
138
164
|
|
|
139
|
-
|
|
140
|
-
|
|
165
|
+
const runes = new Runes(len + parts.length)
|
|
166
|
+
let runeIdx = 0
|
|
167
|
+
for (let idx = 0; idx < len; idx++) {
|
|
168
|
+
runes[runeIdx++] = template[idx]
|
|
169
|
+
if (idx < parts.length) {
|
|
170
|
+
const part = parts[order ? order[idx] : idx]
|
|
171
|
+
runes[runeIdx++] = typeof part === "function" ? part(locale) : part
|
|
172
|
+
}
|
|
141
173
|
}
|
|
142
174
|
|
|
143
175
|
return runes
|
|
144
176
|
}
|
|
145
177
|
}
|
|
146
178
|
|
|
179
|
+
const REGION_HANDLER = {
|
|
180
|
+
set(region, key, value) {
|
|
181
|
+
if (typeof value !== "string") {
|
|
182
|
+
throw new Error("Template must be a string.")
|
|
183
|
+
}
|
|
184
|
+
region[key] = parseTemplate(value)
|
|
185
|
+
return true
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
147
189
|
export const translation = new Translation()
|
|
148
190
|
|
|
149
191
|
export default translation
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "intl-template",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "l10n tagged template literals",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./intl.cjs",
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "bun build --outfile intl.cjs --format cjs intl.js",
|
|
18
|
-
"
|
|
18
|
+
"perf": "node --test intl.perf.test.js",
|
|
19
|
+
"postbuild": "rm *.d.ts && tsc --emitDeclarationOnly --allowJs -d --outDir . intl.js intl.test.js"
|
|
19
20
|
},
|
|
20
21
|
"keywords": [
|
|
21
22
|
"i18n",
|