vite-plugin-ai-auto-i18n 0.5.5 → 0.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/dist/cli/prune.js +1 -1
- package/dist/index.d.ts +69 -3
- package/dist/index.js +3 -3
- package/dist/{registry-Dy0ag6Nv.js → registry-4CzY0FGn.js} +1 -1
- package/dist/runtime/index.d.ts +10 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -138,7 +138,7 @@ If a locale has no translation for an ID, the source text is shown. Nothing brea
|
|
|
138
138
|
|
|
139
139
|
### Dev server
|
|
140
140
|
|
|
141
|
-
Saving a file re-runs phases 1 to 3 for that project and pushes the
|
|
141
|
+
Saving a file re-runs phases 1 to 3 for that project and pushes the catalogues that changed to the browser over `autoi18n:update`, so the page updates without a reload. Locales that did not change are not sent.
|
|
142
142
|
|
|
143
143
|
## 📚 Examples
|
|
144
144
|
|
|
@@ -334,7 +334,7 @@ If a translation batch fails, the other batches are still stored. The failed mes
|
|
|
334
334
|
|
|
335
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.
|
|
336
336
|
|
|
337
|
-
The plugin translates up to `localeConcurrency` locales at once (default 2).
|
|
337
|
+
The plugin translates up to `localeConcurrency` locales at once (default 2). The provider's `concurrency` limit is shared by all locales, so at most `concurrency` batches are in flight per provider instance (not `localeConcurrency × concurrency`). Lower `concurrency` if your provider rate-limits your account.
|
|
338
338
|
|
|
339
339
|
Ambiguous identity matches (duplicate identical strings whose count changed) are logged as warnings. Set `onAmbiguous: "error"` to fail the build instead. When a message is paired by fingerprint, the component name is used to break ties between identical strings, so reordering or renaming components keeps existing IDs.
|
|
340
340
|
|
|
@@ -369,7 +369,7 @@ setLocale(resolveSupported(navigator.languages)); // call before first render
|
|
|
369
369
|
|
|
370
370
|
`subscribe(listener)` returns an unsubscribe function. `getVersion()` changes whenever the locale or the catalogues change, so `useSyncExternalStore(subscribe, getVersion, getVersion)` re-renders components that read `t()` during render. Components wrapped in `memo` must subscribe themselves, or they keep the old text until their props change. List `autoI18n()` before `@vitejs/plugin-react` in `plugins`. The React plugin rewrites JSX in dev before other pre-transforms run, so the plugin must see the original JSX. Add a type declaration for `virtual:autoi18n` (see [example/src/autoi18n-virtual.d.ts](/home/bogdan/git/vite-plugin-ai-i18n/example/src/autoi18n-virtual.d.ts)). The example app uses this mode with a locale switcher.
|
|
371
371
|
|
|
372
|
-
In dev, saving a source file re-indexes the project, translates only new or changed messages and pushes the
|
|
372
|
+
In dev, saving a source file re-indexes the project, translates only new or changed messages and pushes only the catalogues that changed to the browser over an `autoi18n:update` event. Files added or removed are indexed as well. The page is not reloaded, so new JSX text appears once its translation is ready.
|
|
373
373
|
|
|
374
374
|
## ⚙️ Configuration reference
|
|
375
375
|
|
|
@@ -388,7 +388,7 @@ In dev, saving a source file re-indexes the project, translates only new or chan
|
|
|
388
388
|
| `failOnMissingProvider` | `boolean` | `false` | When `true`, the build fails if a locale has untranslated messages and no `provider` is set. |
|
|
389
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. |
|
|
390
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. |
|
|
391
|
-
| `localeConcurrency` | `number` | `2` | Locales translated at the same time. Values below 1 are raised to 1, and fractions are rounded down. |
|
|
391
|
+
| `localeConcurrency` | `number` | `2` | Locales translated at the same time. Values below 1 are raised to 1, and fractions are rounded down. Batches are still capped by the provider's `concurrency`. |
|
|
392
392
|
| `debug` | `boolean` | `false` | Logs each message sent to the provider, each translation received, and each removed orphan, with timestamps. |
|
|
393
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
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. |
|
|
@@ -403,7 +403,7 @@ Create the provider with `new OpenAICompatibleProvider(options)`.
|
|
|
403
403
|
| `model` | `string` | _required_ | Model name sent in each request, for example `"gpt-4.1-mini"`. |
|
|
404
404
|
| `baseURL` | `string` | `"https://api.openai.com/v1"` | Base URL of any OpenAI-compatible API. Requests go to `${baseURL}/chat/completions`. |
|
|
405
405
|
| `batchSize` | `number` | `40` | Messages per request. Smaller batches keep responses within output limits. |
|
|
406
|
-
| `concurrency` | `number` | `4` | Batches sent in parallel
|
|
406
|
+
| `concurrency` | `number` | `4` | Batches sent in parallel across all locales of this provider instance. |
|
|
407
407
|
| `timeoutMs` | `number` | `60000` | Milliseconds before a single request is aborted. |
|
|
408
408
|
| `maxRetries` | `number` | `2` | Extra attempts for network errors, timeouts, 408/409/429/5xx responses and malformed output. |
|
|
409
409
|
| `retryDelayMs` | `number` | `500` | Delay before the first retry. It doubles on each attempt. |
|
|
@@ -419,7 +419,7 @@ Create the provider with `new AnthropicProvider(options)`. It calls the [Anthrop
|
|
|
419
419
|
| `baseURL` | `string` | `"https://api.anthropic.com/v1"` | Base URL of the Messages API. Requests go to `${baseURL}/messages`. |
|
|
420
420
|
| `maxTokens` | `number` | `8192` | Maximum tokens the model may generate per batch. Lower it only if your model limits output. |
|
|
421
421
|
| `batchSize` | `number` | `40` | Messages per request. Smaller batches keep responses within output limits. |
|
|
422
|
-
| `concurrency` | `number` | `4` | Batches sent in parallel
|
|
422
|
+
| `concurrency` | `number` | `4` | Batches sent in parallel across all locales of this provider instance. |
|
|
423
423
|
| `timeoutMs` | `number` | `60000` | Milliseconds before a single request is aborted. |
|
|
424
424
|
| `maxRetries` | `number` | `2` | Extra attempts for network errors, timeouts, 408/409/429/5xx responses and malformed output. |
|
|
425
425
|
| `retryDelayMs` | `number` | `500` | Delay before the first retry. It doubles on each attempt. |
|
|
@@ -435,7 +435,7 @@ Create the provider with `new GeminiProvider(options)`. It calls the [Gemini API
|
|
|
435
435
|
| `baseURL` | `string` | `"https://generativelanguage.googleapis.com/v1beta"` | Base URL of the Gemini API. Requests go to `${baseURL}/models/${model}:generateContent`. |
|
|
436
436
|
| `maxOutputTokens` | `number` | `8192` | Maximum tokens the model may generate per batch. Lower it only if your model limits output. |
|
|
437
437
|
| `batchSize` | `number` | `40` | Messages per request. Smaller batches keep responses within output limits. |
|
|
438
|
-
| `concurrency` | `number` | `4` | Batches sent in parallel
|
|
438
|
+
| `concurrency` | `number` | `4` | Batches sent in parallel across all locales of this provider instance. |
|
|
439
439
|
| `timeoutMs` | `number` | `60000` | Milliseconds before a single request is aborted. |
|
|
440
440
|
| `maxRetries` | `number` | `2` | Extra attempts for network errors, timeouts, 408/409/429/5xx responses and malformed output. |
|
|
441
441
|
| `retryDelayMs` | `number` | `500` | Delay before the first retry. It doubles on each attempt. |
|
package/dist/cli/prune.js
CHANGED
|
@@ -1 +1 @@
|
|
|
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-
|
|
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-4CzY0FGn.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
|
@@ -1,25 +1,39 @@
|
|
|
1
1
|
import { Plugin } from "vite";
|
|
2
|
+
/**
|
|
3
|
+
* What a message was extracted from: `text` for JSX text, `attribute` for a translatable attribute, `mixed` for JSX text
|
|
4
|
+
* containing expressions (which become `{name}` placeholders), and `string` for a string literal marked for translation.
|
|
5
|
+
*/
|
|
2
6
|
type MessageKind = "text" | "attribute" | "mixed" | "string";
|
|
7
|
+
/** A translatable string found in a source file, before it has an ID. */
|
|
3
8
|
interface ExtractedMessage {
|
|
9
|
+
/** The source text, with any HTML entities already decoded. */
|
|
4
10
|
source: string;
|
|
11
|
+
/** Whitespace that surrounded the raw text. It is re-emitted around the translation so layout is unchanged. */
|
|
5
12
|
prefix?: string;
|
|
6
13
|
suffix?: string;
|
|
7
14
|
kind: MessageKind;
|
|
8
15
|
/** For "mixed" messages: placeholder names in the order of the element's expression children. */
|
|
9
16
|
placeholders?: string[];
|
|
17
|
+
/** Source file the message was found in. */
|
|
10
18
|
file: string;
|
|
19
|
+
/** Character offsets of the message in the file. */
|
|
11
20
|
start: number;
|
|
12
21
|
end: number;
|
|
22
|
+
/** Tag or call the message belongs to, for example `button`. */
|
|
13
23
|
tag: string;
|
|
24
|
+
/** Enclosing component name. Used with `fingerprint` to match a message to its registry entry. */
|
|
14
25
|
component: string;
|
|
15
26
|
attribute?: string;
|
|
27
|
+
/** Position within the component, such as the enclosing element. Shown to translators as `Component > context`. */
|
|
16
28
|
context: string;
|
|
29
|
+
/** Hash of the source and its location fields (kind, tag, component, attribute, context), used to match registry entries. */
|
|
17
30
|
fingerprint: string;
|
|
18
31
|
}
|
|
19
32
|
/** A message with the stable ID that the registry assigned to it. */
|
|
20
33
|
type ResolvedMessage = ExtractedMessage & {
|
|
21
34
|
id: string;
|
|
22
35
|
};
|
|
36
|
+
/** One message remembered in the registry. Entries are kept across builds so stable IDs survive edits. */
|
|
23
37
|
type RegistryEntry = Pick<ExtractedMessage, "source" | "kind" | "tag" | "component" | "attribute" | "context" | "fingerprint"> & {
|
|
24
38
|
id: string;
|
|
25
39
|
location: string;
|
|
@@ -27,11 +41,14 @@ type RegistryEntry = Pick<ExtractedMessage, "source" | "kind" | "tag" | "compone
|
|
|
27
41
|
firstSeen: string;
|
|
28
42
|
lastSeen: string;
|
|
29
43
|
};
|
|
44
|
+
/** The persisted `registry.json`: all known messages keyed by ID. `version` allows a future format change. */
|
|
30
45
|
interface Registry {
|
|
31
46
|
version: 1;
|
|
32
47
|
entries: Record<string, RegistryEntry>;
|
|
33
48
|
}
|
|
49
|
+
/** Maps message IDs to text. */
|
|
34
50
|
type Catalog = Record<string, string>;
|
|
51
|
+
/** A message that needs a translation, as sent to a provider. */
|
|
35
52
|
interface TranslationRequest {
|
|
36
53
|
id: string;
|
|
37
54
|
source: string;
|
|
@@ -39,6 +56,10 @@ interface TranslationRequest {
|
|
|
39
56
|
kind: MessageKind;
|
|
40
57
|
placeholders?: string[];
|
|
41
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Translates a batch of messages into one target locale. Return a catalogue with a key for each message that was
|
|
61
|
+
* translated. Missing keys are left untranslated. Throw a `PartialTranslationError` to keep the keys already done.
|
|
62
|
+
*/
|
|
42
63
|
interface TranslationProvider {
|
|
43
64
|
translate(input: {
|
|
44
65
|
sourceLocale: string;
|
|
@@ -62,6 +83,7 @@ export declare class PartialTranslationError extends Error {
|
|
|
62
83
|
readonly failed: number;
|
|
63
84
|
constructor(message: string, partial: Catalog, failed: number);
|
|
64
85
|
}
|
|
86
|
+
/** A provider paired with the name used to refer to it in configuration and log messages. */
|
|
65
87
|
interface NamedProvider {
|
|
66
88
|
name: string;
|
|
67
89
|
provider: TranslationProvider;
|
|
@@ -70,6 +92,9 @@ interface NamedProvider {
|
|
|
70
92
|
* Tries providers in order. Messages a provider did not translate (a failed request, or a partial result) are sent
|
|
71
93
|
* to the next provider. Throws only when nothing could be translated, or a PartialTranslationError when some messages
|
|
72
94
|
* are still missing after every provider has been tried.
|
|
95
|
+
*
|
|
96
|
+
* @param providers Providers in the order they should be tried.
|
|
97
|
+
* @param onFallback Called with a human-readable message each time a provider hands work to the next one.
|
|
73
98
|
*/
|
|
74
99
|
export declare class FallbackProvider implements TranslationProvider {
|
|
75
100
|
private readonly providers;
|
|
@@ -83,8 +108,14 @@ export declare class FallbackProvider implements TranslationProvider {
|
|
|
83
108
|
}
|
|
84
109
|
/**
|
|
85
110
|
* Normalizes the `provider` option to one provider. Returns undefined when no provider is configured.
|
|
111
|
+
*
|
|
112
|
+
* A single provider (or a bare provider object) is returned as is. A named map is validated, ordered so that `primary`
|
|
113
|
+
* comes first, and wrapped in a {@link FallbackProvider} when it holds more than one entry.
|
|
114
|
+
*
|
|
115
|
+
* @throws If an entry is not a provider, or `primary` names a provider that is not configured.
|
|
86
116
|
*/
|
|
87
117
|
export declare function resolveProvider(config: ProviderConfig | undefined, onFallback?: (message: string) => void): TranslationProvider | undefined;
|
|
118
|
+
/** Tuning options shared by every HTTP provider that sends messages in batches. */
|
|
88
119
|
interface BatchedProviderOptions {
|
|
89
120
|
/** Messages sent per request. Smaller batches keep responses within output limits. Default 40. */
|
|
90
121
|
batchSize?: number;
|
|
@@ -94,9 +125,10 @@ interface BatchedProviderOptions {
|
|
|
94
125
|
maxRetries?: number;
|
|
95
126
|
/** Base delay before a retry; doubles on each attempt and is jittered. A Retry-After header overrides it (max 60 s). Default 500. */
|
|
96
127
|
retryDelayMs?: number;
|
|
97
|
-
/** Batches sent in parallel. Default 4. */
|
|
128
|
+
/** Batches sent in parallel across all locales of one provider instance. Default 4. */
|
|
98
129
|
concurrency?: number;
|
|
99
130
|
}
|
|
131
|
+
/** One translation call: the locale pair and the messages to translate between them. */
|
|
100
132
|
interface TranslateInput {
|
|
101
133
|
sourceLocale: string;
|
|
102
134
|
targetLocale: string;
|
|
@@ -108,44 +140,68 @@ interface TranslateInput {
|
|
|
108
140
|
*/
|
|
109
141
|
declare abstract class BatchedProvider<O extends BatchedProviderOptions> implements TranslationProvider {
|
|
110
142
|
protected readonly options: O;
|
|
143
|
+
private readonly slots;
|
|
111
144
|
constructor(options: O);
|
|
112
145
|
/**
|
|
113
146
|
* Translates in parallel batches. If every batch fails, the first error is thrown. If only some fail,
|
|
114
147
|
* a PartialTranslationError carries the translations that succeeded.
|
|
115
148
|
*/
|
|
116
149
|
translate(input: TranslateInput): Promise<Catalog>;
|
|
150
|
+
/**
|
|
151
|
+
* Runs one batch, retrying retryable failures with exponential backoff (`retryDelayMs * 2^attempt`, jittered)
|
|
152
|
+
* up to `maxRetries` extra attempts. Non-retryable errors are rethrown immediately.
|
|
153
|
+
*/
|
|
117
154
|
private translateBatch;
|
|
118
155
|
/** Sends one batch to the provider and returns the translations it produced for the requested IDs. */
|
|
119
156
|
protected abstract request(input: TranslateInput): Promise<Catalog>;
|
|
120
157
|
/** Parses the model's text reply, which must be a JSON object mapping IDs to strings. */
|
|
121
158
|
protected toCatalog(content: string | undefined, messages: TranslationRequest[]): Catalog;
|
|
122
159
|
}
|
|
160
|
+
/** Options for an OpenAI or OpenAI-compatible chat completions endpoint. */
|
|
123
161
|
interface OpenAICompatibleOptions extends BatchedProviderOptions {
|
|
124
162
|
apiKey: string;
|
|
125
163
|
model: string;
|
|
164
|
+
/** Overrides the API root, for example to target a proxy or a self-hosted compatible server. */
|
|
126
165
|
baseURL?: string;
|
|
127
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Translates batches through the `/chat/completions` endpoint. Requests use JSON mode so the reply is a parseable
|
|
169
|
+
* object of translations.
|
|
170
|
+
*/
|
|
128
171
|
export declare class OpenAICompatibleProvider extends BatchedProvider<OpenAICompatibleOptions> {
|
|
129
172
|
protected request(input: TranslateInput): Promise<Catalog>;
|
|
130
173
|
}
|
|
174
|
+
/** Options for the Anthropic Messages API. */
|
|
131
175
|
interface AnthropicOptions extends BatchedProviderOptions {
|
|
132
176
|
apiKey: string;
|
|
133
177
|
model: string;
|
|
178
|
+
/** Overrides the API root, for example to target a proxy. */
|
|
134
179
|
baseURL?: string;
|
|
135
180
|
/** Maximum tokens the model may generate per batch. Default 8192. */
|
|
136
181
|
maxTokens?: number;
|
|
137
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* Translates batches through the Anthropic Messages API. The reply's text blocks are joined before parsing.
|
|
185
|
+
*/
|
|
138
186
|
export declare class AnthropicProvider extends BatchedProvider<AnthropicOptions> {
|
|
187
|
+
/** Sends one batch and parses the model's text reply; a surrounding Markdown code fence is stripped first. */
|
|
139
188
|
protected request(input: TranslateInput): Promise<Catalog>;
|
|
140
189
|
}
|
|
190
|
+
/** Options for the Gemini `generateContent` API. */
|
|
141
191
|
interface GeminiOptions extends BatchedProviderOptions {
|
|
142
192
|
apiKey: string;
|
|
143
193
|
model: string;
|
|
194
|
+
/** Overrides the API root, for example to target a proxy. */
|
|
144
195
|
baseURL?: string;
|
|
145
196
|
/** Maximum tokens the model may generate per batch. Default 8192. */
|
|
146
197
|
maxOutputTokens?: number;
|
|
147
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* Translates batches through the Gemini `generateContent` API. The system prompt goes in `systemInstruction`, and
|
|
201
|
+
* `responseMimeType` requests JSON output.
|
|
202
|
+
*/
|
|
148
203
|
export declare class GeminiProvider extends BatchedProvider<GeminiOptions> {
|
|
204
|
+
/** Sends one batch and parses the first candidate's text; a surrounding Markdown code fence is stripped first. */
|
|
149
205
|
protected request(input: TranslateInput): Promise<Catalog>;
|
|
150
206
|
}
|
|
151
207
|
export interface AutoI18nOptions {
|
|
@@ -174,8 +230,8 @@ export interface AutoI18nOptions {
|
|
|
174
230
|
*/
|
|
175
231
|
orphanRetentionMinutes?: number;
|
|
176
232
|
/**
|
|
177
|
-
* Locales translated at the same time. Default 2.
|
|
178
|
-
*
|
|
233
|
+
* Locales translated at the same time. Default 2. The provider's `concurrency` option caps the batches in
|
|
234
|
+
* flight across all locales, so the total number of parallel requests does not multiply with this value.
|
|
179
235
|
*/
|
|
180
236
|
localeConcurrency?: number;
|
|
181
237
|
/** Log each message sent to the translation provider and each translation received, with timestamps. Default false. */
|
|
@@ -188,5 +244,15 @@ export interface AutoI18nOptions {
|
|
|
188
244
|
/** Text of the block comment that marks a string literal for translation. Default `i18n`. */
|
|
189
245
|
stringMarker?: string;
|
|
190
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* Vite plugin that extracts translatable strings from source files, translates them and serves the catalogues.
|
|
249
|
+
* Options are validated and the provider is resolved immediately, so a bad configuration throws before the build starts.
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* ```ts
|
|
253
|
+
* import autoI18n from "vite-plugin-ai-i18n";
|
|
254
|
+
* export default defineConfig({ plugins: [autoI18n({ locales: ["en", "de"] })] });
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
191
257
|
export default function autoI18n(options: AutoI18nOptions): Plugin;
|
|
192
258
|
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,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-Dy0ag6Nv.js";import{n as A,t as j}from"./resolve-ELDMKdoA.js";import*as M from"valibot";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 = ${
|
|
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 E,u as D,w as O,x as k,y as A}from"./registry-4CzY0FGn.js";import{n as ee,t as te}from"./resolve-ELDMKdoA.js";import*as j from"valibot";import{glob as ne}from"tinyglobby";import{readFile as re,rm as ie,stat as ae}from"node:fs/promises";import M from"node:path";import{createHash as oe}from"node:crypto";import{parseSync as se}from"oxc-parser";import ce from"magic-string";async function le(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}var ue=class{available;waiters=[];constructor(e){this.available=Math.max(1,Math.floor(e))}async run(e){await this.acquire();try{return await e()}finally{this.release()}}acquire(){return this.available>0?(this.available--,Promise.resolve()):new Promise(e=>this.waiters.push(e))}release(){let e=this.waiters.shift();e?e():this.available++}};function de(e){return[...e.matchAll(A)].map(e=>e[1])}function fe(e,t){let n=new Set(e),r=new Set(t);return n.size===r.size&&[...n].every(e=>r.has(e))}function N(e,t){let n=de(e);return e.trim()!==``&&n.length===t.length&&fe(n,t)}var P=class extends Error{partial;failed;constructor(e,t,n){super(e),this.partial=t,this.failed=n,this.name=`PartialTranslationError`}};async function pe(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 P))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`&&!N(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 me(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 le(e.locales,e.localeConcurrency,t=>pe({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 F(e){return e.replace(/\s+/g,` `).trim()}function I(e){let t=[F(e.source),e.kind,e.tag.toLowerCase(),e.attribute??``,e.context].join(``);return oe(`sha256`).update(t).digest(`hex`).slice(0,24)}function L(e,t){let n=se(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 R(e,t){let n=[],r=e=>{if(t(e,n)!==!1){n.push(e);for(let t of Object.keys(e)){if(t===`parent`)continue;let n=e[t];Array.isArray(n)?n.forEach(e=>z(e)&&r(e)):z(n)&&r(n)}n.pop()}};r(e)}function z(e){return typeof e==`object`&&!!e&&typeof e.type==`string`}function B(e){return z(e)&&e.type===`Literal`&&typeof e.value==`string`}function he(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 V(e){return e.includes(`&`)?e.replace(k,(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 H(e){return B(e)?V(e.value):void 0}function U(e){return V(e.value)}function ge(e){return e.attributes.some(e=>e.type===`JSXAttribute`&&e.name.type===`JSXIdentifier`&&e.name.name===`translate`&&H(e.value)===`no`)}function _e(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 ve=(e,t)=>e.type===`FunctionExpression`?!(t?.type===`MethodDefinition`||t?.type===`Property`&&(t.method||t.kind!==`init`)):e.type===`FunctionDeclaration`||e.type===`ArrowFunctionExpression`;function ye(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&&ge(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}=${H(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 be(e,t,n={}){let{program:r,commentsByEnd:i}=L(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 _e(e)||g.test(t)&&o(t).includes(`children`)};function c(e){let t=e.length-1;for(;t>=0&&!ve(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=F(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=I(m),a.push(m)}function u(e,n){if(G([...n,e]))return;let r=U(e),i=F(r);if(!i)return;let o=ye(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=I(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(!(_e(i.name)?p.has(r):o(s).includes(r))||G([...n,e])||!B(e.value))return;let l=F(V(e.value.value));if(!l||!/[A-Za-z]/.test(l))return;let u=ye(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=I(f),a.push(f)}function f(r,o){if(!B(r)||o[o.length-1]?.type===`JSXAttribute`||o.some(e=>e.start===r.start)||!he(e,i,r.start,n.stringMarker??`i18n`))return;let s=F(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=I(l),a.push(l)}return R(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 xe(e,n,r,i,a,o=!1,s=`en`){let{program:c}=L(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=r.map(e=>e.start).sort((e,t)=>e-t),d=(e,t)=>{let n=0,r=u.length;for(;n<r;){let t=n+r>>1;u[t]<e?n=t+1:r=t}return n<u.length&&u[n]<t},p=[];R(c,e=>{if(!d(e.start,e.end))return!1;if(e.type===`JSXElement`){let t=l.mixed.get(e.start);t&&p.push({kind:`mixed`,node:e,message:t})}else if(e.type===`JSXText`){let t=l.text.get(e.start);t&&p.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&&p.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&&p.push({kind:`string`,node:e,message:t})}});let m=new ce(e),h=!1,g=!1,_=e=>i===s?e.source:a[e.id]??e.source,v=e=>o?(h=!0,`${t}(${JSON.stringify(e.id)}, ${JSON.stringify(e.source)})`):JSON.stringify(_(e)),y=e=>{let t=e.prefix??``,n=e.suffix??``;return o?[t&&JSON.stringify(t),v(e),n&&JSON.stringify(n)].filter(Boolean).join(` + `):JSON.stringify(`${t}${_(e)}${n}`)};for(let e of p.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=>m.slice(i[e].start,i[e].end),s=n.openingElement.end,c=n.closingElement.start;if(o){g=!0;let e=r.map((e,t)=>`${JSON.stringify(e)}: ${a(t)}`).join(`, `);m.overwrite(s,c,`{${f}(${y(t)}, {${e}})}`);continue}let l=_(t),u=N(l,r)?l:t.source,d=[],p=0,h=e=>{e&&d.push(`{${JSON.stringify(e)}}`)};h(t.prefix??``);for(let e of u.matchAll(A))h(u.slice(p,e.index)),d.push(`{${a(r.indexOf(e[1]))}}`),p=e.index+e[0].length;h(u.slice(p)+(t.suffix??``)),m.overwrite(s,c,d.join(``))}else e.kind===`text`?m.overwrite(e.node.start,e.node.end,`{${y(t)}}`):e.kind===`attribute`?m.overwrite(e.node.start,e.node.end,o?`{${v(t)}}`:v(t)):m.overwrite(e.node.start,e.node.end,v(t))}let b=[...h?[`t as ${t}`]:[],...g?[`format as ${f}`]:[]];return b.length&&m.prepend(`import { ${b.join(`, `)} } from "${O}";\n`),{code:m.toString(),map:m.generateMap({source:n,hires:!0,includeContent:!0})}}function Se(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 Ce(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 = ${te.toString()};`,`const createLocaleLoader = ${ee.toString()};`,`const formatPlaceholders = ${Se.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 the catalogues that changed 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]) return;`,` messages = data.catalogs[currentLocale];`,` notify();`,` });`,`}`].join(`
|
|
3
3
|
`)+`
|
|
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.output))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.output.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.output.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.output.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.issues.map(e=>`${e.path?.map(e=>e.key).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};
|
|
4
|
+
`}async function q(e){try{let t=await ae(e);return`${t.mtimeMs}:${t.size}`}catch{return}}function we(e,t){if(!e)return!1;let n=Object.keys(t);return n.length===Object.keys(e).length&&n.every(n=>e[n]===t[n])}var Te=class{extractions=/* @__PURE__ */ new Map;fileMessages=/* @__PURE__ */ new Map;catalogs=/* @__PURE__ */ new Map;catalogFiles={};warnedFiles=/* @__PURE__ */ new Set;legacySidecarsRemoved=!1;localeFiles=/* @__PURE__ */ new Map;registryCache;hashOf(e){return this.extractions.get(e)?.hash}async readLocale(t,n){let r=await q(t),i=this.localeFiles.get(t);if(r!==void 0&&i?.stamp===r)return i.value;let a=await e(t,n);return r!==void 0&&this.localeFiles.set(t,{stamp:r,value:a}),a}async writeLocale(e,t){await y(e,t);let n=await q(e);n!==void 0&&this.localeFiles.set(e,{stamp:n,value:t})}async readRegistry(e,t){let n=await q(e);if(n!==void 0&&this.registryCache?.stamp===n)return this.registryCache.value;let r=await E(e,t);return n!==void 0&&(this.registryCache={stamp:n,value:r}),r}async writeRegistry(e,t){await v(e,t);let n=await q(e);n!==void 0&&(this.registryCache={stamp:n,value:t})}},Ee=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 P&&Object.assign(t,e.partial);let s=this.providers[a+1];s&&n.some(e=>t[e.id]===void 0)&&this.onFallback(`${o} failed (${De(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 P(`${n.length} messages failed to translate: ${De(i)}`,t,n.length)}};const De=e=>e instanceof Error?e.message:String(e),Oe=e=>typeof e==`object`&&!!e&&typeof e.translate==`function`;function ke(e,t){if(e===void 0)return;if(Oe(e))return e;let{primary:n,...r}=e,i=[];for(let[e,t]of Object.entries(r))if(t!==void 0){if(!Oe(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 Ee(i,t)}var J=class extends Error{retryable;retryAfterMs;constructor(e,t,n){super(e),this.retryable=t,this.retryAfterMs=n}};function Ae(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 je=e=>new Promise(t=>setTimeout(t,e));function Y(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 X(e){return JSON.stringify(e.map(e=>({id:e.id,text:e.source,context:e.context,kind:e.kind})))}async function Z(e){let t=e.status===408||e.status===409||e.status===429||e.status>=500;return await e.body?.cancel().catch(()=>void 0),new J(`Translation API failed (${e.status})`,t,Ae(e.headers.get(`retry-after`)))}var Q=class{options;slots;constructor(e){this.options=e,this.slots=new ue(e.concurrency??4)}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 Promise.allSettled(n.map(t=>this.slots.run(()=>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 P(`${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 J&&!e.retryable||r>=t)throw e;let i=e instanceof J?e.retryAfterMs:void 0;await je(i??n*2**r*(.5+Math.random()/2))}}toCatalog(e,t){if(!e)throw new J(`Translation provider returned no content`,!0);let n;try{n=JSON.parse(e)}catch{throw new J(`Translation provider returned invalid JSON`,!0)}let r=j.safeParse(a,n);if(!r.success)throw new J(`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.output))i.has(e)&&typeof t==`string`&&t.trim()&&(o[e]=t);return o}},Me=class extends Q{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:Y(e)},{role:`user`,content:X(e.messages)}]})});if(!t.ok)throw await Z(t);let n=j.safeParse(c,await t.json());return this.toCatalog(n.success?n.output.choices[0].message.content??void 0:void 0,e.messages)}},Ne=class extends Q{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:Y(e),messages:[{role:`user`,content:X(e.messages)}]})});if(!t.ok)throw await Z(t);let n=j.safeParse(s,await t.json()),r=n.success?n.output.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)}},Pe=class extends Q{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:Y(e)}]},contents:[{role:`user`,parts:[{text:X(e.messages)}]}],generationConfig:{responseMimeType:`application/json`,maxOutputTokens:this.options.maxOutputTokens??8192}})});if(!n.ok)throw await Z(n);let r=j.safeParse(u,await n.json()),i=r.success?(r.output.candidates[0]?.content?.parts??[]).map(e=>e.text??``).join(``):``;return this.toCatalog(i.trim().replace(/^```(?:json)?\s*|\s*```$/g,``)||void 0,e.messages)}};function Fe(e){let t=j.safeParse(i,e);if(t.success)return;let n=t.issues.map(e=>`${e.path?.map(e=>e.key).join(`.`)||`options`}: ${e.message}`).join(`; `);throw Error(`autoi18n: invalid options (${n}).`)}const $=e=>({info:t=>e.info(t),warn:t=>e.warn(t),error:t=>e.error(t)});async function Ie(e,t){await Promise.all(t.map(t=>ie(M.join(e,`${t}.sources.json`),{force:!0})))}function Le(e){Fe(e);let t=e.sourceLocale??`en`,n=e.directory??`.autoi18n`,i=e.delivery??`build`,a=e.debug??!1,s={componentProps:e.componentProps,stringMarker:e.stringMarker},c=e.orphanRetentionMinutes??43200,u,f=ke(e.provider,e=>u?.warn(e)),p,m=``,h=new Te,g,_=Promise.resolve(),v,y=()=>e.locale??process.env.AUTOI18N_LOCALE??t;function b(){let n=e.locales.filter(e=>e!==t),r=e.locale??process.env.AUTOI18N_LOCALE;return i===`build`&&r&&p.command!==`serve`?n.filter(e=>e===r):n}async function S(r,i){u=r;let g=i!==void 0&&h.extractions.has(i),_=g?[...h.extractions.keys()]:await ne(e.include??D,{cwd:p.root,ignore:e.exclude??x,absolute:!0}),v=/* @__PURE__ */ new Map,y=[];for(let e of _){let t=h.extractions.get(e),n;if(t&&g&&e!==i)n=t;else{let r=await re(e,`utf8`),i=o(r);n=t?.hash===i?t:{hash:i,messages:be(r,e,s)}}v.set(e,n),y.push({file:e,messages:n.messages})}let S=M.resolve(p.root,n,`locales`),C=b(),w=/* @__PURE__ */ new Map;for(let e of C)w.set(e,await h.readLocale(l(S,e),r.warn));let T=await me({files:y,registry:await h.readRegistry(m,r.warn),orphanRetentionMinutes:c,sourceLocale:t,locales:C,localeConcurrency:Math.max(1,Math.floor(e.localeConcurrency??2)),previous:w,provider:f,debug:a});if(a)for(let e of T.pruned)d(`removed ${e.id} ${JSON.stringify(e.source)} (last seen ${e.lastSeen})`);if(T.pruned.length&&r.info(`autoi18n: removed ${T.pruned.length} registry entries no longer in the source.`),await h.writeRegistry(m,T.registry),T.ambiguous.length){let t=`autoi18n: ${T.ambiguous.length} duplicated texts changed occurrence count, so their IDs may have moved (e.g. "${T.ambiguous[0]}").`;e.onAmbiguous===`error`?r.error(t):r.warn(t)}await h.writeLocale(l(S,t),T.sourceFile),h.legacySidecarsRemoved||=(await Ie(S,[t,...e.locales]),!0);let E=/* @__PURE__ */ new Map([[t,T.sourceFile.messages]]);for(let[e,t]of T.locales)await h.writeLocale(l(S,e),t.file),E.set(e,t.file.messages);for(let e of T.failures.keys())E.set(e,w.get(e)?.messages??{});let O=[...E].filter(([e,t])=>!we(h.catalogs.get(e),t)).map(([e])=>e),k=0;h.fileMessages=/* @__PURE__ */ new Map;for(let{file:e,messages:t}of y)h.fileMessages.set(e,T.messages.slice(k,k+t.length)),k+=t.length;h.extractions=v,h.catalogs=E,h.catalogFiles=Object.fromEntries(e.locales.map(e=>[e,l(S,e)]));for(let[t,n]of T.locales)n.requested&&!f&&e.failOnMissingProvider&&r.error(`Missing ${n.requested} ${t} translations and no provider is configured.`),n.rejected&&r.info(`autoi18n: rejected ${n.rejected} ${t} translations with changed placeholders.`),n.untranslated&&(f?r.warn:r.info)(`autoi18n: ${n.untranslated} ${t} messages have no translation and fall back to the source text.`),n.failed&&r.warn(`autoi18n: ${n.failed} ${t} translation batches failed (${n.failure}); they will be requested again on the next build.`);T.messages.length&&r.info(`autoi18n: indexed ${T.messages.length} messages across ${_.length} files.`);let A=[...T.failures];for(let[e,t]of A.slice(1))r.warn(`autoi18n: ${e} translation failed: ${t.message}`);if(A.length)throw A[0][1];return O}function C(e,t){let n=_.then(()=>S(e,t));return _=n.then(()=>void 0,()=>void 0),n}function w(e){e.length&&g?.ws.send({type:`custom`,event:r,data:{catalogs:Object.fromEntries(e.map(e=>[e,h.catalogs.get(e)]))}})}async function T(e){try{w(await C($(p.logger),e))}catch(e){p.logger.error(`autoi18n: re-translation failed: ${e.message}`)}}function E(){clearTimeout(v),v=setTimeout(()=>void T(),150)}function k(t){if(!/\.[jt]sx?$/.test(t))return!1;let n=M.relative(p.root,t).split(M.sep).join(`/`);return(e.include??D).some(e=>M.matchesGlob(n,e))&&!(e.exclude??x).some(e=>M.matchesGlob(n,e))}return{name:`vite-plugin-autoi18n`,enforce:`pre`,configResolved(r){p=r,m=M.resolve(p.root,n,`registry.json`);let i=y();if(i!==t&&!e.locales.includes(i))throw Error(`autoi18n: locale "${i}" is not one of the configured locales (${e.locales.join(`, `)}).`)},resolveId(e){if(e===`virtual:autoi18n`)return`\0${O}`},load(e){if(e===`\0virtual:autoi18n`)return Ce(h.catalogFiles,t)},configureServer(e){g=e,e.watcher.on(`add`,e=>{k(e)&&E()}),e.watcher.on(`unlink`,e=>{h.fileMessages.has(e)&&E()})},closeBundle(){clearTimeout(v)},async buildStart(){await S({info:e=>this.info(e),warn:e=>this.warn(e),error:e=>this.error(e)})},async transform(e,n){let r=n.split(`?`)[0];if(!h.fileMessages.has(r))return null;let a=o(e);a!==h.hashOf(r)&&await C($(p.logger),r);let s=h.fileMessages.get(r);if(!s||a!==h.hashOf(r))return h.warnedFiles.has(r)||(h.warnedFiles.add(r),this.warn(`autoi18n: ${r} changed while it was being indexed; its messages are left untranslated.`)),null;let c=p.command===`serve`?t:y(),l=xe(e,r,s,c,h.catalogs.get(c)??{},i===`runtime`,t);return{code:l.code,map:l.map}},async handleHotUpdate({file:e}){if(e.startsWith(M.resolve(p.root,n,`locales`)+M.sep))return[];h.fileMessages.has(e)&&await T(e)}}}export{Ne as AnthropicProvider,Ee as FallbackProvider,Pe as GeminiProvider,Me as OpenAICompatibleProvider,P as PartialTranslationError,Le as default,ke as resolveProvider};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import*as e from"valibot";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.pipe(e.array(e.object({message:e.object({content:e.nullish(e.string())})})),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.picklist([`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.picklist([`active`,`orphaned`]),firstSeen:e.string(),lastSeen:e.string()}),b=e.object({version:e.literal(1),entries:e.record(e.string(),e.unknown())}),x=e.pipe(e.string(),e.minLength(1)),S=e.object({locales:e.array(x),sourceLocale:e.optional(x),delivery:e.optional(e.picklist([`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.picklist([`warn`,`error`])),orphanRetentionMinutes:e.optional(e.union([e.pipe(e.number(),e.minValue(0)),e.literal(1/0)])),localeConcurrency:e.optional(e.pipe(e.number(),e.minValue(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.output.messages,n,t),sources:T(i.output.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,
|
|
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.pipe(e.array(e.object({message:e.object({content:e.nullish(e.string())})})),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.picklist([`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.picklist([`active`,`orphaned`]),firstSeen:e.string(),lastSeen:e.string()}),b=e.object({version:e.literal(1),entries:e.record(e.string(),e.unknown())}),x=e.pipe(e.string(),e.minLength(1)),S=e.object({locales:e.array(x),sourceLocale:e.optional(x),delivery:e.optional(e.picklist([`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.picklist([`warn`,`error`])),orphanRetentionMinutes:e.optional(e.union([e.pipe(e.number(),e.minValue(0)),e.literal(1/0)])),localeConcurrency:e.optional(e.pipe(e.number(),e.minValue(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.output.messages,n,t),sources:T(i.output.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,R=`virtual:autoi18n`,ee=`__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.output.entries)){let i=e.safeParse(y,o);i.success?a[r]=i.output: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,f=/* @__PURE__ */ new Map;for(let e of a){let t=f.get(e.component)??[];t.push(e),f.set(e.component,t)}r.forEach((e,t)=>{let n=f.get(e.component)?.shift();n&&(u[t]=n,d.add(n))});let p=a.filter(e=>!d.has(e)),m=0;r.forEach((e,t)=>{u[t]===void 0&&m<p.length&&(u[t]=p[m++])});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,ee 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,R as w,q as x,Y as y};
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
* Picks the best supported locale for a list of preferred locales (for example `navigator.languages`).
|
|
3
3
|
* This function is also embedded into the generated `virtual:autoi18n` module via `toString()`,
|
|
4
4
|
* so it must stay self-contained and must not reference anything outside its body.
|
|
5
|
+
*
|
|
6
|
+
* @param preferred Locales in priority order, such as `navigator.languages`.
|
|
7
|
+
* @param supported Locales the app has catalogues for.
|
|
8
|
+
* @param fallback Returned when no preferred locale matches.
|
|
9
|
+
* @returns The supported locale that matches first, preferring an exact match over a same-language match.
|
|
5
10
|
*/
|
|
6
11
|
export declare function resolveLocale(preferred: string[], supported: string[], fallback?: string): string;
|
|
12
|
+
/** Lazily imports one locale's catalogue. Resolves to a module whose default export, or the object itself, is the catalogue. */
|
|
7
13
|
type LocaleLoader = () => Promise<{
|
|
8
14
|
default: Record<string, string>;
|
|
9
15
|
} | Record<string, string>>;
|
|
@@ -11,6 +17,10 @@ type LocaleLoader = () => Promise<{
|
|
|
11
17
|
* Returns a cached catalogue loader. Unknown locales load the fallback, and a failed load is not cached.
|
|
12
18
|
* `prime` stores a catalogue that was already fetched, such as one pushed by the dev server.
|
|
13
19
|
* Serialised into the runtime module via toString(), so it must not reference anything outside its body.
|
|
20
|
+
*
|
|
21
|
+
* @param loaders Loader for each supported locale, keyed by locale code.
|
|
22
|
+
* @param fallback Locale loaded when the requested one has no loader.
|
|
23
|
+
* @returns A function that resolves a locale to its catalogue, with a `prime(locale, catalog)` method.
|
|
14
24
|
*/
|
|
15
25
|
export declare function createLocaleLoader(loaders: Record<string, LocaleLoader>, fallback?: string): {
|
|
16
26
|
(locale: string): Promise<Record<string, string>>;
|