@routa/i18n-vue 0.1.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/LICENSE +21 -0
- package/README.md +193 -0
- package/dist/icu-7NWX4KPY.js +214 -0
- package/dist/icu-DcjP9e-V.cjs +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +1 -0
- package/dist/index.mjs +247 -0
- package/dist/vite-plugin.d.ts +1 -0
- package/dist/vite-plugin.js +16 -0
- package/dist/vite-plugin.mjs +464 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Routa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# @routa/i18n-vue
|
|
2
|
+
|
|
3
|
+
Vue-first adapter for the Routa i18n runtime.
|
|
4
|
+
|
|
5
|
+
This package wraps the instance-based core runtime with:
|
|
6
|
+
|
|
7
|
+
- `createI18n()` for Vue apps
|
|
8
|
+
- `app.use(i18n)` installation
|
|
9
|
+
- `useI18n()` and `useLocale()`
|
|
10
|
+
- default-instance convenience exports such as `t` and `setLocale`
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import path from 'node:path'
|
|
16
|
+
import { defineConfig } from 'vite'
|
|
17
|
+
import vue from '@vitejs/plugin-vue'
|
|
18
|
+
import { i18nPlugin } from '@routa/i18n-vue/vite-plugin'
|
|
19
|
+
|
|
20
|
+
export default defineConfig({
|
|
21
|
+
plugins: [
|
|
22
|
+
vue(),
|
|
23
|
+
i18nPlugin({
|
|
24
|
+
localesDir: path.resolve(__dirname, 'locales'),
|
|
25
|
+
outputDir: path.resolve(__dirname, '.i18n'),
|
|
26
|
+
sourceLocale: 'zh-CN',
|
|
27
|
+
include: [path.resolve(__dirname, 'src')],
|
|
28
|
+
}),
|
|
29
|
+
],
|
|
30
|
+
})
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Locale Files
|
|
34
|
+
|
|
35
|
+
Use the same filesystem-domain layout as the core runtime:
|
|
36
|
+
|
|
37
|
+
```text
|
|
38
|
+
locales/
|
|
39
|
+
zh-CN/
|
|
40
|
+
nav.ts
|
|
41
|
+
user.ts
|
|
42
|
+
en-US/
|
|
43
|
+
nav.ts
|
|
44
|
+
user.ts
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## App Installation
|
|
48
|
+
|
|
49
|
+
Create and install a Vue i18n instance:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { createApp } from 'vue'
|
|
53
|
+
import App from './App.vue'
|
|
54
|
+
import { createI18n } from '@routa/i18n-vue'
|
|
55
|
+
|
|
56
|
+
const i18n = createI18n({
|
|
57
|
+
sourceLocale: 'zh-CN',
|
|
58
|
+
initialLocale: 'zh-CN',
|
|
59
|
+
messages: {
|
|
60
|
+
'zh-CN': {
|
|
61
|
+
nav: {
|
|
62
|
+
guide: 'Guide Source',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
'en-US': {
|
|
66
|
+
nav: {
|
|
67
|
+
guide: 'Guide',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
createApp(App).use(i18n).mount('#app')
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Typed Key Usage
|
|
77
|
+
|
|
78
|
+
Import generated keys from `virtual:routa-i18n/keys`:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { k, nav } from 'virtual:routa-i18n/keys'
|
|
82
|
+
import { t } from '@routa/i18n-vue'
|
|
83
|
+
|
|
84
|
+
t(k.nav.guide, 'Guide')
|
|
85
|
+
t(nav.guide, 'Guide')
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The recommended safe path is:
|
|
89
|
+
|
|
90
|
+
- `t(k.xxx.xxx, ...)`
|
|
91
|
+
- `t(domain.xxx, ...)`
|
|
92
|
+
|
|
93
|
+
Avoid raw string keys in application code when type safety matters.
|
|
94
|
+
|
|
95
|
+
## Composition API
|
|
96
|
+
|
|
97
|
+
Use the installed instance inside components:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { computed } from 'vue'
|
|
101
|
+
import { useI18n, useLocale } from '@routa/i18n-vue'
|
|
102
|
+
import { k } from 'virtual:routa-i18n/keys'
|
|
103
|
+
|
|
104
|
+
export function useNavTitle() {
|
|
105
|
+
const { t } = useI18n()
|
|
106
|
+
const locale = useLocale()
|
|
107
|
+
|
|
108
|
+
const title = computed(() => t(k.nav.guide, 'Guide'))
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
title,
|
|
112
|
+
locale,
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`useI18n()` returns the active Vue wrapper instance.
|
|
118
|
+
|
|
119
|
+
`useLocale()` returns a writable computed locale ref.
|
|
120
|
+
|
|
121
|
+
## Default Instance Convenience
|
|
122
|
+
|
|
123
|
+
For simple SPA usage, a default instance is also exported:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import { currentLocale, setLocale, t } from '@routa/i18n-vue'
|
|
127
|
+
import { k } from 'virtual:routa-i18n/keys'
|
|
128
|
+
|
|
129
|
+
t(k.nav.guide, 'Guide')
|
|
130
|
+
await setLocale('en-US')
|
|
131
|
+
console.log(currentLocale.value)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
This is convenient for:
|
|
135
|
+
|
|
136
|
+
- simple single-app SPAs
|
|
137
|
+
- docs sites
|
|
138
|
+
- quick prototypes
|
|
139
|
+
|
|
140
|
+
Prefer explicit instances for:
|
|
141
|
+
|
|
142
|
+
- SSR
|
|
143
|
+
- tests that need isolation
|
|
144
|
+
- multi-app pages
|
|
145
|
+
- library-style Vue integrations
|
|
146
|
+
|
|
147
|
+
## ICU Calls
|
|
148
|
+
|
|
149
|
+
The adapter uses the same `t()` overloads as the core package:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
t(k.nav.guide)
|
|
153
|
+
t(k.nav.guide, 'Guide')
|
|
154
|
+
t(k.user.welcome, { name: 'Ada' })
|
|
155
|
+
t(k.user.welcome, 'Hello, {name}', { name: 'Ada' })
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Supported ICU subset today:
|
|
159
|
+
|
|
160
|
+
- `{name}`
|
|
161
|
+
- `plural`
|
|
162
|
+
- `select`
|
|
163
|
+
- `number`, `date`, and `time`
|
|
164
|
+
|
|
165
|
+
## Runtime Behavior
|
|
166
|
+
|
|
167
|
+
Locale lookup order:
|
|
168
|
+
|
|
169
|
+
1. current locale
|
|
170
|
+
2. source locale
|
|
171
|
+
3. call-site `defaultValue`
|
|
172
|
+
4. key path
|
|
173
|
+
|
|
174
|
+
Locale changes are reactive, so Vue render output updates after `setLocale(...)`.
|
|
175
|
+
|
|
176
|
+
## Relationship To The Core Runtime
|
|
177
|
+
|
|
178
|
+
`@routa/i18n-vue` is a wrapper, not a separate runtime.
|
|
179
|
+
|
|
180
|
+
Use the underlying core runtime when you need:
|
|
181
|
+
|
|
182
|
+
- framework-agnostic runtime access
|
|
183
|
+
- low-level runtime control
|
|
184
|
+
- direct compiler integration
|
|
185
|
+
|
|
186
|
+
Use `@routa/i18n-vue` when you need:
|
|
187
|
+
|
|
188
|
+
- `app.use(...)`
|
|
189
|
+
- `useI18n()`
|
|
190
|
+
- `useLocale()`
|
|
191
|
+
- default Vue-friendly convenience exports
|
|
192
|
+
|
|
193
|
+
For lower-level compiler/runtime details, see [../i18n/README.md](../i18n/README.md).
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
const s = /* @__PURE__ */ new Map();
|
|
2
|
+
function M(n) {
|
|
3
|
+
const r = w(n), i = {};
|
|
4
|
+
return y(r, i), i;
|
|
5
|
+
}
|
|
6
|
+
function O(n, r, i) {
|
|
7
|
+
try {
|
|
8
|
+
const e = w(n);
|
|
9
|
+
return f(e, r, i ?? {}, void 0);
|
|
10
|
+
} catch {
|
|
11
|
+
return S(n, i);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function w(n) {
|
|
15
|
+
const r = s.get(n);
|
|
16
|
+
if (r)
|
|
17
|
+
return r;
|
|
18
|
+
const i = {
|
|
19
|
+
index: 0,
|
|
20
|
+
input: n
|
|
21
|
+
}, e = g(i, !1, !1);
|
|
22
|
+
if (i.index !== i.input.length)
|
|
23
|
+
throw new Error(`Unexpected trailing ICU input at index ${i.index}`);
|
|
24
|
+
return s.set(n, e), e;
|
|
25
|
+
}
|
|
26
|
+
function g(n, r, i) {
|
|
27
|
+
const e = [];
|
|
28
|
+
let t = "";
|
|
29
|
+
for (; n.index < n.input.length; ) {
|
|
30
|
+
const o = n.input[n.index];
|
|
31
|
+
if (o === "{") {
|
|
32
|
+
l(e, t), t = "", n.index += 1, e.push(I(n));
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (o === "}") {
|
|
36
|
+
if (r)
|
|
37
|
+
break;
|
|
38
|
+
throw new Error(`Unexpected ICU closing brace at index ${n.index}`);
|
|
39
|
+
}
|
|
40
|
+
if (o === "#" && i) {
|
|
41
|
+
l(e, t), t = "", n.index += 1, e.push({ type: "pound" });
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
t += o, n.index += 1;
|
|
45
|
+
}
|
|
46
|
+
return l(e, t), e;
|
|
47
|
+
}
|
|
48
|
+
function I(n) {
|
|
49
|
+
u(n);
|
|
50
|
+
const r = h(n);
|
|
51
|
+
if (!r)
|
|
52
|
+
throw new Error(`Expected ICU argument name at index ${n.index}`);
|
|
53
|
+
if (u(n), n.input[n.index] === "}")
|
|
54
|
+
return n.index += 1, { type: "argument", name: r };
|
|
55
|
+
c(n, ","), u(n);
|
|
56
|
+
const i = h(n);
|
|
57
|
+
if (!i)
|
|
58
|
+
throw new Error(`Expected ICU argument kind for "${r}" at index ${n.index}`);
|
|
59
|
+
if (u(n), i === "number" || i === "date" || i === "time") {
|
|
60
|
+
if (n.input[n.index] === ",")
|
|
61
|
+
for (n.index += 1; n.index < n.input.length && n.input[n.index] !== "}"; )
|
|
62
|
+
n.index += 1;
|
|
63
|
+
return c(n, "}"), { type: "format", name: r, kind: i };
|
|
64
|
+
}
|
|
65
|
+
if (i !== "plural" && i !== "select")
|
|
66
|
+
throw new Error(`Unsupported ICU kind "${i}" for "${r}"`);
|
|
67
|
+
c(n, ",");
|
|
68
|
+
const e = E(n, i === "plural");
|
|
69
|
+
return c(n, "}"), {
|
|
70
|
+
type: i,
|
|
71
|
+
name: r,
|
|
72
|
+
options: e
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function E(n, r) {
|
|
76
|
+
const i = {};
|
|
77
|
+
for (; n.index < n.input.length && (u(n), n.input[n.index] !== "}"); ) {
|
|
78
|
+
const e = D(n);
|
|
79
|
+
if (!e)
|
|
80
|
+
throw new Error(`Expected ICU selector at index ${n.index}`);
|
|
81
|
+
u(n), c(n, "{");
|
|
82
|
+
const t = g(n, !0, r);
|
|
83
|
+
c(n, "}"), i[e] = t;
|
|
84
|
+
}
|
|
85
|
+
return i;
|
|
86
|
+
}
|
|
87
|
+
function y(n, r) {
|
|
88
|
+
for (const i of n) {
|
|
89
|
+
if (i.type === "argument") {
|
|
90
|
+
p(r, i.name, { kind: "argument" });
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (i.type === "format") {
|
|
94
|
+
p(r, i.name, { kind: i.kind });
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (i.type === "plural" || i.type === "select") {
|
|
98
|
+
p(r, i.name, {
|
|
99
|
+
kind: i.type,
|
|
100
|
+
selectors: Object.keys(i.options)
|
|
101
|
+
});
|
|
102
|
+
for (const e of Object.values(i.options))
|
|
103
|
+
y(e, r);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function p(n, r, i) {
|
|
108
|
+
var o;
|
|
109
|
+
const e = n[r];
|
|
110
|
+
if (!e) {
|
|
111
|
+
n[r] = {
|
|
112
|
+
kind: i.kind,
|
|
113
|
+
selectors: i.selectors ? [...i.selectors] : void 0
|
|
114
|
+
};
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (e.kind !== i.kind)
|
|
118
|
+
throw new Error(`ICU parameter "${r}" changes kind from "${e.kind}" to "${i.kind}"`);
|
|
119
|
+
if (!((o = i.selectors) != null && o.length))
|
|
120
|
+
return;
|
|
121
|
+
const t = /* @__PURE__ */ new Set([...e.selectors ?? [], ...i.selectors]);
|
|
122
|
+
n[r] = {
|
|
123
|
+
kind: e.kind,
|
|
124
|
+
selectors: [...t]
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function f(n, r, i, e) {
|
|
128
|
+
return n.map((t) => U(t, r, i, e)).join("");
|
|
129
|
+
}
|
|
130
|
+
function U(n, r, i, e) {
|
|
131
|
+
if (n.type === "text")
|
|
132
|
+
return n.value;
|
|
133
|
+
if (n.type === "pound")
|
|
134
|
+
return e === void 0 ? "#" : String(e);
|
|
135
|
+
if (n.type === "argument")
|
|
136
|
+
return b(i[n.name], `{${n.name}}`);
|
|
137
|
+
if (n.type === "format")
|
|
138
|
+
return C(n.kind, i[n.name], r, n.name);
|
|
139
|
+
if (n.type === "select") {
|
|
140
|
+
const N = String(i[n.name] ?? "other"), m = n.options[N] ?? n.options.other;
|
|
141
|
+
return m ? f(m, r, i, e) : "";
|
|
142
|
+
}
|
|
143
|
+
const t = k(i[n.name]);
|
|
144
|
+
if (t === null)
|
|
145
|
+
return `{${n.name}}`;
|
|
146
|
+
const o = n.options[`=${t}`];
|
|
147
|
+
if (o)
|
|
148
|
+
return f(o, r, i, t);
|
|
149
|
+
const $ = new Intl.PluralRules(r).select(t), x = n.options[$] ?? n.options.other;
|
|
150
|
+
return x ? f(x, r, i, t) : "";
|
|
151
|
+
}
|
|
152
|
+
function b(n, r) {
|
|
153
|
+
return n == null ? r : n instanceof Date ? n.toISOString() : String(n);
|
|
154
|
+
}
|
|
155
|
+
function C(n, r, i, e) {
|
|
156
|
+
if (n === "number") {
|
|
157
|
+
const d = k(r);
|
|
158
|
+
return d === null ? `{${e}}` : new Intl.NumberFormat(i).format(d);
|
|
159
|
+
}
|
|
160
|
+
const t = F(r);
|
|
161
|
+
return t ? (n === "date" ? new Intl.DateTimeFormat(i) : new Intl.DateTimeFormat(i, {
|
|
162
|
+
hour: "numeric",
|
|
163
|
+
minute: "numeric"
|
|
164
|
+
})).format(t) : `{${e}}`;
|
|
165
|
+
}
|
|
166
|
+
function S(n, r) {
|
|
167
|
+
return r ? n.replace(/\{(\w+)\}/g, (i, e) => b(r[e], `{${e}}`)) : n;
|
|
168
|
+
}
|
|
169
|
+
function k(n) {
|
|
170
|
+
if (typeof n == "number" && Number.isFinite(n))
|
|
171
|
+
return n;
|
|
172
|
+
if (typeof n == "string" && n.trim() !== "") {
|
|
173
|
+
const r = Number(n);
|
|
174
|
+
return Number.isFinite(r) ? r : null;
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
function F(n) {
|
|
179
|
+
if (n instanceof Date && !Number.isNaN(n.valueOf()))
|
|
180
|
+
return n;
|
|
181
|
+
if (typeof n == "number" && Number.isFinite(n)) {
|
|
182
|
+
const r = new Date(n);
|
|
183
|
+
return Number.isNaN(r.valueOf()) ? null : r;
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
function l(n, r) {
|
|
188
|
+
r && n.push({ type: "text", value: r });
|
|
189
|
+
}
|
|
190
|
+
function u(n) {
|
|
191
|
+
for (; n.index < n.input.length && /\s/.test(n.input[n.index]); )
|
|
192
|
+
n.index += 1;
|
|
193
|
+
}
|
|
194
|
+
function h(n) {
|
|
195
|
+
const r = n.index;
|
|
196
|
+
for (; n.index < n.input.length && /[A-Za-z0-9_$-]/.test(n.input[n.index]); )
|
|
197
|
+
n.index += 1;
|
|
198
|
+
return n.input.slice(r, n.index);
|
|
199
|
+
}
|
|
200
|
+
function D(n) {
|
|
201
|
+
const r = n.index;
|
|
202
|
+
for (; n.index < n.input.length && !/[\s{}]/.test(n.input[n.index]); )
|
|
203
|
+
n.index += 1;
|
|
204
|
+
return n.input.slice(r, n.index);
|
|
205
|
+
}
|
|
206
|
+
function c(n, r) {
|
|
207
|
+
if (u(n), n.input[n.index] !== r)
|
|
208
|
+
throw new Error(`Expected "${r}" at index ${n.index}`);
|
|
209
|
+
n.index += 1;
|
|
210
|
+
}
|
|
211
|
+
export {
|
|
212
|
+
M as c,
|
|
213
|
+
O as f
|
|
214
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";const s=new Map;function N(n){const e=w(n),i={};return y(e,i),i}function E(n,e,i){try{const r=w(n);return f(r,e,i??{},void 0)}catch{return M(n,i)}}function w(n){const e=s.get(n);if(e)return e;const i={index:0,input:n},r=g(i,!1,!1);if(i.index!==i.input.length)throw new Error(`Unexpected trailing ICU input at index ${i.index}`);return s.set(n,r),r}function g(n,e,i){const r=[];let t="";for(;n.index<n.input.length;){const o=n.input[n.index];if(o==="{"){l(r,t),t="",n.index+=1,r.push(U(n));continue}if(o==="}"){if(e)break;throw new Error(`Unexpected ICU closing brace at index ${n.index}`)}if(o==="#"&&i){l(r,t),t="",n.index+=1,r.push({type:"pound"});continue}t+=o,n.index+=1}return l(r,t),r}function U(n){u(n);const e=h(n);if(!e)throw new Error(`Expected ICU argument name at index ${n.index}`);if(u(n),n.input[n.index]==="}")return n.index+=1,{type:"argument",name:e};c(n,","),u(n);const i=h(n);if(!i)throw new Error(`Expected ICU argument kind for "${e}" at index ${n.index}`);if(u(n),i==="number"||i==="date"||i==="time"){if(n.input[n.index]===",")for(n.index+=1;n.index<n.input.length&&n.input[n.index]!=="}";)n.index+=1;return c(n,"}"),{type:"format",name:e,kind:i}}if(i!=="plural"&&i!=="select")throw new Error(`Unsupported ICU kind "${i}" for "${e}"`);c(n,",");const r=C(n,i==="plural");return c(n,"}"),{type:i,name:e,options:r}}function C(n,e){const i={};for(;n.index<n.input.length&&(u(n),n.input[n.index]!=="}");){const r=O(n);if(!r)throw new Error(`Expected ICU selector at index ${n.index}`);u(n),c(n,"{");const t=g(n,!0,e);c(n,"}"),i[r]=t}return i}function y(n,e){for(const i of n){if(i.type==="argument"){p(e,i.name,{kind:"argument"});continue}if(i.type==="format"){p(e,i.name,{kind:i.kind});continue}if(i.type==="plural"||i.type==="select"){p(e,i.name,{kind:i.type,selectors:Object.keys(i.options)});for(const r of Object.values(i.options))y(r,e)}}}function p(n,e,i){var o;const r=n[e];if(!r){n[e]={kind:i.kind,selectors:i.selectors?[...i.selectors]:void 0};return}if(r.kind!==i.kind)throw new Error(`ICU parameter "${e}" changes kind from "${r.kind}" to "${i.kind}"`);if(!((o=i.selectors)!=null&&o.length))return;const t=new Set([...r.selectors??[],...i.selectors]);n[e]={kind:r.kind,selectors:[...t]}}function f(n,e,i,r){return n.map(t=>S(t,e,i,r)).join("")}function S(n,e,i,r){if(n.type==="text")return n.value;if(n.type==="pound")return r===void 0?"#":String(r);if(n.type==="argument")return b(i[n.name],`{${n.name}}`);if(n.type==="format")return F(n.kind,i[n.name],e,n.name);if(n.type==="select"){const I=String(i[n.name]??"other"),m=n.options[I]??n.options.other;return m?f(m,e,i,r):""}const t=k(i[n.name]);if(t===null)return`{${n.name}}`;const o=n.options[`=${t}`];if(o)return f(o,e,i,t);const $=new Intl.PluralRules(e).select(t),x=n.options[$]??n.options.other;return x?f(x,e,i,t):""}function b(n,e){return n==null?e:n instanceof Date?n.toISOString():String(n)}function F(n,e,i,r){if(n==="number"){const d=k(e);return d===null?`{${r}}`:new Intl.NumberFormat(i).format(d)}const t=D(e);return t?(n==="date"?new Intl.DateTimeFormat(i):new Intl.DateTimeFormat(i,{hour:"numeric",minute:"numeric"})).format(t):`{${r}}`}function M(n,e){return e?n.replace(/\{(\w+)\}/g,(i,r)=>b(e[r],`{${r}}`)):n}function k(n){if(typeof n=="number"&&Number.isFinite(n))return n;if(typeof n=="string"&&n.trim()!==""){const e=Number(n);return Number.isFinite(e)?e:null}return null}function D(n){if(n instanceof Date&&!Number.isNaN(n.valueOf()))return n;if(typeof n=="number"&&Number.isFinite(n)){const e=new Date(n);return Number.isNaN(e.valueOf())?null:e}return null}function l(n,e){e&&n.push({type:"text",value:e})}function u(n){for(;n.index<n.input.length&&/\s/.test(n.input[n.index]);)n.index+=1}function h(n){const e=n.index;for(;n.index<n.input.length&&/[A-Za-z0-9_$-]/.test(n.input[n.index]);)n.index+=1;return n.input.slice(e,n.index)}function O(n){const e=n.index;for(;n.index<n.input.length&&!/[\s{}]/.test(n.input[n.index]);)n.index+=1;return n.input.slice(e,n.index)}function c(n,e){if(u(n),n.input[n.index]!==e)throw new Error(`Expected "${e}" at index ${n.index}`);n.index+=1}exports.collectIcuParams=N;exports.formatIcuMessage=E;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { CreateI18nOptions, Locale, TranslateFn } from '../../i18n/src/index';
|
|
2
|
+
import { App, ShallowRef } from 'vue';
|
|
3
|
+
export type { CreateI18nOptions, I18nKey, TranslateFn } from '../../i18n/src/index';
|
|
4
|
+
type LocaleMessages = NonNullable<CreateI18nOptions['messages']>[Locale];
|
|
5
|
+
interface CoreLocaleSignal<T> {
|
|
6
|
+
value: T;
|
|
7
|
+
}
|
|
8
|
+
interface CoreI18nContract {
|
|
9
|
+
readonly locale: CoreLocaleSignal<Locale>;
|
|
10
|
+
readonly sourceLocale: Locale;
|
|
11
|
+
t: TranslateFn;
|
|
12
|
+
getLocale(): Locale;
|
|
13
|
+
setLocale(locale: Locale): Promise<void>;
|
|
14
|
+
preloadLocale(locale: Locale): Promise<void>;
|
|
15
|
+
has(key: string, locale?: Locale): boolean;
|
|
16
|
+
addMessages(locale: Locale, data: LocaleMessages): void;
|
|
17
|
+
dispose(): void;
|
|
18
|
+
}
|
|
19
|
+
export interface VueI18n extends Omit<CoreI18nContract, 'locale' | 't' | 'dispose'> {
|
|
20
|
+
readonly core: CoreI18nContract;
|
|
21
|
+
readonly locale: ShallowRef<Locale>;
|
|
22
|
+
t: TranslateFn;
|
|
23
|
+
install(app: App): void;
|
|
24
|
+
dispose(): void;
|
|
25
|
+
}
|
|
26
|
+
export declare function createI18n(options: CreateI18nOptions): VueI18n;
|
|
27
|
+
export declare function useI18n(): VueI18n;
|
|
28
|
+
export declare function useLocale(): import('vue').WritableComputedRef<string, string>;
|
|
29
|
+
export declare const t: TranslateFn;
|
|
30
|
+
export declare const setLocale: (locale: Locale) => Promise<void>;
|
|
31
|
+
export declare const currentLocale: ShallowRef<string>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var z=Object.defineProperty;var K=(e,t,n)=>t in e?z(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var u=(e,t,n)=>K(e,typeof t!="symbol"?t+"":t,n);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const b=require("./icu-DcjP9e-V.cjs"),m=require("vue");let o=null;class N{constructor(t){u(this,"subscribers",new Set);u(this,"_value");u(this,"version",0);u(this,"depth",0);this._value=t}get value(){return o&&(o.dependencies.has(this)||(o.dependencies.add(this),this.subscribers.add(o),this.depth>=o.depth&&(o.depth=this.depth+1))),this._value}set value(t){if(!Object.is(this._value,t)){this._value=t,this.version++;for(const n of Array.from(this.subscribers))n.notify()}}unsubscribe(t){this.subscribers.delete(t)}peek(){return this._value}}function C(e){return new N(e)}class R{constructor(t){u(this,"dependencies",new Set);u(this,"depth",0);u(this,"active",!0);u(this,"running",!1);this.fn=t,this.run()}cleanup(){for(const t of this.dependencies)t.unsubscribe(this);this.dependencies.clear()}run(){if(!this.active)return;if(this.running)throw new Error("[Routa Engine] Circular dependency detected in effect.");this.cleanup();const t=o;o=this,this.running=!0;try{this.fn()}finally{this.running=!1,o=t}}notify(){this.run()}dispose(){this.active=!1,this.cleanup()}}function q(e){const t=new R(e);return()=>t.dispose()}const y="routa_locale";function O(e){const t=e.storageKey===void 0?y:e.storageKey,n=e.initialLocale??$(t)??e.sourceLocale,s=C(n),a=C(Y(e.messages)),c=e.fallbackLocale??e.sourceLocale,g=e.dev??!0;async function S(r){var f;if(a.value[r])return;const l=(f=e.loaders)==null?void 0:f[r];if(!l)return;const L=await l();I(r,L)}async function A(r){await S(r),s.value=r,F(t,r)}function I(r,l){a.value={...a.value,[r]:M(a.value[r],l)}}function P(r,l=s.value){return typeof v(a.value[l],r)=="string"}const T=(r,l,L)=>{const{defaultValue:f,params:p}=D(l,L),i=String(r),w=v(a.value[s.value],i);if(typeof w=="string")return b.formatIcuMessage(w,s.value,p);const _=v(a.value[e.sourceLocale],i)??(c!==e.sourceLocale?v(a.value[c],i):void 0);return typeof _=="string"?(E(e,g,"source",i,s.value),b.formatIcuMessage(_,e.sourceLocale,p)):f!==void 0?(E(e,g,"default",i,s.value),b.formatIcuMessage(f,s.value,p)):i};return{sourceLocale:e.sourceLocale,locale:s,t:T,getLocale:()=>s.value,setLocale:A,preloadLocale:S,has:P,addMessages:I,dispose(){}}}const d=O({sourceLocale:"zh-CN",initialLocale:$(y)??"zh-CN",storageKey:y});d.t;d.locale;d.setLocale;d.addMessages;function v(e,t){if(!e)return;let n=e;for(const s of t.split(".")){if(!n||typeof n=="string")return;n=n[s]}return n}function D(e,t){return typeof e=="string"?{defaultValue:e,params:t}:{defaultValue:void 0,params:e}}function E(e,t,n,s,a){if(!t)return;const c={type:n,key:s,locale:a,message:n==="source"?`[i18n] Missing "${s}" in locale "${a}", falling back to source locale "${e.sourceLocale}".`:`[i18n] Missing "${s}" in locale "${a}", falling back to call-site defaultValue.`};if(e.warn){e.warn(c);return}typeof console<"u"&&typeof console.warn=="function"&&console.warn(c.message)}function Y(e){return e?Object.fromEntries(Object.entries(e).map(([t,n])=>[t,B(n)])):{}}function B(e){return M(void 0,e)}function M(e,t){const n={...e??{}};for(const[s,a]of Object.entries(t)){if(typeof a=="string"){n[s]=a;continue}const c=n[s];n[s]=M(c&&typeof c!="string"?c:void 0,a)}return n}function $(e){if(!e||typeof localStorage>"u")return null;try{return localStorage.getItem(e)}catch{return null}}function F(e,t){if(!(!e||typeof localStorage>"u"))try{localStorage.setItem(e,t)}catch{}}const j=Symbol("routa-i18n");function G(e){return V(O(e))}function k(){return m.inject(j,h)}function U(){const e=k();return m.computed({get:()=>e.locale.value,set:t=>{e.setLocale(t)}})}const h=V(d),W=h.t,H=h.setLocale,J=h.locale;function V(e){const t=m.shallowRef(e.getLocale()),n=q(()=>{t.value=e.locale.value}),s={sourceLocale:e.sourceLocale,core:e,locale:t,t:((a,c,g)=>(t.value,e.t(a,c,g))),getLocale:e.getLocale,setLocale:e.setLocale,preloadLocale:e.preloadLocale,has:e.has,addMessages:e.addMessages,install(a){a.provide(j,s),a.config.globalProperties.$t=s.t},dispose(){n(),e.dispose()}};return s}exports.createI18n=G;exports.currentLocale=J;exports.setLocale=H;exports.t=W;exports.useI18n=k;exports.useLocale=U;
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
var z = Object.defineProperty;
|
|
2
|
+
var K = (e, t, n) => t in e ? z(e, t, { enumerable: !0, configurable: !0, writable: !0, value: n }) : e[t] = n;
|
|
3
|
+
var o = (e, t, n) => K(e, typeof t != "symbol" ? t + "" : t, n);
|
|
4
|
+
import { f as b } from "./icu-7NWX4KPY.js";
|
|
5
|
+
import { shallowRef as N, inject as P, computed as R } from "vue";
|
|
6
|
+
let u = null;
|
|
7
|
+
class T {
|
|
8
|
+
// Signals are always at depth 0
|
|
9
|
+
constructor(t) {
|
|
10
|
+
o(this, "subscribers", /* @__PURE__ */ new Set());
|
|
11
|
+
o(this, "_value");
|
|
12
|
+
o(this, "version", 0);
|
|
13
|
+
o(this, "depth", 0);
|
|
14
|
+
this._value = t;
|
|
15
|
+
}
|
|
16
|
+
get value() {
|
|
17
|
+
return u && (u.dependencies.has(this) || (u.dependencies.add(this), this.subscribers.add(u), this.depth >= u.depth && (u.depth = this.depth + 1))), this._value;
|
|
18
|
+
}
|
|
19
|
+
set value(t) {
|
|
20
|
+
if (!Object.is(this._value, t)) {
|
|
21
|
+
this._value = t, this.version++;
|
|
22
|
+
for (const n of Array.from(this.subscribers))
|
|
23
|
+
n.notify();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
unsubscribe(t) {
|
|
27
|
+
this.subscribers.delete(t);
|
|
28
|
+
}
|
|
29
|
+
peek() {
|
|
30
|
+
return this._value;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function _(e) {
|
|
34
|
+
return new T(e);
|
|
35
|
+
}
|
|
36
|
+
class D {
|
|
37
|
+
constructor(t) {
|
|
38
|
+
o(this, "dependencies", /* @__PURE__ */ new Set());
|
|
39
|
+
o(this, "depth", 0);
|
|
40
|
+
o(this, "active", !0);
|
|
41
|
+
o(this, "running", !1);
|
|
42
|
+
this.fn = t, this.run();
|
|
43
|
+
}
|
|
44
|
+
cleanup() {
|
|
45
|
+
for (const t of this.dependencies)
|
|
46
|
+
t.unsubscribe(this);
|
|
47
|
+
this.dependencies.clear();
|
|
48
|
+
}
|
|
49
|
+
run() {
|
|
50
|
+
if (!this.active) return;
|
|
51
|
+
if (this.running)
|
|
52
|
+
throw new Error("[Routa Engine] Circular dependency detected in effect.");
|
|
53
|
+
this.cleanup();
|
|
54
|
+
const t = u;
|
|
55
|
+
u = this, this.running = !0;
|
|
56
|
+
try {
|
|
57
|
+
this.fn();
|
|
58
|
+
} finally {
|
|
59
|
+
this.running = !1, u = t;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
notify() {
|
|
63
|
+
this.run();
|
|
64
|
+
}
|
|
65
|
+
dispose() {
|
|
66
|
+
this.active = !1, this.cleanup();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function Y(e) {
|
|
70
|
+
const t = new D(e);
|
|
71
|
+
return () => t.dispose();
|
|
72
|
+
}
|
|
73
|
+
const y = "routa_locale";
|
|
74
|
+
function E(e) {
|
|
75
|
+
const t = e.storageKey === void 0 ? y : e.storageKey, n = e.initialLocale ?? $(t) ?? e.sourceLocale, s = _(n), a = _(F(e.messages)), c = e.fallbackLocale ?? e.sourceLocale, g = e.dev ?? !0;
|
|
76
|
+
async function w(r) {
|
|
77
|
+
var f;
|
|
78
|
+
if (a.value[r])
|
|
79
|
+
return;
|
|
80
|
+
const l = (f = e.loaders) == null ? void 0 : f[r];
|
|
81
|
+
if (!l)
|
|
82
|
+
return;
|
|
83
|
+
const p = await l();
|
|
84
|
+
S(r, p);
|
|
85
|
+
}
|
|
86
|
+
async function k(r) {
|
|
87
|
+
await w(r), s.value = r, U(t, r);
|
|
88
|
+
}
|
|
89
|
+
function S(r, l) {
|
|
90
|
+
a.value = {
|
|
91
|
+
...a.value,
|
|
92
|
+
[r]: m(a.value[r], l)
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function V(r, l = s.value) {
|
|
96
|
+
return typeof v(a.value[l], r) == "string";
|
|
97
|
+
}
|
|
98
|
+
const A = (r, l, p) => {
|
|
99
|
+
const { defaultValue: f, params: L } = B(l, p), i = String(r), M = v(a.value[s.value], i);
|
|
100
|
+
if (typeof M == "string")
|
|
101
|
+
return b(M, s.value, L);
|
|
102
|
+
const I = v(a.value[e.sourceLocale], i) ?? (c !== e.sourceLocale ? v(a.value[c], i) : void 0);
|
|
103
|
+
return typeof I == "string" ? (C(e, g, "source", i, s.value), b(I, e.sourceLocale, L)) : f !== void 0 ? (C(e, g, "default", i, s.value), b(f, s.value, L)) : i;
|
|
104
|
+
};
|
|
105
|
+
return {
|
|
106
|
+
sourceLocale: e.sourceLocale,
|
|
107
|
+
locale: s,
|
|
108
|
+
t: A,
|
|
109
|
+
getLocale: () => s.value,
|
|
110
|
+
setLocale: k,
|
|
111
|
+
preloadLocale: w,
|
|
112
|
+
has: V,
|
|
113
|
+
addMessages: S,
|
|
114
|
+
dispose() {
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const d = E({
|
|
119
|
+
sourceLocale: "zh-CN",
|
|
120
|
+
initialLocale: $(y) ?? "zh-CN",
|
|
121
|
+
storageKey: y
|
|
122
|
+
});
|
|
123
|
+
d.t;
|
|
124
|
+
d.locale;
|
|
125
|
+
d.setLocale;
|
|
126
|
+
d.addMessages;
|
|
127
|
+
function v(e, t) {
|
|
128
|
+
if (!e)
|
|
129
|
+
return;
|
|
130
|
+
let n = e;
|
|
131
|
+
for (const s of t.split(".")) {
|
|
132
|
+
if (!n || typeof n == "string")
|
|
133
|
+
return;
|
|
134
|
+
n = n[s];
|
|
135
|
+
}
|
|
136
|
+
return n;
|
|
137
|
+
}
|
|
138
|
+
function B(e, t) {
|
|
139
|
+
return typeof e == "string" ? {
|
|
140
|
+
defaultValue: e,
|
|
141
|
+
params: t
|
|
142
|
+
} : {
|
|
143
|
+
defaultValue: void 0,
|
|
144
|
+
params: e
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function C(e, t, n, s, a) {
|
|
148
|
+
if (!t)
|
|
149
|
+
return;
|
|
150
|
+
const c = {
|
|
151
|
+
type: n,
|
|
152
|
+
key: s,
|
|
153
|
+
locale: a,
|
|
154
|
+
message: n === "source" ? `[i18n] Missing "${s}" in locale "${a}", falling back to source locale "${e.sourceLocale}".` : `[i18n] Missing "${s}" in locale "${a}", falling back to call-site defaultValue.`
|
|
155
|
+
};
|
|
156
|
+
if (e.warn) {
|
|
157
|
+
e.warn(c);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
typeof console < "u" && typeof console.warn == "function" && console.warn(c.message);
|
|
161
|
+
}
|
|
162
|
+
function F(e) {
|
|
163
|
+
return e ? Object.fromEntries(
|
|
164
|
+
Object.entries(e).map(([t, n]) => [t, G(n)])
|
|
165
|
+
) : {};
|
|
166
|
+
}
|
|
167
|
+
function G(e) {
|
|
168
|
+
return m(void 0, e);
|
|
169
|
+
}
|
|
170
|
+
function m(e, t) {
|
|
171
|
+
const n = { ...e ?? {} };
|
|
172
|
+
for (const [s, a] of Object.entries(t)) {
|
|
173
|
+
if (typeof a == "string") {
|
|
174
|
+
n[s] = a;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const c = n[s];
|
|
178
|
+
n[s] = m(
|
|
179
|
+
c && typeof c != "string" ? c : void 0,
|
|
180
|
+
a
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
return n;
|
|
184
|
+
}
|
|
185
|
+
function $(e) {
|
|
186
|
+
if (!e || typeof localStorage > "u")
|
|
187
|
+
return null;
|
|
188
|
+
try {
|
|
189
|
+
return localStorage.getItem(e);
|
|
190
|
+
} catch {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function U(e, t) {
|
|
195
|
+
if (!(!e || typeof localStorage > "u"))
|
|
196
|
+
try {
|
|
197
|
+
localStorage.setItem(e, t);
|
|
198
|
+
} catch {
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const O = Symbol("routa-i18n");
|
|
202
|
+
function Q(e) {
|
|
203
|
+
return j(E(e));
|
|
204
|
+
}
|
|
205
|
+
function W() {
|
|
206
|
+
return P(O, h);
|
|
207
|
+
}
|
|
208
|
+
function X() {
|
|
209
|
+
const e = W();
|
|
210
|
+
return R({
|
|
211
|
+
get: () => e.locale.value,
|
|
212
|
+
set: (t) => {
|
|
213
|
+
e.setLocale(t);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
const h = j(d), Z = h.t, x = h.setLocale, ee = h.locale;
|
|
218
|
+
function j(e) {
|
|
219
|
+
const t = N(e.getLocale()), n = Y(() => {
|
|
220
|
+
t.value = e.locale.value;
|
|
221
|
+
}), s = {
|
|
222
|
+
sourceLocale: e.sourceLocale,
|
|
223
|
+
core: e,
|
|
224
|
+
locale: t,
|
|
225
|
+
t: ((a, c, g) => (t.value, e.t(a, c, g))),
|
|
226
|
+
getLocale: e.getLocale,
|
|
227
|
+
setLocale: e.setLocale,
|
|
228
|
+
preloadLocale: e.preloadLocale,
|
|
229
|
+
has: e.has,
|
|
230
|
+
addMessages: e.addMessages,
|
|
231
|
+
install(a) {
|
|
232
|
+
a.provide(O, s), a.config.globalProperties.$t = s.t;
|
|
233
|
+
},
|
|
234
|
+
dispose() {
|
|
235
|
+
n(), e.dispose();
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
return s;
|
|
239
|
+
}
|
|
240
|
+
export {
|
|
241
|
+
Q as createI18n,
|
|
242
|
+
ee as currentLocale,
|
|
243
|
+
x as setLocale,
|
|
244
|
+
Z as t,
|
|
245
|
+
W as useI18n,
|
|
246
|
+
X as useLocale
|
|
247
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { i18nPlugin } from '../../i18n/src/vite-plugin';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";var I=Object.defineProperty;var b=(i,t,e)=>t in i?I(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e;var S=(i,t,e)=>b(i,typeof t!="symbol"?t+"":t,e);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("node:path"),o=require("typescript"),p=require("node:fs"),g=require("./icu-DcjP9e-V.cjs");function T(i){return!!i&&typeof i=="object"&&!Array.isArray(i)}class C{constructor(t){S(this,"localeFileCache",new Map);S(this,"lastTranslationsMap",null);this.options=t}setStrict(t){this.options.strict=t}generate(){this.localeFileCache.clear();const{localesDir:t,outputDir:e,sourceLocale:r}=this.options;if(!p.existsSync(t)){this.warn(`[i18n] Locales directory not found: ${t}`);return}const n=this.loadAllLocales(t);if(!n[r])throw new Error(`[i18n] Source locale not found: ${r}`);const a=this.validateLocales(n);this.flushDiagnostics(a),p.mkdirSync(e,{recursive:!0}),this.writeRuntimeArtifacts(n),this.writeVirtualKeyDeclaration(n),this.removeObsoleteArtifacts(e),this.lastTranslationsMap=n}renderVirtualKeyRuntimeModule(){this.lastTranslationsMap||this.generate();const t=this.lastTranslationsMap;if(!t)return`export const k = {} as const
|
|
2
|
+
export default k
|
|
3
|
+
`;const e=t[this.options.sourceLocale],r=Object.keys(e).filter(j);return["export const k = "+this.renderRuntimeKeyTree(e),...r.map(n=>`export const ${n} = k.${n}`),"export default k",""].join(`
|
|
4
|
+
`)}loadAllLocales(t){const e=p.readdirSync(t).filter(n=>p.statSync(u.join(t,n)).isDirectory()).sort((n,s)=>n.localeCompare(s)),r={};for(const n of e)r[n]=this.loadLocaleCatalog(u.join(t,n));return r}loadLocaleCatalog(t){const e=this.getDomainLocaleFiles(t),r={};for(const n of e)r[n.domain]=this.parseLocaleFile(n.filePath);return r}getDomainLocaleFiles(t){return p.readdirSync(t).filter(e=>e==="index.ts"||e.endsWith(".d.ts")?!1:e.endsWith(".ts")||e.endsWith(".json")).map(e=>({domain:u.basename(e,u.extname(e)),filePath:u.join(t,e)})).sort((e,r)=>e.domain.localeCompare(r.domain))}parseLocaleFile(t){const e=u.resolve(t),r=this.localeFileCache.get(e);if(r)return r;if(u.extname(e)===".json"){const l=JSON.parse(p.readFileSync(e,"utf8"));return this.localeFileCache.set(e,l),l}const s=o.createSourceFile(e,p.readFileSync(e,"utf8"),o.ScriptTarget.Latest,!0,o.ScriptKind.TS),a={declarations:this.collectDeclarations(s),sourceFile:s},c=this.parseDefaultExport(s,a);return this.localeFileCache.set(e,c),c}collectDeclarations(t){const e=new Map;for(const r of t.statements)if(o.isVariableStatement(r))for(const n of r.declarationList.declarations)!o.isIdentifier(n.name)||!n.initializer||e.set(n.name.text,n.initializer);return e}parseDefaultExport(t,e){for(const r of t.statements)if(o.isExportAssignment(r)&&!r.isExportEquals)return this.parseLocaleExpression(r.expression,e);throw new Error(`[i18n] Missing default export in locale file: ${t.fileName}`)}parseLocaleExpression(t,e){const r=this.unwrapExpression(t);if(o.isIdentifier(r)){const n=e.declarations.get(r.text);if(!n)throw new Error(`[i18n] Unsupported locale export reference "${r.text}" in ${e.sourceFile.fileName}`);return this.parseLocaleExpression(n,e)}if(!o.isObjectLiteralExpression(r))throw new Error(`[i18n] Locale default export must be an object literal in ${e.sourceFile.fileName}`);return this.parseLocaleObjectLiteral(r,e)}parseLocaleObjectLiteral(t,e){const r={};for(const n of t.properties){if(o.isSpreadAssignment(n))throw new Error(`[i18n] Unsupported spread locale property in ${e.sourceFile.fileName}`);if(!o.isPropertyAssignment(n)&&!o.isShorthandPropertyAssignment(n))throw new Error(`[i18n] Unsupported locale property in ${e.sourceFile.fileName}`);const s=this.readPropertyName(n.name,e.sourceFile),a=o.isShorthandPropertyAssignment(n)?n.name:n.initializer;r[s]=this.parseLocaleValue(a,e)}return r}parseLocaleValue(t,e){const r=this.unwrapExpression(t);if(o.isStringLiteral(r)||o.isNoSubstitutionTemplateLiteral(r))return r.text;if(o.isIdentifier(r)){const n=e.declarations.get(r.text);if(!n)throw new Error(`[i18n] Unsupported locale value reference "${r.text}" in ${e.sourceFile.fileName}`);return this.parseLocaleValue(n,e)}if(o.isObjectLiteralExpression(r))return this.parseLocaleObjectLiteral(r,e);throw new Error(`[i18n] Locale values must be strings or nested objects in ${e.sourceFile.fileName}`)}unwrapExpression(t){let e=t;for(;o.isAsExpression(e)||o.isParenthesizedExpression(e)||o.isSatisfiesExpression(e)||o.isTypeAssertionExpression(e);)e=e.expression;return e}readPropertyName(t,e){if(o.isIdentifier(t)||o.isStringLiteral(t)||o.isNumericLiteral(t))return t.text;throw new Error(`[i18n] Unsupported locale property name "${t.getText(e)}" in ${e.fileName}`)}validateLocales(t){const e=[],r=t[this.options.sourceLocale];this.validateSourceMessages(r,e,"");for(const[n,s]of Object.entries(t))n!==this.options.sourceLocale&&this.assertMatchingLocaleTree(r,s,n,"",e);return e}validateSourceMessages(t,e,r){if(typeof t=="string"){try{g.collectIcuParams(t)}catch(n){e.push(this.createDiagnostic("error",`[i18n] Invalid ICU message at "${r}": ${L(n)}`))}return}for(const[n,s]of Object.entries(t)){const a=r?`${r}.${n}`:n;this.validateSourceMessages(s,e,a)}}assertMatchingLocaleTree(t,e,r,n,s){if(typeof t=="string"){if(typeof e!="string"){s.push(this.createMissingKeyDiagnostic(r,n));return}this.compareMessages(t,e,r,n,s);return}if(!T(e)){this.collectMissingLeafDiagnostics(t,r,n,s);return}for(const[a,c]of Object.entries(t)){const l=n?`${n}.${a}`:a;this.assertMatchingLocaleTree(c,e[a],r,l,s)}}collectMissingLeafDiagnostics(t,e,r,n){if(typeof t=="string"){n.push(this.createMissingKeyDiagnostic(e,r));return}for(const[s,a]of Object.entries(t)){const c=r?`${r}.${s}`:s;this.collectMissingLeafDiagnostics(a,e,c,n)}}compareMessages(t,e,r,n,s){let a,c;try{a=g.collectIcuParams(t),c=g.collectIcuParams(e)}catch(f){s.push(this.createDiagnostic("error",`[i18n] ICU signature error at "${n}" in locale "${r}": ${L(f)}`));return}const l=Object.keys(a).sort(),d=Object.keys(c).sort();if(l.join("|")!==d.join("|")){s.push(this.createDiagnostic("error",`[i18n] ICU params mismatch at "${n}" in locale "${r}". Expected [${l.join(", ")}], received [${d.join(", ")}].`));return}for(const f of l){const m=a[f],h=c[f];if(!h||m.kind!==h.kind){s.push(this.createDiagnostic("error",`[i18n] ICU param "${f}" at "${n}" in locale "${r}" must stay "${m.kind}".`));continue}if(m.kind==="select"){const E=new Set([...m.selectors??[],"other"]);for(const w of E)(h.selectors??[]).includes(w)||s.push(this.createDiagnostic("error",`[i18n] ICU select "${f}" at "${n}" in locale "${r}" is missing selector "${w}".`))}m.kind==="plural"&&!(h.selectors??[]).includes("other")&&s.push(this.createDiagnostic("error",`[i18n] ICU plural "${f}" at "${n}" in locale "${r}" must include "other".`))}}createMissingKeyDiagnostic(t,e){const[r="unknown"]=e.split("."),n=u.join(this.options.localesDir,t,`${r}.ts`),s=this.options.strict===!1?"warn":"error";return this.createDiagnostic(s,`[i18n] Locale "${t}" is missing key "${e}". File: ${n}. Reference locale: ${this.options.sourceLocale}`)}createDiagnostic(t,e){return{level:t,message:e}}flushDiagnostics(t){for(const r of t.filter(n=>n.level==="warn"))this.warn(r.message);const e=t.filter(r=>r.level==="error");if(e.length>0)throw new Error(e.map(r=>r.message).join(`
|
|
5
|
+
`))}writeRuntimeArtifacts(t){const e=u.join(this.options.outputDir,"runtime");p.mkdirSync(e,{recursive:!0});const r=Object.keys(t).sort((n,s)=>n.localeCompare(s));p.writeFileSync(u.join(e,"manifest.json"),`${JSON.stringify({sourceLocale:this.options.sourceLocale,locales:r},null,2)}
|
|
6
|
+
`);for(const n of r)p.writeFileSync(u.join(e,`${n}.json`),`${JSON.stringify(t[n],null,2)}
|
|
7
|
+
`)}writeVirtualKeyDeclaration(t){const e=t[this.options.sourceLocale],r=Object.keys(e).filter(j),n=["declare module 'virtual:routa-i18n/keys' {"," import type { I18nKey, I18nPrimitive } from '@routa/i18n'","",` export declare const k: ${this.renderDeclarationTree(e,t,"",1)}`,...r.map(s=>` export declare const ${s}: typeof k.${s}`),"}",""].join(`
|
|
8
|
+
`);p.writeFileSync(u.join(this.options.outputDir,"virtual-routa-i18n-keys.d.ts"),n)}renderDeclarationTree(t,e,r="",n=0){const s=" ".repeat(n),a=" ".repeat(n+1);return typeof t=="string"?`I18nKey<'${$(r)}', ${this.renderParamsType(t)}>`:`{
|
|
9
|
+
${Object.entries(t).map(([l,d])=>{const f=r?`${r}.${l}`:l,m=this.renderPropertyName(l);return typeof d=="string"?`${this.renderHoverDoc(e,f,n+1)}
|
|
10
|
+
${a}${m}: ${this.renderDeclarationTree(d,e,f,n+1)}`:`${a}${m}: ${this.renderDeclarationTree(d,e,f,n+1)}`}).join(`,
|
|
11
|
+
`)}
|
|
12
|
+
${s}}`}renderHoverDoc(t,e,r){const n=" ".repeat(r),s=[`${n}/**`];for(const[a,c]of Object.entries(t)){const l=this.getTranslationByPath(c,e);l!==null&&s.push(`${n} * ${a}: ${P(l)}`)}return s.push(`${n} */`),s.join(`
|
|
13
|
+
`)}renderParamsType(t){const e=g.collectIcuParams(t),r=Object.entries(e);return r.length===0?"undefined":`{ ${r.map(([s,a])=>`${this.renderPropertyName(s)}: ${this.renderParamMetaType(a)}`).join("; ")} }`}renderParamMetaType(t){var r;return t.kind==="argument"?"I18nPrimitive":t.kind==="number"||t.kind==="plural"?"number":t.kind==="date"||t.kind==="time"?"Date | number":((r=t.selectors)!=null&&r.length?t.selectors:["other"]).map(n=>`'${$(n)}'`).join(" | ")}renderRuntimeKeyTree(t,e="",r=0){const n=" ".repeat(r),s=" ".repeat(r+1);return typeof t=="string"?`'${$(e)}'`:`{
|
|
14
|
+
${Object.entries(t).map(([c,l])=>{const d=e?`${e}.${c}`:c;return`${s}${this.renderPropertyName(c)}: ${this.renderRuntimeKeyTree(l,d,r+1)}`}).join(`,
|
|
15
|
+
`)}
|
|
16
|
+
${n}}`}getTranslationByPath(t,e){const r=e.split(".");let n=t;for(const s of r){if(!n||typeof n=="string")return null;n=n[s]}return typeof n=="string"?n:null}renderPropertyName(t){return j(t)?t:`'${$(t)}'`}removeObsoleteArtifacts(t){for(const e of["keys.ts","keys.gen.ts","meta.json","source-catalog.ts"]){const r=u.join(t,e);p.existsSync(r)&&p.unlinkSync(r)}}warn(t){if(this.options.onWarn){this.options.onWarn(t);return}console.warn(t)}}function $(i){return i.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}function P(i){return i.replace(/\r?\n/g,"\\n").replace(/\*\//g,"*\\/")}function L(i){return i instanceof Error?i.message:String(i)}function j(i){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(i)}const x="virtual:routa-i18n/keys",D=`\0${x}`;function v(i){const t=new C(i),e=i.include.map(r=>y(u.resolve(r)));return{name:"routa:i18n",resolveId(r){return r===x?D:null},load(r){return r===D?t.renderVirtualKeyRuntimeModule():null},configResolved(r){t.setStrict(i.strict??(r.command==="build"||process.env.CI==="true")),t.generate()},transform(r,n){const s=y(n.split("?")[0]);if(!e.some(l=>s.startsWith(l))||!O(n,s))return null;const c=F(r,s);return c===r?null:{code:c,map:null}},configureServer(r){r.watcher.add(u.resolve(i.localesDir)),r.watcher.on("change",n=>{y(n).startsWith(y(u.resolve(i.localesDir)))&&(t.generate(),r.ws.send({type:"full-reload"}))})}}}function F(i,t){const e=o.createSourceFile(t,i,o.ScriptTarget.Latest,!0,A(t)),r=M(e);if(r.size===0)return i;const n=[],s=c=>{if(o.isPropertyAccessExpression(c)){const l=N(c,r);if(l){n.push({start:c.getStart(e),end:c.getEnd(),replacement:`'${l}'`});return}}o.forEachChild(c,s)};if(s(e),n.length===0)return i;let a=i;for(const c of n.sort((l,d)=>d.start-l.start))a=`${a.slice(0,c.start)}${c.replacement}${a.slice(c.end)}`;return a}function M(i){var e,r;const t=new Map;for(const n of i.statements){if(!o.isImportDeclaration(n)||!((e=n.importClause)!=null&&e.namedBindings)||!o.isStringLiteral(n.moduleSpecifier)||n.moduleSpecifier.text!==x)continue;const s=n.importClause.namedBindings;if(o.isNamedImports(s))for(const a of s.elements){const c=((r=a.propertyName)==null?void 0:r.text)??a.name.text;t.set(a.name.text,c==="k"?"":c)}}return t}function N(i,t){const e=[i.name.text];let r=i.expression;for(;o.isPropertyAccessExpression(r);)e.unshift(r.name.text),r=r.expression;if(!o.isIdentifier(r))return null;const n=t.get(r.text);return n===void 0?null:n?`${n}.${e.join(".")}`:e.join(".")}function O(i,t){return/\.(ts|tsx|js|jsx|vue)$/.test(t)||i.includes(".vue?")||i.includes("&lang.ts")||i.includes("&lang.js")}function A(i){return i.endsWith(".tsx")?o.ScriptKind.TSX:i.endsWith(".jsx")?o.ScriptKind.JSX:i.endsWith(".js")?o.ScriptKind.JS:o.ScriptKind.TS}function y(i){return i.replaceAll("\\","/")}exports.i18nPlugin=v;
|
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
var I = Object.defineProperty;
|
|
2
|
+
var b = (i, t, e) => t in i ? I(i, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : i[t] = e;
|
|
3
|
+
var x = (i, t, e) => b(i, typeof t != "symbol" ? t + "" : t, e);
|
|
4
|
+
import u from "node:path";
|
|
5
|
+
import o from "typescript";
|
|
6
|
+
import p from "node:fs";
|
|
7
|
+
import { c as g } from "./icu-7NWX4KPY.js";
|
|
8
|
+
function T(i) {
|
|
9
|
+
return !!i && typeof i == "object" && !Array.isArray(i);
|
|
10
|
+
}
|
|
11
|
+
class C {
|
|
12
|
+
constructor(t) {
|
|
13
|
+
x(this, "localeFileCache", /* @__PURE__ */ new Map());
|
|
14
|
+
x(this, "lastTranslationsMap", null);
|
|
15
|
+
this.options = t;
|
|
16
|
+
}
|
|
17
|
+
setStrict(t) {
|
|
18
|
+
this.options.strict = t;
|
|
19
|
+
}
|
|
20
|
+
generate() {
|
|
21
|
+
this.localeFileCache.clear();
|
|
22
|
+
const { localesDir: t, outputDir: e, sourceLocale: r } = this.options;
|
|
23
|
+
if (!p.existsSync(t)) {
|
|
24
|
+
this.warn(`[i18n] Locales directory not found: ${t}`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const n = this.loadAllLocales(t);
|
|
28
|
+
if (!n[r])
|
|
29
|
+
throw new Error(`[i18n] Source locale not found: ${r}`);
|
|
30
|
+
const a = this.validateLocales(n);
|
|
31
|
+
this.flushDiagnostics(a), p.mkdirSync(e, { recursive: !0 }), this.writeRuntimeArtifacts(n), this.writeVirtualKeyDeclaration(n), this.removeObsoleteArtifacts(e), this.lastTranslationsMap = n;
|
|
32
|
+
}
|
|
33
|
+
renderVirtualKeyRuntimeModule() {
|
|
34
|
+
this.lastTranslationsMap || this.generate();
|
|
35
|
+
const t = this.lastTranslationsMap;
|
|
36
|
+
if (!t)
|
|
37
|
+
return `export const k = {} as const
|
|
38
|
+
export default k
|
|
39
|
+
`;
|
|
40
|
+
const e = t[this.options.sourceLocale], r = Object.keys(e).filter(S);
|
|
41
|
+
return [
|
|
42
|
+
"export const k = " + this.renderRuntimeKeyTree(e),
|
|
43
|
+
...r.map((n) => `export const ${n} = k.${n}`),
|
|
44
|
+
"export default k",
|
|
45
|
+
""
|
|
46
|
+
].join(`
|
|
47
|
+
`);
|
|
48
|
+
}
|
|
49
|
+
loadAllLocales(t) {
|
|
50
|
+
const e = p.readdirSync(t).filter((n) => p.statSync(u.join(t, n)).isDirectory()).sort((n, s) => n.localeCompare(s)), r = {};
|
|
51
|
+
for (const n of e)
|
|
52
|
+
r[n] = this.loadLocaleCatalog(u.join(t, n));
|
|
53
|
+
return r;
|
|
54
|
+
}
|
|
55
|
+
loadLocaleCatalog(t) {
|
|
56
|
+
const e = this.getDomainLocaleFiles(t), r = {};
|
|
57
|
+
for (const n of e)
|
|
58
|
+
r[n.domain] = this.parseLocaleFile(n.filePath);
|
|
59
|
+
return r;
|
|
60
|
+
}
|
|
61
|
+
getDomainLocaleFiles(t) {
|
|
62
|
+
return p.readdirSync(t).filter((e) => e === "index.ts" || e.endsWith(".d.ts") ? !1 : e.endsWith(".ts") || e.endsWith(".json")).map((e) => ({
|
|
63
|
+
domain: u.basename(e, u.extname(e)),
|
|
64
|
+
filePath: u.join(t, e)
|
|
65
|
+
})).sort((e, r) => e.domain.localeCompare(r.domain));
|
|
66
|
+
}
|
|
67
|
+
parseLocaleFile(t) {
|
|
68
|
+
const e = u.resolve(t), r = this.localeFileCache.get(e);
|
|
69
|
+
if (r)
|
|
70
|
+
return r;
|
|
71
|
+
if (u.extname(e) === ".json") {
|
|
72
|
+
const l = JSON.parse(p.readFileSync(e, "utf8"));
|
|
73
|
+
return this.localeFileCache.set(e, l), l;
|
|
74
|
+
}
|
|
75
|
+
const s = o.createSourceFile(
|
|
76
|
+
e,
|
|
77
|
+
p.readFileSync(e, "utf8"),
|
|
78
|
+
o.ScriptTarget.Latest,
|
|
79
|
+
!0,
|
|
80
|
+
o.ScriptKind.TS
|
|
81
|
+
), a = {
|
|
82
|
+
declarations: this.collectDeclarations(s),
|
|
83
|
+
sourceFile: s
|
|
84
|
+
}, c = this.parseDefaultExport(s, a);
|
|
85
|
+
return this.localeFileCache.set(e, c), c;
|
|
86
|
+
}
|
|
87
|
+
collectDeclarations(t) {
|
|
88
|
+
const e = /* @__PURE__ */ new Map();
|
|
89
|
+
for (const r of t.statements)
|
|
90
|
+
if (o.isVariableStatement(r))
|
|
91
|
+
for (const n of r.declarationList.declarations)
|
|
92
|
+
!o.isIdentifier(n.name) || !n.initializer || e.set(n.name.text, n.initializer);
|
|
93
|
+
return e;
|
|
94
|
+
}
|
|
95
|
+
parseDefaultExport(t, e) {
|
|
96
|
+
for (const r of t.statements)
|
|
97
|
+
if (o.isExportAssignment(r) && !r.isExportEquals)
|
|
98
|
+
return this.parseLocaleExpression(r.expression, e);
|
|
99
|
+
throw new Error(`[i18n] Missing default export in locale file: ${t.fileName}`);
|
|
100
|
+
}
|
|
101
|
+
parseLocaleExpression(t, e) {
|
|
102
|
+
const r = this.unwrapExpression(t);
|
|
103
|
+
if (o.isIdentifier(r)) {
|
|
104
|
+
const n = e.declarations.get(r.text);
|
|
105
|
+
if (!n)
|
|
106
|
+
throw new Error(`[i18n] Unsupported locale export reference "${r.text}" in ${e.sourceFile.fileName}`);
|
|
107
|
+
return this.parseLocaleExpression(n, e);
|
|
108
|
+
}
|
|
109
|
+
if (!o.isObjectLiteralExpression(r))
|
|
110
|
+
throw new Error(`[i18n] Locale default export must be an object literal in ${e.sourceFile.fileName}`);
|
|
111
|
+
return this.parseLocaleObjectLiteral(r, e);
|
|
112
|
+
}
|
|
113
|
+
parseLocaleObjectLiteral(t, e) {
|
|
114
|
+
const r = {};
|
|
115
|
+
for (const n of t.properties) {
|
|
116
|
+
if (o.isSpreadAssignment(n))
|
|
117
|
+
throw new Error(`[i18n] Unsupported spread locale property in ${e.sourceFile.fileName}`);
|
|
118
|
+
if (!o.isPropertyAssignment(n) && !o.isShorthandPropertyAssignment(n))
|
|
119
|
+
throw new Error(`[i18n] Unsupported locale property in ${e.sourceFile.fileName}`);
|
|
120
|
+
const s = this.readPropertyName(n.name, e.sourceFile), a = o.isShorthandPropertyAssignment(n) ? n.name : n.initializer;
|
|
121
|
+
r[s] = this.parseLocaleValue(a, e);
|
|
122
|
+
}
|
|
123
|
+
return r;
|
|
124
|
+
}
|
|
125
|
+
parseLocaleValue(t, e) {
|
|
126
|
+
const r = this.unwrapExpression(t);
|
|
127
|
+
if (o.isStringLiteral(r) || o.isNoSubstitutionTemplateLiteral(r))
|
|
128
|
+
return r.text;
|
|
129
|
+
if (o.isIdentifier(r)) {
|
|
130
|
+
const n = e.declarations.get(r.text);
|
|
131
|
+
if (!n)
|
|
132
|
+
throw new Error(`[i18n] Unsupported locale value reference "${r.text}" in ${e.sourceFile.fileName}`);
|
|
133
|
+
return this.parseLocaleValue(n, e);
|
|
134
|
+
}
|
|
135
|
+
if (o.isObjectLiteralExpression(r))
|
|
136
|
+
return this.parseLocaleObjectLiteral(r, e);
|
|
137
|
+
throw new Error(`[i18n] Locale values must be strings or nested objects in ${e.sourceFile.fileName}`);
|
|
138
|
+
}
|
|
139
|
+
unwrapExpression(t) {
|
|
140
|
+
let e = t;
|
|
141
|
+
for (; o.isAsExpression(e) || o.isParenthesizedExpression(e) || o.isSatisfiesExpression(e) || o.isTypeAssertionExpression(e); )
|
|
142
|
+
e = e.expression;
|
|
143
|
+
return e;
|
|
144
|
+
}
|
|
145
|
+
readPropertyName(t, e) {
|
|
146
|
+
if (o.isIdentifier(t) || o.isStringLiteral(t) || o.isNumericLiteral(t))
|
|
147
|
+
return t.text;
|
|
148
|
+
throw new Error(`[i18n] Unsupported locale property name "${t.getText(e)}" in ${e.fileName}`);
|
|
149
|
+
}
|
|
150
|
+
validateLocales(t) {
|
|
151
|
+
const e = [], r = t[this.options.sourceLocale];
|
|
152
|
+
this.validateSourceMessages(r, e, "");
|
|
153
|
+
for (const [n, s] of Object.entries(t))
|
|
154
|
+
n !== this.options.sourceLocale && this.assertMatchingLocaleTree(r, s, n, "", e);
|
|
155
|
+
return e;
|
|
156
|
+
}
|
|
157
|
+
validateSourceMessages(t, e, r) {
|
|
158
|
+
if (typeof t == "string") {
|
|
159
|
+
try {
|
|
160
|
+
g(t);
|
|
161
|
+
} catch (n) {
|
|
162
|
+
e.push(this.createDiagnostic("error", `[i18n] Invalid ICU message at "${r}": ${L(n)}`));
|
|
163
|
+
}
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
for (const [n, s] of Object.entries(t)) {
|
|
167
|
+
const a = r ? `${r}.${n}` : n;
|
|
168
|
+
this.validateSourceMessages(s, e, a);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
assertMatchingLocaleTree(t, e, r, n, s) {
|
|
172
|
+
if (typeof t == "string") {
|
|
173
|
+
if (typeof e != "string") {
|
|
174
|
+
s.push(this.createMissingKeyDiagnostic(r, n));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
this.compareMessages(t, e, r, n, s);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (!T(e)) {
|
|
181
|
+
this.collectMissingLeafDiagnostics(t, r, n, s);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
for (const [a, c] of Object.entries(t)) {
|
|
185
|
+
const l = n ? `${n}.${a}` : a;
|
|
186
|
+
this.assertMatchingLocaleTree(c, e[a], r, l, s);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
collectMissingLeafDiagnostics(t, e, r, n) {
|
|
190
|
+
if (typeof t == "string") {
|
|
191
|
+
n.push(this.createMissingKeyDiagnostic(e, r));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
for (const [s, a] of Object.entries(t)) {
|
|
195
|
+
const c = r ? `${r}.${s}` : s;
|
|
196
|
+
this.collectMissingLeafDiagnostics(a, e, c, n);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
compareMessages(t, e, r, n, s) {
|
|
200
|
+
let a, c;
|
|
201
|
+
try {
|
|
202
|
+
a = g(t), c = g(e);
|
|
203
|
+
} catch (f) {
|
|
204
|
+
s.push(
|
|
205
|
+
this.createDiagnostic("error", `[i18n] ICU signature error at "${n}" in locale "${r}": ${L(f)}`)
|
|
206
|
+
);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const l = Object.keys(a).sort(), d = Object.keys(c).sort();
|
|
210
|
+
if (l.join("|") !== d.join("|")) {
|
|
211
|
+
s.push(
|
|
212
|
+
this.createDiagnostic(
|
|
213
|
+
"error",
|
|
214
|
+
`[i18n] ICU params mismatch at "${n}" in locale "${r}". Expected [${l.join(", ")}], received [${d.join(", ")}].`
|
|
215
|
+
)
|
|
216
|
+
);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
for (const f of l) {
|
|
220
|
+
const m = a[f], h = c[f];
|
|
221
|
+
if (!h || m.kind !== h.kind) {
|
|
222
|
+
s.push(
|
|
223
|
+
this.createDiagnostic(
|
|
224
|
+
"error",
|
|
225
|
+
`[i18n] ICU param "${f}" at "${n}" in locale "${r}" must stay "${m.kind}".`
|
|
226
|
+
)
|
|
227
|
+
);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (m.kind === "select") {
|
|
231
|
+
const E = /* @__PURE__ */ new Set([...m.selectors ?? [], "other"]);
|
|
232
|
+
for (const w of E)
|
|
233
|
+
(h.selectors ?? []).includes(w) || s.push(
|
|
234
|
+
this.createDiagnostic(
|
|
235
|
+
"error",
|
|
236
|
+
`[i18n] ICU select "${f}" at "${n}" in locale "${r}" is missing selector "${w}".`
|
|
237
|
+
)
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
m.kind === "plural" && !(h.selectors ?? []).includes("other") && s.push(
|
|
241
|
+
this.createDiagnostic(
|
|
242
|
+
"error",
|
|
243
|
+
`[i18n] ICU plural "${f}" at "${n}" in locale "${r}" must include "other".`
|
|
244
|
+
)
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
createMissingKeyDiagnostic(t, e) {
|
|
249
|
+
const [r = "unknown"] = e.split("."), n = u.join(this.options.localesDir, t, `${r}.ts`), s = this.options.strict === !1 ? "warn" : "error";
|
|
250
|
+
return this.createDiagnostic(
|
|
251
|
+
s,
|
|
252
|
+
`[i18n] Locale "${t}" is missing key "${e}". File: ${n}. Reference locale: ${this.options.sourceLocale}`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
createDiagnostic(t, e) {
|
|
256
|
+
return { level: t, message: e };
|
|
257
|
+
}
|
|
258
|
+
flushDiagnostics(t) {
|
|
259
|
+
for (const r of t.filter((n) => n.level === "warn"))
|
|
260
|
+
this.warn(r.message);
|
|
261
|
+
const e = t.filter((r) => r.level === "error");
|
|
262
|
+
if (e.length > 0)
|
|
263
|
+
throw new Error(e.map((r) => r.message).join(`
|
|
264
|
+
`));
|
|
265
|
+
}
|
|
266
|
+
writeRuntimeArtifacts(t) {
|
|
267
|
+
const e = u.join(this.options.outputDir, "runtime");
|
|
268
|
+
p.mkdirSync(e, { recursive: !0 });
|
|
269
|
+
const r = Object.keys(t).sort((n, s) => n.localeCompare(s));
|
|
270
|
+
p.writeFileSync(
|
|
271
|
+
u.join(e, "manifest.json"),
|
|
272
|
+
`${JSON.stringify({ sourceLocale: this.options.sourceLocale, locales: r }, null, 2)}
|
|
273
|
+
`
|
|
274
|
+
);
|
|
275
|
+
for (const n of r)
|
|
276
|
+
p.writeFileSync(
|
|
277
|
+
u.join(e, `${n}.json`),
|
|
278
|
+
`${JSON.stringify(t[n], null, 2)}
|
|
279
|
+
`
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
writeVirtualKeyDeclaration(t) {
|
|
283
|
+
const e = t[this.options.sourceLocale], r = Object.keys(e).filter(S), n = [
|
|
284
|
+
"declare module 'virtual:routa-i18n/keys' {",
|
|
285
|
+
" import type { I18nKey, I18nPrimitive } from '@routa/i18n'",
|
|
286
|
+
"",
|
|
287
|
+
` export declare const k: ${this.renderDeclarationTree(e, t, "", 1)}`,
|
|
288
|
+
...r.map((s) => ` export declare const ${s}: typeof k.${s}`),
|
|
289
|
+
"}",
|
|
290
|
+
""
|
|
291
|
+
].join(`
|
|
292
|
+
`);
|
|
293
|
+
p.writeFileSync(u.join(this.options.outputDir, "virtual-routa-i18n-keys.d.ts"), n);
|
|
294
|
+
}
|
|
295
|
+
renderDeclarationTree(t, e, r = "", n = 0) {
|
|
296
|
+
const s = " ".repeat(n), a = " ".repeat(n + 1);
|
|
297
|
+
return typeof t == "string" ? `I18nKey<'${$(r)}', ${this.renderParamsType(t)}>` : `{
|
|
298
|
+
${Object.entries(t).map(([l, d]) => {
|
|
299
|
+
const f = r ? `${r}.${l}` : l, m = this.renderPropertyName(l);
|
|
300
|
+
return typeof d == "string" ? `${this.renderHoverDoc(e, f, n + 1)}
|
|
301
|
+
${a}${m}: ${this.renderDeclarationTree(d, e, f, n + 1)}` : `${a}${m}: ${this.renderDeclarationTree(d, e, f, n + 1)}`;
|
|
302
|
+
}).join(`,
|
|
303
|
+
`)}
|
|
304
|
+
${s}}`;
|
|
305
|
+
}
|
|
306
|
+
renderHoverDoc(t, e, r) {
|
|
307
|
+
const n = " ".repeat(r), s = [`${n}/**`];
|
|
308
|
+
for (const [a, c] of Object.entries(t)) {
|
|
309
|
+
const l = this.getTranslationByPath(c, e);
|
|
310
|
+
l !== null && s.push(`${n} * ${a}: ${F(l)}`);
|
|
311
|
+
}
|
|
312
|
+
return s.push(`${n} */`), s.join(`
|
|
313
|
+
`);
|
|
314
|
+
}
|
|
315
|
+
renderParamsType(t) {
|
|
316
|
+
const e = g(t), r = Object.entries(e);
|
|
317
|
+
return r.length === 0 ? "undefined" : `{ ${r.map(([s, a]) => `${this.renderPropertyName(s)}: ${this.renderParamMetaType(a)}`).join("; ")} }`;
|
|
318
|
+
}
|
|
319
|
+
renderParamMetaType(t) {
|
|
320
|
+
var r;
|
|
321
|
+
return t.kind === "argument" ? "I18nPrimitive" : t.kind === "number" || t.kind === "plural" ? "number" : t.kind === "date" || t.kind === "time" ? "Date | number" : ((r = t.selectors) != null && r.length ? t.selectors : ["other"]).map((n) => `'${$(n)}'`).join(" | ");
|
|
322
|
+
}
|
|
323
|
+
renderRuntimeKeyTree(t, e = "", r = 0) {
|
|
324
|
+
const n = " ".repeat(r), s = " ".repeat(r + 1);
|
|
325
|
+
return typeof t == "string" ? `'${$(e)}'` : `{
|
|
326
|
+
${Object.entries(t).map(([c, l]) => {
|
|
327
|
+
const d = e ? `${e}.${c}` : c;
|
|
328
|
+
return `${s}${this.renderPropertyName(c)}: ${this.renderRuntimeKeyTree(l, d, r + 1)}`;
|
|
329
|
+
}).join(`,
|
|
330
|
+
`)}
|
|
331
|
+
${n}}`;
|
|
332
|
+
}
|
|
333
|
+
getTranslationByPath(t, e) {
|
|
334
|
+
const r = e.split(".");
|
|
335
|
+
let n = t;
|
|
336
|
+
for (const s of r) {
|
|
337
|
+
if (!n || typeof n == "string")
|
|
338
|
+
return null;
|
|
339
|
+
n = n[s];
|
|
340
|
+
}
|
|
341
|
+
return typeof n == "string" ? n : null;
|
|
342
|
+
}
|
|
343
|
+
renderPropertyName(t) {
|
|
344
|
+
return S(t) ? t : `'${$(t)}'`;
|
|
345
|
+
}
|
|
346
|
+
removeObsoleteArtifacts(t) {
|
|
347
|
+
for (const e of ["keys.ts", "keys.gen.ts", "meta.json", "source-catalog.ts"]) {
|
|
348
|
+
const r = u.join(t, e);
|
|
349
|
+
p.existsSync(r) && p.unlinkSync(r);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
warn(t) {
|
|
353
|
+
if (this.options.onWarn) {
|
|
354
|
+
this.options.onWarn(t);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
console.warn(t);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function $(i) {
|
|
361
|
+
return i.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
362
|
+
}
|
|
363
|
+
function F(i) {
|
|
364
|
+
return i.replace(/\r?\n/g, "\\n").replace(/\*\//g, "*\\/");
|
|
365
|
+
}
|
|
366
|
+
function L(i) {
|
|
367
|
+
return i instanceof Error ? i.message : String(i);
|
|
368
|
+
}
|
|
369
|
+
function S(i) {
|
|
370
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(i);
|
|
371
|
+
}
|
|
372
|
+
const j = "virtual:routa-i18n/keys", D = `\0${j}`;
|
|
373
|
+
function z(i) {
|
|
374
|
+
const t = new C(i), e = i.include.map((r) => y(u.resolve(r)));
|
|
375
|
+
return {
|
|
376
|
+
name: "routa:i18n",
|
|
377
|
+
resolveId(r) {
|
|
378
|
+
return r === j ? D : null;
|
|
379
|
+
},
|
|
380
|
+
load(r) {
|
|
381
|
+
return r === D ? t.renderVirtualKeyRuntimeModule() : null;
|
|
382
|
+
},
|
|
383
|
+
configResolved(r) {
|
|
384
|
+
t.setStrict(i.strict ?? (r.command === "build" || process.env.CI === "true")), t.generate();
|
|
385
|
+
},
|
|
386
|
+
transform(r, n) {
|
|
387
|
+
const s = y(n.split("?")[0]);
|
|
388
|
+
if (!e.some((l) => s.startsWith(l)) || !O(n, s))
|
|
389
|
+
return null;
|
|
390
|
+
const c = v(r, s);
|
|
391
|
+
return c === r ? null : {
|
|
392
|
+
code: c,
|
|
393
|
+
map: null
|
|
394
|
+
};
|
|
395
|
+
},
|
|
396
|
+
configureServer(r) {
|
|
397
|
+
r.watcher.add(u.resolve(i.localesDir)), r.watcher.on("change", (n) => {
|
|
398
|
+
y(n).startsWith(y(u.resolve(i.localesDir))) && (t.generate(), r.ws.send({ type: "full-reload" }));
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
function v(i, t) {
|
|
404
|
+
const e = o.createSourceFile(t, i, o.ScriptTarget.Latest, !0, A(t)), r = M(e);
|
|
405
|
+
if (r.size === 0)
|
|
406
|
+
return i;
|
|
407
|
+
const n = [], s = (c) => {
|
|
408
|
+
if (o.isPropertyAccessExpression(c)) {
|
|
409
|
+
const l = N(c, r);
|
|
410
|
+
if (l) {
|
|
411
|
+
n.push({
|
|
412
|
+
start: c.getStart(e),
|
|
413
|
+
end: c.getEnd(),
|
|
414
|
+
replacement: `'${l}'`
|
|
415
|
+
});
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
o.forEachChild(c, s);
|
|
420
|
+
};
|
|
421
|
+
if (s(e), n.length === 0)
|
|
422
|
+
return i;
|
|
423
|
+
let a = i;
|
|
424
|
+
for (const c of n.sort((l, d) => d.start - l.start))
|
|
425
|
+
a = `${a.slice(0, c.start)}${c.replacement}${a.slice(c.end)}`;
|
|
426
|
+
return a;
|
|
427
|
+
}
|
|
428
|
+
function M(i) {
|
|
429
|
+
var e, r;
|
|
430
|
+
const t = /* @__PURE__ */ new Map();
|
|
431
|
+
for (const n of i.statements) {
|
|
432
|
+
if (!o.isImportDeclaration(n) || !((e = n.importClause) != null && e.namedBindings) || !o.isStringLiteral(n.moduleSpecifier) || n.moduleSpecifier.text !== j)
|
|
433
|
+
continue;
|
|
434
|
+
const s = n.importClause.namedBindings;
|
|
435
|
+
if (o.isNamedImports(s))
|
|
436
|
+
for (const a of s.elements) {
|
|
437
|
+
const c = ((r = a.propertyName) == null ? void 0 : r.text) ?? a.name.text;
|
|
438
|
+
t.set(a.name.text, c === "k" ? "" : c);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return t;
|
|
442
|
+
}
|
|
443
|
+
function N(i, t) {
|
|
444
|
+
const e = [i.name.text];
|
|
445
|
+
let r = i.expression;
|
|
446
|
+
for (; o.isPropertyAccessExpression(r); )
|
|
447
|
+
e.unshift(r.name.text), r = r.expression;
|
|
448
|
+
if (!o.isIdentifier(r))
|
|
449
|
+
return null;
|
|
450
|
+
const n = t.get(r.text);
|
|
451
|
+
return n === void 0 ? null : n ? `${n}.${e.join(".")}` : e.join(".");
|
|
452
|
+
}
|
|
453
|
+
function O(i, t) {
|
|
454
|
+
return /\.(ts|tsx|js|jsx|vue)$/.test(t) || i.includes(".vue?") || i.includes("&lang.ts") || i.includes("&lang.js");
|
|
455
|
+
}
|
|
456
|
+
function A(i) {
|
|
457
|
+
return i.endsWith(".tsx") ? o.ScriptKind.TSX : i.endsWith(".jsx") ? o.ScriptKind.JSX : i.endsWith(".js") ? o.ScriptKind.JS : o.ScriptKind.TS;
|
|
458
|
+
}
|
|
459
|
+
function y(i) {
|
|
460
|
+
return i.replaceAll("\\", "/");
|
|
461
|
+
}
|
|
462
|
+
export {
|
|
463
|
+
z as i18nPlugin
|
|
464
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@routa/i18n-vue",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public",
|
|
11
|
+
"registry": "https://registry.npmjs.org/"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "http://git.sunbtech.com/wangzg/routa.git"
|
|
16
|
+
},
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "http://git.sunbtech.com/wangzg/routa/-/issues"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "http://git.sunbtech.com/wangzg/routa",
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"import": "./dist/index.mjs",
|
|
28
|
+
"require": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./vite-plugin": {
|
|
31
|
+
"types": "./dist/vite-plugin.d.ts",
|
|
32
|
+
"import": "./dist/vite-plugin.mjs",
|
|
33
|
+
"require": "./dist/vite-plugin.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "vite build",
|
|
38
|
+
"dev": "vite build --watch",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"type-check": "tsc --noEmit"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"typescript": "~6.0.3"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"vue": "^3.5.0"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"vue": "^3.5.13",
|
|
50
|
+
"vite": "^6.4.2",
|
|
51
|
+
"vite-plugin-dts": "^4.5.4"
|
|
52
|
+
}
|
|
53
|
+
}
|