vite-plugin-ai-auto-i18n 0.5.1 → 0.5.3
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 +15 -0
- package/README.md +310 -20
- package/dist/cli/prune.js +1 -1
- package/dist/index.d.ts +84 -15
- package/dist/index.js +3 -3
- package/dist/registry-Dxz-5jhc.js +2 -0
- package/package.json +17 -2
- package/dist/registry-CLbwA2b4.js +0 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 rand0mC0d3r
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,20 +1,252 @@
|
|
|
1
|
-
# 🌍 vite-plugin-autoi18n
|
|
1
|
+
# 🌍 vite-plugin-autoi18n 🚀
|
|
2
2
|
|
|
3
3
|
Implicit build-time localization for JSX: author English directly, let the compiler extract user-facing copy, persist message identities, and optionally translate through an AI provider.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
> **Status: beta.** The configuration options and the `.autoi18n/` file formats are intended to stay stable. Review the [known limitations](#known-limitations) before shipping to production, and pin the version while in beta.
|
|
6
|
+
|
|
7
|
+
## 🧩 Scope
|
|
6
8
|
|
|
7
9
|
- Extracts JSX text nodes and static string JSX attributes.
|
|
8
|
-
- Extracts string literals outside JSX only when they are marked with a leading `/* i18n */`
|
|
10
|
+
- Extracts string literals outside JSX only when they are marked with a leading block comment (`/* i18n */` by default, configurable with `stringMarker`).
|
|
9
11
|
- Uses tag/attribute/semantic-attribute/handler context to form fingerprints; component names and file paths are excluded so ordinary moves and renames do not automatically invalidate IDs.
|
|
10
12
|
- Persists opaque message IDs in `.autoi18n/registry.json`.
|
|
11
13
|
- Marks disappeared messages as orphaned and deletes them after a retention period (default 30 days, see `orphanRetentionMinutes`).
|
|
12
|
-
- Supports a pluggable translation provider; includes an OpenAI-compatible implementation.
|
|
14
|
+
- Supports a pluggable translation provider; includes an OpenAI-compatible implementation and an Anthropic (Claude) implementation.
|
|
13
15
|
- Writes locale JSON catalogues and keeps source English as fallback.
|
|
14
16
|
- Two delivery modes: `build` (one compiled bundle per locale) and `runtime` (one bundle, locales loaded on demand through `virtual:autoi18n`).
|
|
15
17
|
- Re-translates a message when its source text changes, tracked in the `sources` map of `.autoi18n/locales/<locale>.json`.
|
|
16
18
|
- Includes a deliberately varied document-dashboard example.
|
|
17
19
|
|
|
20
|
+
## 🔄 How it works: four phases
|
|
21
|
+
|
|
22
|
+
Every build (and every dev-server save) runs the same four phases. The examples below follow one component through them. IDs are shortened for readability; real IDs look like `msg_6f5e12bdbc7a4804`.
|
|
23
|
+
|
|
24
|
+
### Phase 0: the source you write
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
// src/Doc.tsx
|
|
28
|
+
export function Doc({ count }: { count: number }) {
|
|
29
|
+
return (
|
|
30
|
+
<section>
|
|
31
|
+
<button title="Save the current document">Save</button>
|
|
32
|
+
<p>You have {count} documents</p>
|
|
33
|
+
</section>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Phase 1: extract
|
|
39
|
+
|
|
40
|
+
The compiler parses each file in `include` and records every translatable string with its kind, tag, component and a fingerprint. Nothing is written yet.
|
|
41
|
+
|
|
42
|
+
```jsonc
|
|
43
|
+
[
|
|
44
|
+
{ "source": "Save the current document", "kind": "attribute", "tag": "button", "attribute": "title", "component": "Doc", "context": "button | title | title=Save the current document" },
|
|
45
|
+
{ "source": "Save", "kind": "text", "tag": "button", "component": "Doc", "context": "button | title=Save the current document" },
|
|
46
|
+
{ "source": "You have {count} documents", "kind": "mixed", "placeholders": ["count"], "tag": "p", "component": "Doc", "context": "p" }
|
|
47
|
+
]
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Mixed sentences keep their simple expressions as named placeholders, so the translator sees `{count}` as a token to preserve.
|
|
51
|
+
|
|
52
|
+
### Phase 2: identify
|
|
53
|
+
|
|
54
|
+
Each extracted message is matched against `.autoi18n/registry.json`. A match keeps the existing ID. A new message gets a new opaque ID. A message that disappears is marked `orphaned`, not deleted, so its ID can come back.
|
|
55
|
+
|
|
56
|
+
```jsonc
|
|
57
|
+
// .autoi18n/registry.json (excerpt)
|
|
58
|
+
{
|
|
59
|
+
"version": 1,
|
|
60
|
+
"entries": {
|
|
61
|
+
"msg_a1": {
|
|
62
|
+
"id": "msg_a1",
|
|
63
|
+
"source": "Save the current document",
|
|
64
|
+
"kind": "attribute",
|
|
65
|
+
"tag": "button",
|
|
66
|
+
"component": "Doc",
|
|
67
|
+
"attribute": "title",
|
|
68
|
+
"fingerprint": "58365c073009b7b7b2aa27b1",
|
|
69
|
+
"location": "src/Doc.tsx:4",
|
|
70
|
+
"status": "active",
|
|
71
|
+
"firstSeen": "2026-11-02T17:28:36.128Z",
|
|
72
|
+
"lastSeen": "2026-11-14T14:54:42.201Z"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Component names and file paths are not part of the fingerprint, so moving or renaming a file does not change the ID.
|
|
79
|
+
|
|
80
|
+
### Phase 3: translate
|
|
81
|
+
|
|
82
|
+
Only new or changed messages (source text changed since the last build) are sent to the provider. Results are stored in `.autoi18n/locales/<locale>.json`, next to the source text each translation was made from.
|
|
83
|
+
|
|
84
|
+
```jsonc
|
|
85
|
+
// .autoi18n/locales/de.json
|
|
86
|
+
{
|
|
87
|
+
"messages": {
|
|
88
|
+
"msg_a1": "Dokument speichern",
|
|
89
|
+
"msg_a2": "Speichern",
|
|
90
|
+
"msg_a3": "Du hast {count} Dokumente"
|
|
91
|
+
},
|
|
92
|
+
"sources": {
|
|
93
|
+
"msg_a1": "Save the current document",
|
|
94
|
+
"msg_a2": "Save",
|
|
95
|
+
"msg_a3": "You have {count} documents"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Translations never overwrite an existing entry silently. If the source text changes, the stored `sources` entry no longer matches and the message is translated again.
|
|
101
|
+
|
|
102
|
+
### Phase 4: transform
|
|
103
|
+
|
|
104
|
+
The source is rewritten according to `delivery`.
|
|
105
|
+
|
|
106
|
+
**`delivery: "build"`** (one compiled bundle per locale) bakes the German text into the code. The `t()` call disappears:
|
|
107
|
+
|
|
108
|
+
```diff
|
|
109
|
+
export function Doc({ count }: { count: number }) {
|
|
110
|
+
return (
|
|
111
|
+
<section>
|
|
112
|
+
- <button title="Save the current document">Save</button>
|
|
113
|
+
- <p>You have {count} documents</p>
|
|
114
|
+
+ <button title="Dokument speichern">{"Speichern"}</button>
|
|
115
|
+
+ <p>{"Du hast "}{count}{" Dokumente"}</p>
|
|
116
|
+
</section>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**`delivery: "runtime"`** (one bundle, catalogues loaded in the browser) replaces each string with a lookup. The source text stays in the code as the fallback:
|
|
122
|
+
|
|
123
|
+
```diff
|
|
124
|
+
+import { t as __autoI18nT, format as __autoI18nFormat } from "virtual:autoi18n";
|
|
125
|
+
export function Doc({ count }: { count: number }) {
|
|
126
|
+
return (
|
|
127
|
+
<section>
|
|
128
|
+
- <button title="Save the current document">Save</button>
|
|
129
|
+
- <p>You have {count} documents</p>
|
|
130
|
+
+ <button title={__autoI18nT("msg_a1", "Save the current document")}>{__autoI18nT("msg_a2", "Save")}</button>
|
|
131
|
+
+ <p>{__autoI18nFormat(__autoI18nT("msg_a3", "You have {count} documents"), {"count": count})}</p>
|
|
132
|
+
</section>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
If a locale has no translation for an ID, the source text is shown. Nothing breaks while translations are missing.
|
|
138
|
+
|
|
139
|
+
### Dev server
|
|
140
|
+
|
|
141
|
+
Saving a file re-runs phases 1 to 3 for that project and pushes the updated catalogues to the browser over `autoi18n:update`, so the page updates without a reload.
|
|
142
|
+
|
|
143
|
+
## 📚 Examples
|
|
144
|
+
|
|
145
|
+
### Minimal build with one locale per CI job
|
|
146
|
+
|
|
147
|
+
Use `locale` or the `AUTOI18N_LOCALE` variable. Only that locale is translated and compiled:
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
// vite.config.ts
|
|
151
|
+
autoI18n({
|
|
152
|
+
locales: ["en", "de", "fr"],
|
|
153
|
+
locale: process.env.AUTOI18N_LOCALE, // undefined = all locales
|
|
154
|
+
provider: new OpenAICompatibleProvider({ apiKey: process.env.OPENAI_API_KEY!, model: "gpt-4.1-mini" }),
|
|
155
|
+
failOnMissingProvider: true
|
|
156
|
+
})
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
```yaml
|
|
160
|
+
# .github/workflows/build.yml (excerpt)
|
|
161
|
+
strategy:
|
|
162
|
+
matrix:
|
|
163
|
+
locale: [en, de, fr]
|
|
164
|
+
steps:
|
|
165
|
+
- run: AUTOI18N_LOCALE=${{ matrix.locale }} npx vite build --outDir dist/${{ matrix.locale }}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Runtime delivery with a locale switcher
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
// vite.config.ts
|
|
172
|
+
autoI18n({ locales: ["en", "de", "fr"], delivery: "runtime" }), // no provider: source text is shown until translations exist
|
|
173
|
+
react() // keep autoI18n before the React plugin
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
// src/LocaleSwitcher.tsx
|
|
178
|
+
import { useSyncExternalStore } from "react";
|
|
179
|
+
import { getLocale, getVersion, locales, setLocale, subscribe } from "virtual:autoi18n";
|
|
180
|
+
|
|
181
|
+
export function LocaleSwitcher() {
|
|
182
|
+
// Re-render when the locale or any catalogue changes
|
|
183
|
+
useSyncExternalStore(subscribe, getVersion, getVersion);
|
|
184
|
+
return (
|
|
185
|
+
<select value={getLocale()} onChange={e => setLocale(e.target.value)}>
|
|
186
|
+
{locales.map(code => <option key={code} value={code} translate="no">{code}</option>)}
|
|
187
|
+
</select>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Marking copy that should stay untranslated
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
<h2 translate="no">Sample code</h2>
|
|
196
|
+
<code title="Product SKU">SKU-1042</code>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`translate="no"` also covers the element's descendants and attributes.
|
|
200
|
+
|
|
201
|
+
### Translating strings outside JSX
|
|
202
|
+
|
|
203
|
+
Put a block comment directly before the literal. Lookups run when the expression is evaluated, so put marked strings inside a component or function, not at module level, in runtime delivery:
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
export function Items() {
|
|
207
|
+
const items = [{ label: /* i18n */ "Shared with your team" }, { label: "Private" }];
|
|
208
|
+
return <ul>{items.map(i => <li key={i.label}>{i.label}</li>)}</ul>;
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Only `"Shared with your team"` is translated. `"Private"` has no marker and stays English.
|
|
213
|
+
|
|
214
|
+
### Custom marker and custom components
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
autoI18n({
|
|
218
|
+
locales: ["en", "de"],
|
|
219
|
+
stringMarker: "t", // write /* t */ "Save" instead
|
|
220
|
+
componentProps: {
|
|
221
|
+
Button: ["aria-label", "title", "children"], // <Button>Save</Button> is translated
|
|
222
|
+
Tooltip: ["content"]
|
|
223
|
+
}
|
|
224
|
+
})
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
With this config, `<Button title="Delete">Delete {count} files</Button>` extracts both the attribute and the text as a placeholder message.
|
|
228
|
+
|
|
229
|
+
### Writing your own provider
|
|
230
|
+
|
|
231
|
+
Any object with a `translate` method works:
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
import type { TranslationProvider } from "vite-plugin-ai-auto-i18n";
|
|
235
|
+
|
|
236
|
+
const deepl: TranslationProvider = {
|
|
237
|
+
async translate({ sourceLocale, targetLocale, messages }) {
|
|
238
|
+
const out: Record<string, string> = {};
|
|
239
|
+
for (const m of messages) {
|
|
240
|
+
// Keep {placeholders} intact; the plugin rejects translations that change them.
|
|
241
|
+
out[m.id] = await callYourService(m.source, targetLocale);
|
|
242
|
+
}
|
|
243
|
+
return out;
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Return only the IDs you translated. Throw `PartialTranslationError` to keep a partial result.
|
|
249
|
+
|
|
18
250
|
## 📦 Installation
|
|
19
251
|
|
|
20
252
|
### 1️⃣ Install the package
|
|
@@ -25,16 +257,18 @@ In your React + Vite project:
|
|
|
25
257
|
npm install -D vite-plugin-ai-auto-i18n
|
|
26
258
|
```
|
|
27
259
|
|
|
28
|
-
Works the same with `pnpm add -D vite-plugin-ai-auto-i18n` or `yarn add -D vite-plugin-ai-auto-i18n`. It requires Vite
|
|
260
|
+
Works the same with `pnpm add -D vite-plugin-ai-auto-i18n` or `yarn add -D vite-plugin-ai-auto-i18n`. It requires Vite 8 or newer (a peer dependency) and Node.js 26 or newer. For React, also keep `@vitejs/plugin-react` in your Vite config.
|
|
29
261
|
|
|
30
262
|
### 2️⃣ Add your API key
|
|
31
263
|
|
|
32
|
-
Put the key in `.env.local` and keep that file out of git:
|
|
264
|
+
Put the key for your provider in `.env.local` and keep that file out of git. For OpenAI or any OpenAI-compatible API:
|
|
33
265
|
|
|
34
266
|
```sh
|
|
35
267
|
OPENAI_API_KEY=your-key-here
|
|
36
268
|
```
|
|
37
269
|
|
|
270
|
+
For Claude, use `ANTHROPIC_API_KEY` instead (see [AnthropicProvider options](#anthropicprovider-options)). For Gemini, use `GEMINI_API_KEY` (see [GeminiProvider options](#geminiprovider-options)).
|
|
271
|
+
|
|
38
272
|
### 3️⃣ Configure Vite
|
|
39
273
|
|
|
40
274
|
Add the plugin to `vite.config.ts`. `loadEnv` reads `.env.local` for the config file:
|
|
@@ -42,7 +276,7 @@ Add the plugin to `vite.config.ts`. `loadEnv` reads `.env.local` for the config
|
|
|
42
276
|
```ts
|
|
43
277
|
import { defineConfig, loadEnv } from "vite";
|
|
44
278
|
import react from "@vitejs/plugin-react";
|
|
45
|
-
import autoI18n, { OpenAICompatibleProvider } from "vite-plugin-ai-auto-i18n";
|
|
279
|
+
import autoI18n, { AnthropicProvider, OpenAICompatibleProvider } from "vite-plugin-ai-auto-i18n";
|
|
46
280
|
|
|
47
281
|
export default defineConfig(({ mode }) => {
|
|
48
282
|
const env = loadEnv(mode, process.cwd(), "");
|
|
@@ -52,10 +286,17 @@ export default defineConfig(({ mode }) => {
|
|
|
52
286
|
autoI18n({
|
|
53
287
|
locales: ["en", "de", "fr"],
|
|
54
288
|
sourceLocale: "en",
|
|
55
|
-
provider:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
289
|
+
provider: {
|
|
290
|
+
primary: "anthropic",
|
|
291
|
+
anthropic: new AnthropicProvider({
|
|
292
|
+
apiKey: env.ANTHROPIC_API_KEY!,
|
|
293
|
+
model: "claude-haiku-5-5"
|
|
294
|
+
}),
|
|
295
|
+
openai: new OpenAICompatibleProvider({
|
|
296
|
+
apiKey: env.OPENAI_API_KEY!,
|
|
297
|
+
model: "gpt-4.1-mini"
|
|
298
|
+
})
|
|
299
|
+
}
|
|
59
300
|
}),
|
|
60
301
|
react() // in runtime delivery, keep autoI18n before the React plugin
|
|
61
302
|
]
|
|
@@ -91,7 +332,7 @@ If a translation batch fails, the other batches are still stored. The failed mes
|
|
|
91
332
|
|
|
92
333
|
🔑 Translations require a provider. With `failOnMissingProvider: true` the build fails when a locale is missing translations and no provider is configured, instead of silently shipping English.
|
|
93
334
|
|
|
94
|
-
`OpenAICompatibleProvider`
|
|
335
|
+
All built-in providers (`OpenAICompatibleProvider`, `AnthropicProvider` and `GeminiProvider`) send messages in batches (`batchSize`, default 40), run up to `concurrency` batches at once (default 4), abort a request after `timeoutMs` (default 60000), and retry network errors, timeouts, 408/409/429 and 5xx responses or malformed output up to `maxRetries` times (default 2) with exponential backoff from `retryDelayMs` (default 500). Other 4xx errors fail immediately. If the provider omits some messages, the rest are kept and the missing ones stay in the source language until the next build retries them; the build logs how many were left untranslated.
|
|
95
336
|
|
|
96
337
|
The plugin translates up to `localeConcurrency` locales at once (default 2). Total parallel requests are roughly `localeConcurrency × concurrency`, so lower one or both if your provider rate-limits your account.
|
|
97
338
|
|
|
@@ -109,7 +350,7 @@ Messages removed from the source are kept as orphans in `registry.json` so their
|
|
|
109
350
|
|
|
110
351
|
🙈 To keep copy out of automatic translation, add `translate="no"` to the element (for example `<h2 translate="no">Sample code</h2>`). This is the standard HTML marker, and it also covers the element's descendants and attributes such as `title`.
|
|
111
352
|
|
|
112
|
-
🏷️ To translate copy outside JSX, put a `/* i18n */`
|
|
353
|
+
🏷️ To translate copy outside JSX, put a block comment with the marker text (`/* i18n */` by default, or your `stringMarker`) directly before the string literal, for example `const items = [{ label: /* i18n */ "Shared with your team" }];`. Marked strings are translated in both build and runtime delivery. In runtime delivery the lookup runs when the expression is evaluated, so a marked string in a module-level constant keeps the locale active at import time; put such literals inside a component or function that runs at render. Unmarked string literals are never extracted.
|
|
113
354
|
|
|
114
355
|
Corrupt `registry.json` or catalogue files are renamed to `<file>.corrupt-<timestamp>` and a warning is logged; the plugin continues with an empty state. Writes are atomic, so an interrupted build does not leave a truncated file.
|
|
115
356
|
|
|
@@ -143,13 +384,14 @@ In dev, saving a source file re-indexes the project, translates only new or chan
|
|
|
143
384
|
| `include` | `string[]` | `["src/**/*.{tsx,jsx,ts,js}"]` | Glob patterns of source files to scan, relative to the Vite root. |
|
|
144
385
|
| `exclude` | `string[]` | `["**/*.test.*", "**/node_modules/**"]` | Glob patterns of files to skip, relative to the Vite root. |
|
|
145
386
|
| `directory` | `string` | `".autoi18n"` | Folder for `registry.json` and `locales/`, relative to the Vite root. |
|
|
146
|
-
| `provider` | `TranslationProvider` | `undefined` | Translates missing and changed messages. Without one, untranslated messages fall back to the source text. |
|
|
387
|
+
| `provider` | `TranslationProvider \| { primary?, [name]: TranslationProvider \| undefined }` | `undefined` | Translates missing and changed messages. Pass one provider, or named providers with a `primary` (see [Multiple providers and fallback](#multiple-providers-and-fallback)). Without one, untranslated messages fall back to the source text. |
|
|
147
388
|
| `failOnMissingProvider` | `boolean` | `false` | When `true`, the build fails if a locale has untranslated messages and no `provider` is set. |
|
|
148
389
|
| `onAmbiguous` | `"warn" \| "error"` | `"warn"` | What to do when duplicated identical texts change their occurrence count, so their IDs may have moved. `"error"` fails the build. |
|
|
149
390
|
| `orphanRetentionMinutes` | `number` | `43200` (30 days) | Minutes an orphaned message is kept before it is deleted. `0` deletes on the next build; `Infinity` keeps forever. |
|
|
150
391
|
| `localeConcurrency` | `number` | `2` | Locales translated at the same time. Values below 1 are raised to 1, and fractions are rounded down. |
|
|
151
392
|
| `debug` | `boolean` | `false` | Logs each message sent to the provider, each translation received, and each removed orphan, with timestamps. |
|
|
152
|
-
| `componentProps` | `Record<string, string[]>` | `{}` | Attributes of custom components whose string values are UI copy, keyed by component name, for example `{ Button: ["aria-label", "title"] }`. Only the listed attributes are extracted from those components. |
|
|
393
|
+
| `componentProps` | `Record<string, string[]>` | `{}` | Attributes of custom components whose string values are UI copy, keyed by component name, for example `{ Button: ["aria-label", "title"] }`. Only the listed attributes are extracted from those components. The pseudo-prop `children` also makes a component's text children translatable, including sentences with `{name}` expressions: `{ Button: ["children", "aria-label"] }` extracts `<Button>Save</Button>` and `<Button>Delete {count} files</Button>`. |
|
|
394
|
+
| `stringMarker` | `string` | `"i18n"` | Text of the block comment that marks a string literal outside JSX for translation, for example `/* i18n */ "Save"`. Set it to `"t"` to use `/* t */ "Save"` instead. |
|
|
153
395
|
|
|
154
396
|
### `OpenAICompatibleProvider` options
|
|
155
397
|
|
|
@@ -166,7 +408,55 @@ Create the provider with `new OpenAICompatibleProvider(options)`.
|
|
|
166
408
|
| `maxRetries` | `number` | `2` | Extra attempts for network errors, timeouts, 408/409/429/5xx responses and malformed output. |
|
|
167
409
|
| `retryDelayMs` | `number` | `500` | Delay before the first retry. It doubles on each attempt. |
|
|
168
410
|
|
|
169
|
-
###
|
|
411
|
+
### `AnthropicProvider` options
|
|
412
|
+
|
|
413
|
+
Create the provider with `new AnthropicProvider(options)`. It calls the [Anthropic Messages API](https://docs.anthropic.com/en/api/messages). The batching, concurrency and retry options are the same as for `OpenAICompatibleProvider`.
|
|
414
|
+
|
|
415
|
+
| Option | Type | Default | Description |
|
|
416
|
+
|---|---|---|---|
|
|
417
|
+
| `apiKey` | `string` | _required_ | Anthropic API key, sent as `x-api-key`. Read it from an environment variable and do not commit it. |
|
|
418
|
+
| `model` | `string` | _required_ | Claude model name sent in each request, for example `"claude-haiku-5-5"`. |
|
|
419
|
+
| `baseURL` | `string` | `"https://api.anthropic.com/v1"` | Base URL of the Messages API. Requests go to `${baseURL}/messages`. |
|
|
420
|
+
| `maxTokens` | `number` | `8192` | Maximum tokens the model may generate per batch. Lower it only if your model limits output. |
|
|
421
|
+
| `batchSize` | `number` | `40` | Messages per request. Smaller batches keep responses within output limits. |
|
|
422
|
+
| `concurrency` | `number` | `4` | Batches sent in parallel for one locale. |
|
|
423
|
+
| `timeoutMs` | `number` | `60000` | Milliseconds before a single request is aborted. |
|
|
424
|
+
| `maxRetries` | `number` | `2` | Extra attempts for network errors, timeouts, 408/409/429/5xx responses and malformed output. |
|
|
425
|
+
| `retryDelayMs` | `number` | `500` | Delay before the first retry. It doubles on each attempt. |
|
|
426
|
+
|
|
427
|
+
### `GeminiProvider` options
|
|
428
|
+
|
|
429
|
+
Create the provider with `new GeminiProvider(options)`. It calls the [Gemini API `generateContent` endpoint](https://ai.google.dev/api/generate-content) in JSON output mode. The batching, concurrency and retry options are the same as for `OpenAICompatibleProvider`.
|
|
430
|
+
|
|
431
|
+
| Option | Type | Default | Description |
|
|
432
|
+
|---|---|---|---|
|
|
433
|
+
| `apiKey` | `string` | _required_ | Gemini API key, sent as `x-goog-api-key`. Read it from an environment variable and do not commit it. |
|
|
434
|
+
| `model` | `string` | _required_ | Gemini model name, for example `"gemini-3.5-flash-lite"`. |
|
|
435
|
+
| `baseURL` | `string` | `"https://generativelanguage.googleapis.com/v1beta"` | Base URL of the Gemini API. Requests go to `${baseURL}/models/${model}:generateContent`. |
|
|
436
|
+
| `maxOutputTokens` | `number` | `8192` | Maximum tokens the model may generate per batch. Lower it only if your model limits output. |
|
|
437
|
+
| `batchSize` | `number` | `40` | Messages per request. Smaller batches keep responses within output limits. |
|
|
438
|
+
| `concurrency` | `number` | `4` | Batches sent in parallel for one locale. |
|
|
439
|
+
| `timeoutMs` | `number` | `60000` | Milliseconds before a single request is aborted. |
|
|
440
|
+
| `maxRetries` | `number` | `2` | Extra attempts for network errors, timeouts, 408/409/429/5xx responses and malformed output. |
|
|
441
|
+
| `retryDelayMs` | `number` | `500` | Delay before the first retry. It doubles on each attempt. |
|
|
442
|
+
|
|
443
|
+
### Multiple providers and fallback
|
|
444
|
+
|
|
445
|
+
Pass `provider` as an object with one entry per provider. `primary` names the one tried first. Entries set to `undefined` are skipped, so you can keep every provider in the config and enable each one by its key:
|
|
446
|
+
|
|
447
|
+
```ts
|
|
448
|
+
provider: {
|
|
449
|
+
primary: "anthropic",
|
|
450
|
+
anthropic: env.ANTHROPIC_API_KEY ? new AnthropicProvider({ apiKey: env.ANTHROPIC_API_KEY, model: "claude-haiku-5-5" }) : undefined,
|
|
451
|
+
openai: env.OPENAI_API_KEY ? new OpenAICompatibleProvider({ apiKey: env.OPENAI_API_KEY, model: "gpt-4.1-mini" }) : undefined,
|
|
452
|
+
gemini: env.GEMINI_API_KEY ? new GeminiProvider({ apiKey: env.GEMINI_API_KEY, model: "gemini-3.5-flash-lite" }) : undefined
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
If the primary fails (for example a bad key, an outage, or rate limits that survive the retries), the messages it did not translate are sent to the next configured provider, in declaration order. Fallback happens per batch: a partial result keeps the translations that did come back, and only the missing messages move on. The build logs a warning each time it falls back. If every provider fails, the locale keeps its previous file, as with a single provider.
|
|
457
|
+
|
|
458
|
+
Naming a `primary` that is not configured fails the build with a clear error. Passing a single provider instance (`provider: new AnthropicProvider(...)`) still works and needs no map.
|
|
459
|
+
|
|
170
460
|
|
|
171
461
|
Any object with this shape can be passed as `provider`:
|
|
172
462
|
|
|
@@ -183,7 +473,7 @@ interface TranslationRequest {
|
|
|
183
473
|
id: string; // opaque ID to echo back in the result
|
|
184
474
|
source: string; // English text
|
|
185
475
|
context: string; // for example "Toolbar > Save button"
|
|
186
|
-
kind: "text" | "attribute" | "mixed" | "string"; // text node, static attribute, JSX with expressions, or a
|
|
476
|
+
kind: "text" | "attribute" | "mixed" | "string"; // text node, static attribute, JSX with expressions, or a marked string literal
|
|
187
477
|
placeholders?: string[]; // {name} placeholders that must survive translation
|
|
188
478
|
}
|
|
189
479
|
```
|
|
@@ -217,15 +507,15 @@ npx vitest run --coverage --coverage.include='src/**' --coverage.reporter=text
|
|
|
217
507
|
|
|
218
508
|
Critical modules such as the plugin hooks (`src/index.ts`), translation pipeline (`src/translate.ts`), build engine (`src/engine.ts`), runtime helpers (`src/runtime/`), and prune CLI (`src/cli/prune.ts`) are all at or above 98% statements.
|
|
219
509
|
|
|
220
|
-
##
|
|
510
|
+
## Known limitations
|
|
221
511
|
|
|
222
|
-
This is a
|
|
512
|
+
This is a beta release. Review these limitations before shipping to production:
|
|
223
513
|
|
|
224
514
|
1. Runtime delivery loads a catalogue per locale; a message's source text is used until its catalogue has loaded.
|
|
225
515
|
2. Mixed JSX sentences with simple expressions (`You have {count} documents`, where `count` is an identifier or member path) are translated as one message with named `{name}` placeholders; translations that change the placeholders, or that repeat a placeholder, are rejected. A placeholder must appear exactly once because build mode copies each source expression, so a repeat would evaluate it twice. Sentences with complex expressions (calls, JSX, computed access) are left entirely in the source language. ICU plural generation is not implemented yet.
|
|
226
516
|
3. Duplicate identical occurrences are paired by stable source ordering. This handles ordinary line shifts, but cannot guarantee identity if indistinguishable duplicates are reordered or moved independently. Such cases are reported through `onAmbiguous`; they are not resolved automatically.
|
|
227
517
|
4. Dev-server HMR re-indexes the whole project after saved or added/removed source files (debounced by 150 ms) rather than only the changed module. This is simple and correct, but may be slow in very large projects.
|
|
228
|
-
5. Only JSX text, and the attributes `alt`, `title`, `placeholder`, `label`, `summary`, `aria-label`, `aria-description`, `aria-placeholder`, `aria-roledescription` and `aria-valuetext` on intrinsic (lowercase) elements, are extracted automatically. String literals outside JSX are extracted only when marked with `/* i18n
|
|
518
|
+
5. Only JSX text, and the attributes `alt`, `title`, `placeholder`, `label`, `summary`, `aria-label`, `aria-description`, `aria-placeholder`, `aria-roledescription` and `aria-valuetext` on intrinsic (lowercase) elements, are extracted automatically. String literals outside JSX are extracted only when marked with the `stringMarker` comment (`/* i18n */` by default). Text inside capitalised components (for example `<Button>Save</Button>`) is extracted only when the component lists `children` in `componentProps`. Attributes of capitalised components are extracted only when listed in `componentProps`. Other attributes such as `href` are not extracted. Unmarked JS string literals, template literals, Markdown, server-rendered content and rich-text components need explicit policies.
|
|
229
519
|
6. The example uses a React plugin in its Vite config; install `@vitejs/plugin-react` in the consuming example.
|
|
230
520
|
7. The initial fingerprint is a heuristic. It excludes file and line position, but component names and nearby semantic signals can still change during refactors.
|
|
231
521
|
|
package/dist/cli/prune.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{A as e,R as t,d as n,i as r,j as i,k as a,n as o,t as s}from"../registry-Dxz-5jhc.js";import{readdir as c}from"node:fs/promises";import l from"node:path";import{existsSync as u}from"node:fs";async function d(t,n){if(!u(t))return[];let r=[];for(let o of await c(t)){if(!o.endsWith(`.json`))continue;let s=l.join(t,o),c=await e(s,e=>console.warn(e)),u=a(c.messages,n),d=a(c.sources,n),f=[.../* @__PURE__ */ new Set([...u.removed,...d.removed])];f.length&&(await i(s,{messages:u.catalog,sources:d.catalog}),r.push({file:s,removed:f}))}return r}function f(e){let t={directory:`.autoi18n`,retentionMinutes:n,debug:!1};for(let n=0;n<e.length;n++){let r=e[n];if(r===`--debug`)t.debug=!0;else if(r===`--directory`||r===`--retention-minutes`){let i=e[++n];if(i===void 0)throw Error(`${r} needs a value`);r===`--directory`?t.directory=i:t.retentionMinutes=Number(i)}else throw Error(`Unknown option ${r}`)}if(!(t.retentionMinutes>=0))throw Error(`--retention-minutes must be a number of minutes, 0 or more`);return t}async function p(){let e=f(process.argv.slice(2)),n=l.resolve(process.cwd(),e.directory,`registry.json`);if(!u(n)){console.log(`autoi18n: no registry found at ${n}; nothing to prune.`);return}let i=await s(n,e=>console.warn(e)),a=o(i,e.retentionMinutes);a.length&&await r(n,i);let c=new Set(Object.values(i.entries).filter(e=>e.status===`active`).map(e=>e.id)),p=await d(l.join(l.dirname(n),`locales`),c);if(e.debug){for(let e of a)t(`removed ${e.id} ${JSON.stringify(e.source)} (last seen ${e.lastSeen})`);for(let{file:e,removed:n}of p)for(let r of n)t(`removed ${r} from ${e}`)}console.log(`autoi18n: removed ${a.length} registry entries orphaned for more than ${e.retentionMinutes} minutes.`),p.length&&console.log(`autoi18n: removed stale keys from ${p.length} locale files.`)}p().catch(e=>{console.error(`autoi18n: ${e.message}`),process.exitCode=1});export{};
|
package/dist/index.d.ts
CHANGED
|
@@ -46,6 +46,13 @@ interface TranslationProvider {
|
|
|
46
46
|
messages: TranslationRequest[];
|
|
47
47
|
}): Promise<Catalog>;
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* A single provider, or named providers. In the named form, `primary` picks the provider tried first; the rest are
|
|
51
|
+
* fallbacks in declaration order. Entries set to `undefined` are skipped, so unconfigured providers can stay in the map.
|
|
52
|
+
*/
|
|
53
|
+
type ProviderConfig = TranslationProvider | ({
|
|
54
|
+
primary?: string;
|
|
55
|
+
} & Record<string, TranslationProvider | string | undefined>);
|
|
49
56
|
/**
|
|
50
57
|
* Some batches of a translation request failed while others succeeded. `partial` holds the translations
|
|
51
58
|
* that did come back, so the caller can keep them and report the rest.
|
|
@@ -55,10 +62,30 @@ export declare class PartialTranslationError extends Error {
|
|
|
55
62
|
readonly failed: number;
|
|
56
63
|
constructor(message: string, partial: Catalog, failed: number);
|
|
57
64
|
}
|
|
58
|
-
interface
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
65
|
+
interface NamedProvider {
|
|
66
|
+
name: string;
|
|
67
|
+
provider: TranslationProvider;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Tries providers in order. Messages a provider did not translate (a failed request, or a partial result) are sent
|
|
71
|
+
* to the next provider. Throws only when nothing could be translated, or a PartialTranslationError when some messages
|
|
72
|
+
* are still missing after every provider has been tried.
|
|
73
|
+
*/
|
|
74
|
+
export declare class FallbackProvider implements TranslationProvider {
|
|
75
|
+
private readonly providers;
|
|
76
|
+
private readonly onFallback;
|
|
77
|
+
constructor(providers: NamedProvider[], onFallback?: (message: string) => void);
|
|
78
|
+
translate(input: {
|
|
79
|
+
sourceLocale: string;
|
|
80
|
+
targetLocale: string;
|
|
81
|
+
messages: TranslationRequest[];
|
|
82
|
+
}): Promise<Catalog>;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Normalizes the `provider` option to one provider. Returns undefined when no provider is configured.
|
|
86
|
+
*/
|
|
87
|
+
export declare function resolveProvider(config: ProviderConfig | undefined, onFallback?: (message: string) => void): TranslationProvider | undefined;
|
|
88
|
+
interface BatchedProviderOptions {
|
|
62
89
|
/** Messages sent per request. Smaller batches keep responses within output limits. Default 40. */
|
|
63
90
|
batchSize?: number;
|
|
64
91
|
/** Abort a single request after this many milliseconds. Default 60000. */
|
|
@@ -70,20 +97,56 @@ interface OpenAICompatibleOptions {
|
|
|
70
97
|
/** Batches sent in parallel. Default 4. */
|
|
71
98
|
concurrency?: number;
|
|
72
99
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
100
|
+
interface TranslateInput {
|
|
101
|
+
sourceLocale: string;
|
|
102
|
+
targetLocale: string;
|
|
103
|
+
messages: TranslationRequest[];
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Shared batching, concurrency and retry logic for HTTP translation providers.
|
|
107
|
+
* Subclasses implement one request per batch and return the translations it produced.
|
|
108
|
+
*/
|
|
109
|
+
declare abstract class BatchedProvider<O extends BatchedProviderOptions> implements TranslationProvider {
|
|
110
|
+
protected readonly options: O;
|
|
111
|
+
constructor(options: O);
|
|
76
112
|
/**
|
|
77
113
|
* Translates in parallel batches. If every batch fails, the first error is thrown. If only some fail,
|
|
78
114
|
* a PartialTranslationError carries the translations that succeeded.
|
|
79
115
|
*/
|
|
80
|
-
translate(input:
|
|
81
|
-
sourceLocale: string;
|
|
82
|
-
targetLocale: string;
|
|
83
|
-
messages: TranslationRequest[];
|
|
84
|
-
}): Promise<Catalog>;
|
|
116
|
+
translate(input: TranslateInput): Promise<Catalog>;
|
|
85
117
|
private translateBatch;
|
|
86
|
-
|
|
118
|
+
/** Sends one batch to the provider and returns the translations it produced for the requested IDs. */
|
|
119
|
+
protected abstract request(input: TranslateInput): Promise<Catalog>;
|
|
120
|
+
/** Parses the model's text reply, which must be a JSON object mapping IDs to strings. */
|
|
121
|
+
protected toCatalog(content: string | undefined, messages: TranslationRequest[]): Catalog;
|
|
122
|
+
}
|
|
123
|
+
interface OpenAICompatibleOptions extends BatchedProviderOptions {
|
|
124
|
+
apiKey: string;
|
|
125
|
+
model: string;
|
|
126
|
+
baseURL?: string;
|
|
127
|
+
}
|
|
128
|
+
export declare class OpenAICompatibleProvider extends BatchedProvider<OpenAICompatibleOptions> {
|
|
129
|
+
protected request(input: TranslateInput): Promise<Catalog>;
|
|
130
|
+
}
|
|
131
|
+
interface AnthropicOptions extends BatchedProviderOptions {
|
|
132
|
+
apiKey: string;
|
|
133
|
+
model: string;
|
|
134
|
+
baseURL?: string;
|
|
135
|
+
/** Maximum tokens the model may generate per batch. Default 8192. */
|
|
136
|
+
maxTokens?: number;
|
|
137
|
+
}
|
|
138
|
+
export declare class AnthropicProvider extends BatchedProvider<AnthropicOptions> {
|
|
139
|
+
protected request(input: TranslateInput): Promise<Catalog>;
|
|
140
|
+
}
|
|
141
|
+
interface GeminiOptions extends BatchedProviderOptions {
|
|
142
|
+
apiKey: string;
|
|
143
|
+
model: string;
|
|
144
|
+
baseURL?: string;
|
|
145
|
+
/** Maximum tokens the model may generate per batch. Default 8192. */
|
|
146
|
+
maxOutputTokens?: number;
|
|
147
|
+
}
|
|
148
|
+
export declare class GeminiProvider extends BatchedProvider<GeminiOptions> {
|
|
149
|
+
protected request(input: TranslateInput): Promise<Catalog>;
|
|
87
150
|
}
|
|
88
151
|
export interface AutoI18nOptions {
|
|
89
152
|
locales: string[];
|
|
@@ -98,7 +161,11 @@ export interface AutoI18nOptions {
|
|
|
98
161
|
include?: string[];
|
|
99
162
|
exclude?: string[];
|
|
100
163
|
directory?: string;
|
|
101
|
-
|
|
164
|
+
/**
|
|
165
|
+
* One provider, or named providers such as `{ primary: "anthropic", anthropic, openai }`. The primary is tried first;
|
|
166
|
+
* when it fails, messages it did not translate go to the next configured provider.
|
|
167
|
+
*/
|
|
168
|
+
provider?: ProviderConfig;
|
|
102
169
|
failOnMissingProvider?: boolean;
|
|
103
170
|
onAmbiguous?: "warn" | "error";
|
|
104
171
|
/**
|
|
@@ -118,6 +185,8 @@ export interface AutoI18nOptions {
|
|
|
118
185
|
* `{ Button: ["aria-label", "title"] }`. Only these attributes are extracted from those components.
|
|
119
186
|
*/
|
|
120
187
|
componentProps?: Record<string, string[]>;
|
|
188
|
+
/** Text of the block comment that marks a string literal for translation. Default `i18n`. */
|
|
189
|
+
stringMarker?: string;
|
|
121
190
|
}
|
|
122
191
|
export default function autoI18n(options: AutoI18nOptions): Plugin;
|
|
123
|
-
export type { Catalog, ExtractedMessage, MessageKind, OpenAICompatibleOptions, Registry, RegistryEntry, ResolvedMessage, TranslationProvider, TranslationRequest };
|
|
192
|
+
export type { AnthropicOptions, Catalog, ExtractedMessage, GeminiOptions, MessageKind, OpenAICompatibleOptions, ProviderConfig, Registry, RegistryEntry, ResolvedMessage, TranslationProvider, TranslationRequest };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{a as e,c as t,d as n,f as r,h as i,i as a,l as o,m as s,n as c,o as l,p as u,r as d,s as f,u as p}from"./registry-CLbwA2b4.js";import{n as m,t as h}from"./resolve-ELDMKdoA.js";import{glob as g}from"tinyglobby";import{readFile as _,rm as v}from"node:fs/promises";import y from"node:path";import{createHash as b}from"node:crypto";import{parseSync as x}from"oxc-parser";import S from"magic-string";async function C(e,t,n){let r=Array(e.length),i=0;return await Promise.all(Array.from({length:Math.min(Math.max(1,t),e.length)},async()=>{for(;i<e.length;){let t=i++;try{r[t]={status:`fulfilled`,value:await n(e[t])}}catch(e){r[t]={status:`rejected`,reason:e}}}})),r}const w=`\\{([^{}]+)\\}`,T=RegExp(w,`g`);function E(e){return[...e.matchAll(T)].map(e=>e[1])}function D(e,t){let n=new Set(e),r=new Set(t);return n.size===r.size&&[...n].every(e=>r.has(e))}function O(e,t){let n=E(e);return e.trim()!==``&&n.length===t.length&&D(n,t)}var k=class extends Error{partial;failed;constructor(e,t,n){super(e),this.partial=t,this.failed=n,this.name=`PartialTranslationError`}};async function A(e){let{provider:n,sourceLocale:r,locale:a,requests:o,previous:s,debug:c}=e,l=new Set(o.map(e=>e.id)),u=t(s.messages,l).catalog,d=t(s.sources,l).catalog,f=o.filter(e=>u[e.id]===void 0||d[e.id]!==e.source),p={},m=0,h;if(f.length&&n){if(c)for(let e of f)i(`sent ${r} -> ${a} ${e.id}: ${JSON.stringify(e.source)} (context: ${e.context})`);try{p=await n.translate({sourceLocale:r,targetLocale:a,messages:f})}catch(e){if(!(e instanceof k))throw e;p=e.partial,m=e.failed,h=e.message}if(c)for(let e of f){let t=p[e.id];i(`received ${r} -> ${a} ${e.id}: ${typeof t==`string`?JSON.stringify(t):`(no translation)`}`)}}let g={...u},_={...d},v=0;for(let e of f){delete g[e.id],delete _[e.id];let t=p[e.id];typeof t==`string`&&t.trim()&&(e.kind===`mixed`&&!O(t,e.placeholders??[])?v++:(g[e.id]=t,_[e.id]=e.source))}let y=o.filter(e=>g[e.id]===void 0).length;return{file:{messages:g,sources:_},untranslated:y,rejected:v,requested:f.length,failed:m,failure:h}}async function ee(e){let t=structuredClone(e.registry),{ambiguous:n,messages:r}=a(t,e.files.flatMap(e=>e.messages)),i=d(t,e.orphanRetentionMinutes),o={};for(let e of r)o[e.id]=e.source;let s=r.map(e=>({id:e.id,source:e.source,context:`${e.component} > ${e.context}`,kind:e.kind,placeholders:e.placeholders})),c=await C(e.locales,e.localeConcurrency,t=>A({provider:e.provider,sourceLocale:e.sourceLocale,locale:t,requests:s,previous:e.previous.get(t)??l(),debug:e.debug})),u=/* @__PURE__ */ new Map,f=/* @__PURE__ */ new Map;return c.forEach((t,n)=>{let r=e.locales[n];t.status===`fulfilled`?u.set(r,t.value):f.set(r,t.reason)}),{registry:t,ambiguous:n,pruned:i,messages:r,sourceFile:{messages:o,sources:{}},locales:u,failures:f}}function j(e){return e.replace(/\s+/g,` `).trim()}function M(e){let t=[j(e.source),e.kind,e.tag.toLowerCase(),e.attribute??``,e.context].join(``);return b(`sha256`).update(t).digest(`hex`).slice(0,24)}function N(e,t){let n=x(t,e,{lang:`tsx`,sourceType:`unambiguous`});if(n.errors.length)throw SyntaxError(`${t}: ${n.errors[0].message}`);return{program:n.program,commentsByEnd:new Map(n.comments.map(e=>[e.end,e]))}}function P(e,t){let n=[],r=e=>{t(e,n),n.push(e);for(let t of Object.keys(e)){if(t===`parent`)continue;let n=e[t];Array.isArray(n)?n.forEach(e=>F(e)&&r(e)):F(n)&&r(n)}n.pop()};r(e)}function F(e){return typeof e==`object`&&!!e&&typeof e.type==`string`}function I(e){return F(e)&&e.type===`Literal`&&typeof e.value==`string`}function te(e,t,n,r){let i=n;for(;;){let n=i;for(;n>0&&/\s/.test(e[n-1]);)n--;let a=t.get(n);if(!a)return!1;if(a.type===`Block`&&a.value.trim()===r)return!0;i=a.start}}const ne=new Map(`quot:0022 amp:0026 apos:0027 lt:003C gt:003E nbsp:00A0 iexcl:00A1 cent:00A2 pound:00A3 curren:00A4 yen:00A5 brvbar:00A6 sect:00A7 uml:00A8 copy:00A9 ordf:00AA laquo:00AB not:00AC shy:00AD reg:00AE macr:00AF deg:00B0 plusmn:00B1 sup2:00B2 sup3:00B3 acute:00B4 micro:00B5 para:00B6 middot:00B7 cedil:00B8 sup1:00B9 ordm:00BA raquo:00BB frac14:00BC frac12:00BD frac34:00BE iquest:00BF Agrave:00C0 Aacute:00C1 Acirc:00C2 Atilde:00C3 Auml:00C4 Aring:00C5 AElig:00C6 Ccedil:00C7 Egrave:00C8 Eacute:00C9 Ecirc:00CA Euml:00CB Igrave:00CC Iacute:00CD Icirc:00CE Iuml:00CF ETH:00D0 Ntilde:00D1 Ograve:00D2 Oacute:00D3 Ocirc:00D4 Otilde:00D5 Ouml:00D6 times:00D7 Oslash:00D8 Ugrave:00D9 Uacute:00DA Ucirc:00DB Uuml:00DC Yacute:00DD THORN:00DE szlig:00DF agrave:00E0 aacute:00E1 acirc:00E2 atilde:00E3 auml:00E4 aring:00E5 aelig:00E6 ccedil:00E7 egrave:00E8 eacute:00E9 ecirc:00EA euml:00EB igrave:00EC iacute:00ED icirc:00EE iuml:00EF eth:00F0 ntilde:00F1 ograve:00F2 oacute:00F3 ocirc:00F4 otilde:00F5 ouml:00F6 divide:00F7 oslash:00F8 ugrave:00F9 uacute:00FA ucirc:00FB uuml:00FC yacute:00FD thorn:00FE yuml:00FF OElig:0152 oelig:0153 Scaron:0160 scaron:0161 Yuml:0178 fnof:0192 circ:02C6 tilde:02DC Alpha:0391 Beta:0392 Gamma:0393 Delta:0394 Epsilon:0395 Zeta:0396 Eta:0397 Theta:0398 Iota:0399 Kappa:039A Lambda:039B Mu:039C Nu:039D Xi:039E Omicron:039F Pi:03A0 Rho:03A1 Sigma:03A3 Tau:03A4 Upsilon:03A5 Phi:03A6 Chi:03A7 Psi:03A8 Omega:03A9 alpha:03B1 beta:03B2 gamma:03B3 delta:03B4 epsilon:03B5 zeta:03B6 eta:03B7 theta:03B8 iota:03B9 kappa:03BA lambda:03BB mu:03BC nu:03BD xi:03BE omicron:03BF pi:03C0 rho:03C1 sigmaf:03C2 sigma:03C3 tau:03C4 upsilon:03C5 phi:03C6 chi:03C7 psi:03C8 omega:03C9 thetasym:03D1 upsih:03D2 piv:03D6 ensp:2002 emsp:2003 thinsp:2009 zwnj:200C zwj:200D lrm:200E rlm:200F ndash:2013 mdash:2014 lsquo:2018 rsquo:2019 sbquo:201A ldquo:201C rdquo:201D bdquo:201E dagger:2020 Dagger:2021 bull:2022 hellip:2026 permil:2030 prime:2032 Prime:2033 lsaquo:2039 rsaquo:203A oline:203E frasl:2044 euro:20AC image:2111 weierp:2118 real:211C trade:2122 alefsym:2135 larr:2190 uarr:2191 rarr:2192 darr:2193 harr:2194 crarr:21B5 lArr:21D0 uArr:21D1 rArr:21D2 dArr:21D3 hArr:21D4 forall:2200 part:2202 exist:2203 empty:2205 nabla:2207 isin:2208 notin:2209 ni:220B prod:220F sum:2211 minus:2212 lowast:2217 radic:221A prop:221D infin:221E ang:2220 and:2227 or:2228 cap:2229 cup:222A int:222B there4:2234 sim:223C cong:2245 asymp:2248 ne:2260 equiv:2261 le:2264 ge:2265 sub:2282 sup:2283 nsub:2284 sube:2286 supe:2287 oplus:2295 otimes:2297 perp:22A5 sdot:22C5 lceil:2308 rceil:2309 lfloor:230A rfloor:230B lang:2329 rang:232A loz:25CA spades:2660 clubs:2663 hearts:2665 diams:2666`.split(` `).map(e=>{let[t,n]=e.split(`:`);return[t,String.fromCharCode(parseInt(n,16))]})),re=/&(?:#(x[0-9a-fA-F]+|[0-9]+)|([A-Za-z0-9]+));/g;function L(e){return e.includes(`&`)?e.replace(re,(e,t,n)=>{if(n!==void 0)return ne.get(n)??e;let r=t.startsWith(`x`)?parseInt(t.slice(1),16):parseInt(t,10);return r<=1114111?String.fromCodePoint(r):e}):e}const ie=/^[A-Z]/,ae=/* @__PURE__ */ new Set([`script`,`style`,`code`,`pre`]),oe=/* @__PURE__ */ new Set([`alt`,`title`,`placeholder`,`label`,`summary`,`aria-label`,`aria-description`,`aria-placeholder`,`aria-roledescription`,`aria-valuetext`]);function R(e){return I(e)?L(e.value):void 0}function z(e){return L(e.value)}function se(e){return e.attributes.some(e=>e.type===`JSXAttribute`&&e.name.type===`JSXIdentifier`&&e.name.name===`translate`&&R(e.value)===`no`)}function B(e){return e.type===`JSXIdentifier`&&!ie.test(e.name)&&!ae.has(e.name)}function V(e){if(e.type===`Identifier`)return e.name;if(e.type===`MemberExpression`&&!e.computed&&e.property.type===`Identifier`){let t=V(e.object);return t?`${t}.${e.property.name}`:void 0}}const ce=(e,t)=>e.type===`FunctionExpression`?!(t?.type===`MethodDefinition`||t?.type===`Property`&&(t.method||t.kind!==`init`)):e.type===`FunctionDeclaration`||e.type===`ArrowFunctionExpression`;function H(e,t){for(let n=e.length-1;n>=0;n--)if(t(e[n]))return n;return-1}function U(e){return e.some(e=>{let t=e.type===`JSXOpeningElement`?e:e.type===`JSXElement`?e.openingElement:void 0;return t!==void 0&&se(t)})}function W(e,t,n){let r=e.attributes.filter(e=>e.type===`JSXAttribute`&&e.name.type===`JSXIdentifier`&&[`aria-label`,`title`,`name`,`type`,`role`].includes(e.name.name)).map(e=>`${e.name.name}=${R(e.value)??`{expr}`}`).sort().join(`;`),i=e.attributes.filter(e=>e.type===`JSXAttribute`&&e.name.type===`JSXIdentifier`&&/^on[A-Z]/.test(e.name.name)).map(e=>e.name.name).sort().join(`,`);return[t,n??``,r,i].filter(Boolean).join(` | `)}function le(e,t,n={}){let{program:r,commentsByEnd:i}=N(e,t),a=[],o=e=>Object.prototype.hasOwnProperty.call(n.componentProps??{},e)?n.componentProps[e]:[];function s(e){let t=e.length-1;for(;t>=0&&!ce(e[t],e[t-1]);)t--;if(t<0)return`<module>`;let n=e[t],r=e[t-1];return r?.type===`VariableDeclarator`&&r.id.type===`Identifier`?r.id.name:n.type===`FunctionDeclaration`&&n.id?n.id.name:`<anonymous>`}function c(e,n){if(U([...n,e]))return;let r=e.children,i=r.filter(e=>e.type===`JSXExpressionContainer`);if(!i.length||r.some(e=>e.type!==`JSXText`&&e.type!==`JSXExpressionContainer`)||!i.every(e=>V(e.expression)!==void 0)||!r.some(e=>e.type===`JSXText`&&/[A-Za-z]/.test(z(e))))return;let o=e.openingElement.name;if(!B(o))return;let c=[];for(let e of i){let t=V(e.expression),n=t;for(let e=2;c.includes(n);e++)n=`${t}_${e}`;c.push(n)}let l=``,u=0;for(let e of r)l+=e.type===`JSXText`?z(e):`{${c[u++]}}`;let d=j(l);if(!d)return;let f=o.name,p={source:d,prefix:l.match(/^\s*/)?.[0]??``,suffix:l.match(/\s*$/)?.[0]??``,kind:`mixed`,placeholders:c,file:t,start:e.start,end:e.end,tag:f,component:s(n),context:W(e.openingElement,f),fingerprint:``};p.fingerprint=M(p),a.push(p)}function l(e,n){if(U([...n,e]))return;let r=z(e),i=j(r);if(!i)return;let o=H(n,e=>e.type===`JSXElement`);if(o<0)return;let c=n[o];if(c.children.some(e=>e.type===`JSXExpressionContainer`))return;let l=c.openingElement.name;if(!B(l))return;let u=l.name,d={source:i,prefix:r.match(/^\s*/)?.[0]??``,suffix:r.match(/\s*$/)?.[0]??``,kind:`text`,file:t,start:e.start,end:e.end,tag:u,component:s(n.slice(0,o)),context:W(c.openingElement,u),fingerprint:``};d.fingerprint=M(d),a.push(d)}function u(e,n){if(e.name.type!==`JSXIdentifier`)return;let r=e.name.name,i=n[n.length-1];if(i?.type!==`JSXOpeningElement`)return;let c=i.name.type===`JSXIdentifier`?i.name.name:``;if(!(B(i.name)?oe.has(r):o(c).includes(r))||U([...n,e])||!I(e.value))return;let l=j(L(e.value.value));if(!l||!/[A-Za-z]/.test(l))return;let u=H(n,e=>e.type===`JSXElement`);if(u<0)return;let d=n[u],f={source:l,kind:`attribute`,file:t,start:e.value.start,end:e.value.end,tag:c,component:s(n.slice(0,u)),attribute:r,context:W(d.openingElement,c,r),fingerprint:``};f.fingerprint=M(f),a.push(f)}function d(n,r){if(!I(n)||r[r.length-1]?.type===`JSXAttribute`||r.some(e=>e.start===n.start)||!te(e,i,n.start,`i18n`))return;let o=j(n.value);if(!o||!/[A-Za-z]/.test(o))return;let c={source:o,kind:`string`,file:t,start:n.start,end:n.end,tag:``,component:s(r),context:``,fingerprint:``};c.fingerprint=M(c),a.push(c)}return P(r,(e,t)=>{switch(e.type){case`JSXElement`:return c(e,t);case`JSXText`:return l(e,t);case`JSXAttribute`:return u(e,t);case`Literal`:return d(e,t)}}),a.sort((e,t)=>e.start-t.start)}const G=`virtual:autoi18n`,K=`__autoI18nT`,q=`__autoI18nFormat`;function ue(e,t,n,r,i,a=!1,o=`en`){let{program:s}=N(e,t),c={mixed:/* @__PURE__ */ new Map,text:/* @__PURE__ */ new Map,attribute:/* @__PURE__ */ new Map,string:/* @__PURE__ */ new Map};for(let e of n)c[e.kind]?.set(e.start,e);let l=[];P(s,e=>{if(e.type===`JSXElement`){let t=c.mixed.get(e.start);t&&l.push({kind:`mixed`,node:e,message:t})}else if(e.type===`JSXText`){let t=c.text.get(e.start);t&&l.push({kind:`text`,node:e,message:t})}else if(e.type===`JSXAttribute`&&e.value?.type===`Literal`){let t=c.attribute.get(e.value.start);t&&l.push({kind:`attribute`,node:e.value,message:t})}else if(e.type===`Literal`&&typeof e.value==`string`){let t=c.string.get(e.start);t&&l.push({kind:`string`,node:e,message:t})}});let u=new S(e),d=!1,f=!1,p=e=>r===o?e.source:i[e.id]??e.source,m=e=>a?(d=!0,`${K}(${JSON.stringify(e.id)}, ${JSON.stringify(e.source)})`):JSON.stringify(p(e)),h=e=>{let t=e.prefix??``,n=e.suffix??``;return a?[t&&JSON.stringify(t),m(e),n&&JSON.stringify(n)].filter(Boolean).join(` + `):JSON.stringify(`${t}${p(e)}${n}`)};for(let e of l.reverse()){let{message:t}=e;if(e.kind===`mixed`){let n=e.node;if(!n.closingElement)continue;let r=t.placeholders??[],i=n.children.flatMap(e=>e.type===`JSXExpressionContainer`?[e.expression]:[]),o=e=>u.slice(i[e].start,i[e].end),s=n.openingElement.end,c=n.closingElement.start;if(a){f=!0;let e=r.map((e,t)=>`${JSON.stringify(e)}: ${o(t)}`).join(`, `);u.overwrite(s,c,`{${q}(${h(t)}, {${e}})}`);continue}let l=p(t),d=O(l,r)?l:t.source,m=[],g=0,_=e=>{e&&m.push(`{${JSON.stringify(e)}}`)};_(t.prefix??``);for(let e of d.matchAll(T))_(d.slice(g,e.index)),m.push(`{${o(r.indexOf(e[1]))}}`),g=e.index+e[0].length;_(d.slice(g)+(t.suffix??``)),u.overwrite(s,c,m.join(``))}else e.kind===`text`?u.overwrite(e.node.start,e.node.end,`{${h(t)}}`):e.kind===`attribute`?u.overwrite(e.node.start,e.node.end,a?`{${m(t)}}`:m(t)):u.overwrite(e.node.start,e.node.end,m(t))}let g=[...d?[`t as ${K}`]:[],...f?[`format as ${q}`]:[]];return g.length&&u.prepend(`import { ${g.join(`, `)} } from "${G}";\n`),{code:u.toString(),map:u.generateMap({source:t,hires:!0,includeContent:!0})}}function de(e,t,n){let r=[],i=0;for(let a of e.matchAll(n)){if(!Object.hasOwn(t,a[1]))continue;let n=a.index??0;n>i&&r.push(e.slice(i,n)),r.push(t[a[1]]),i=n+a[0].length}return i<e.length&&r.push(e.slice(i)),r}function fe(e,t){return[`const loaders = {\n${Object.entries(e).map(([e,t])=>` ${JSON.stringify(e)}: () => import(${JSON.stringify(t)}).then(m => (m.default ?? m).messages)`).join(`,
|
|
2
|
-
`)}\n};`,`const sourceLocale = ${JSON.stringify(t)};`,`const resolveLocale = ${
|
|
1
|
+
import{A as e,C as t,D as n,E as r,F as i,I as a,L as o,M as s,N as c,O as l,P as u,R as d,S as f,T as p,_ as m,b as h,c as g,g as _,i as v,j as y,k as b,l as x,m as S,n as C,o as w,r as T,t as ee,u as E,w as D,x as O,y as k}from"./registry-Dxz-5jhc.js";import{n as A,t as j}from"./resolve-ELDMKdoA.js";import*as M from"zod/mini";import{glob as te}from"tinyglobby";import{readFile as ne,rm as N}from"node:fs/promises";import P from"node:path";import{createHash as F}from"node:crypto";import{parseSync as I}from"oxc-parser";import re from"magic-string";async function ie(e,t,n){let r=Array(e.length),i=0;return await Promise.all(Array.from({length:Math.min(Math.max(1,t),e.length)},async()=>{for(;i<e.length;){let t=i++;try{r[t]={status:`fulfilled`,value:await n(e[t])}}catch(e){r[t]={status:`rejected`,reason:e}}}})),r}function ae(e){return[...e.matchAll(k)].map(e=>e[1])}function oe(e,t){let n=new Set(e),r=new Set(t);return n.size===r.size&&[...n].every(e=>r.has(e))}function se(e,t){let n=ae(e);return e.trim()!==``&&n.length===t.length&&oe(n,t)}var L=class extends Error{partial;failed;constructor(e,t,n){super(e),this.partial=t,this.failed=n,this.name=`PartialTranslationError`}};async function ce(e){let{provider:t,sourceLocale:n,locale:r,requests:i,previous:a,debug:o}=e,s=new Set(i.map(e=>e.id)),c=b(a.messages,s).catalog,l=b(a.sources,s).catalog,u=i.filter(e=>c[e.id]===void 0||l[e.id]!==e.source),f={},p=0,m;if(u.length&&t){if(o)for(let e of u)d(`sent ${n} -> ${r} ${e.id}: ${JSON.stringify(e.source)} (context: ${e.context})`);try{f=await t.translate({sourceLocale:n,targetLocale:r,messages:u})}catch(e){if(!(e instanceof L))throw e;f=e.partial,p=e.failed,m=e.message}if(o)for(let e of u){let t=f[e.id];d(`received ${n} -> ${r} ${e.id}: ${typeof t==`string`?JSON.stringify(t):`(no translation)`}`)}}let h={...c},g={...l},_=0;for(let e of u){delete h[e.id],delete g[e.id];let t=f[e.id];typeof t==`string`&&t.trim()&&(e.kind===`mixed`&&!se(t,e.placeholders??[])?_++:(h[e.id]=t,g[e.id]=e.source))}let v=i.filter(e=>h[e.id]===void 0).length;return{file:{messages:h,sources:g},untranslated:v,rejected:_,requested:u.length,failed:p,failure:m}}async function le(e){let t=structuredClone(e.registry),{ambiguous:r,messages:i}=T(t,e.files.flatMap(e=>e.messages)),a=C(t,e.orphanRetentionMinutes),o={};for(let e of i)o[e.id]=e.source;let s=i.map(e=>({id:e.id,source:e.source,context:`${e.component} > ${e.context}`,kind:e.kind,placeholders:e.placeholders})),c=await ie(e.locales,e.localeConcurrency,t=>ce({provider:e.provider,sourceLocale:e.sourceLocale,locale:t,requests:s,previous:e.previous.get(t)??n(),debug:e.debug})),l=/* @__PURE__ */ new Map,u=/* @__PURE__ */ new Map;return c.forEach((t,n)=>{let r=e.locales[n];t.status===`fulfilled`?l.set(r,t.value):u.set(r,t.reason)}),{registry:t,ambiguous:r,pruned:a,messages:i,sourceFile:{messages:o,sources:{}},locales:l,failures:u}}function R(e){return e.replace(/\s+/g,` `).trim()}function z(e){let t=[R(e.source),e.kind,e.tag.toLowerCase(),e.attribute??``,e.context].join(``);return F(`sha256`).update(t).digest(`hex`).slice(0,24)}function ue(e,t){let n=I(t,e,{lang:`tsx`,sourceType:`unambiguous`});if(n.errors.length)throw SyntaxError(`${t}: ${n.errors[0].message}`);return{program:n.program,commentsByEnd:new Map(n.comments.map(e=>[e.end,e]))}}function de(e,t){let n=[],r=e=>{t(e,n),n.push(e);for(let t of Object.keys(e)){if(t===`parent`)continue;let n=e[t];Array.isArray(n)?n.forEach(e=>B(e)&&r(e)):B(n)&&r(n)}n.pop()};r(e)}function B(e){return typeof e==`object`&&!!e&&typeof e.type==`string`}function V(e){return B(e)&&e.type===`Literal`&&typeof e.value==`string`}function fe(e,t,n,r){let i=n;for(;;){let n=i;for(;n>0&&/\s/.test(e[n-1]);)n--;let a=t.get(n);if(!a)return!1;if(a.type===`Block`&&a.value.trim()===r)return!0;i=a.start}}function H(e){return e.includes(`&`)?e.replace(O,(e,t,n)=>{if(n!==void 0)return m.get(n)??e;let r=t.startsWith(`x`)?parseInt(t.slice(1),16):parseInt(t,10);return r<=1114111?String.fromCodePoint(r):e}):e}function pe(e){return V(e)?H(e.value):void 0}function U(e){return H(e.value)}function me(e){return e.attributes.some(e=>e.type===`JSXAttribute`&&e.name.type===`JSXIdentifier`&&e.name.name===`translate`&&pe(e.value)===`no`)}function he(e){return e.type===`JSXIdentifier`&&!g.test(e.name)&&!S.has(e.name)}function W(e){if(e.type===`Identifier`)return e.name;if(e.type===`MemberExpression`&&!e.computed&&e.property.type===`Identifier`){let t=W(e.object);return t?`${t}.${e.property.name}`:void 0}}const ge=(e,t)=>e.type===`FunctionExpression`?!(t?.type===`MethodDefinition`||t?.type===`Property`&&(t.method||t.kind!==`init`)):e.type===`FunctionDeclaration`||e.type===`ArrowFunctionExpression`;function _e(e,t){for(let n=e.length-1;n>=0;n--)if(t(e[n]))return n;return-1}function G(e){return e.some(e=>{let t=e.type===`JSXOpeningElement`?e:e.type===`JSXElement`?e.openingElement:void 0;return t!==void 0&&me(t)})}function K(e,t,n){let r=e.attributes.filter(e=>e.type===`JSXAttribute`&&e.name.type===`JSXIdentifier`&&[`aria-label`,`title`,`name`,`type`,`role`].includes(e.name.name)).map(e=>`${e.name.name}=${pe(e.value)??`{expr}`}`).sort().join(`;`),i=e.attributes.filter(e=>e.type===`JSXAttribute`&&e.name.type===`JSXIdentifier`&&/^on[A-Z]/.test(e.name.name)).map(e=>e.name.name).sort().join(`,`);return[t,n??``,r,i].filter(Boolean).join(` | `)}function ve(e,t,n={}){let{program:r,commentsByEnd:i}=ue(e,t),a=[],o=e=>Object.prototype.hasOwnProperty.call(n.componentProps??{},e)?n.componentProps[e]:[],s=e=>{if(e.type!==`JSXIdentifier`)return!1;let t=e.name;return he(e)||g.test(t)&&o(t).includes(`children`)};function c(e){let t=e.length-1;for(;t>=0&&!ge(e[t],e[t-1]);)t--;if(t<0)return`<module>`;let n=e[t],r=e[t-1];return r?.type===`VariableDeclarator`&&r.id.type===`Identifier`?r.id.name:n.type===`FunctionDeclaration`&&n.id?n.id.name:`<anonymous>`}function l(e,n){if(G([...n,e]))return;let r=e.children,i=r.filter(e=>e.type===`JSXExpressionContainer`);if(!i.length||r.some(e=>e.type!==`JSXText`&&e.type!==`JSXExpressionContainer`)||!i.every(e=>W(e.expression)!==void 0)||!r.some(e=>e.type===`JSXText`&&/[A-Za-z]/.test(U(e))))return;let o=e.openingElement.name;if(!s(o))return;let l=[];for(let e of i){let t=W(e.expression),n=t;for(let e=2;l.includes(n);e++)n=`${t}_${e}`;l.push(n)}let u=``,d=0;for(let e of r)u+=e.type===`JSXText`?U(e):`{${l[d++]}}`;let f=R(u);if(!f)return;let p=o.name,m={source:f,prefix:u.match(/^\s*/)?.[0]??``,suffix:u.match(/\s*$/)?.[0]??``,kind:`mixed`,placeholders:l,file:t,start:e.start,end:e.end,tag:p,component:c(n),context:K(e.openingElement,p),fingerprint:``};m.fingerprint=z(m),a.push(m)}function u(e,n){if(G([...n,e]))return;let r=U(e),i=R(r);if(!i)return;let o=_e(n,e=>e.type===`JSXElement`);if(o<0)return;let l=n[o];if(l.children.some(e=>e.type===`JSXExpressionContainer`))return;let u=l.openingElement.name;if(!s(u))return;let d=u.name,f={source:i,prefix:r.match(/^\s*/)?.[0]??``,suffix:r.match(/\s*$/)?.[0]??``,kind:`text`,file:t,start:e.start,end:e.end,tag:d,component:c(n.slice(0,o)),context:K(l.openingElement,d),fingerprint:``};f.fingerprint=z(f),a.push(f)}function d(e,n){if(e.name.type!==`JSXIdentifier`)return;let r=e.name.name,i=n[n.length-1];if(i?.type!==`JSXOpeningElement`)return;let s=i.name.type===`JSXIdentifier`?i.name.name:``;if(!(he(i.name)?p.has(r):o(s).includes(r))||G([...n,e])||!V(e.value))return;let l=R(H(e.value.value));if(!l||!/[A-Za-z]/.test(l))return;let u=_e(n,e=>e.type===`JSXElement`);if(u<0)return;let d=n[u],f={source:l,kind:`attribute`,file:t,start:e.value.start,end:e.value.end,tag:s,component:c(n.slice(0,u)),attribute:r,context:K(d.openingElement,s,r),fingerprint:``};f.fingerprint=z(f),a.push(f)}function f(r,o){if(!V(r)||o[o.length-1]?.type===`JSXAttribute`||o.some(e=>e.start===r.start)||!fe(e,i,r.start,n.stringMarker??`i18n`))return;let s=R(r.value);if(!s||!/[A-Za-z]/.test(s))return;let l={source:s,kind:`string`,file:t,start:r.start,end:r.end,tag:``,component:c(o),context:``,fingerprint:``};l.fingerprint=z(l),a.push(l)}return de(r,(e,t)=>{switch(e.type){case`JSXElement`:return l(e,t);case`JSXText`:return u(e,t);case`JSXAttribute`:return d(e,t);case`Literal`:return f(e,t)}}),a.sort((e,t)=>e.start-t.start)}function ye(e,n,r,i,a,o=!1,s=`en`){let{program:c}=ue(e,n),l={mixed:/* @__PURE__ */ new Map,text:/* @__PURE__ */ new Map,attribute:/* @__PURE__ */ new Map,string:/* @__PURE__ */ new Map};for(let e of r)l[e.kind]?.set(e.start,e);let u=[];de(c,e=>{if(e.type===`JSXElement`){let t=l.mixed.get(e.start);t&&u.push({kind:`mixed`,node:e,message:t})}else if(e.type===`JSXText`){let t=l.text.get(e.start);t&&u.push({kind:`text`,node:e,message:t})}else if(e.type===`JSXAttribute`&&e.value?.type===`Literal`){let t=l.attribute.get(e.value.start);t&&u.push({kind:`attribute`,node:e.value,message:t})}else if(e.type===`Literal`&&typeof e.value==`string`){let t=l.string.get(e.start);t&&u.push({kind:`string`,node:e,message:t})}});let d=new re(e),p=!1,m=!1,h=e=>i===s?e.source:a[e.id]??e.source,g=e=>o?(p=!0,`${t}(${JSON.stringify(e.id)}, ${JSON.stringify(e.source)})`):JSON.stringify(h(e)),_=e=>{let t=e.prefix??``,n=e.suffix??``;return o?[t&&JSON.stringify(t),g(e),n&&JSON.stringify(n)].filter(Boolean).join(` + `):JSON.stringify(`${t}${h(e)}${n}`)};for(let e of u.reverse()){let{message:t}=e;if(e.kind===`mixed`){let n=e.node;if(!n.closingElement)continue;let r=t.placeholders??[],i=n.children.flatMap(e=>e.type===`JSXExpressionContainer`?[e.expression]:[]),a=e=>d.slice(i[e].start,i[e].end),s=n.openingElement.end,c=n.closingElement.start;if(o){m=!0;let e=r.map((e,t)=>`${JSON.stringify(e)}: ${a(t)}`).join(`, `);d.overwrite(s,c,`{${f}(${_(t)}, {${e}})}`);continue}let l=h(t),u=se(l,r)?l:t.source,p=[],g=0,v=e=>{e&&p.push(`{${JSON.stringify(e)}}`)};v(t.prefix??``);for(let e of u.matchAll(k))v(u.slice(g,e.index)),p.push(`{${a(r.indexOf(e[1]))}}`),g=e.index+e[0].length;v(u.slice(g)+(t.suffix??``)),d.overwrite(s,c,p.join(``))}else e.kind===`text`?d.overwrite(e.node.start,e.node.end,`{${_(t)}}`):e.kind===`attribute`?d.overwrite(e.node.start,e.node.end,o?`{${g(t)}}`:g(t)):d.overwrite(e.node.start,e.node.end,g(t))}let v=[...p?[`t as ${t}`]:[],...m?[`format as ${f}`]:[]];return v.length&&d.prepend(`import { ${v.join(`, `)} } from "${D}";\n`),{code:d.toString(),map:d.generateMap({source:n,hires:!0,includeContent:!0})}}function be(e,t,n){let r=[],i=0;for(let a of e.matchAll(n)){if(!Object.hasOwn(t,a[1]))continue;let n=a.index??0;n>i&&r.push(e.slice(i,n)),r.push(t[a[1]]),i=n+a[0].length}return i<e.length&&r.push(e.slice(i)),r}function xe(e,t){return[`const loaders = {\n${Object.entries(e).map(([e,t])=>` ${JSON.stringify(e)}: () => import(${JSON.stringify(t)}).then(m => (m.default ?? m).messages)`).join(`,
|
|
2
|
+
`)}\n};`,`const sourceLocale = ${JSON.stringify(t)};`,`const resolveLocale = ${j.toString()};`,`const createLocaleLoader = ${A.toString()};`,`const formatPlaceholders = ${be.toString()};`,`const PLACEHOLDER = new RegExp(${JSON.stringify(h)}, "g");`,`const loadLocale = createLocaleLoader(loaders, sourceLocale);`,`const listeners = new Set();`,`let currentLocale = sourceLocale;`,`let messages = {};`,`let version = 0;`,`let latestRequest = 0;`,`export const locales = Object.keys(loaders);`,`export function getLocale() { return currentLocale; }`,`export function getVersion() { return version; }`,`export function resolveSupported(preferred) { return resolveLocale(preferred, locales, sourceLocale); }`,`export async function setLocale(locale) {`,` const request = ++latestRequest;`,` let selected = loaders[locale] ? locale : sourceLocale;`,` let catalog;`,` try {`,` catalog = await loadLocale(selected);`,` } catch (error) {`,` console.warn('autoi18n: could not load "' + selected + '", falling back to "' + sourceLocale + '".', error);`,` selected = sourceLocale;`,` try {`,` catalog = await loadLocale(selected);`,` } catch (fallbackError) {`,` // Keep the current locale and messages so the UI stays consistent. Texts without a catalogue show their source.`,` console.error('autoi18n: could not load the source catalogue "' + sourceLocale + '".', fallbackError);`,` return;`,` }`,` }`,` // A slower earlier request must not overwrite the locale the user picked last.`,` if (request !== latestRequest) return;`,` messages = catalog;`,` currentLocale = selected;`,` notify();`,`}`,`function notify() {`,` version++;`,` listeners.forEach(fn => fn(currentLocale));`,`}`,`export function subscribe(fn) { listeners.add(fn); return () => { listeners.delete(fn); }; }`,`export function t(id, source) { return messages[id] ?? source; }`,`export function format(text, values) { return formatPlaceholders(text, values, PLACEHOLDER); }`,`// Dev server pushes fresh catalogues after a source change; no page reload is needed.`,`if (import.meta.hot) {`,` import.meta.hot.on("autoi18n:update", data => {`,` for (const [locale, catalog] of Object.entries(data.catalogs)) loadLocale.prime(locale, catalog);`,` if (data.catalogs[currentLocale]) messages = data.catalogs[currentLocale];`,` notify();`,` });`,`}`].join(`
|
|
3
3
|
`)+`
|
|
4
|
-
`}const J=6e4;var Y=class extends Error{retryable;retryAfterMs;constructor(e,t,n){super(e),this.retryable=t,this.retryAfterMs=n}};function pe(e,t=Date.now()){if(!e)return;let n=Number(e);if(Number.isFinite(n))return Math.min(Math.max(n,0)*1e3,J);let r=Date.parse(e);if(!Number.isNaN(r))return Math.min(Math.max(r-t,0),J)}const me=e=>new Promise(t=>setTimeout(t,e));var he=class{options;constructor(e){this.options=e}async translate(e){let t=Math.max(1,this.options.batchSize??40),n=[];for(let r=0;r<e.messages.length;r+=t)n.push(e.messages.slice(r,r+t));let r=await C(n,this.options.concurrency??4,t=>this.translateBatch({...e,messages:t})),i={},a=[],o=0,s=0;if(r.forEach((e,t)=>{e.status===`fulfilled`?(s++,Object.assign(i,e.value)):(a.push(e.reason),o+=n[t].length)}),!a.length)return i;throw s?new k(`${o} messages failed to translate: ${a[0].message}`,i,o):a[0]}async translateBatch(e){let t=this.options.maxRetries??2,n=this.options.retryDelayMs??500;for(let r=0;;r++)try{return await this.request(e)}catch(e){if(e instanceof Y&&!e.retryable||r>=t)throw e;let i=e instanceof Y?e.retryAfterMs:void 0;await me(i??n*2**r*(.5+Math.random()/2))}}async request(e){let t=await fetch(`${this.options.baseURL??`https://api.openai.com/v1`}/chat/completions`,{method:`POST`,signal:AbortSignal.timeout(this.options.timeoutMs??6e4),headers:{Authorization:`Bearer ${this.options.apiKey}`,"Content-Type":`application/json`},body:JSON.stringify({model:this.options.model,temperature:0,response_format:{type:`json_object`},messages:[{role:`system`,content:`You are a professional software localization engine. Translate UI messages from ${e.sourceLocale} to ${e.targetLocale}. Preserve placeholders exactly. Respect context and UI conventions. Return ONLY a JSON object mapping message IDs to translated strings. Do not translate product names or code.`},{role:`user`,content:JSON.stringify(e.messages.map(e=>({id:e.id,text:e.source,context:e.context,kind:e.kind})))}]})});if(!t.ok){let e=t.status===408||t.status===409||t.status===429||t.status>=500;throw await t.body?.cancel().catch(()=>void 0),new Y(`Translation API failed (${t.status})`,e,pe(t.headers.get(`retry-after`)))}let r=n.safeParse(await t.json()),i=r.success?r.data.choices[0].message.content:void 0;if(!i)throw new Y(`Translation provider returned no content`,!0);let a;try{a=JSON.parse(i)}catch{throw new Y(`Translation provider returned invalid JSON`,!0)}let o=u.safeParse(a);if(!o.success)throw new Y(`Translation provider returned a non-object`,!0);let s=new Set(e.messages.map(e=>e.id)),c={};for(let[e,t]of Object.entries(o.data))s.has(e)&&typeof t==`string`&&t.trim()&&(c[e]=t);return c}};const X=[`src/**/*.{tsx,jsx,ts,js}`],Z=[`**/*.test.*`,`**/node_modules/**`];function ge(e){let t=r.safeParse(e);if(t.success)return;let n=t.error.issues.map(e=>`${e.path.join(`.`)||`options`}: ${e.message}`).join(`; `);throw Error(`autoi18n: invalid options (${n}).`)}const Q=e=>({info:t=>e.info(t),warn:t=>e.warn(t),error:t=>e.error(t)});async function $(e,t){await Promise.all(t.map(t=>v(y.join(e,`${t}.sources.json`),{force:!0})))}function _e(t){ge(t);let n=t.sourceLocale??`en`,r=t.directory??`.autoi18n`,a=t.delivery??`build`,l=t.debug??!1,u={componentProps:t.componentProps},d=t.orphanRetentionMinutes??43200,m,h=``,v={},b=/* @__PURE__ */ new Map,x=/* @__PURE__ */ new Map,S=/* @__PURE__ */ new Map,C=/* @__PURE__ */ new Map,w=/* @__PURE__ */ new Set,T,E=Promise.resolve(),D,O=()=>t.locale??process.env.AUTOI18N_LOCALE??n;function k(){let e=t.locales.filter(e=>e!==n),r=t.locale??process.env.AUTOI18N_LOCALE;return a===`build`&&r&&m.command!==`serve`?e.filter(e=>e===r):e}async function A(a,w){let T=w!==void 0&&b.has(w),E=T?[...b.keys()]:await g(t.include??X,{cwd:m.root,ignore:t.exclude??Z,absolute:!0}),D=/* @__PURE__ */ new Map,O=[];for(let e of E){let t=b.get(e),n;if(t&&T&&e!==w)n=t;else{let r=await _(e,`utf8`),i=s(r);n=t?.hash===i?t:{hash:i,messages:le(r,e,u)}}D.set(e,n),O.push({file:e,messages:n.messages})}let A=y.resolve(m.root,r,`locales`),j=k(),M=/* @__PURE__ */ new Map;for(let e of j)M.set(e,await o(f(A,e),a.warn));let N=await ee({files:O,registry:await c(h,a.warn),orphanRetentionMinutes:d,sourceLocale:n,locales:j,localeConcurrency:Math.max(1,Math.floor(t.localeConcurrency??2)),previous:M,provider:t.provider,debug:l});if(l)for(let e of N.pruned)i(`removed ${e.id} ${JSON.stringify(e.source)} (last seen ${e.lastSeen})`);if(N.pruned.length&&a.info(`autoi18n: removed ${N.pruned.length} registry entries no longer in the source.`),await e(h,N.registry),N.ambiguous.length){let e=`autoi18n: ${N.ambiguous.length} duplicated texts changed occurrence count, so their IDs may have moved (e.g. "${N.ambiguous[0]}").`;t.onAmbiguous===`error`?a.error(e):a.warn(e)}await p(f(A,n),N.sourceFile),await $(A,[n,...t.locales]);let P=/* @__PURE__ */ new Map([[n,N.sourceFile.messages]]);for(let[e,t]of N.locales)await p(f(A,e),t.file),P.set(e,t.file.messages);for(let e of N.failures.keys())P.set(e,M.get(e)?.messages??{});let F=0;x=/* @__PURE__ */ new Map;for(let{file:e,messages:t}of O)x.set(e,N.messages.slice(F,F+t.length)),F+=t.length;S=new Map([...D].map(([e,t])=>[e,t.hash])),b=D,C=P,v=Object.fromEntries(t.locales.map(e=>[e,f(A,e)]));for(let[e,n]of N.locales)n.requested&&!t.provider&&t.failOnMissingProvider&&a.error(`Missing ${n.requested} ${e} translations and no provider is configured.`),n.rejected&&a.info(`autoi18n: rejected ${n.rejected} ${e} translations with changed placeholders.`),n.untranslated&&(t.provider?a.warn:a.info)(`autoi18n: ${n.untranslated} ${e} messages have no translation and fall back to the source text.`),n.failed&&a.warn(`autoi18n: ${n.failed} ${e} translation batches failed (${n.failure}); they will be requested again on the next build.`);N.messages.length&&a.info(`autoi18n: indexed ${N.messages.length} messages across ${E.length} files.`);let I=[...N.failures];for(let[e,t]of I.slice(1))a.warn(`autoi18n: ${e} translation failed: ${t.message}`);if(I.length)throw I[0][1]}function j(e,t){let n=E.then(()=>A(e,t));return E=n.catch(()=>void 0),n}function M(){T?.ws.send({type:`custom`,event:`autoi18n:update`,data:{catalogs:Object.fromEntries(C)}})}async function N(e){try{await j(Q(m.logger),e),M()}catch(e){m.logger.error(`autoi18n: re-translation failed: ${e.message}`)}}function P(){clearTimeout(D),D=setTimeout(()=>void N(),150)}function F(e){if(!/\.[jt]sx?$/.test(e))return!1;let n=y.relative(m.root,e).split(y.sep).join(`/`);return(t.include??X).some(e=>y.matchesGlob(n,e))&&!(t.exclude??Z).some(e=>y.matchesGlob(n,e))}return{name:`vite-plugin-autoi18n`,enforce:`pre`,configResolved(e){m=e,h=y.resolve(m.root,r,`registry.json`);let i=O();if(i!==n&&!t.locales.includes(i))throw Error(`autoi18n: locale "${i}" is not one of the configured locales (${t.locales.join(`, `)}).`)},resolveId(e){if(e===`virtual:autoi18n`)return`\0${G}`},load(e){if(e===`\0virtual:autoi18n`)return fe(v,n)},configureServer(e){T=e,e.watcher.on(`add`,e=>{F(e)&&P()}),e.watcher.on(`unlink`,e=>{x.has(e)&&P()})},closeBundle(){clearTimeout(D)},async buildStart(){await A({info:e=>this.info(e),warn:e=>this.warn(e),error:e=>this.error(e)})},async transform(e,t){let r=t.split(`?`)[0];if(!x.has(r))return null;s(e)!==S.get(r)&&await j(Q(m.logger),r);let i=x.get(r);if(!i||s(e)!==S.get(r))return w.has(r)||(w.add(r),this.warn(`autoi18n: ${r} changed while it was being indexed; its messages are left untranslated.`)),null;let o=m.command===`serve`?n:O(),c=ue(e,r,i,o,C.get(o)??{},a===`runtime`,n);return{code:c.code,map:c.map}},async handleHotUpdate({file:e}){if(e.startsWith(y.resolve(m.root,r,`locales`)+y.sep))return[];x.has(e)&&await N(e)}}}export{he as OpenAICompatibleProvider,k as PartialTranslationError,_e as default};
|
|
4
|
+
`}var Se=class{providers;onFallback;constructor(e,t=()=>{}){this.providers=e,this.onFallback=t}async translate(e){let t={},n=e.messages,r,i;for(let[a,{name:o,provider:s}]of this.providers.entries()){try{Object.assign(t,await s.translate({...e,messages:n}))}catch(e){r??=e,i=e,e instanceof L&&Object.assign(t,e.partial);let s=this.providers[a+1];s&&n.some(e=>t[e.id]===void 0)&&this.onFallback(`${o} failed (${q(e)}); sending the rest to ${s.name}.`)}if(n=n.filter(e=>t[e.id]===void 0),!n.length)return t}throw Object.keys(t).length===0?r:new L(`${n.length} messages failed to translate: ${q(i)}`,t,n.length)}};const q=e=>e instanceof Error?e.message:String(e),Ce=e=>typeof e==`object`&&!!e&&typeof e.translate==`function`;function J(e,t){if(e===void 0)return;if(Ce(e))return e;let{primary:n,...r}=e,i=[];for(let[e,t]of Object.entries(r))if(t!==void 0){if(!Ce(t))throw Error(`autoi18n: provider "${e}" must be an object with a translate(input) method.`);i.push({name:e,provider:t})}if(n!==void 0){let e=i.findIndex(e=>e.name===n);if(e<0)throw Error(`autoi18n: primary provider "${n}" is not configured.`);i.unshift(...i.splice(e,1))}if(i.length!==0)return i.length===1?i[0].provider:new Se(i,t)}var Y=class extends Error{retryable;retryAfterMs;constructor(e,t,n){super(e),this.retryable=t,this.retryAfterMs=n}};function we(e,t=Date.now()){if(!e)return;let n=Number(e);if(Number.isFinite(n))return Math.min(Math.max(n,0)*1e3,_);let r=Date.parse(e);if(!Number.isNaN(r))return Math.min(Math.max(r-t,0),_)}const Te=e=>new Promise(t=>setTimeout(t,e));function X(e){return`You are a professional software localization engine. Translate UI messages from ${e.sourceLocale} to ${e.targetLocale}. Preserve placeholders exactly. Respect context and UI conventions. Return ONLY a JSON object mapping message IDs to translated strings. Do not translate product names or code.`}function Z(e){return JSON.stringify(e.map(e=>({id:e.id,text:e.source,context:e.context,kind:e.kind})))}async function Q(e){let t=e.status===408||e.status===409||e.status===429||e.status>=500;return await e.body?.cancel().catch(()=>void 0),new Y(`Translation API failed (${e.status})`,t,we(e.headers.get(`retry-after`)))}var $=class{options;constructor(e){this.options=e}async translate(e){let t=Math.max(1,this.options.batchSize??40),n=[];for(let r=0;r<e.messages.length;r+=t)n.push(e.messages.slice(r,r+t));let r=await ie(n,this.options.concurrency??4,t=>this.translateBatch({...e,messages:t})),i={},a=[],o=0,s=0;if(r.forEach((e,t)=>{e.status===`fulfilled`?(s++,Object.assign(i,e.value)):(a.push(e.reason),o+=n[t].length)}),!a.length)return i;throw s?new L(`${o} messages failed to translate: ${a[0].message}`,i,o):a[0]}async translateBatch(e){let t=this.options.maxRetries??2,n=this.options.retryDelayMs??500;for(let r=0;;r++)try{return await this.request(e)}catch(e){if(e instanceof Y&&!e.retryable||r>=t)throw e;let i=e instanceof Y?e.retryAfterMs:void 0;await Te(i??n*2**r*(.5+Math.random()/2))}}toCatalog(e,t){if(!e)throw new Y(`Translation provider returned no content`,!0);let n;try{n=JSON.parse(e)}catch{throw new Y(`Translation provider returned invalid JSON`,!0)}let r=M.safeParse(a,n);if(!r.success)throw new Y(`Translation provider returned a non-object`,!0);let i=new Set(t.map(e=>e.id)),o={};for(let[e,t]of Object.entries(r.data))i.has(e)&&typeof t==`string`&&t.trim()&&(o[e]=t);return o}},Ee=class extends ${async request(e){let t=await fetch(`${this.options.baseURL??`https://api.openai.com/v1`}/chat/completions`,{method:`POST`,signal:AbortSignal.timeout(this.options.timeoutMs??6e4),headers:{Authorization:`Bearer ${this.options.apiKey}`,"Content-Type":`application/json`},body:JSON.stringify({model:this.options.model,temperature:0,response_format:{type:`json_object`},messages:[{role:`system`,content:X(e)},{role:`user`,content:Z(e.messages)}]})});if(!t.ok)throw await Q(t);let n=M.safeParse(c,await t.json());return this.toCatalog(n.success?n.data.choices[0].message.content??void 0:void 0,e.messages)}},De=class extends ${async request(e){let t=await fetch(`${this.options.baseURL??`https://api.anthropic.com/v1`}/messages`,{method:`POST`,signal:AbortSignal.timeout(this.options.timeoutMs??6e4),headers:{"x-api-key":this.options.apiKey,"anthropic-version":w,"content-type":`application/json`},body:JSON.stringify({model:this.options.model,max_tokens:this.options.maxTokens??8192,system:X(e),messages:[{role:`user`,content:Z(e.messages)}]})});if(!t.ok)throw await Q(t);let n=M.safeParse(s,await t.json()),r=n.success?n.data.content.filter(e=>e.type===`text`).map(e=>e.text??``).join(``):``;return this.toCatalog(r.trim().replace(/^```(?:json)?\s*|\s*```$/g,``)||void 0,e.messages)}},Oe=class extends ${async request(e){let t=this.options.baseURL??`https://generativelanguage.googleapis.com/v1beta`,n=await fetch(`${t}/models/${encodeURIComponent(this.options.model)}:generateContent`,{method:`POST`,signal:AbortSignal.timeout(this.options.timeoutMs??6e4),headers:{"x-goog-api-key":this.options.apiKey,"content-type":`application/json`},body:JSON.stringify({systemInstruction:{parts:[{text:X(e)}]},contents:[{role:`user`,parts:[{text:Z(e.messages)}]}],generationConfig:{responseMimeType:`application/json`,maxOutputTokens:this.options.maxOutputTokens??8192}})});if(!n.ok)throw await Q(n);let r=M.safeParse(u,await n.json()),i=r.success?(r.data.candidates[0]?.content?.parts??[]).map(e=>e.text??``).join(``):``;return this.toCatalog(i.trim().replace(/^```(?:json)?\s*|\s*```$/g,``)||void 0,e.messages)}};function ke(e){let t=M.safeParse(i,e);if(t.success)return;let n=t.error.issues.map(e=>`${e.path.join(`.`)||`options`}: ${e.message}`).join(`; `);throw Error(`autoi18n: invalid options (${n}).`)}const Ae=e=>({info:t=>e.info(t),warn:t=>e.warn(t),error:t=>e.error(t)});async function je(e,t){await Promise.all(t.map(t=>N(P.join(e,`${t}.sources.json`),{force:!0})))}function Me(t){ke(t),J(t.provider);let n=t.sourceLocale??`en`,i=t.directory??`.autoi18n`,a=t.delivery??`build`,s=t.debug??!1,c={componentProps:t.componentProps,stringMarker:t.stringMarker},u=t.orphanRetentionMinutes??43200,f,p=``,m={},h=/* @__PURE__ */ new Map,g=/* @__PURE__ */ new Map,_=/* @__PURE__ */ new Map,b=/* @__PURE__ */ new Map,S=/* @__PURE__ */ new Set,C,w=Promise.resolve(),T,O=()=>t.locale??process.env.AUTOI18N_LOCALE??n;function k(){let e=t.locales.filter(e=>e!==n),r=t.locale??process.env.AUTOI18N_LOCALE;return a===`build`&&r&&f.command!==`serve`?e.filter(e=>e===r):e}async function A(r,a){let S=J(t.provider,r.warn),C=a!==void 0&&h.has(a),w=C?[...h.keys()]:await te(t.include??E,{cwd:f.root,ignore:t.exclude??x,absolute:!0}),T=/* @__PURE__ */ new Map,D=[];for(let e of w){let t=h.get(e),n;if(t&&C&&e!==a)n=t;else{let r=await ne(e,`utf8`),i=o(r);n=t?.hash===i?t:{hash:i,messages:ve(r,e,c)}}T.set(e,n),D.push({file:e,messages:n.messages})}let O=P.resolve(f.root,i,`locales`),A=k(),j=/* @__PURE__ */ new Map;for(let t of A)j.set(t,await e(l(O,t),r.warn));let M=await le({files:D,registry:await ee(p,r.warn),orphanRetentionMinutes:u,sourceLocale:n,locales:A,localeConcurrency:Math.max(1,Math.floor(t.localeConcurrency??2)),previous:j,provider:S,debug:s});if(s)for(let e of M.pruned)d(`removed ${e.id} ${JSON.stringify(e.source)} (last seen ${e.lastSeen})`);if(M.pruned.length&&r.info(`autoi18n: removed ${M.pruned.length} registry entries no longer in the source.`),await v(p,M.registry),M.ambiguous.length){let e=`autoi18n: ${M.ambiguous.length} duplicated texts changed occurrence count, so their IDs may have moved (e.g. "${M.ambiguous[0]}").`;t.onAmbiguous===`error`?r.error(e):r.warn(e)}await y(l(O,n),M.sourceFile),await je(O,[n,...t.locales]);let N=/* @__PURE__ */ new Map([[n,M.sourceFile.messages]]);for(let[e,t]of M.locales)await y(l(O,e),t.file),N.set(e,t.file.messages);for(let e of M.failures.keys())N.set(e,j.get(e)?.messages??{});let F=0;g=/* @__PURE__ */ new Map;for(let{file:e,messages:t}of D)g.set(e,M.messages.slice(F,F+t.length)),F+=t.length;_=new Map([...T].map(([e,t])=>[e,t.hash])),h=T,b=N,m=Object.fromEntries(t.locales.map(e=>[e,l(O,e)]));for(let[e,n]of M.locales)n.requested&&!S&&t.failOnMissingProvider&&r.error(`Missing ${n.requested} ${e} translations and no provider is configured.`),n.rejected&&r.info(`autoi18n: rejected ${n.rejected} ${e} translations with changed placeholders.`),n.untranslated&&(S?r.warn:r.info)(`autoi18n: ${n.untranslated} ${e} messages have no translation and fall back to the source text.`),n.failed&&r.warn(`autoi18n: ${n.failed} ${e} translation batches failed (${n.failure}); they will be requested again on the next build.`);M.messages.length&&r.info(`autoi18n: indexed ${M.messages.length} messages across ${w.length} files.`);let I=[...M.failures];for(let[e,t]of I.slice(1))r.warn(`autoi18n: ${e} translation failed: ${t.message}`);if(I.length)throw I[0][1]}function j(e,t){let n=w.then(()=>A(e,t));return w=n.catch(()=>void 0),n}function M(){C?.ws.send({type:`custom`,event:r,data:{catalogs:Object.fromEntries(b)}})}async function N(e){try{await j(Ae(f.logger),e),M()}catch(e){f.logger.error(`autoi18n: re-translation failed: ${e.message}`)}}function F(){clearTimeout(T),T=setTimeout(()=>void N(),150)}function I(e){if(!/\.[jt]sx?$/.test(e))return!1;let n=P.relative(f.root,e).split(P.sep).join(`/`);return(t.include??E).some(e=>P.matchesGlob(n,e))&&!(t.exclude??x).some(e=>P.matchesGlob(n,e))}return{name:`vite-plugin-autoi18n`,enforce:`pre`,configResolved(e){f=e,p=P.resolve(f.root,i,`registry.json`);let r=O();if(r!==n&&!t.locales.includes(r))throw Error(`autoi18n: locale "${r}" is not one of the configured locales (${t.locales.join(`, `)}).`)},resolveId(e){if(e===`virtual:autoi18n`)return`\0${D}`},load(e){if(e===`\0virtual:autoi18n`)return xe(m,n)},configureServer(e){C=e,e.watcher.on(`add`,e=>{I(e)&&F()}),e.watcher.on(`unlink`,e=>{g.has(e)&&F()})},closeBundle(){clearTimeout(T)},async buildStart(){await A({info:e=>this.info(e),warn:e=>this.warn(e),error:e=>this.error(e)})},async transform(e,t){let r=t.split(`?`)[0];if(!g.has(r))return null;o(e)!==_.get(r)&&await j(Ae(f.logger),r);let i=g.get(r);if(!i||o(e)!==_.get(r))return S.has(r)||(S.add(r),this.warn(`autoi18n: ${r} changed while it was being indexed; its messages are left untranslated.`)),null;let s=f.command===`serve`?n:O(),c=ye(e,r,i,s,b.get(s)??{},a===`runtime`,n);return{code:c.code,map:c.map}},async handleHotUpdate({file:e}){if(e.startsWith(P.resolve(f.root,i,`locales`)+P.sep))return[];g.has(e)&&await N(e)}}}export{De as AnthropicProvider,Se as FallbackProvider,Oe as GeminiProvider,Ee as OpenAICompatibleProvider,L as PartialTranslationError,Me as default,J as resolveProvider};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import*as e from"zod/mini";import{mkdir as t,readFile as n,rename as r,writeFile as i}from"node:fs/promises";import a from"node:path";import{createHash as o,randomUUID as s}from"node:crypto";const c=e=>console.log(`[autoi18n debug] ${(/* @__PURE__ */ new Date()).toISOString()} ${e}`),l=e=>JSON.stringify(e,null,2)+`
|
|
2
|
+
`,u=e=>o(`sha256`).update(e).digest(`hex`);async function d(e,t,i){let a;try{a=await n(e,`utf8`)}catch(n){return n.code===`ENOENT`||i?.(`autoi18n: could not read ${e}: ${n.message}`),t}try{return JSON.parse(a)}catch(n){let a=`${e}.corrupt-${Date.now()}`;return await r(e,a).catch(()=>void 0),i?.(`autoi18n: ${e} is not valid JSON (${n.message}); moved to ${a} and starting fresh.`),t}}async function f(e,n){await t(a.dirname(e),{recursive:!0});let o=`${e}.${process.pid}.${s()}.tmp`;await i(o,n,`utf8`),await r(o,e)}async function p(e,t){await n(e,`utf8`).catch(()=>void 0)!==t&&await f(e,t)}const m=e.object({choices:e.array(e.object({message:e.object({content:e.nullish(e.string())})})).check(e.minLength(1))}),h=e.object({content:e.array(e.object({type:e.string(),text:e.optional(e.string())}))}),g=e.object({candidates:e.array(e.object({content:e.optional(e.object({parts:e.optional(e.array(e.object({text:e.optional(e.string())})))}))}))}),_=e.record(e.string(),e.unknown()),v=e.object({messages:e.record(e.string(),e.unknown()),sources:e.record(e.string(),e.unknown())}),y=e.object({id:e.string(),source:e.string(),kind:e.enum([`text`,`attribute`,`mixed`,`string`]),tag:e.string(),component:e.string(),attribute:e.optional(e.string()),context:e.string(),fingerprint:e.string(),location:e.string(),status:e.enum([`active`,`orphaned`]),firstSeen:e.string(),lastSeen:e.string()}),b=e.object({version:e.literal(1),entries:e.record(e.string(),e.unknown())}),x=e.string().check(e.minLength(1)),S=e.object({locales:e.array(x),sourceLocale:e.optional(x),delivery:e.optional(e.enum([`build`,`runtime`])),locale:e.optional(x),include:e.optional(e.array(e.string())),exclude:e.optional(e.array(e.string())),directory:e.optional(x),provider:e.optional(e.custom(e=>e===void 0||typeof e==`object`&&!!e)),failOnMissingProvider:e.optional(e.boolean()),onAmbiguous:e.optional(e.enum([`warn`,`error`])),orphanRetentionMinutes:e.optional(e.union([e.number().check(e.minimum(0)),e.literal(1/0)])),localeConcurrency:e.optional(e.number().check(e.minimum(1))),componentProps:e.optional(e.record(e.string(),e.array(e.string()))),stringMarker:e.optional(x),debug:e.optional(e.boolean())}),C=()=>({messages:{},sources:{}});function w(e,t){return a.join(e,`${t}.json`)}const T=(e,t,n)=>{let r={};for(let[i,a]of Object.entries(e))typeof a==`string`?r[i]=a:t?.(`autoi18n: dropped non-text value for "${i}" in ${n}.`);return r};async function E(t,n){let r=await d(t,null,n),i=e.safeParse(v,r);return i.success?{messages:T(i.data.messages,n,t),sources:T(i.data.sources,n,t)}:C()}const D=(e,t)=>p(e,l(t));function O(e,t){let n={},r=[];for(let[i,a]of Object.entries(e))t.has(i)?n[i]=a:r.push(i);return{catalog:n,removed:r}}const k=`autoi18n:update`,A=[`src/**/*.{tsx,jsx,ts,js}`],j=[`**/*.test.*`,`**/node_modules/**`],M=`https://api.anthropic.com/v1`,N=`2023-06-01`,P=`https://api.openai.com/v1`,F=`https://generativelanguage.googleapis.com/v1beta`,I=6e4,L=43200,ee=`virtual:autoi18n`,R=`__autoI18nT`,z=`__autoI18nFormat`,B=/^[A-Z]/,V=/* @__PURE__ */ new Set([`script`,`style`,`code`,`pre`]),H=`children`,U=/* @__PURE__ */ new Set([`alt`,`title`,`placeholder`,`label`,`summary`,`aria-label`,`aria-description`,`aria-placeholder`,`aria-roledescription`,`aria-valuetext`]),W=`i18n`,G=`translate`,K=new Map(`quot:0022 amp:0026 apos:0027 lt:003C gt:003E nbsp:00A0 iexcl:00A1 cent:00A2 pound:00A3 curren:00A4 yen:00A5 brvbar:00A6 sect:00A7 uml:00A8 copy:00A9 ordf:00AA laquo:00AB not:00AC shy:00AD reg:00AE macr:00AF deg:00B0 plusmn:00B1 sup2:00B2 sup3:00B3 acute:00B4 micro:00B5 para:00B6 middot:00B7 cedil:00B8 sup1:00B9 ordm:00BA raquo:00BB frac14:00BC frac12:00BD frac34:00BE iquest:00BF Agrave:00C0 Aacute:00C1 Acirc:00C2 Atilde:00C3 Auml:00C4 Aring:00C5 AElig:00C6 Ccedil:00C7 Egrave:00C8 Eacute:00C9 Ecirc:00CA Euml:00CB Igrave:00CC Iacute:00CD Icirc:00CE Iuml:00CF ETH:00D0 Ntilde:00D1 Ograve:00D2 Oacute:00D3 Ocirc:00D4 Otilde:00D5 Ouml:00D6 times:00D7 Oslash:00D8 Ugrave:00D9 Uacute:00DA Ucirc:00DB Uuml:00DC Yacute:00DD THORN:00DE szlig:00DF agrave:00E0 aacute:00E1 acirc:00E2 atilde:00E3 auml:00E4 aring:00E5 aelig:00E6 ccedil:00E7 egrave:00E8 eacute:00E9 ecirc:00EA euml:00EB igrave:00EC iacute:00ED icirc:00EE iuml:00EF eth:00F0 ntilde:00F1 ograve:00F2 oacute:00F3 ocirc:00F4 otilde:00F5 ouml:00F6 divide:00F7 oslash:00F8 ugrave:00F9 uacute:00FA ucirc:00FB uuml:00FC yacute:00FD thorn:00FE yuml:00FF OElig:0152 oelig:0153 Scaron:0160 scaron:0161 Yuml:0178 fnof:0192 circ:02C6 tilde:02DC Alpha:0391 Beta:0392 Gamma:0393 Delta:0394 Epsilon:0395 Zeta:0396 Eta:0397 Theta:0398 Iota:0399 Kappa:039A Lambda:039B Mu:039C Nu:039D Xi:039E Omicron:039F Pi:03A0 Rho:03A1 Sigma:03A3 Tau:03A4 Upsilon:03A5 Phi:03A6 Chi:03A7 Psi:03A8 Omega:03A9 alpha:03B1 beta:03B2 gamma:03B3 delta:03B4 epsilon:03B5 zeta:03B6 eta:03B7 theta:03B8 iota:03B9 kappa:03BA lambda:03BB mu:03BC nu:03BD xi:03BE omicron:03BF pi:03C0 rho:03C1 sigmaf:03C2 sigma:03C3 tau:03C4 upsilon:03C5 phi:03C6 chi:03C7 psi:03C8 omega:03C9 thetasym:03D1 upsih:03D2 piv:03D6 ensp:2002 emsp:2003 thinsp:2009 zwnj:200C zwj:200D lrm:200E rlm:200F ndash:2013 mdash:2014 lsquo:2018 rsquo:2019 sbquo:201A ldquo:201C rdquo:201D bdquo:201E dagger:2020 Dagger:2021 bull:2022 hellip:2026 permil:2030 prime:2032 Prime:2033 lsaquo:2039 rsaquo:203A oline:203E frasl:2044 euro:20AC image:2111 weierp:2118 real:211C trade:2122 alefsym:2135 larr:2190 uarr:2191 rarr:2192 darr:2193 harr:2194 crarr:21B5 lArr:21D0 uArr:21D1 rArr:21D2 dArr:21D3 hArr:21D4 forall:2200 part:2202 exist:2203 empty:2205 nabla:2207 isin:2208 notin:2209 ni:220B prod:220F sum:2211 minus:2212 lowast:2217 radic:221A prop:221D infin:221E ang:2220 and:2227 or:2228 cap:2229 cup:222A int:222B there4:2234 sim:223C cong:2245 asymp:2248 ne:2260 equiv:2261 le:2264 ge:2265 sub:2282 sup:2283 nsub:2284 sube:2286 supe:2287 oplus:2295 otimes:2297 perp:22A5 sdot:22C5 lceil:2308 rceil:2309 lfloor:230A rfloor:230B lang:2329 rang:232A loz:25CA spades:2660 clubs:2663 hearts:2665 diams:2666`.split(` `).map(e=>{let[t,n]=e.split(`:`);return[t,String.fromCharCode(parseInt(n,16))]})),q=/&(?:#(x[0-9a-fA-F]+|[0-9]+)|([A-Za-z0-9]+));/g,J=`\\{([^{}]+)\\}`,Y=RegExp(J,`g`),X=()=>({version:1,entries:{}});async function Z(t,n){let r=await d(t,null,n);if(r===null)return X();let i=e.safeParse(b,r);if(!i.success)return n?.(`autoi18n: ${t} has an unsupported format; message IDs will be regenerated.`),X();let a={};for(let[r,o]of Object.entries(i.data.entries)){let i=e.safeParse(y,o);i.success?a[r]=i.data:n?.(`autoi18n: dropped invalid registry entry "${r}" in ${t}.`)}return{version:1,entries:a}}function Q(e,t){return`${e}:${String(t).padStart(12,`0`)}`}function $(e){let t=e.lastIndexOf(`:`);return Q(e.slice(0,t),Number(e.slice(t+1)))}function te(e,t){let n=(/* @__PURE__ */ new Date()).toISOString(),r=Object.values(e.entries),i=/* @__PURE__ */ new Map;for(let e of r){let t=i.get(e.fingerprint)??[];t.push(e),i.set(e.fingerprint,t)}let a=/* @__PURE__ */ new Map;for(let e of t){let t=a.get(e.fingerprint)??[];t.push(e),a.set(e.fingerprint,t)}let o=/* @__PURE__ */ new Map,c=/* @__PURE__ */ new Set,l=[];for(let[t,r]of a){let a=(i.get(t)??[]).filter(e=>e.status===`active`);r.sort((e,t)=>Q(e.file,e.start).localeCompare(Q(t.file,t.start))),a.sort((e,t)=>$(e.location).localeCompare($(t.location))),r.length!==a.length&&(r.length>1||a.length>1)&&l.push(r[0].source);let u=Array(r.length),d=/* @__PURE__ */ new Set;r.forEach((e,t)=>{let n=a.find(t=>!d.has(t)&&t.component===e.component);n&&(u[t]=n,d.add(n))});let f=a.filter(e=>!d.has(e)),p=0;r.forEach((e,t)=>{u[t]===void 0&&p<f.length&&(u[t]=f[p++])});for(let[t,i]of r.entries()){let r=u[t];if(r)o.set(i,r.id),r.status=`active`,r.lastSeen=n,r.source=i.source,r.component=i.component,r.location=`${i.file}:${i.start}`,c.add(r.id);else{let t=`msg_${s().replaceAll(`-`,``).slice(0,16)}`;o.set(i,t),e.entries[t]={id:t,source:i.source,kind:i.kind,tag:i.tag,component:i.component,attribute:i.attribute,context:i.context,fingerprint:i.fingerprint,location:`${i.file}:${i.start}`,status:`active`,firstSeen:n,lastSeen:n},c.add(t)}}}for(let t of Object.values(e.entries))c.has(t.id)||(t.status=`orphaned`);return{registry:e,ambiguous:l,messages:t.map(e=>{let t=o.get(e);if(t===void 0)throw Error(`autoi18n: no message ID was assigned to ${e.file}:${e.start}`);return{...e,id:t}})}}function ne(e,t,n=Date.now()){let r=n-t*6e4,i=[];for(let[t,n]of Object.entries(e.entries))n.status===`orphaned`&&Date.parse(n.lastSeen)<=r&&(delete e.entries[t],i.push(n));return i}async function re(e,t){await p(e,l(t))}export{E as A,R as C,C as D,k as E,S as F,_ as I,u as L,h as M,m as N,w as O,g as P,c as R,z as S,U as T,K as _,M as a,J as b,B as c,L as d,W as f,I as g,G as h,re as i,D as j,O as k,j as l,V as m,ne as n,N as o,F as p,te as r,H as s,Z as t,A as u,P as v,ee as w,q as x,Y as y};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-ai-auto-i18n",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Implicit, build-time AI localization for JSX applications",
|
|
6
6
|
"license": "ISC",
|
|
@@ -8,8 +8,23 @@
|
|
|
8
8
|
"node": ">=26.0.0"
|
|
9
9
|
},
|
|
10
10
|
"keywords": [
|
|
11
|
-
"vite
|
|
11
|
+
"vite",
|
|
12
|
+
"vite-plugin",
|
|
13
|
+
"i18n",
|
|
14
|
+
"localization",
|
|
15
|
+
"translation",
|
|
16
|
+
"react",
|
|
17
|
+
"jsx",
|
|
18
|
+
"ai"
|
|
12
19
|
],
|
|
20
|
+
"homepage": "https://github.com/rand0mC0d3r/vite-plugin-ai-i18n#readme",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/rand0mC0d3r/vite-plugin-ai-i18n.git"
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/rand0mC0d3r/vite-plugin-ai-i18n/issues"
|
|
27
|
+
},
|
|
13
28
|
"main": "./dist/index.js",
|
|
14
29
|
"types": "./dist/index.d.ts",
|
|
15
30
|
"exports": {
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{mkdir as e,readFile as t,rename as n,writeFile as r}from"node:fs/promises";import i from"node:path";import{createHash as a,randomUUID as o}from"node:crypto";import{z as s}from"zod";const c=e=>console.log(`[autoi18n debug] ${(/* @__PURE__ */ new Date()).toISOString()} ${e}`),l=e=>JSON.stringify(e,null,2)+`
|
|
2
|
-
`,u=e=>a(`sha256`).update(e).digest(`hex`);async function d(e,r,i){let a;try{a=await t(e,`utf8`)}catch(t){return t.code===`ENOENT`||i?.(`autoi18n: could not read ${e}: ${t.message}`),r}try{return JSON.parse(a)}catch(t){let a=`${e}.corrupt-${Date.now()}`;return await n(e,a).catch(()=>void 0),i?.(`autoi18n: ${e} is not valid JSON (${t.message}); moved to ${a} and starting fresh.`),r}}async function f(t,a){await e(i.dirname(t),{recursive:!0});let s=`${t}.${process.pid}.${o()}.tmp`;await r(s,a,`utf8`),await n(s,t)}async function p(e,n){await t(e,`utf8`).catch(()=>void 0)!==n&&await f(e,n)}const m=s.object({choices:s.array(s.object({message:s.object({content:s.string().nullish()})})).min(1)}),h=s.record(s.string(),s.unknown()),g=s.object({messages:s.record(s.string(),s.unknown()),sources:s.record(s.string(),s.unknown())}),_=s.object({id:s.string(),source:s.string(),kind:s.enum([`text`,`attribute`,`mixed`,`string`]),tag:s.string(),component:s.string(),attribute:s.string().optional(),context:s.string(),fingerprint:s.string(),location:s.string(),status:s.enum([`active`,`orphaned`]),firstSeen:s.string(),lastSeen:s.string()}),v=s.object({version:s.literal(1),entries:s.record(s.string(),s.unknown())}),y=s.object({locales:s.array(s.string().min(1)),sourceLocale:s.string().min(1).optional(),delivery:s.enum([`build`,`runtime`]).optional(),locale:s.string().min(1).optional(),include:s.array(s.string()).optional(),exclude:s.array(s.string()).optional(),directory:s.string().min(1).optional(),provider:s.custom(e=>e===void 0||typeof e==`object`&&!!e&&typeof e.translate==`function`).optional(),failOnMissingProvider:s.boolean().optional(),onAmbiguous:s.enum([`warn`,`error`]).optional(),orphanRetentionMinutes:s.union([s.number().min(0),s.literal(1/0)]).optional(),localeConcurrency:s.number().min(1).optional(),componentProps:s.record(s.string(),s.array(s.string())).optional(),debug:s.boolean().optional()}),b=()=>({messages:{},sources:{}});function x(e,t){return i.join(e,`${t}.json`)}const S=(e,t,n)=>{let r={};for(let[i,a]of Object.entries(e))typeof a==`string`?r[i]=a:t?.(`autoi18n: dropped non-text value for "${i}" in ${n}.`);return r};async function C(e,t){let n=await d(e,null,t),r=g.safeParse(n);return r.success?{messages:S(r.data.messages,t,e),sources:S(r.data.sources,t,e)}:b()}const w=(e,t)=>p(e,l(t));function T(e,t){let n={},r=[];for(let[i,a]of Object.entries(e))t.has(i)?n[i]=a:r.push(i);return{catalog:n,removed:r}}const E=()=>({version:1,entries:{}});async function D(e,t){let n=await d(e,null,t);if(n===null)return E();let r=v.safeParse(n);if(!r.success)return t?.(`autoi18n: ${e} has an unsupported format; message IDs will be regenerated.`),E();let i={};for(let[n,a]of Object.entries(r.data.entries)){let r=_.safeParse(a);r.success?i[n]=r.data:t?.(`autoi18n: dropped invalid registry entry "${n}" in ${e}.`)}return{version:1,entries:i}}function O(e,t){return`${e}:${String(t).padStart(12,`0`)}`}function k(e){let t=e.lastIndexOf(`:`);return O(e.slice(0,t),Number(e.slice(t+1)))}function A(e,t){let n=(/* @__PURE__ */ new Date()).toISOString(),r=Object.values(e.entries),i=/* @__PURE__ */ new Map;for(let e of r){let t=i.get(e.fingerprint)??[];t.push(e),i.set(e.fingerprint,t)}let a=/* @__PURE__ */ new Map;for(let e of t){let t=a.get(e.fingerprint)??[];t.push(e),a.set(e.fingerprint,t)}let s=/* @__PURE__ */ new Map,c=/* @__PURE__ */ new Set,l=[];for(let[t,r]of a){let a=(i.get(t)??[]).filter(e=>e.status===`active`);r.sort((e,t)=>O(e.file,e.start).localeCompare(O(t.file,t.start))),a.sort((e,t)=>k(e.location).localeCompare(k(t.location))),r.length!==a.length&&(r.length>1||a.length>1)&&l.push(r[0].source);let u=Array(r.length),d=/* @__PURE__ */ new Set;r.forEach((e,t)=>{let n=a.find(t=>!d.has(t)&&t.component===e.component);n&&(u[t]=n,d.add(n))});let f=a.filter(e=>!d.has(e)),p=0;r.forEach((e,t)=>{u[t]===void 0&&p<f.length&&(u[t]=f[p++])});for(let[t,i]of r.entries()){let r=u[t];if(r)s.set(i,r.id),r.status=`active`,r.lastSeen=n,r.source=i.source,r.component=i.component,r.location=`${i.file}:${i.start}`,c.add(r.id);else{let t=`msg_${o().replaceAll(`-`,``).slice(0,16)}`;s.set(i,t),e.entries[t]={id:t,source:i.source,kind:i.kind,tag:i.tag,component:i.component,attribute:i.attribute,context:i.context,fingerprint:i.fingerprint,location:`${i.file}:${i.start}`,status:`active`,firstSeen:n,lastSeen:n},c.add(t)}}}for(let t of Object.values(e.entries))c.has(t.id)||(t.status=`orphaned`);return{registry:e,ambiguous:l,messages:t.map(e=>{let t=s.get(e);if(t===void 0)throw Error(`autoi18n: no message ID was assigned to ${e.file}:${e.start}`);return{...e,id:t}})}}const j=43200;function M(e,t,n=Date.now()){let r=n-t*6e4,i=[];for(let[t,n]of Object.entries(e.entries))n.status===`orphaned`&&Date.parse(n.lastSeen)<=r&&(delete e.entries[t],i.push(n));return i}async function N(e,t){await p(e,l(t))}export{N as a,T as c,m as d,y as f,c as h,A as i,C as l,u as m,D as n,b as o,h as p,M as r,x as s,j as t,w as u};
|