react-autolocale 0.1.0 → 0.3.0-beta.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 +248 -16
- package/dist/cli.js +800 -46
- package/dist/config-CYHyGpdX.js +21 -0
- package/dist/config.d.ts +56 -0
- package/dist/config.js +1 -0
- package/dist/index.d.ts +64 -3
- package/dist/index.js +154 -24
- package/dist/project-CGAWETvT.js +1400 -0
- package/dist/{redirect-CKNY4ru5.js → redirect-pDFblLdm.js} +5 -2
- package/dist/suggestion-CkZQqMvZ.js +24 -0
- package/dist/translate-COrJX1KS.d.ts +22 -0
- package/dist/vite.d.ts +12 -3
- package/dist/vite.js +82 -782
- package/package.json +22 -6
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# react-autolocale
|
|
2
2
|
|
|
3
|
-
|
|
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 or Anthropic Claude), 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
|
+
|
|
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.
|
|
4
6
|
|
|
5
7
|
## Quick start
|
|
6
8
|
|
|
@@ -10,12 +12,19 @@ Wrap your React + Vite app in one component. `vite build` produces a fully trans
|
|
|
10
12
|
npm install react-autolocale # or: yarn add react-autolocale | pnpm add react-autolocale
|
|
11
13
|
```
|
|
12
14
|
|
|
13
|
-
**2.
|
|
15
|
+
**2. Set it up**
|
|
14
16
|
|
|
15
17
|
```bash
|
|
16
18
|
npx react-autolocale init # or: yarn react-autolocale init | pnpm exec react-autolocale init
|
|
17
19
|
```
|
|
18
20
|
|
|
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
|
|
25
|
+
|
|
26
|
+
It then writes `auto-locale.config.ts` and adds the plugin to `vite.config`. It downloads and translates nothing.
|
|
27
|
+
|
|
19
28
|
**3. Wrap your app once**
|
|
20
29
|
|
|
21
30
|
```tsx
|
|
@@ -23,24 +32,147 @@ npx react-autolocale init # or: yarn react-autolocale init | pnpm exe
|
|
|
23
32
|
import { AutoScale } from "react-autolocale";
|
|
24
33
|
|
|
25
34
|
createRoot(document.getElementById("root")!).render(
|
|
26
|
-
<AutoScale
|
|
35
|
+
<AutoScale exclude={["Acme Cloud"]}>
|
|
27
36
|
<App />
|
|
28
37
|
</AutoScale>,
|
|
29
38
|
);
|
|
30
39
|
```
|
|
31
40
|
|
|
32
|
-
**4.
|
|
41
|
+
**4. Generate translations, then develop and build as usual**
|
|
33
42
|
|
|
34
43
|
```bash
|
|
35
|
-
|
|
36
|
-
npm run
|
|
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/, ...
|
|
37
47
|
```
|
|
38
48
|
|
|
39
|
-
|
|
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.
|
|
40
50
|
|
|
41
51
|
Optional: `npx react-autolocale add switcher` copies an editable language switcher into your project (see below).
|
|
42
52
|
|
|
43
|
-
|
|
53
|
+
## Workflow and commands
|
|
54
|
+
|
|
55
|
+
| Command | What it does | Network / models |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| `init` | Creates `auto-locale.config.ts`, sets up the provider (API key into `.env` if you paste one), adds the Vite plugin. Keeps an existing config unless you confirm (or pass `--force`). | None |
|
|
58
|
+
| `scan` | Extracts strings and updates `.auto-locale/manifest.json` and the source catalog (`en.json`). | None |
|
|
59
|
+
| `translate` | Translates strings missing from the catalogs with the configured provider. Retries anything that failed before. `--lang fr,es` limits it. | Provider only |
|
|
60
|
+
| `generate` | `scan` then `translate`. Incremental: existing translations are kept. | Provider only |
|
|
61
|
+
| `sync` | Same as `generate`. Use it after editing text. | Provider only |
|
|
62
|
+
| `clean` | Lists strings no longer in the source; removes their translations only after you confirm (`--yes` in scripts). | None |
|
|
63
|
+
| `export` | Copies the translations into `react-autolocale.overrides.json` for hand editing. | None |
|
|
64
|
+
| `add switcher` / `add banner` | Copies an editable component into your project. | None |
|
|
65
|
+
|
|
66
|
+
`init` also accepts flags, so it can run without prompts in scripts: `--source en --locales fr,es --provider local|openai|anthropic|ollama --model <id> --api-key-env <NAME> --output <dir> --yes`. Every command takes `--root <dir>`; the default is the nearest folder with a `package.json`.
|
|
67
|
+
|
|
68
|
+
A `generate` run looks like this:
|
|
69
|
+
|
|
70
|
+
```text
|
|
71
|
+
React Auto Locale
|
|
72
|
+
|
|
73
|
+
✓ Scanned 24 source files
|
|
74
|
+
✓ Found 3 new strings (84 in use)
|
|
75
|
+
Using openai:gpt-6-astra.
|
|
76
|
+
✓ Kept 243 existing translations
|
|
77
|
+
✓ Translated 9 strings (fr 3, es 3, de 3)
|
|
78
|
+
✓ Updated 3 locale catalogs in src/generated/auto-locale/
|
|
79
|
+
|
|
80
|
+
No source files modified.
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Failures are listed by language and message id, and the command exits with code 1. They show the source text until you fix them. `npx react-autolocale translate` retries just those strings.
|
|
84
|
+
|
|
85
|
+
## Configuration
|
|
86
|
+
|
|
87
|
+
`auto-locale.config.ts` (written by `init`, also `.mts`/`.js`/`.mjs`):
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { defineConfig } from "react-autolocale/config";
|
|
91
|
+
|
|
92
|
+
export default defineConfig({
|
|
93
|
+
sourceLocale: "en",
|
|
94
|
+
locales: ["en", "fr", "es", "de"],
|
|
95
|
+
provider: { name: "openai", model: "gpt-6-astra", apiKeyEnv: "OPENAI_API_KEY" },
|
|
96
|
+
// output: "./src/generated/auto-locale", // optional, see "Generated files"
|
|
97
|
+
// include: ["src"], // folders scanned for JSX
|
|
98
|
+
// exclude: ["Acme Cloud"], // phrases never translated (adds to <AutoScale exclude>)
|
|
99
|
+
// glossary: { fr: { Roadmap: "Feuille de route" } },
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
When the config sets `sourceLocale` and `locales`, `<AutoScale>` needs no language props. Projects without a config file keep working: the languages come from `<AutoScale original="en" languages={["fr", "es"]}>` and the local model is used. `seo` and `siteUrl` stay on `<AutoScale>`. Invalid settings, such as an unknown language code, a half-set language pair or an unknown provider, stop every command with a message naming the setting.
|
|
104
|
+
|
|
105
|
+
## Choosing a translation engine
|
|
106
|
+
|
|
107
|
+
The engine only matters to `translate`/`generate`/`sync`. Extraction, the catalogs, the build and the runtime work the same with every engine.
|
|
108
|
+
|
|
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.
|
|
114
|
+
|
|
115
|
+
**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
|
+
- **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.
|
|
117
|
+
- **Keys**: they are read from `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` (change the name with `apiKeyEnv`), from the environment or from `.env` / `.env.local` in the project root. Variables already set in your shell win. If you paste a key into `init`, it is appended to `.env` (never over an existing value) and `.env` is added to `.gitignore`. Keys are never printed or written into the config, the catalogs or the bundle. A missing key stops the command before any request is made.
|
|
118
|
+
- **Defaults**: `gpt-6-astra` for OpenAI, and `claude-opus-5-5` at `effort: "low"` for Anthropic. Short UI strings rarely need more; Claude refusals fall back server-side on models that support it.
|
|
119
|
+
- **Requests**: strings are sent in batches of 25, 3 at a time, with each string's element (`button`, `h1`, ...) as context. Replies are structured JSON with one translation per string; a batch that comes back the wrong size is retried one string at a time. The SDKs time out after 2 minutes and retry rate limits and server errors 3 times.
|
|
120
|
+
- **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.
|
|
121
|
+
|
|
122
|
+
**Ollama** (`{ name: "ollama", model: "llama3.1", url? }`) runs a local LLM that receives the context too.
|
|
123
|
+
|
|
124
|
+
**Your own engine**: set `provider` to a function (or an object with `translate`). You can wrap DeepL, Google Cloud Translation, a self-hosted API or another LLM:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
provider: async ({ texts, contexts, from, to }) => texts.map((t, i) => myTranslate(t, from, to, contexts[i])),
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
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.
|
|
131
|
+
|
|
132
|
+
## Generated files and Git
|
|
133
|
+
|
|
134
|
+
```text
|
|
135
|
+
my-app/
|
|
136
|
+
├── src/
|
|
137
|
+
│ └── generated/auto-locale/ # commit this: what the app ships
|
|
138
|
+
│ ├── en.json # message id -> source text
|
|
139
|
+
│ ├── fr.json # message id -> translation
|
|
140
|
+
│ ├── banner.json # the language banner's phrases
|
|
141
|
+
│ └── index.ts # sourceLocale, locales, catalog file names
|
|
142
|
+
├── .auto-locale/
|
|
143
|
+
│ ├── manifest.json # commit this: ids, source text, element, files, status
|
|
144
|
+
│ └── cache.json # ignored by init: every machine translation, for reuse
|
|
145
|
+
└── auto-locale.config.ts
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
- **Location**:
|
|
149
|
+
- `src/generated/auto-locale/` when the project has a root-level `src/` folder, otherwise `generated/auto-locale/`.
|
|
150
|
+
- An `output` in the config always wins.
|
|
151
|
+
- The CLI, the scanner and the Vite plugin all resolve the location the same way.
|
|
152
|
+
- **Message ids**:
|
|
153
|
+
- `msg_` plus a hash of the whitespace-normalized text, so moving code or editing other files never changes an id.
|
|
154
|
+
- Identical text shares one id and one translation. Use `useLocalized` when one occurrence needs different wording.
|
|
155
|
+
- If two different strings ever produced the same id, `scan` stops and names them; it never silently mixes them up.
|
|
156
|
+
- **Small diffs**:
|
|
157
|
+
- Catalogs are pretty-printed JSON with sorted keys, written only when something changed, and written atomically (temp file + rename).
|
|
158
|
+
- A new string adds one line per catalog, and existing entries are never rewritten. You can edit `fr.json` by hand, and later runs keep your edit. An empty value counts as missing.
|
|
159
|
+
- **Removed strings**:
|
|
160
|
+
- Marked `unused` in the manifest. Their translations stay until `clean` removes them, but they are not shipped to the browser.
|
|
161
|
+
- If the text comes back, its translation is restored from `.auto-locale/cache.json` without calling the provider.
|
|
162
|
+
- **Moving the output**:
|
|
163
|
+
- After `output` changes, the next `scan` copies any missing target catalogs to the new folder and warns you.
|
|
164
|
+
- The old folder is never moved or deleted; remove it yourself once you've checked the new one.
|
|
165
|
+
|
|
166
|
+
## Production builds
|
|
167
|
+
|
|
168
|
+
`vite build` and `vite dev` only read the catalogs, the manifest and the overrides file. They never download or load a translation model, call a translation API, regenerate catalogs, or need an API key. A build works offline in CI with only the committed files. A string without a translation renders its source text, and the build prints a one-line count (`Untranslated strings (shown in en): fr 3. Run npx react-autolocale sync.`). In development the same notice appears when you add new text.
|
|
169
|
+
|
|
170
|
+
## Upgrading from earlier versions
|
|
171
|
+
|
|
172
|
+
Earlier versions translated during `vite build` and kept translations in `node_modules/.cache/react-autolocale/`:
|
|
173
|
+
- **Before you run anything new**: builds still read that folder, with a warning, so nothing breaks.
|
|
174
|
+
- **After `npx react-autolocale generate`**: those translations are copied into the new catalogs without being retranslated. The old folder is left in place.
|
|
175
|
+
- **Moved options**: `engine` and `glossary` in `vite.config` are now ignored, with a warning. Move them to `auto-locale.config.ts` as `provider` and `glossary`. The `overrides` and `routes` plugin options and `react-autolocale.overrides.json` work as before.
|
|
44
176
|
|
|
45
177
|
## Multi-page apps (React Router)
|
|
46
178
|
|
|
@@ -71,12 +203,56 @@ Your host must serve `/fr/about/index.html` for `/fr/about`, which static hosts
|
|
|
71
203
|
|
|
72
204
|
## Fixing a translation
|
|
73
205
|
|
|
206
|
+
Edit the value in the catalog (`src/generated/auto-locale/fr.json`) and commit it. Later runs keep your edit.
|
|
207
|
+
|
|
208
|
+
Prefer to edit by source text? Run `npx react-autolocale export`: it writes every translation into `react-autolocale.overrides.json` in your project root, keyed by the original text. Entries in that file always win over the catalogs, apply on the next build or dev reload, and are skipped by `translate`.
|
|
209
|
+
|
|
210
|
+
You can also fix a few strings in code:
|
|
211
|
+
|
|
74
212
|
Machine translation sometimes gets short UI words wrong (a nav link "Home" may come back as "Sommaire"). Correct any string in the plugin options; overrides always win:
|
|
75
213
|
|
|
76
214
|
```ts
|
|
77
215
|
reactAutolocale({ overrides: { fr: { Home: "Accueil" }, es: { Home: "Inicio" } } })
|
|
78
216
|
```
|
|
79
217
|
|
|
218
|
+
## Different values per language (prices, links, and more)
|
|
219
|
+
|
|
220
|
+
Some things should differ by language, not just be translated: a price, a link to a localized page, a phone number.
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
import { useLocalized, Localized, ForLanguages, useFormat } from "react-autolocale";
|
|
224
|
+
|
|
225
|
+
// A value: falls back to `default`, then to the original language
|
|
226
|
+
const href = useLocalized({ en: "/pricing", fr: "/tarifs", es: "/precios", default: "/pricing" });
|
|
227
|
+
<a href={href}>Pricing</a> // "Pricing" itself is still translated for you
|
|
228
|
+
|
|
229
|
+
// A block of UI
|
|
230
|
+
<Localized en={<UsPhone />} fr={<FrPhone />} default={<UsPhone />} />
|
|
231
|
+
|
|
232
|
+
// Content for some languages only (local offers, legal notices)
|
|
233
|
+
<ForLanguages only={["fr", "es"]}>Free shipping in Europe</ForLanguages>
|
|
234
|
+
<ForLanguages except={["ja"]}><CookieNotice /></ForLanguages>
|
|
235
|
+
|
|
236
|
+
// Numbers, prices and dates in the visitor's format
|
|
237
|
+
const format = useFormat();
|
|
238
|
+
format.currency(1234.5, "EUR"); // "€1,234.50" in English, "1 234,50 €" in French
|
|
239
|
+
format.number(1250000); // "1,250,000" / "1 250 000"
|
|
240
|
+
format.percent(0.15); // "15%" / "15 %"
|
|
241
|
+
format.date(new Date()); // "Oct 9, 2026" / "9 oct. 2026"
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
All of these are part of the prerendered pages, so `/fr/` and `/en/` ship with their own values in the HTML. Values you pick in code are your own data and are never machine-translated. `basePath` from `useLanguage()` is the current prefix (`/fr`) for building internal links. With React Router, the page you link to must be a route you define (here `/tarifs`); the build does not rename routes by language.
|
|
245
|
+
|
|
246
|
+
## Better translations: glossary and context
|
|
247
|
+
|
|
248
|
+
Add a `glossary` in `auto-locale.config.ts` for terms that must always translate a given way. It is applied before the engine sees the text, with every engine:
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
glossary: { fr: { Roadmap: "Feuille de route" }, es: { Roadmap: "Hoja de ruta" } },
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
The local models translate each string without knowing where it appears, so a lone "Home" in a nav bar can come back wrong. OpenAI, Claude, Ollama and custom engines receive the element each string sits in (`"button"`, `"a"`, `"h1"`, `"placeholder"`, ...), which fixes most of these.
|
|
255
|
+
|
|
80
256
|
## Language switcher
|
|
81
257
|
|
|
82
258
|
Switching is just navigation to `/fr/` (instant, no reload; a page load when `seo` is on). You choose the UI:
|
|
@@ -95,7 +271,15 @@ const { language, languages, setLanguage, basePath } = useLanguage();
|
|
|
95
271
|
|
|
96
272
|
## What gets translated
|
|
97
273
|
|
|
98
|
-
Static JSX text, plus `placeholder`, `title`, `alt`, `aria-label` on HTML elements.
|
|
274
|
+
- 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.
|
|
275
|
+
- **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.
|
|
276
|
+
- **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).
|
|
277
|
+
- **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.
|
|
278
|
+
|
|
279
|
+
```tsx
|
|
280
|
+
<p translate="no">Acme Cloud</p> // never translated
|
|
281
|
+
<section translate="no"><Terminal /></section> // nothing inside is translated
|
|
282
|
+
```
|
|
99
283
|
|
|
100
284
|
## Supported languages
|
|
101
285
|
|
|
@@ -142,15 +326,50 @@ Use these codes in `languages={[...]}`. The source (`original`) language can be
|
|
|
142
326
|
| `tl` | Tagalog | NLLB |
|
|
143
327
|
| `en` | English (default source) | NLLB when used as a target |
|
|
144
328
|
|
|
145
|
-
OPUS-MT models
|
|
329
|
+
With the local engine, OPUS-MT models (about 110 MB each on disk) are used when the source language is English. NLLB-200 is one larger download (about 870 MB) shared by all the languages marked NLLB, and is also used for any non-English source. Cloud engines handle every code in the table. Right-to-left languages automatically get `dir="rtl"` on the page.
|
|
146
330
|
|
|
147
|
-
|
|
331
|
+
### Quality by language
|
|
148
332
|
|
|
149
|
-
|
|
333
|
+
Every supported language was run through the real models on sample UI sentences: all 37 produce output in the right writing system, with no runaway or repeated text. How good that output is varies. French, Spanish, German, Italian, Dutch, Swedish, Danish, Japanese and Korean read naturally on ordinary sentences. Short imperative labels are the weak spot in many languages: "Save 20%" comes back as "save a file" in Russian, Ukrainian, Hungarian, Hindi and Romanian, "Sign in" comes back wrong in Russian, Chinese, Arabic and Vietnamese, and Arabic and Vietnamese could not keep the number in "Save 20% on your first order" (the original English is kept for such strings rather than risking a wrong number). Treat these as drafts: review the languages you ship in `react-autolocale.overrides.json`, add a `glossary` for key terms, or use an engine that gets context (see above). You can re-run the check yourself with `RUN_MODELS=1 pnpm exec vitest run test/models.test.ts`.
|
|
150
334
|
|
|
151
335
|
## Automatic language navigation
|
|
152
336
|
|
|
153
|
-
|
|
337
|
+
Whether a visitor is moved depends on the URL they land on:
|
|
338
|
+
|
|
339
|
+
| Where they land | What happens |
|
|
340
|
+
| --- | --- |
|
|
341
|
+
| `/` or `/about` (no language in the URL) | Sent to their saved language, otherwise their browser language, if you support it (a French browser lands on `/fr/about`). |
|
|
342
|
+
| `/en/about` or `/fr/about` (explicit language URL) | Never redirected. The page they asked for is the page they get. |
|
|
343
|
+
|
|
344
|
+
- A language chosen in the switcher is remembered and wins on later visits. The browser language itself is not saved; it is checked each time.
|
|
345
|
+
- The browser's language setting is used, not the visitor's physical location. Unsupported browser languages stay on the original.
|
|
346
|
+
- The root page redirects before it paints, so visitors don't see a flash of the wrong language.
|
|
347
|
+
|
|
348
|
+
This follows common practice. Redirecting the bare root by browser language is widespread, while forcing visitors off an explicit language URL is avoided: shared links and search results should open the page they point to, redirect loops are avoided, and it respects someone who chose a language on purpose. Search engines are told which URL belongs to which language through `hreflang` tags (see SEO below), so a French searcher is normally shown the `/fr/` URL in the first place. Visitors who land on another language's URL can use the language switcher.
|
|
349
|
+
|
|
350
|
+
## Suggesting the visitor's language (optional banner)
|
|
351
|
+
|
|
352
|
+
Instead of forcing a redirect on an explicit URL, you can offer the visitor their browser language: a French visitor on `/en/about` sees "Cette page est également disponible en Français." with a button to switch and one to dismiss. It appears only when the browser language is supported and differs from the page, is shown after the page loads (so it never changes the HTML search engines see), remembers a dismissal, and stays hidden once the visitor has picked a language. The text is shown in the visitor's own language.
|
|
353
|
+
|
|
354
|
+
It is off unless you add it, and you decide how it looks:
|
|
355
|
+
|
|
356
|
+
```tsx
|
|
357
|
+
import { LanguageBanner } from "react-autolocale";
|
|
358
|
+
|
|
359
|
+
<LanguageBanner /> // ready-made bar, style it with className / style
|
|
360
|
+
<LanguageBanner className="my-bar" /> // your own CSS replaces the default look
|
|
361
|
+
|
|
362
|
+
<LanguageBanner> // or build the markup yourself
|
|
363
|
+
{(s) => (
|
|
364
|
+
<aside>
|
|
365
|
+
{s.text} <button onClick={s.accept}>{s.switchLabel}</button>
|
|
366
|
+
<button onClick={s.dismiss}>{s.dismissLabel}</button>
|
|
367
|
+
</aside>
|
|
368
|
+
)}
|
|
369
|
+
</LanguageBanner>
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
Prefer to own the component? `npx react-autolocale add banner` copies an editable `LanguageBanner` into your project. For full control use the hook, `useLanguageSuggestion()`, which returns `null` or `{ language, native, text, switchLabel, dismissLabel, accept, dismiss }`.
|
|
154
373
|
|
|
155
374
|
## SEO (opt-in)
|
|
156
375
|
|
|
@@ -171,10 +390,23 @@ With `seo` the build also:
|
|
|
171
390
|
|
|
172
391
|
With `seo`, switching language navigates to the real page (so its head tags are correct) instead of swapping in place. Brand names in the title are translated like any text, so add them to `exclude`.
|
|
173
392
|
|
|
174
|
-
##
|
|
393
|
+
## Troubleshooting
|
|
394
|
+
|
|
395
|
+
- **"No translations in src/generated/auto-locale/ yet" during a build**: run `npx react-autolocale generate`, then commit the folder.
|
|
396
|
+
- **"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.
|
|
397
|
+
- **"This provider needs the openai package"** (or `@anthropic-ai/sdk`): install it as a dev dependency of your app.
|
|
398
|
+
- **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.
|
|
399
|
+
- **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.
|
|
400
|
+
- **Interrupted run**: everything finished before the interruption was saved; run the same command again.
|
|
401
|
+
- **Corrupted JSON**: commands stop and name the file instead of treating it as empty; fix it or restore it from Git.
|
|
402
|
+
|
|
403
|
+
## Limits
|
|
175
404
|
|
|
176
|
-
-
|
|
405
|
+
- 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.
|
|
406
|
+
- Variables in text must be plain names or property paths; no plural rules.
|
|
177
407
|
- Dynamic routes (`/users/:id`) only get pages you list in `routes`, or that are linked.
|
|
178
408
|
- `<AutoScale>` props must be literals so the plugin can read them at build time.
|
|
409
|
+
- The CLI reads `auto-locale.config.ts`, not `vite.config`. Set `include` there if you change the plugin's `include`.
|
|
410
|
+
- Identical text always shares one translation.
|
|
179
411
|
- Use Vite's default `base: "/"`.
|
|
180
|
-
- Machine translation is draft quality.
|
|
412
|
+
- Machine translation is draft quality. Short labels, long sentences with variables and less common languages need a human look: use the overrides file.
|