react-autolocale 0.3.0-beta.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,52 +1,86 @@
1
1
  # react-autolocale
2
2
 
3
- Write your React + Vite app in one language. react-autolocale extracts the text, translates it with the engine you choose (a free local model, OpenAI, Anthropic Claude or Google Gemini), and `vite build` produces a static page for every language (`/en/`, `/fr/`, `/es/`, ...). You don't call `t()` or manage keys, and your source files are never modified.
3
+ Write your React + Vite app in one language. react-autolocale extracts the text, translates it with the engine you choose (Google Gemini, OpenAI, Anthropic Claude, or as a last option a free local model), and `vite build` produces a static page for every language (`/en/`, `/fr/`, `/es/`, ...). You don't call `t()` or manage keys, and your source files are never modified.
4
4
 
5
5
  Translation is a separate, explicit command. Builds only read the generated catalogs. They never download a model, call an API or wait for a translation job.
6
6
 
7
7
  ## Quick start
8
8
 
9
+ You need a React + Vite project and an API key from the engine you choose: [Google Gemini](https://aistudio.google.com/apikey), [OpenAI](https://platform.openai.com/api-keys) or [Anthropic](https://console.anthropic.com/settings/keys). A free local model is also available, but it is the last option, not the recommended one (see [Choosing a translation engine](#choosing-a-translation-engine)).
10
+
9
11
  **1. Install** (npm, yarn or pnpm)
10
12
 
11
13
  ```bash
12
- npm install react-autolocale # or: yarn add react-autolocale | pnpm add react-autolocale
14
+ npm install react-autolocale # or: yarn add react-autolocale | pnpm add react-autolocale
13
15
  ```
14
16
 
15
- **2. Set it up**
17
+ **2. Run the setup wizard**
16
18
 
17
19
  ```bash
18
- npx react-autolocale init # or: yarn react-autolocale init | pnpm exec react-autolocale init
20
+ npx react-autolocale init # or: yarn react-autolocale init | pnpm exec react-autolocale init
19
21
  ```
20
22
 
21
- The wizard detects your project and asks for:
22
- - your source language and target languages
23
- - a translation engine: a local model or a cloud provider (see [Choosing a translation engine](#choosing-a-translation-engine))
24
- - where the catalogs go
23
+ It asks these questions, in this order:
24
+
25
+ | Question | What to answer |
26
+ | --- | --- |
27
+ | **Source language** | The language your code's text is written in. Press **Enter** for English (`en`), or type **one** code such as `fr`. Not a list. |
28
+ | **Target languages** | The languages to add, comma-separated, for example `fr,es,de`. Pressing Enter on an empty line just asks again. |
29
+ | **How would you like to translate?** | Type the **number** of a cloud provider (OpenAI, Anthropic Claude or Google Gemini). Nothing is chosen for you. The local model is the last option. |
30
+ | **Catalog folder** | Press **Enter** to keep the default. |
31
+ | **API key** | Paste your key; what you type is hidden. It is saved to `.env`, and `.env` is added to `.gitignore`. Press Enter to skip and add `GEMINI_API_KEY=...` (or `OPENAI_API_KEY` / `ANTHROPIC_API_KEY`) to `.env` yourself later. |
25
32
 
26
- It then writes `auto-locale.config.ts` and adds the plugin to `vite.config`. It downloads and translates nothing.
33
+ It writes `auto-locale.config.ts` and adds the plugin to your `vite.config`. It translates and downloads nothing. To skip the questions: `npx react-autolocale init --provider gemini --source en --locales fr,es`.
27
34
 
28
- **3. Wrap your app once**
35
+ **3. Wrap your app once.** Open the file that renders `<App />` (usually `src/main.tsx` or `src/main.jsx`) and add the import and the wrapper. Keep `<StrictMode>` on the outside if you have it:
29
36
 
30
37
  ```tsx
31
- // main.tsx
38
+ import { StrictMode } from "react";
39
+ import { createRoot } from "react-dom/client";
32
40
  import { AutoScale } from "react-autolocale";
41
+ import App from "./App";
33
42
 
34
43
  createRoot(document.getElementById("root")!).render(
35
- <AutoScale exclude={["Acme Cloud"]}>
36
- <App />
37
- </AutoScale>,
44
+ <StrictMode>
45
+ <AutoScale>
46
+ <App />
47
+ </AutoScale>
48
+ </StrictMode>,
38
49
  );
39
50
  ```
40
51
 
41
- **4. Generate translations, then develop and build as usual**
52
+ No props are needed: the languages come from `auto-locale.config.ts`. Without this wrapper the translations exist, but the site stays in the original language.
53
+
54
+ **4. Generate the translations**
55
+
56
+ ```bash
57
+ npx react-autolocale generate
58
+ ```
59
+
60
+ You should see which engine is used and a count, for example `Using gemini:gemini-3.8-flash.` and `Translated 314 strings (fr 157, es 157)`. The translations are saved in `src/generated/auto-locale/`.
61
+
62
+ **This is the only step that uses your API key.** You run it on your own computer, once, and again only when your text changes. Nothing after this step needs the key or the internet.
63
+
64
+ **5. Run it and check it**
42
65
 
43
66
  ```bash
44
- npx react-autolocale generate # extract strings, translate new ones, write the catalogs
45
- npm run dev
46
- npm run build # one static page per language and route: dist/fr/, dist/fr/about/, ...
67
+ npm run dev # open http://localhost:5173/fr/ and /es/ (use the port it prints)
68
+ npm run build && npm run preview # the finished pages: dist/fr/, dist/es/, ...
47
69
  ```
48
70
 
49
- After changing text in your components, run `npx react-autolocale sync`. Only new strings are translated, and a running dev server reloads when the command finishes.
71
+ You do **not** need the API key for this step, or for deploying: `build` and `preview` only read the translations you already generated. Put the key in `.env` on your own computer and nowhere else, so you never add it to Vercel, Netlify or your CI.
72
+
73
+ Read the translated pages. Machine translation can get short labels or brand names wrong: fix them as described in [Fixing a translation](#fixing-a-translation), and list brand names in `<AutoScale exclude={["Acme Cloud"]}>`.
74
+
75
+ **6. Commit the translations**
76
+
77
+ ```bash
78
+ git add src/generated/auto-locale
79
+ ```
80
+
81
+ Commit that folder so builds and deploys never need an API key or a model. Never commit `.env`.
82
+
83
+ **Whenever you change text in your components**, run `npx react-autolocale sync`. Only new strings are translated, and a running dev server reloads when it finishes.
50
84
 
51
85
  Optional: `npx react-autolocale add switcher` copies an editable language switcher into your project (see below).
52
86
 
@@ -104,13 +138,13 @@ When the config sets `sourceLocale` and `locales`, `<AutoScale>` needs no langua
104
138
 
105
139
  ## Choosing a translation engine
106
140
 
107
- The engine only matters to `translate`/`generate`/`sync`. Extraction, the catalogs, the build and the runtime work the same with every engine.
141
+ The engine only matters to `translate`/`generate`/`sync`. Extraction, the catalogs, the build and the runtime work the same with every engine. `init` never picks one for you.
108
142
 
109
- **Local model** (`{ name: "local" }`, the default). It is free and runs on your machine with no API key: OPUS-MT for English sources and NLLB-200 for everything else, through Transformers.js. The cost is disk, RAM and CPU time:
110
- - **Downloads**: on the first `translate`, about 110 MB per English→X language and 870 MB for NLLB-200 (non-English sources, and Portuguese, Japanese, Korean and the other NLLB languages below). If your project doesn't have `@huggingface/transformers`, a 470 MB engine is also installed once into `~/.cache/react-autolocale/engine`.
111
- - **Reuse**: models are cached in `~/.cache/react-autolocale` and shared by every project. A loaded model is kept for the whole run.
112
- - **Speed**: OPUS-MT translates 8 strings per call; on an Apple-silicon Mac that measured about 5× faster than one at a time (32 UI strings in about 0.65 s per language once the model was loaded). NLLB runs one string at a time, because batching produced garbage, so it is much slower.
113
- - **Progress and interruption**: progress is printed per language, and work is saved after every batch, so Ctrl+C or a crash loses at most one batch.
143
+ | Engine | Quality | What you need | Cost |
144
+ | --- | --- | --- | --- |
145
+ | **Google Gemini**, **OpenAI**, **Anthropic Claude** | Best. Each string is sent with the element it appears in. | An API key | Billed by the provider (Gemini has a free tier with low limits) |
146
+ | **Ollama** | Good, depends on the model. | Ollama installed, with a model | Free |
147
+ | **Local model** (last option) | **May not match the cloud models**, especially short labels and less common languages. | A **large download** on first use and a fast-enough CPU | Free |
114
148
 
115
149
  **OpenAI** (`{ name: "openai", model?, apiKeyEnv?, baseURL? }`) and **Anthropic Claude** (`{ name: "anthropic", model?, apiKeyEnv?, effort? }`) avoid the downloads and are usually quicker for large jobs. You need an API key, and usage is billed by the provider.
116
150
  - **Setup**: install the official SDK in your project (`npm install -D openai` or `npm install -D @anthropic-ai/sdk`; `init` offers to do it). Only the CLI loads it; it never reaches the browser bundle.
@@ -120,7 +154,7 @@ The engine only matters to `translate`/`generate`/`sync`. Extraction, the catalo
120
154
 
121
155
  **Google Gemini** (`{ name: "gemini", model?, apiKeyEnv?, baseURL? }`) works the same way, with nothing to install: it calls Gemini's REST API directly, so there is no SDK.
122
156
  - **Key**: create one in [Google AI Studio](https://aistudio.google.com/apikey) and put `GEMINI_API_KEY=...` in `.env` (or let `init` save it for you). Change the variable name with `apiKeyEnv`.
123
- - **Model**: `gemini-2.5-flash` by default. Set `model` to any model id shown in AI Studio, for example a newer Flash or a Pro model for harder text.
157
+ - **Model**: `gemini-3.8-flash` by default. Set `model` to any model id shown in AI Studio, for example a newer Flash or a Pro model for harder text.
124
158
  - **Behavior**: the same batching and context as the other cloud providers, with structured JSON replies. Rate limits (429) and server errors are retried with backoff, honoring `Retry-After`. A rejected key, a wrong model name or an exhausted quota stops the run with a clear message before anything is stored. Free-tier keys have low rate limits, so large first runs may need to be repeated; finished work is saved after every batch.
125
159
  - **Choose it**: `npx react-autolocale init --provider gemini`, or in the config: `provider: { name: "gemini" }`.
126
160
  - **Errors**: a rejected key, an unknown model or a used-up quota stops the run instead of failing every batch. A failing provider is never swapped for another one.
@@ -133,6 +167,16 @@ The engine only matters to `translate`/`generate`/`sync`. Extraction, the catalo
133
167
  provider: async ({ texts, contexts, from, to }) => texts.map((t, i) => myTranslate(t, from, to, contexts[i])),
134
168
  ```
135
169
 
170
+ **Local model** (`{ name: "local" }`) is the last option, not the recommended one. It is free and works offline with no API key, but you should know what you are choosing:
171
+ - **You must download models first.** `init` and builds download nothing; the first `translate`/`generate` does.
172
+ - **The quality may be lower than the cloud models above.** Short labels such as "Home" or "Save" and less common languages are the usual weak spots. Review the result, and fix strings in the overrides file.
173
+
174
+ It runs on your machine through Transformers.js, using OPUS-MT for English sources and NLLB-200 for everything else. The cost is disk, RAM and CPU time:
175
+ - **Downloads**: on the first `translate`, about 110 MB per English→X language and 870 MB for NLLB-200 (non-English sources, and Portuguese, Japanese, Korean and the other NLLB languages below). If your project doesn't have `@huggingface/transformers`, a 470 MB engine is also installed once into `~/.cache/react-autolocale/engine`.
176
+ - **Reuse**: models are cached in `~/.cache/react-autolocale` and shared by every project. A loaded model is kept for the whole run.
177
+ - **Speed**: OPUS-MT translates 8 strings per call; on an Apple-silicon Mac that measured about 5× faster than one at a time (32 UI strings in about 0.65 s per language once the model was loaded). NLLB runs one string at a time, because batching produced garbage, so it is much slower.
178
+ - **Progress and interruption**: progress is printed per language, and work is saved after every batch, so Ctrl+C or a crash loses at most one batch.
179
+
136
180
  Engines receive text with protected pieces replaced by tokens (`X0X`, `{name}`) and must return them unchanged. Whatever the engine, the results are checked: a translation that drops a token or changes a number is retried once with numbers locked, and is otherwise reported as failed. It is never stored.
137
181
 
138
182
  ## Generated files and Git
@@ -277,16 +321,45 @@ const { language, languages, setLanguage, basePath } = useLanguage();
277
321
 
278
322
  ## What gets translated
279
323
 
280
- - Static JSX text, plus `placeholder`, `title`, `alt`, `aria-label`, `aria-placeholder` and `aria-description` on HTML elements. Other attributes (class names, styles, URLs, ids, event handlers) and props on your own components are never touched. JSX whitespace is collapsed the way React renders it.
281
- - **Text with variables**: `<p>Welcome, {user.name}. You have {count} new messages</p>` is translated as one sentence (`Welcome, {name}. You have {count} new messages`) and your values are filled in at runtime. Variables must be plain names or property paths (`name`, `user.name`, `props.count`). Text mixed with function calls, conditions or nested elements is left as written. There is no plural handling, so write `{count} message(s)` or branch in your code.
282
- - **Not translated**: anything with `translate="no"` (the standard HTML attribute, inherited by children), `exclude` phrases, `<code>` / `<pre>` / `<script>` / `<style>`, text made only of numbers and symbols, and data that arrives at runtime (API responses, user content).
283
- - **Numbers** are kept exactly as written. `translate` checks that every digit (prices, phone numbers, hours, versions) survives translation and re-translates with the number locked if not. If that still fails, the original text is kept instead of shipping a wrong number. Use `useFormat()` (below) for numbers you compute.
324
+ You write normal code and nothing in your files changes. The scanner finds text wherever people usually put it, not only in JSX:
325
+
326
+ | Where the text is written | Example |
327
+ | --- | --- |
328
+ | JSX text | `<h1>Welcome</h1>` |
329
+ | Sentences with variables, links or bold words | `<p>Hello {user.name}</p>`, `<p>Read the <a href="/terms">terms</a> now</p>` (one message: `Read the {1} now`, so word order can change per language) |
330
+ | Attributes | `placeholder`, `title`, `alt`, `aria-label` (and `aria-description`, `aria-placeholder`, ...), `<input type="submit" value="Send">`, `<meta name="description" content="...">`, `data-tooltip` |
331
+ | Props on your own components | `<Hero title="Welcome" subtitle="Build it" />`, `<Chart text="VEHICLE · VAN" />` (not code-like props such as `variant`, `size`, `className`, `to`, `href`, `icon`) |
332
+ | Expressions in JSX | `{open ? "Close menu" : "Open menu"}`, `{error && "Something went wrong"}`, `{name \|\| "Guest"}`, `` {`Hello, ${name}!`} ``, `{"Page " + n + " of " + total}` |
333
+ | Data in arrays and objects, in the same file or a shared content file | `const nav = ["Home", "Pricing"]`, `{ title: "Fast", body: "Ships in seconds." }`, `[["Weight", "1.56 kg"]]`, `export const specs = {...}` in `site.js` |
334
+ | JSON files your code imports | `import faq from "./faq.json"` (keys such as `id`, `url`, `type`, `href` are skipped) |
335
+ | Text inside logic | `useState("Loading...")`, `setError("Invalid email")`, `alert("Saved!")`, `toast.success("Done")`, `document.title = "Dashboard"`, default props, functions that return labels |
336
+ | Hand-written elements | `React.createElement("h1", null, "Hello")` |
337
+
338
+ Numbers are kept exactly as written: `translate` checks that every digit (prices, phone numbers, hours, versions) survives translation, re-translates with the number locked if not, and otherwise keeps the original text. Use `useFormat()` (below) for numbers you compute.
339
+
340
+ ### How it tells text from code
341
+
342
+ A string is translated when it reads like text a person sees. URLs, file names, class names (`"flex items-center"`), CSS values, colors, SVG paths, ids, lowercase code words (`"primary"`, `"submit"`), date formats and similar are left alone. Whole categories of code are never touched: object keys, `import` paths, TypeScript types and enums, `className`/`style`/`href`/`id` and other code attributes, selectors, storage keys, `fetch` and `console` calls, analytics events, class helpers (`clsx`, `cn`), and `throw new Error(...)`.
343
+
344
+ **A string your code depends on is never translated, anywhere in the project.** If the code compares it (`status === "Active"`, `case "Open":`), looks it up (`icons["Home"]`, `{ Home: ... }`), checks it (`.includes("Pending")`) or names it in a type (`type S = "Open" | "Closed"`), that exact text is left as written everywhere, so nothing breaks.
345
+
346
+ ### When it gets one wrong
347
+
348
+ - `// i18n-ignore` on or above a string (or its statement) leaves it alone; `translate="no"` on an element leaves it and everything inside it alone; `exclude` lists phrases such as brand names that are never translated.
349
+ - `msg("text")` forces a string to be translated wherever the automatic rules can't see it, such as a lowercase word: `import { msg } from "react-autolocale"` then `msg("open")`, `msg("Hello {name}", { name })`. It returns the text, so it is safe without the plugin.
284
350
 
285
351
  ```tsx
286
352
  <p translate="no">Acme Cloud</p> // never translated
287
- <section translate="no"><Terminal /></section> // nothing inside is translated
353
+ const DEBUG_LABEL = "Internal build"; // i18n-ignore
288
354
  ```
289
355
 
356
+ ### Good to know
357
+
358
+ - **Switching language reloads the page** when your app has text outside React components (data in a `site.js`, `useState` starting values, JSON): that text is translated when the page loads, so the new language needs a fresh page. Apps that only use text in components switch in place without a reload. Either way each language has its own URL.
359
+ - **Compare ids, not labels.** A tab labelled `"All"` that your code checks with `=== "All"` stays in English (the code needs that exact text). To translate the label, compare something else: `{ id: "all", label: "All" }` and `filter === "all"`.
360
+ - Text passed through your own function (`t(...)`-style helpers) and data that arrives at runtime (API responses, user-written content) is not translated: it does not exist at build time. Text you write in code around those values is.
361
+ - Not translated yet: Markdown/MDX files, HTML inside strings (`dangerouslySetInnerHTML`), and CSS `content:`.
362
+
290
363
  ## Supported languages
291
364
 
292
365
  Use these codes in `languages={[...]}`. The source (`original`) language can be any of them.
@@ -398,8 +471,12 @@ With `seo`, switching language navigates to the real page (so its head tags are
398
471
 
399
472
  ## Troubleshooting
400
473
 
474
+ - **`init` says `"fr, es" is not a supported code` at "Source language"**: that question takes one code (your code's language, usually `en`). The list of languages to add comes at the next question.
475
+ - **The site is still in English after `generate`**: wrap your app in `<AutoScale>` (step 3 of the quick start), restart `npm run dev`, and open `/fr/` or `/es/`.
476
+ - **`init` printed instructions with `original="en" languages={...}`**: you have the older 0.2.0, which works differently. Upgrade: `npm install react-autolocale@latest` (see [Upgrading from earlier versions](#upgrading-from-earlier-versions)).
401
477
  - **"No translations in src/generated/auto-locale/ yet" during a build**: run `npx react-autolocale generate`, then commit the folder.
402
478
  - **"OPENAI_API_KEY is not set"**: add it to `.env` in the project root or export it in your shell, then rerun `translate`. No request was made.
479
+ - **Gemini says "model not found" or "no longer available"**: Google retires models over time. Set `model` in the provider to a current id from [AI Studio](https://aistudio.google.com) (`provider: { name: "gemini", model: "<id>" }`) and run `generate` again; strings that already succeeded are kept.
403
480
  - **"This provider needs the openai package"** (or `@anthropic-ai/sdk`): install it as a dev dependency of your app.
404
481
  - **Some strings failed**: the report lists them by id, language and reason. Rerun `npx react-autolocale translate`, or write the value into the catalog or the overrides file yourself.
405
482
  - **The local model is slow**: NLLB languages run one string at a time on the CPU. Translate a subset with `--lang`, or switch `provider` to OpenAI or Claude for big jobs. Existing translations are kept either way.
@@ -409,7 +486,8 @@ With `seo`, switching language navigates to the real page (so its head tags are
409
486
  ## Limits
410
487
 
411
488
  - Data that arrives at runtime (API responses, user-written content) is not translated: it does not exist at build time. Text you write in JSX around those values is.
412
- - Variables in text must be plain names or property paths; no plural rules.
489
+ - No plural rules: write `{count} message(s)` or branch in your code (`count === 1 ? "1 message" : `${count} messages``; both branches are translated).
490
+ - Strings your code compares, uses as keys or names in types are left as written (see "How it tells text from code"). Lowercase single words in data (`["red", "green"]`) look like code and are skipped; wrap them with `msg()`.
413
491
  - Dynamic routes (`/users/:id`) only get pages you list in `routes`, or that are linked.
414
492
  - `<AutoScale>` props must be literals so the plugin can read them at build time.
415
493
  - The CLI reads `auto-locale.config.ts`, not `vite.config`. Set `include` there if you change the plugin's `include`.
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { F as toTranslator, I as getLanguage, M as TranslationError, N as createLocalTranslator, P as ollama, S as scanProject, a as findSite, b as manifestFile, c as hasRootSrc, d as OVERRIDES_FILE, f as exportTranslations, g as importLegacyCache, h as catalogFile, i as defaultInclude, j as writeJson, k as ensureBanner, l as resolveOutput, m as readOverrides, n as loadConfigFile, o as META_DIR, r as loadProject, s as defaultOutput, t as findConfigFile, u as toProjectPath, v as removeFromCatalogs, x as readManifest, y as translateMissing } from "./project-BazDFtnp.js";
3
- import { n as DEFAULT_MODELS, t as DEFAULT_KEY_ENV } from "./config-U-SpTtCW.js";
4
- import path from "node:path";
2
+ import { B as getLanguage, F as writeJson, I as TranslationError, L as createLocalTranslator, N as ensureBanner, R as ollama, S as scanProject, a as findSite, b as manifestFile, c as hasRootSrc, d as OVERRIDES_FILE, f as exportTranslations, g as importLegacyCache, h as catalogFile, i as defaultInclude, l as resolveOutput, m as readOverrides, n as loadConfigFile, o as META_DIR, r as loadProject, s as defaultOutput, t as findConfigFile, u as toProjectPath, v as removeFromCatalogs, x as readManifest, y as translateMissing, z as toTranslator } from "./project-DcYRIvym.js";
3
+ import { n as DEFAULT_MODELS, t as DEFAULT_KEY_ENV } from "./config-ChdN65Rg.js";
5
4
  import fs from "node:fs";
5
+ import path from "node:path";
6
6
  import { spawnSync } from "node:child_process";
7
7
  import { createRequire } from "node:module";
8
8
  import { pathToFileURL } from "node:url";
@@ -622,11 +622,6 @@ async function runInit(root, flags, prompt, out) {
622
622
  if (bad.length) throw new Error(`Unsupported language code${bad.length > 1 ? "s" : ""}: ${bad.join(", ")}.`);
623
623
  }
624
624
  const name = flags.provider ?? await prompt.choose("How would you like to translate your application?", [
625
- {
626
- value: "local",
627
- label: "Local model (OPUS-MT / NLLB-200)",
628
- help: "Free and runs on your machine; no API key. Models are large (about 110 MB per English->X language, 870 MB for\nother pairs) and translating on the CPU uses RAM and time, so big apps or many languages can be slow.\nModels are cached in ~/.cache/react-autolocale and downloaded only when you run `translate` or `generate`,\nnever during init or builds."
629
- },
630
625
  {
631
626
  value: "openai",
632
627
  label: "Cloud AI: OpenAI",
@@ -641,8 +636,14 @@ async function runInit(root, flags, prompt, out) {
641
636
  value: "gemini",
642
637
  label: "Cloud AI: Google Gemini",
643
638
  help: "Uses a hosted AI model. No large downloads, and usually faster for big translation jobs. Needs an API key;\nprovider usage may be billed to your account."
639
+ },
640
+ {
641
+ value: "local",
642
+ label: "Local model (OPUS-MT / NLLB-200): free, but a large download and lower quality",
643
+ help: "Free and works offline, with no API key, but it needs a large download the first time (about 110 MB per\nEnglish->X language, 870 MB for other pairs) and translating on your CPU is slow for big apps. The quality may\nnot match the cloud models, especially for short labels and less common languages, so review the result.\nModels are cached in ~/.cache/react-autolocale and downloaded only when you run `translate` or `generate`,\nnever during init or builds."
644
644
  }
645
- ], "local");
645
+ ]);
646
+ if (!name) throw new Error("Choose a translation engine: --provider openai|anthropic|gemini|local|ollama (add --model for ollama).");
646
647
  let provider;
647
648
  if (name === "openai" || name === "anthropic" || name === "gemini") provider = {
648
649
  name,
@@ -796,7 +797,8 @@ function createPrompter(interactive) {
796
797
  console.log(` ${i + 1}) ${o.label}`);
797
798
  if (o.help) console.log(o.help.replace(/^/gm, " ") + "\n");
798
799
  });
799
- const def = String(options.findIndex((o) => o.value === fallback) + 1);
800
+ const index = options.findIndex((o) => o.value === fallback);
801
+ const def = index >= 0 ? String(index + 1) : "";
800
802
  for (;;) {
801
803
  const answer = await ask("Choose a number", def);
802
804
  const picked = options[Number(answer) - 1];
@@ -13,7 +13,7 @@ const PROVIDER_NAMES = [
13
13
  const DEFAULT_MODELS = {
14
14
  openai: "gpt-6-astra",
15
15
  anthropic: "claude-opus-5-5",
16
- gemini: "gemini-2.5-flash"
16
+ gemini: "gemini-3.8-flash"
17
17
  };
18
18
  const DEFAULT_KEY_ENV = {
19
19
  openai: "OPENAI_API_KEY",
package/dist/config.d.ts CHANGED
@@ -33,7 +33,7 @@ interface AnthropicProvider {
33
33
  /** Google Gemini through its REST API. No SDK needed. Get a key at https://aistudio.google.com/apikey. */
34
34
  interface GeminiProvider {
35
35
  name: "gemini";
36
- /** Defaults to "gemini-2.5-flash". Use any model id listed in Google AI Studio. */
36
+ /** Defaults to "gemini-3.8-flash". Use any model id listed in Google AI Studio. */
37
37
  model?: string;
38
38
  /** Environment variable holding the key. Defaults to GEMINI_API_KEY. */
39
39
  apiKeyEnv?: string;
package/dist/config.js CHANGED
@@ -1 +1 @@
1
- export { i as defineConfig } from "./config-U-SpTtCW.js";
1
+ export { i as defineConfig } from "./config-ChdN65Rg.js";
package/dist/index.d.ts CHANGED
@@ -10,6 +10,16 @@ declare global {
10
10
  };
11
11
  }
12
12
  }
13
+ /**
14
+ * Marks any string for translation, wherever the automatic rules cannot see it (a lowercase word, text built in an
15
+ * unusual way): `msg("Open")`, `msg("Hello {name}", { name })`. The build looks the text up in the catalogs; without
16
+ * the plugin it simply returns the text.
17
+ */
18
+ export declare function msg(text: string, vars?: Record<string, unknown>): string;
19
+ /** @internal Used by the Vite plugin for a string outside any component. */
20
+ export declare function __rlText(i: string, s: string): string;
21
+ /** @internal Same, for a string with variables: `Hello, ${name}` becomes ("Hello, {name}", { name }). */
22
+ export declare function __rlFmt(i: string, s: string, vars: Record<string, unknown>): string;
13
23
  export interface AutoScaleProps {
14
24
  /** Language the app is written in, e.g. "en". Must be a literal so the build can read it. */
15
25
  original: string;
@@ -37,7 +47,9 @@ export declare function __T({ i, s }: {
37
47
  s: string;
38
48
  }): ReactElement;
39
49
  /** @internal Inserted by the Vite plugin for translated attributes (placeholder, alt, ...). */
40
- export declare function __useT(): (i: string, s: string) => string;
50
+ export declare function __useT(): ((i: string, s: string) => string) & {
51
+ f: (i: string, s: string, vars: Record<string, unknown>) => string;
52
+ };
41
53
  /** @internal Inserted by the Vite plugin for text with variables, e.g. `Hello {name}`. */
42
54
  export declare function __TD({ i, s, v }: {
43
55
  i: string;
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { r as redirectTarget, t as STORAGE_KEY } from "./redirect-pDFblLdm.js";
2
2
  import { n as DISMISSED_KEY, r as suggestLanguage, t as BANNER_EN } from "./suggestion-CkZQqMvZ.js";
3
3
  import { Fragment, createContext, createElement, useCallback, useContext, useEffect, useMemo, useState } from "react";
4
- import { banner, languages, loaders, original, originalNative, seo } from "virtual:react-autolocale";
4
+ import { banner, eager, languages, loaders, original, originalNative, seo } from "virtual:react-autolocale";
5
5
  //#region src/runtime/index.ts
6
6
  const hasDom = typeof window !== "undefined" && typeof document !== "undefined";
7
7
  const codes = /* @__PURE__ */ new Set([original, ...languages.map((l) => l.code)]);
@@ -22,6 +22,43 @@ function load(code) {
22
22
  if (!loader) return Promise.resolve({});
23
23
  return loader().then((m) => m.default).catch(() => ({})).then((dict) => (cache.set(code, dict), dict));
24
24
  }
25
+ /**
26
+ * Strings written outside React components (data in a content file, helpers, class components) are looked up
27
+ * here, at the moment the code runs. The dictionary is the one for the language in the URL, taken from the page
28
+ * itself (production, prerendering) or from the catalogs the dev server inlines.
29
+ */
30
+ let currentDict = null;
31
+ /** Set once such a string has been used: it cannot follow an in-place language change, so switching reloads. */
32
+ let staticUsed = false;
33
+ function dictNow() {
34
+ if (currentDict) return currentDict;
35
+ const boot = hasDom ? window.__AUTOSCALE__ : void 0;
36
+ const lang = (hasDom ? prefixOf(window.location.pathname) : null) ?? boot?.lang ?? original;
37
+ if (boot && boot.lang === lang) currentDict = boot.dict;
38
+ else currentDict = lang === original ? {} : eager[lang] ?? cache.get(lang) ?? {};
39
+ return currentDict;
40
+ }
41
+ function fill(template, vars) {
42
+ return template.replace(/\{(\w+)\}/g, (match, name) => name in vars ? String(vars[name] ?? "") : match);
43
+ }
44
+ /**
45
+ * Marks any string for translation, wherever the automatic rules cannot see it (a lowercase word, text built in an
46
+ * unusual way): `msg("Open")`, `msg("Hello {name}", { name })`. The build looks the text up in the catalogs; without
47
+ * the plugin it simply returns the text.
48
+ */
49
+ function msg(text, vars) {
50
+ return vars ? fill(text, vars) : text;
51
+ }
52
+ /** @internal Used by the Vite plugin for a string outside any component. */
53
+ function __rlText(i, s) {
54
+ staticUsed = true;
55
+ return dictNow()[i] || s;
56
+ }
57
+ /** @internal Same, for a string with variables: `Hello, ${name}` becomes ("Hello, {name}", { name }). */
58
+ function __rlFmt(i, s, vars) {
59
+ staticUsed = true;
60
+ return fill(dictNow()[i] || s, vars);
61
+ }
25
62
  const Context = createContext({
26
63
  language: original,
27
64
  dict: {},
@@ -34,6 +71,7 @@ function initialState() {
34
71
  };
35
72
  const boot = window.__AUTOSCALE__;
36
73
  if (boot) cache.set(boot.lang, boot.dict);
74
+ for (const [code, dict] of Object.entries(eager)) if (!cache.has(code)) cache.set(code, dict);
37
75
  const { pathname, search, hash } = window.location;
38
76
  const fromUrl = prefixOf(pathname);
39
77
  if (fromUrl === null) {
@@ -65,13 +103,14 @@ function initialState() {
65
103
  */
66
104
  function AutoScale({ children }) {
67
105
  const [state, setState] = useState(initialState);
106
+ currentDict = state.dict;
68
107
  const apply = useCallback(async (code, push) => {
69
108
  if (!codes.has(code)) return;
70
109
  if (push && hasDom) {
71
110
  try {
72
111
  window.localStorage.setItem(STORAGE_KEY, code);
73
112
  } catch {}
74
- if (seo) {
113
+ if (seo || staticUsed) {
75
114
  window.location.assign(pathFor(code));
76
115
  return;
77
116
  }
@@ -108,7 +147,11 @@ function __T({ i, s }) {
108
147
  /** @internal Inserted by the Vite plugin for translated attributes (placeholder, alt, ...). */
109
148
  function __useT() {
110
149
  const { dict } = useContext(Context);
111
- return useCallback((i, s) => dict[i] || s, [dict]);
150
+ return useMemo(() => {
151
+ const lookup = ((i, s) => dict[i] || s);
152
+ lookup.f = (i, s, vars) => fill(dict[i] || s, vars);
153
+ return lookup;
154
+ }, [dict]);
112
155
  }
113
156
  /** @internal Inserted by the Vite plugin for text with variables, e.g. `Hello {name}`. */
114
157
  function __TD({ i, s, v }) {
@@ -263,4 +306,4 @@ function LanguageSwitcher({ className, style, label = "Language" }) {
263
306
  }, l.native)));
264
307
  }
265
308
  //#endregion
266
- export { AutoScale, ForLanguages, LanguageBanner, LanguageSwitcher, Localized, __T, __TD, __useT, useFormat, useLanguage, useLanguageSuggestion, useLocalized };
309
+ export { AutoScale, ForLanguages, LanguageBanner, LanguageSwitcher, Localized, __T, __TD, __rlFmt, __rlText, __useT, msg, useFormat, useLanguage, useLanguageSuggestion, useLocalized };