livelocale 0.12.1-next.17 → 1.0.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
@@ -5,11 +5,11 @@
5
5
  <img src="brand/logo.svg" alt="livelocale" height="64">
6
6
  </picture>
7
7
  </h1>
8
- <p><strong>Click any text in your app. Fix the translation. Ship it as a pull request.</strong></p>
9
- <p>Keep next-intl or react-i18next. LiveLocale adds in-context editing for Next.js and React + Vite: no account, no server, no browser extension. Your translations stay as JSON in git.</p>
8
+ <p><strong>The editing and review layer on top of next-intl or react-i18next.</strong></p>
9
+ <p>Your content people fix text on the real site, and you get each fix as an edit to your JSON locale files in git, or as a pull request. For Next.js and React + Vite: no account, no server, no browser extension.</p>
10
10
 
11
11
  <p align="center">
12
- <a href="https://github.com/Araby99/livelocale/actions/workflows/ci.yml"><img src="https://github.com/Araby99/livelocale/actions/workflows/ci.yml/badge.svg" alt="CI Status" /></a>
12
+ <a href="https://github.com/Araby99/livelocale/actions/workflows/ci.yml?query=branch%3Amain"><img src="https://github.com/Araby99/livelocale/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI Status" /></a>
13
13
  <a href="https://www.npmjs.com/package/livelocale"><img src="https://img.shields.io/npm/v/livelocale.svg?style=flat-square" alt="NPM Version" /></a>
14
14
  <a href="https://www.npmjs.com/package/livelocale"><img src="https://img.shields.io/npm/dm/livelocale.svg?style=flat-square" alt="NPM Downloads" /></a>
15
15
  <a href="https://github.com/Araby99/livelocale/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/livelocale.svg?style=flat-square" alt="License" /></a>
@@ -18,53 +18,36 @@
18
18
 
19
19
  ---
20
20
 
21
- ## 💡 How It Works: Zero-Width Unicode Watermarking
21
+ **It is not an i18n library.** next-intl or react-i18next keeps doing routing, locale detection,
22
+ date and number formatting and message rendering; livelocale only edits and reviews the text.
22
23
 
23
- `livelocale` bridges your running application UI with your local JSON translation files using **Zero-Width Unicode Watermarking**.
24
+ - **Freelancing?** Your client fixes their own copy through a
25
+ [share link](https://livelocale.js.org/docs/guides/share-links.html), and you review a pull
26
+ request instead of collecting screenshots.
27
+ - **Only want CI?** [`npx livelocale check --hardcoded`](https://livelocale.js.org/docs/guides/ci-check.html)
28
+ works without the Studio. Translating on your own? The CI check alone catches missing keys and
29
+ hard-coded text.
24
30
 
25
- 1. **Watermark Encoding:** In development, when a translation key is looked up, the library prepends an invisible Unicode watermark (using combinations of `\u200D` (ZWJ), `\u200B` (ZWS), and `\u200C` (ZWNJ)) to the text.
26
- 2. **DOM Scanning:** A lightweight dev-only browser listener scans the active DOM, decodes the watermarks, and dynamically hooks visual highlights onto translated elements.
27
- 3. **Input & Clipboard Sanitization:** To ensure the watermarks never pollute your database, forms, or clipboard, the library automatically intercepts `'copy'`/`'paste'` events and patches controlled input elements at runtime.
28
- 4. **Zero Production Footprint:** In production mode, all interceptors, listeners, and wrapper logic are bypassed completely, returning plain strings without overhead.
31
+ Run your app beside the livelocale Studio, hold Alt and click a line of text, and fix it. The save
32
+ goes straight into your locale JSON file as an ordinary edit you commit. In development every
33
+ translated string carries an invisible watermark, so the Studio knows which key you clicked; in
34
+ production nothing is added. Share links let someone without a checkout fix text on your deployed
35
+ site and send the change back as a pull request.
29
36
 
30
- ---
31
-
32
- ## ✨ Features
33
-
34
- - 🎯 **In-Context Visual Editing:** Hover over any translated text to highlight it; click to instantly edit in the visual panel.
35
- - 🔴 **Missing Key Indicators:** Identifies translation keys that are missing in the active locale but exist in the codebase or other translation files, highlighting them with red indicators.
36
- - 🔄 **Smart Fallback Placeholders:** Automatically renders translation placeholders from alternative languages for missing keys in development, keeping them visible and fully click-to-edit inline.
37
- - 📁 **Namespaced Folder Support:** Handles nested multi-file folder layouts (e.g., `locales/en/auth.json`) natively, merging files during load and separating them during mutation.
38
- - 🧼 **Input & Form Sanitization:** Patches controlled inputs and intercepts clipboard actions to strip watermarks in development automatically.
39
- - ⚡ **Atomic File Operations:** Prevents Next.js hot module replacement (HMR) reading corrupt half-written files via atomic temp-to-final writes.
40
- - 🔒 **Secure Local Boundaries:** Restricts directory traversal, verifies origins, and supports deep schemas up to 30 levels of recursion.
41
- - 🤖 **CLI Migration Tool:** Automatically parses and wraps standard `react-i18next` hooks with a single command.
42
- - 🔢 **Plural forms editor:** ICU `plural` / `select` messages are edited one branch at a time, with the categories your locale needs (Arabic gets all six) pre-filled from CLDR rules.
43
- - 🧭 **Namespace-aware key scanning:** `useTranslations('home')`, `getTranslations`, `useTranslation('auth')`, and the wrap helpers are understood, so the Locale Directory shows `home.title`, not a false "missing" `title`.
44
- - 🧩 **Placeholder safety:** Saves that drop or invent a `{placeholder}`, `{{interpolation}}`, `%s`, or `<tag>` are held back with a clear warning, and broken ICU plural/select syntax is rejected, unless you choose Save anyway.
45
- - 🌍 **Preview mode for non-developers:** Run a token-protected Studio against a preview deployment; every save becomes a commit on a GitHub branch and one click opens the pull request.
46
- - 🧾 **Review, commit, undo:** The Studio's Changes panel shows git status and a diff of your locale files, commits them with a message, discards them, and undoes any edit from the session.
47
- - 🩺 **Doctor:** `npx livelocale doctor` finds the usual integration mistakes before you open the Studio.
48
- - ✅ **Translation check for CI:** `npx livelocale check` fails the build on missing keys, placeholder drift between locales, and broken ICU syntax, and lists keys nothing uses, and, with `--hardcoded`, text that never went through translation.
49
- - 🛑 **Safety switches:** `LIVELOCALE=off` kill switch, and automatic shut-off on hosting platforms so a staging build can never leak watermarks.
50
- - 🧭 **App Router and Pages Router:** One handler works with both `route.ts` and `pages/api` signatures.
51
- - 🌐 **Works beyond localhost:3000:** Point the Studio at any port or hostname with `--app` or `?app=`, and allow Codespaces or custom origins with one env var.
52
-
53
- ---
37
+ **Docs: [livelocale.js.org/docs](https://livelocale.js.org/docs/)**
54
38
 
55
- ## ⚡ Quick Setup
56
-
57
- ### 1. Install Dependency
39
+ ## Install
58
40
 
59
41
  ```bash
60
42
  npm install livelocale
61
43
  ```
62
44
 
63
- ---
45
+ It is a dependency, not a dev dependency: your app imports `livelocale/react` and
46
+ `livelocale/server` at run time. livelocale needs Node.js 18 or later.
64
47
 
65
- ### 2. Add the Client Listener
48
+ ## 30 seconds with Next.js
66
49
 
67
- Mount the `LiveLocaleProvider` inside your root layout. This launches the dev-mode DOM scanners and intercepts clipboard copy/paste actions.
50
+ Render the provider once in your root layout:
68
51
 
69
52
  ```tsx
70
53
  // app/layout.tsx
@@ -82,969 +65,67 @@ export default function RootLayout({ children }) {
82
65
  }
83
66
  ```
84
67
 
85
- ---
86
-
87
- ### 3. Initialize Server Mutation Handler
88
-
89
- Initialize the server-side API endpoints automatically by running:
68
+ Let `init` write the API route the Studio saves through, then load your strings with
69
+ `createTranslations`:
90
70
 
91
71
  ```bash
92
72
  npx livelocale init
93
73
  ```
94
74
 
95
- _The CLI will check for TypeScript/JavaScript and write the appropriate mutation file (`/app/api/livelocale/route.ts` or `/pages/api/livelocale.ts`) directly into your project._
96
-
97
- > [!NOTE]
98
- > For manual setup, create `app/api/livelocale/route.ts` with:
99
- >
100
- > ```typescript
101
- > import { createLiveLocaleHandler } from 'livelocale/server';
102
- > import * as path from 'path';
103
- >
104
- > const handler = createLiveLocaleHandler({
105
- > localesPath: path.resolve('./locales'),
106
- > });
107
- >
108
- > export const GET = handler;
109
- > export const POST = handler;
110
- > export const OPTIONS = handler;
111
- > ```
112
- >
113
- > For the **Pages Router**, create `pages/api/livelocale.ts` instead and default-export the
114
- > same handler. It accepts the `(req, res)` signature automatically:
115
- >
116
- > ```typescript
117
- > import { createLiveLocaleHandler } from 'livelocale/server';
118
- > import * as path from 'path';
119
- >
120
- > export default createLiveLocaleHandler({ localesPath: path.resolve('./locales') });
121
- > ```
122
-
123
- ---
124
-
125
- ### 4. Enable Key Watermarking
126
-
127
- Wrap translation lookups to inject dev-only watermarks.
128
-
129
- #### Option A: Server Components (Direct Loader)
130
-
131
- Use `createTranslations` to load flat JSON files or directory-based namespaces:
132
-
133
75
  ```tsx
76
+ // app/page.tsx
134
77
  import { createTranslations } from 'livelocale/server';
135
78
 
136
79
  export default async function Page({ searchParams }) {
137
80
  const { locale = 'en' } = await searchParams;
138
81
  const t = createTranslations(locale, {
139
- supportedLocales: ['en', 'ar', 'es'],
82
+ supportedLocales: ['en', 'ar'],
140
83
  localesDir: './locales',
141
84
  });
142
85
 
143
- return <h1>{t.home?.welcome}</h1>;
144
- }
145
- ```
146
-
147
- #### Option B: Client-side Hooks (react-i18next or next-intl)
148
-
149
- Wrap hook return values with `wrapTranslationEngine`:
150
-
151
- ```tsx
152
- 'use client';
153
- import { useTranslation } from 'react-i18next';
154
- import { wrapTranslationEngine } from 'livelocale/client';
155
-
156
- export default function Page() {
157
- const { t: rawT } = useTranslation('auth');
158
- const t = wrapTranslationEngine(rawT, { keyPrefix: 'auth' });
159
-
160
- return <h1>{t('login.title')}</h1>;
86
+ return <h1>{t.home?.title}</h1>;
161
87
  }
162
88
  ```
163
89
 
164
- ---
165
-
166
- ### 5. Launch the Visual Studio
167
-
168
- Boot the editing studio next to your local development server:
169
-
170
- ```bash
171
- npx livelocale studio --port 3010 --app http://localhost:3000
172
- ```
173
-
174
- Open `http://localhost:3010` to view the Studio, loaded with your app. `--app` defaults to
175
- `http://localhost:3000`; you can also switch apps without restarting by opening
176
- `http://localhost:3010/?app=http://localhost:3001`.
177
-
178
- The preview bar's address field shows the page you are on. Type a path and press Enter to open it
179
- in the current language. Suggestions list your recent pages and, on a share link, every page of the
180
- site. Reload reloads the page you are on.
181
-
182
- The Studio has the new LiveLocale look, in light and dark. It speaks English and Arabic; the share
183
- link follows the browser language, and the switch is in the header. Share links also follow your
184
- system's light or dark setting; the dev Studio starts dark until you pick System, Light or Dark.
185
- Its fonts ship inside the package, so the Studio loads nothing from Google. Every control works by
186
- keyboard, with a visible focus ring.
187
-
188
- The look is the brand's "hunk" palette: amber fills for the patch, the primary button and the
189
- edited row, with Martian Mono and IBM Plex type. The bundled fonts (Martian Mono, IBM Plex Sans,
190
- IBM Plex Mono and IBM Plex Sans Arabic) are under the SIL Open Font License 1.1; each family's
191
- licence ships in `dist/studio/fonts-licenses/`.
192
-
193
- ---
194
-
195
- ### 6. Check your setup
196
-
197
- ```bash
198
- npx livelocale doctor --app http://localhost:3000
199
- ```
200
-
201
- The doctor verifies the route handler, the provider mount, middleware matchers, frame-blocking
202
- headers, and locale JSON, then probes the running app. It exits non-zero on blocking problems, so
203
- it can also run in CI.
204
-
205
- ---
206
-
207
- ## 🛠️ CLI Reference
208
-
209
- ### `init`
210
-
211
- Generates API route handlers based on your router structure (App vs Pages Router) and configuration language (TypeScript vs JavaScript).
212
-
213
- ```bash
214
- npx livelocale init [--dir <path>]
215
- ```
216
-
217
- ### `init --share`
218
-
219
- Sets up the share link for your stack and, on a GitHub remote, writes
220
- `.github/workflows/livelocale.yml`, which turns change links into pull requests. It prints every
221
- file it creates, updates, leaves unchanged or skips, then what is left for you to do.
222
-
223
- ```bash
224
- npx livelocale init --share [--locales <path>] [--source <locale>] [--branches <a,b>] [--overwrite] [--dir <path>]
225
- ```
226
-
227
- | Stack | Build change |
228
- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
229
- | CRA / webpack / Rsbuild | `"build": "react-scripts build && livelocale pack build --locales <path> --source <locale>"` on CRA; webpack and Rsbuild pack `dist` instead of `build`. This is an explicit `&&`, not a `postbuild` hook, because Yarn Berry and some pnpm versions skip pre/post scripts, and hosts that call the bundler directly skip them too ([yarn](https://v2.yarnpkg.com/advanced/lifecycle-scripts), [pnpm](https://pnpm.io/cli/run)). |
230
- | Next.js `output: 'export'` | `"build:preview": "livelocale build --preview -- next build"`, deployed to a staging location, because the HTML is pre-rendered and must be watermarked at build time. |
231
- | Next.js on Vercel/Node | For the Preview environment, use the build command `livelocale build --preview -- next build`. It packs into a gitignored `public/livelocale/`. |
232
- | Custom CI | `LIVELOCALE=preview <your build>` (only for SSR/SSG), then `npx livelocale pack <outDir>`. |
233
-
234
- For Vite it adds `share: true` to your `liveLocaleVite({ ... })` call. The scripts it writes also
235
- pass `--locales` and `--source`; webpack and Rsbuild pack `dist`.
236
-
237
- - `--locales` — locales directory relative to the project (default: the first of `locales`,
238
- `src/locales`, `public/locales`, `src/i18n/locales` and `messages` that holds locale JSON).
239
- - `--source` — source locale (default `en`).
240
- - `--branches` — branches a change link may target (default: the repository's default branch, at
241
- most 10). Merge the workflow into each of them too: a pull request into a branch runs that
242
- branch's copy of the workflow.
243
- - `--overwrite` — replace an existing workflow file whose content differs (it is skipped otherwise).
244
- - On another git host no workflow is written; use `npx livelocale apply "<link>" --pr`.
245
-
246
- Commit and push the result: the workflow must be on the default branch.
247
-
248
- ### `studio`
249
-
250
- Spins up a lightweight, CORS-enabled HTTP server serving the Visual Studio assets statically.
251
-
252
- ```bash
253
- npx livelocale studio [--port <number>] [--app <origin>]
254
- ```
255
-
256
- - `--port` — port for the Studio itself (default `3010`).
257
- - `--app` — origin of the app being edited (default `http://localhost:3000`).
258
-
259
- ### `doctor`
260
-
261
- Inspects the project for the integration problems the Studio cannot recover from, and optionally
262
- probes the running app.
263
-
264
- ```bash
265
- npx livelocale doctor [--dir <path>] [--locales <path>] [--app <url>] [--share] [--token <token>]
266
- ```
267
-
268
- - `--locales` — locales directory relative to the project (default `locales`).
269
- - `--app` — also check that the mutation API answers and that pages are embeddable. A path is
270
- the app base: `--app https://user.github.io/repo/` probes a GitHub Pages project site (a
271
- trailing `index.html` is dropped).
272
- - `--share` — run the share link probes even when `<base>livelocale/manifest.json` does not
273
- answer. They also run on their own whenever the manifest answers with JSON. They check the
274
- Studio page, the manifest, `flag.js` and its tag first in `<head>`, frame headers
275
- (`X-Frame-Options: DENY` or `frame-ancestors 'none'` breaks the Studio; `SAMEORIGIN` is fine),
276
- `script-src`, and service workers, then print `🔗 Share link: <base>livelocale/index.html`.
277
- When a Workbox service worker (vite-plugin-pwa, workbox-webpack-plugin) would answer the Studio
278
- with your app shell, add this line to its Workbox options:
279
- `navigateFallbackDenylist: [/^\/livelocale\//]`.
280
- - `--token` — access token for a preview deployment (defaults to `LIVELOCALE_TOKEN`); with it the
281
- doctor also reports the server mode and repository access problems.
282
-
283
- ### `check`
284
-
285
- A read-only consistency check of your translations, built for CI. It runs in any `NODE_ENV`.
90
+ Start the app, and the Studio in a second terminal:
286
91
 
287
92
  ```bash
288
- npx livelocale check [--dir <path>] [--locales <path>] [--source <locale>] [--no-unused] [--strict] [--json] [--hardcoded] [--allow <word>]
93
+ npx next dev
94
+ npx livelocale studio
289
95
  ```
290
96
 
291
- | Finding | Level |
292
- | ------------------------------------------------------------------------------- | ------- |
293
- | Locale file is not valid JSON | problem |
294
- | Broken ICU syntax (unbalanced braces, `plural` without `other`) | problem |
295
- | Key used in code but missing from a locale | problem |
296
- | Key in the source locale but missing from another locale | problem |
297
- | Placeholders differ from the source locale (`{name}`, `{{count}}`, `%s`, `<b>`) | problem |
298
- | Empty value | warning |
299
- | Key never referenced in code | warning |
300
-
301
- - `--source` — locale the others are compared against (default `en`, else the first locale).
302
- - `--no-unused` — skip the unused-key report. Keys built at run time (``t(`status.${s}`)``) cannot be
303
- detected, so review this list before deleting anything.
304
- - `--strict` — treat warnings as problems.
305
- - `--json` — print the full report for other tools.
306
-
307
- The command exits with code 1 when there are problems. Code references resolve namespaces the same
308
- way the Studio does, an object access such as `t.home` covers every key under `home`, and i18next
309
- plural families (`items_one`, `items_other`, …) count as one key, so Arabic's six forms against
310
- English's two are not reported.
311
-
312
- ```yaml
313
- # .github/workflows/i18n.yml
314
- - run: npx livelocale check
315
- ```
316
-
317
- ### Catch untranslated text before launch
318
-
319
- `--hardcoded` adds three findings to `check`:
320
-
321
- | Finding | Level |
322
- | ----------------------------------------------------------------------------------------- | ------- |
323
- | `hardcoded`: JSX text or a user-facing attribute that never went through translation | problem |
324
- | `untranslated`: a translation identical to the source locale | warning |
325
- | `wrong-script`: a translation mostly in Latin letters for a non-Latin locale (`ar`, `ja`) | warning |
326
-
327
- Hardcoded text always fails the run. The other two are warnings and fail only with `--strict`.
328
- Text is read from JSX children and from the attributes a user reads or hears (`alt`, `title`,
329
- `placeholder`, `aria-label`, `label` and the other `aria-` text attributes); each finding prints
330
- its file, line and column, and the source text shortened to 60 characters. Translation values are
331
- never printed.
332
-
333
- ```bash
334
- npx livelocale check --hardcoded
335
- ```
336
-
337
- ```yaml
338
- # .github/workflows/i18n.yml
339
- - run: npx livelocale check --hardcoded
340
- ```
341
-
342
- Without `--hardcoded`, `check` behaves as before, so an upgrade does not break an existing pipeline.
343
-
344
- **Opting out.** Put `// livelocale-ignore-next-line` (or `{/* livelocale-ignore-next-line */}`
345
- inside JSX) either on the line above the text or on the line above the element that holds it,
346
- which is where Prettier leaves it for multi-line JSX:
347
-
348
- ```tsx
349
- // livelocale-ignore-next-line
350
- const label = <span>LiveLocale</span>;
351
-
352
- <footer>
353
- {/* livelocale-ignore-next-line */}
354
- <img
355
- src="/brand/livelocale-logo.svg"
356
- alt="LiveLocale logo"
357
- width={120}
358
- height={32}
359
- className="logo"
360
- />
361
- </footer>;
362
- ```
363
-
364
- A `// livelocale-ignore-file` comment at the top of a file skips the whole file. Brand names and
365
- other words that are never translated go in a `livelocale.allow` file at the project root, one per
366
- line; a `key:` line exempts a translation key from `untranslated` and `wrong-script`, for a value
367
- that is the same in every language:
368
-
369
- ```text
370
- # livelocale.allow
371
- LiveLocale
372
- GitHub
373
- key:checkout.brand
374
- ```
375
-
376
- `--allow <word>` adds a word or phrase for one run, and can be repeated. Allowed words are matched
377
- as whole words, case-sensitively.
378
-
379
- **What is skipped.** Test and story files (`*.test.*`, `*.spec.*`, `*.stories.*`, `*.cy.*`,
380
- `*.e2e.*`), the folders `__tests__`, `__mocks__`, `__fixtures__`, `fixtures`, `.storybook`, `e2e`
381
- and `cypress`, `.d.ts` files, folders the key scanner ignores (`node_modules`, build output) and
382
- paths in `.gitignore`. Code-like strings are not reported: URLs, emails, paths, ids, `kebab-case`,
383
- `snake_case`, `camelCase` and `CONSTANT` tokens, dotted keys, and shell commands. Elements such as
384
- `<code>`, `<pre>`, `<kbd>`, `<script>`, `<style>` and `<Trans>` are not looked into.
385
-
386
- **Limits.** Only JSX is checked: Vue, Svelte, Angular and HTML templates are not, and neither are
387
- object literals such as page metadata. And text built at run time is not seen: a template string
388
- with substitutions, a concatenation or a value from an API.
389
-
390
- **Requirement.** The JSX scan uses your project's own `typescript`; without it the other checks
391
- still run and the command exits with code 2. Install it with:
392
-
393
- ```bash
394
- npm install -D typescript
395
- ```
396
-
397
- ### `pack`
398
-
399
- Turns a finished static build into a share link: it writes the Studio, `manifest.json`, the locale
400
- files and `flag.js` into `<outDir>/livelocale/` and links `flag.js` from every HTML page. Run it
401
- after your build, for example `"build": "vite build && livelocale pack dist"`.
402
-
403
- ```bash
404
- npx livelocale pack <outDir> [--dir <path>] [--locales <path>] [--source <locale>] [--base <path>] [--expose-repo] [--no-inject]
405
- ```
406
-
407
- - `<outDir>` — the built site folder, relative to `--dir` (for example `dist`, `build` or `out`).
408
- - `--locales` — locales directory relative to the project (default `locales`).
409
- - `--source` — source locale (default `en`, else the first locale).
410
- - `--base` — site path the app is served from, such as `/repo/` (default: detected from the
411
- project, else `/`). A relative base (`./`) prints a warning; pass an absolute one if the app has
412
- client-side routes.
413
- - `--expose-repo` — put the GitHub `owner/repo` into `livelocale/manifest.json` (off by default).
414
- - `--no-inject` — do not add the `flag.js` script to HTML files. Next.js outputs are never injected.
415
-
416
- The share link is `<your site><base>livelocale/index.html`. Nothing is written outside
417
- `<outDir>/livelocale/` except that one script tag, and an existing `livelocale/` folder is replaced
418
- only when pack wrote it.
419
-
420
- `manifest.json` now lists the site's HTML pages (`pages`, at most 2,000) for the Studio's
421
- suggestions.
422
-
423
- ### `build`
424
-
425
- Runs your build command and packs its output in one step. The environment variables work the same
426
- on Windows, macOS and Linux, so no `cross-env` is needed.
427
-
428
- ```bash
429
- npx livelocale build [--preview] [--out <dir>] [pack options] -- <your build command>
430
- ```
431
-
432
- - `--preview` — run the command with `LIVELOCALE=preview` and `NEXT_PUBLIC_LIVELOCALE=preview`, so
433
- server-rendered and pre-rendered pages carry watermarks.
434
- - `--out` — the build output folder (default: the first of `dist/`, `build/` or `out/` that holds an
435
- `index.html` written by this build). Next.js projects always pack into `public/livelocale/`
436
- before the build runs, so Next copies it into the output.
437
- - `--dir`, `--locales`, `--source`, `--base` and `--expose-repo` work as they do for `pack`.
438
-
439
- | Stack | Build change |
440
- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
441
- | Next.js `output: 'export'` | `"build:preview": "livelocale build --preview -- next build"`, deployed to a staging location, because the HTML is pre-rendered and must be watermarked at build time. |
442
- | Next.js on Vercel/Node | For the Preview environment, use the build command `livelocale build --preview -- next build`. It packs into a gitignored `public/livelocale/`. |
443
- | Custom CI | `LIVELOCALE=preview <your build>` (only for SSR/SSG), then `npx livelocale pack <outDir>`. |
444
-
445
- A static export stops before building when `app/api/livelocale/route.*` exports POST, because export
446
- mode only allows GET route handlers; the share link does not need that route.
447
-
448
- ### `apply`
449
-
450
- Applies a LiveLocale change link (the `lc1.` link a translator sends from the share link) to your
451
- locale files. Every edit is checked again against your own files and source locale before anything
452
- is written.
453
-
454
- ```bash
455
- npx livelocale apply "<link>" [--pr] [--dir <path>] [--locales <path>] [--source <locale>] [--force] [--dry-run]
456
- npx livelocale apply changes.txt
457
- npx livelocale apply - < changes.txt
458
- npx livelocale apply --clipboard
459
- ```
460
-
461
- - The input is the change link itself (or the Studio link that carries it), a file that holds it
462
- (the change file Send offers, or a saved email or issue), `-` to read standard input, or
463
- `--clipboard` to read the clipboard. Pass the link or `--clipboard`, not both.
464
- - cmd.exe cuts command lines at 8,191 characters, so on Windows pass a long link as a file or with
465
- `--clipboard`.
466
- - `--pr` — commit the changes on a new `livelocale/apply-<time>` branch, push it and open a pull
467
- request.
468
- - `--locales` and `--source` work as they do for `check` (default `locales`, and `en`, else the
469
- first locale).
470
- - `--dry-run` — plan the edits and print the summary without writing any file.
471
- - `--force` — also apply edits the translator saved with "Save anyway" (for example a dropped
472
- placeholder). Without it they are reported as `needs --force` and skipped.
473
-
474
- The summary lists each edit's locale, key and status (applied, already done, conflict, needs
475
- --force or rejected) and never prints translation values. The exit code is 0 when every edit was
476
- applied or already done, and 1 when any edit is a conflict, needs `--force` or was rejected, or
477
- when the link cannot be read.
478
-
479
- #### `apply --github-event`
480
-
481
- ```bash
482
- npx livelocale apply --github-event --locales <path> --source <locale> [--branches <a,b>] [--dir <path>]
483
- ```
97
+ Open `http://localhost:3010`, hold Alt (Option on a Mac), click a line of text and press
98
+ **Save Changes**. `git diff locales/` shows the fix.
484
99
 
485
- For the generated `.github/workflows/livelocale.yml` workflow only; do not run it yourself. It reads
486
- the issue, comment, dispatch or pull request event from `$GITHUB_EVENT_PATH` (never from the
487
- command line) and needs `GITHUB_TOKEN`.
100
+ ## Supported versions
488
101
 
489
- - Only a maintainer (OWNER, MEMBER or COLLABORATOR, or write access) makes it write. Anyone else
490
- gets the reply "Waiting for a maintainer: comment `/livelocale apply`".
491
- - It writes one commit on a `livelocale/issue-<n>` branch (`livelocale/run-<id>` for a manual
492
- run), opens or reuses the pull request, and comments the link on the issue. While that pull
493
- request is open, a later apply on the same issue adds one commit to it. Once it is merged or
494
- closed, the next apply opens a new pull request from `livelocale/issue-<n>-<runId>` (with `-2`,
495
- `-3`, … while that name is taken), and later applies add to that one while it is open; a
496
- branch is never force-pushed. When the repository does not let GitHub Actions open pull
497
- requests, the comment carries a one-click compare link instead.
498
- - `--locales` and `--dir` are relative to the repository root. `--branches` lists the branches a
499
- link may target (default: the repository's default branch); a link made from any other branch
500
- targets the default branch, and the comment says so.
501
- - `--force` is not accepted; a maintainer comments `/livelocale apply --force` instead.
502
- - When the pull request is merged or closed, it closes the issue.
102
+ | Needs | Tested in CI |
103
+ | ----------------------------------------- | ------------------------------------------ |
104
+ | Node.js 18 or later | Node.js 20 and 24; the built package on 18 |
105
+ | Next.js 13 or later | Next.js 16 |
106
+ | React 18 or later | React 18 |
107
+ | Vite 5 or later | Vite 8 |
108
+ | TypeScript 4.7 to 6 (`check --hardcoded`) | TypeScript 5 |
503
109
 
504
- ### `migrate`
110
+ From 1.0 on, livelocale follows semantic versioning, and share links people already sent keep
111
+ working in newer versions. [Versioning and stability](https://livelocale.js.org/docs/reference/stability.html)
112
+ lists what that covers and what is internal.
505
113
 
506
- Performs static analysis to scan and automatically wrap existing `react-i18next` hooks with `wrapTranslationEngine`.
507
-
508
- ```bash
509
- npx livelocale migrate [--dir <path>] [--exclude <dirs>] [--dry-run]
510
- ```
511
-
512
- `migrate` needs the `typescript` package to parse your source. It is an optional dependency so
513
- JavaScript-only projects are not forced to install it; `init` and `studio` never need it.
514
-
515
- ---
516
-
517
- ## ⚙️ Non-Next.js Integration (React + Vite SPA)
518
-
519
- If you are using a standard React Client-Side SPA (like Vite), you don't have Next.js API endpoints. You can use the built-in Vite plugin inside `vite.config.ts` to automatically capture the mutation endpoints:
520
-
521
- ```typescript
522
- import { defineConfig } from 'vite';
523
- import react from '@vitejs/plugin-react';
524
- import { liveLocaleVite } from 'livelocale/vite';
525
-
526
- export default defineConfig({
527
- plugins: [
528
- react(),
529
- liveLocaleVite({
530
- localesPath: './locales', // path to your locales directory
531
- }),
532
- ],
533
- });
534
- ```
535
-
536
- ### Share link from `vite build`
537
-
538
- Set `share: true` and `vite build` also writes a share link: the Studio, `manifest.json`, the
539
- locale files and `flag.js` go into `<outDir>/livelocale/`, and `index.html` loads
540
- `<base>livelocale/flag.js` first in `<head>`. Deploy the output anywhere static and send
541
- `<origin><base>livelocale/index.html`. Without `share` the build output is unchanged.
542
-
543
- ```typescript
544
- liveLocaleVite({
545
- localesPath: './locales',
546
- share: true, // write <outDir>/livelocale/ on vite build (default: false)
547
- exposeRepo: false, // put the GitHub owner/repo into the manifest (default: false)
548
- sourceLocale: 'en', // default: en when present, else the first locale
549
- shareBase: '/app/', // the site path the HTML is served from (default: Vite base)
550
- });
551
- ```
552
-
553
- | Option | Default | What it does |
554
- | -------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
555
- | `share` | `false` | Write the share Studio into `<outDir>/livelocale/` on `vite build`. SSR and library builds are skipped. |
556
- | `exposeRepo` | `false` | Put the GitHub `owner/repo` into the published manifest and the change link. Leave it off for a private repository. |
557
- | `sourceLocale` | `en` when present, else the first locale | The locale placeholders are checked against. |
558
- | `shareBase` | Vite `base` | The path the HTML is served from. Set it when `base` is a full URL (a CDN), which the build otherwise rejects with `BAD_BASE`, or when the HTML is served from a path other than `base`. |
559
-
560
- A relative `base` (`./`) links `flag.js` per HTML file and prints a warning: pages served by a
561
- client-side route fallback cannot load it, so prefer an absolute `base` for apps with such routes.
562
-
563
- ---
564
-
565
- ## 🧾 Reviewing and committing from the Studio
566
-
567
- Every save writes to disk immediately. The **Changes** button in the Studio header opens a panel
568
- that keeps those writes reviewable:
569
-
570
- - **Locale files** lists what git sees as modified, added, or deleted under your locales directory,
571
- with the current branch and an on-demand diff.
572
- - **Commit** stages and commits _only_ the locales directory with the message you type. Other
573
- work in your tree is never touched. Push when you are ready.
574
- - **Discard** reverts the locales directory to the last commit (tracked files restored, new locale
575
- files removed) after a confirmation.
576
- - **This session** lists each edit you saved with its previous and new value. **Undo** writes the
577
- previous value back through the normal save path.
578
-
579
- Git integration needs the `git` command on PATH and the locales directory inside a repository. It
580
- is on by default and can be turned off per handler:
581
-
582
- ```typescript
583
- createLiveLocaleHandler({ localesPath: path.resolve('./locales'), git: false });
584
- ```
585
-
586
- Rewrites keep your files diff-friendly: the existing indentation unit (tabs, 2 or 4 spaces) and
587
- trailing newline are preserved.
588
-
589
- ## 🔢 Editing plural forms
590
-
591
- When a message is a single ICU `plural`, `select`, or `selectordinal` argument, optionally with text
592
- around it, the editor replaces the textarea with one field per branch:
593
-
594
- - Fields are the union of the branches already in the message (including explicit cases such as
595
- `=0`) and the CLDR categories of the active locale, from `Intl.PluralRules`. English shows
596
- `one` and `other`; Arabic shows `zero`, `one`, `two`, `few`, `many`, `other`; Japanese shows
597
- `other` only. Required categories that are still empty are flagged.
598
- - `#` stands for the number. Text before and after the argument is editable separately.
599
- - Empty branches are dropped from the saved message, except `other`, which ICU requires.
600
- - **Edit raw** switches back to the plain textarea at any time.
601
-
602
- ## 🧩 Placeholder safety
603
-
604
- Most production incidents caused by copy edits come from a placeholder that went missing. The
605
- Studio and the API guard against that:
606
-
607
- - The editor shows the placeholders found in the reference text as chips. A chip turns red when
608
- your translation no longer contains it and amber when you introduce one the reference lacks.
609
- - On save, the server compares the placeholder sets of the reference and the new value. A
610
- mismatch answers `422 PLACEHOLDER_MISMATCH` and the Studio shows the reason with **Save
611
- anyway** and **Keep editing**.
612
- - Structurally broken ICU messages (unbalanced braces, a `plural`/`select` without an `other`
613
- branch) answer `422 MESSAGE_SYNTAX` with the same override.
614
-
615
- Recognised syntaxes: ICU/next-intl/react-intl `{name}`, `{count, plural, …}` (with apostrophe
616
- quoting), i18next `{{name}}` and `$t(key)`, printf `%s` / `%1$s`, and rich-text tags such as
617
- `<b>` or `<0>`. The reference is the source-language value for other locales, and the previous
618
- value when you edit the source language itself.
619
-
620
- For API clients: send `reference` with the POST to enable the check, and `force: true` to bypass
621
- it. Without `reference` only the syntax check runs.
622
-
623
- ## 🔗 Share link on any host
624
-
625
- Translators fix text on your live site with no account, and every fix becomes a pull request with
626
- one click.
627
-
628
- A share link needs no server, no account and no credential anywhere. The Studio ships inside your
629
- build at `<base>livelocale/`, so any host that serves the site serves the Studio too: Apache,
630
- Nginx, S3, GitHub Pages, Netlify, Cloudflare Pages or Vercel. The translator's fixes stay in their
631
- own browser until they send you a change link. On GitHub you turn that link into a pull request
632
- with one click; on any other git host, with one command.
633
-
634
- ### Setup
635
-
636
- 1. Run `npx livelocale init --share` in your project. It sets up the build for your stack (see the
637
- table below) and, on a GitHub remote, writes `.github/workflows/livelocale.yml`. See
638
- [`init --share`](#init---share) for its options.
639
- 2. Commit and push the result. The workflow must be on the default branch, because GitHub runs
640
- issue and comment workflows only from there.
641
- 3. On GitHub, tick the one setting below.
642
- 4. Deploy as usual, then check the deployment and print the share link (next step).
643
-
644
- On a GitHub remote, `init --share` writes this workflow (`X.Y.Z` is the livelocale version you
645
- installed, `locales`, `en` and `main` come from your project):
646
-
647
- ````yaml
648
- # Written by `npx livelocale init --share`. Turns LiveLocale change links into pull requests.
649
- # livelocale reads the link from the event file; it never reaches a shell command.
650
- name: LiveLocale
651
- on:
652
- issues: { types: [opened] }
653
- issue_comment: { types: [created] }
654
- pull_request: { types: [closed] }
655
- workflow_dispatch: { inputs: { link: { description: 'LiveLocale change link', required: true } } }
656
- permissions: { contents: write, pull-requests: write, issues: write }
657
- concurrency: { group: 'livelocale-${{ github.event.issue.number || github.run_id }}', queue: max }
658
- jobs:
659
- apply:
660
- if: >-
661
- github.event_name == 'workflow_dispatch' ||
662
- (github.event_name == 'issues' && contains(github.event.issue.body, '```livelocale')) ||
663
- (github.event_name == 'issue_comment' && !github.event.issue.pull_request &&
664
- startsWith(github.event.comment.body, '/livelocale')) ||
665
- (github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'livelocale/issue-') &&
666
- github.event.pull_request.base.ref != github.event.repository.default_branch)
667
- runs-on: ubuntu-latest
668
- steps:
669
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
670
- with: { persist-credentials: false }
671
- - run: npx --yes livelocale@X.Y.Z apply --github-event --locales locales --source en --branches main
672
- env: { GITHUB_TOKEN: '${{ github.token }}' }
673
- ````
674
-
675
- It reads the change link only from the event file, never from a shell command, and it has no
676
- `pull_request_target` trigger.
677
-
678
- To set up the build by hand instead:
679
-
680
- | Stack | Build change |
681
- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
682
- | Vite | `share: true` in `liveLocaleVite(...)` (see [Share link from `vite build`](#share-link-from-vite-build)). |
683
- | CRA | `"build": "<bundler> && livelocale pack build --locales <path> --source <locale>"`, for example `"build": "react-scripts build && livelocale pack build --locales locales --source en"`. |
684
- | webpack and Rsbuild | The same, packing `dist`: `"build": "<bundler> && livelocale pack dist --locales <path> --source <locale>"`. |
685
- | Next.js `output: 'export'` | `livelocale build --preview -- next build`, deployed to a staging location, because the HTML is pre-rendered and must be watermarked. |
686
- | Next.js on Vercel/Node | `livelocale build --preview -- next build` as the Preview environment's build command. It packs into a gitignored `public/livelocale/`. |
687
- | Custom CI | `LIVELOCALE=preview <build>` (only for SSR/SSG), then `npx livelocale pack <outDir>`. |
688
-
689
- For CRA, webpack and Rsbuild, use an explicit `&&`, not a `postbuild` script: Yarn Berry and some
690
- pnpm versions skip pre/post scripts, and hosts that call the bundler directly skip them too.
691
-
692
- Deploy the output as usual, then check it and print the share link:
693
-
694
- ```bash
695
- npx livelocale doctor --app <url>
696
- ```
697
-
698
- The doctor checks the Studio page, the manifest, `flag.js`, frame headers and service workers, then
699
- prints `🔗 Share link: <base>livelocale/index.html`. Pin that link for your translators.
700
-
701
- #### The one-checkbox setting
702
-
703
- In the repository's **Settings → Actions → General → Workflow permissions**, tick
704
- **Allow GitHub Actions to create and approve pull requests**. Without it the workflow still
705
- commits the branch, but GitHub refuses to let it open the pull request; the issue comment then
706
- carries a one-click compare link that opens the pull request form already filled in, and you
707
- press **Create pull request** there.
708
-
709
- ### Translator steps
710
-
711
- 1. Open the share link in a desktop browser. There is no sign-up, no password and nothing to
712
- install. If the site has its own login, sign in as usual.
713
- 2. On the first visit, read the three tips and, optionally, type your name so you are credited in
714
- the pull request.
715
- 3. Check the language shown at the top. It is detected from the page text; change it if it is
716
- wrong.
717
- 4. Hold **Alt** (Option on Mac) and click the wrong text. The editor shows the source text, the
718
- current translation, the placeholders that must stay, and one field per plural form.
719
- 5. Type the fix and press **Save** (Ctrl+Enter). When a placeholder is missing, a message explains
720
- it, and **Save anyway** stays available; it marks the edit for the developer.
721
- 6. Keep browsing and fixing. Changes survive reloads and closing the tab. A counter shows how many
722
- are not sent yet ("5 changes not sent"), and **Changes** lists each one with Undo.
723
- 7. Click **Send to developer** and add an optional note. Then choose **Copy link** and paste it in
724
- any chat or email, or **Download file** for a large batch.
725
- 8. Sent changes stay in the list, marked "Sent".
726
-
727
- ### Developer
728
-
729
- Open the change link. The Studio opens in **Review mode**, with the proposed edits shown read-only
730
- on the real page and a before/after table with the check results. Then turn it into a pull request.
731
-
732
- **On GitHub, with the workflow:**
733
-
734
- - Press **Create pull request** in Review mode. It opens a GitHub issue already filled in with the
735
- change link; press **Submit new issue**. About a minute later the workflow has committed the
736
- edits on a `livelocale/issue-<n>` branch, opened the pull request and commented its link on the
737
- issue. Without `exposeRepo: true`, Review mode asks for `owner/repo` the first time and remembers
738
- it in that browser.
739
- - With issues turned off, or for a link you would rather not file: **Actions → LiveLocale → Run
740
- workflow**, paste the link and run it. The result is on the run's summary page.
741
- - **The trust gate.** The workflow writes only for a maintainer: the issue author or the
742
- commenter must be an owner, member or collaborator, or have write access. In an organization
743
- that includes any member or invited collaborator, even one with read-only access: they can turn a
744
- link into a pull request, but only people with write access can merge it. For anyone else it
745
- replies "Waiting for a maintainer: comment `/livelocale apply`", and nothing is written until a
746
- maintainer posts that comment on the issue. Edits the translator saved with "Save anyway" are
747
- skipped as `needs --force`; comment `/livelocale apply --force` to include them.
748
- - **Base branches.** By default every pull request targets the default branch. To let links made
749
- from other builds (for example a `staging` deployment) target their own branch, list the
750
- branches with `npx livelocale init --share --branches main,staging`. A link from any other
751
- branch targets the default branch, and the comment says so. A pull request into the default
752
- branch says `Closes #n`, so merging it closes the issue; into another branch it says `Refs #n`,
753
- because GitHub only closes issues from the default branch, and LiveLocale closes the issue
754
- itself when that pull request is merged or closed. Also merge `.github/workflows/livelocale.yml`
755
- into each extra branch: pull requests into it run workflows from that branch, so LiveLocale cannot
756
- close their issues otherwise.
757
-
758
- **On any git host, from your machine:**
759
-
760
- ```bash
761
- npx livelocale apply "<link or file>" --pr
762
- ```
763
-
764
- This works with your own `git` login: it commits only the locale files on a new
765
- `livelocale/apply-<time>` branch and pushes it. On GitHub it opens the pull request with `gh`;
766
- elsewhere it prints the branch to open a pull or merge request from. Every edit is checked again
767
- against your own files first. For a long link, use `npx livelocale apply --clipboard`, or pass `-`
768
- and pipe the link in (`npx livelocale apply - < changes.txt`). See [`apply`](#apply) for every
769
- option.
770
-
771
- #### Run CI on LiveLocale PRs without the approval click
772
-
773
- Pull requests opened with the workflow's `GITHUB_TOKEN` start their CI in an approval-required
774
- state: someone has to press **Approve workflows to run** on each one. Pull requests opened with a
775
- GitHub App token start CI as usual. To use one:
776
-
777
- 1. Create a GitHub App with **Contents**, **Pull requests** and **Issues** set to read and write,
778
- and install it on the repository.
779
- 2. Store its Client ID (on the App's settings page) as the repository variable
780
- `LIVELOCALE_APP_CLIENT_ID` and its private key as the secret `LIVELOCALE_APP_PRIVATE_KEY`.
781
- 3. In `.github/workflows/livelocale.yml`, add this step before the `apply` step and give its token
782
- to the `apply` step as `GITHUB_TOKEN`:
783
-
784
- ```yaml
785
- - id: app-token
786
- uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
787
- with:
788
- {
789
- client-id: '${{ vars.LIVELOCALE_APP_CLIENT_ID }}',
790
- private-key: '${{ secrets.LIVELOCALE_APP_PRIVATE_KEY }}',
791
- }
792
- - run: npx --yes livelocale@X.Y.Z apply --github-event --locales locales --source en --branches main
793
- env: { GITHUB_TOKEN: '${{ steps.app-token.outputs.token }}' }
794
- ```
795
-
796
- `init --share` leaves an edited workflow alone unless you pass `--overwrite`.
797
-
798
- ### Translators with GitHub access
799
-
800
- **Open on GitHub** in the Send dialog lets a translator file the issue themselves, so the pull
801
- request opens with no developer click when the translator is a collaborator (otherwise a
802
- maintainer comments `/livelocale apply`). It is available only with `exposeRepo: true`, because it
803
- needs the repository's `owner/repo` in the public manifest. Turn it on for a private repository
804
- whose translators already have GitHub access; with the default, translators use **Copy link** or
805
- **Download file**, and the developer path is unchanged.
806
-
807
- ### What "Sent" means
808
-
809
- In this mode "Sent" means the translator handed the link over; it is not a pull request until a
810
- developer applies it.
811
-
812
- ### Hosting notes
813
-
814
- - **S3 and CloudFront:** the link must end in `index.html`, because an S3 REST origin does not
815
- resolve folder indexes.
816
- - **GitHub Pages:** the folder is `livelocale/`, with no underscore for Jekyll to drop, and a
817
- project site's `/repo/` base path is handled.
818
- - **Netlify and Cloudflare Pages:** `_headers` must not send DENY (`X-Frame-Options: DENY` or
819
- `frame-ancestors 'none'`); SAMEORIGIN is fine. Every per-deploy URL is a separate origin with its
820
- own drafts, so share the branch alias (for example
821
- `https://staging--site.netlify.app/livelocale/index.html`), not a per-deploy URL.
822
- - **Basic auth and host password protection** carry over, because the Studio is on the same site.
823
- - **Vercel:** Deployment Protection must be off for the deployment you share, or the translator
824
- must have access to it. `doctor --app` reports the login wall.
825
- - **PWA:** a Workbox service worker (vite-plugin-pwa, workbox-webpack-plugin) can answer the Studio
826
- with your app shell. Add `navigateFallbackDenylist: [/^\/livelocale\//]` to its Workbox options.
827
- - **Relative bases** (`base: './'`, CRA `homepage: '.'`): pages served by a client-side route
828
- fallback cannot load `flag.js`, so pack prints a warning recommending an absolute base
829
- (`--base /path/`) when the app has such routes.
830
-
831
- ### Security
832
-
833
- - **No credential anywhere.** The share link, the build, the host and the browser hold no token.
834
- Write access stays with the developer's own git login.
835
- - **A leaked link only proposes text.** A developer still has to apply it, and a person has to
836
- merge the pull request. `apply` re-validates every edit against your own source locale.
837
- - **`exposeRepo: false` by default.** The GitHub `owner/repo` is published only with
838
- `exposeRepo: true` (or `pack --expose-repo`).
839
- - **The Studio is `noindex`**, so search engines do not list it.
840
- - **`manifest.json` is public.** `<base>livelocale/manifest.json` makes every string and key name
841
- public, including keys the app does not show.
842
- - **The same-origin risk is accepted.** The Studio frames your app on the same origin, so app
843
- scripts can read and alter drafts. This is accepted because no secret is stored, and you review
844
- every edit before it is merged.
845
-
846
- ### Honest risks
847
-
848
- - **Issues in public repositories are public.** A change link filed as an issue, with its text,
849
- the translator's name and note, is visible to everyone.
850
- - **Bot pull requests wait for a click.** CI on a pull request the workflow opens waits until
851
- someone presses **Approve workflows to run**, unless you use the
852
- [App token](#run-ci-on-livelocale-prs-without-the-approval-click).
853
- - **Vercel may refuse the commit.** Vercel can block deployments of commits whose author is not a
854
- member of the Vercel team. The commit author is the maintainer who triggered the run, with a
855
- `Translated-by:` trailer for the translator.
856
- - **One-click automation is GitHub-only.** Other git hosts use
857
- `npx livelocale apply "<link>" --pr`, which works anywhere git works.
858
-
859
- ### Limits
860
-
861
- - **Desktop only.** Alt+Click needs a keyboard; phones and tablets can only view.
862
- - **Drafts live in one browser.** Clearing site data, a private window, or Safari's 7-day cap on
863
- script-writable storage can lose unsent work. Send often, or use **Download backup** in Changes.
864
- - **One app per repository.**
865
-
866
- ## 🌍 Preview mode: editing without a checkout
867
-
868
- Development mode needs a developer's machine. Preview mode lets a product manager, translator, or
869
- reviewer edit copy against a **deployed preview** of the app, with changes going to a git branch
870
- and a pull request instead of a local file. Nothing is written to the server's filesystem.
871
-
872
- ### How it fits together
873
-
874
- 1. The app is deployed from a branch (say `i18n/preview-edits`) with preview mode on.
875
- 2. The Studio, hosted anywhere or run locally with `--app <preview-url>`, embeds that deployment.
876
- 3. Each save calls the app's mutation API, which commits the change to `i18n/preview-edits`
877
- through the GitHub API. The live preview in the iframe updates immediately; the deployment
878
- itself updates when your platform rebuilds the branch.
879
- 4. **Open pull request** in the Changes panel proposes the branch to `main`. Reviewers merge.
880
-
881
- ### Server setup
882
-
883
- ```typescript
884
- // app/api/livelocale/route.ts
885
- import { createLiveLocaleHandler } from 'livelocale/server';
886
-
887
- const handler = createLiveLocaleHandler({
888
- localesPath: 'locales', // repository-relative in GitHub mode
889
- allowedOrigins: ['https://i18n-studio.example.com'], // where the Studio is served from
890
- github: {
891
- repo: 'acme/site',
892
- branch: 'i18n/preview-edits',
893
- baseBranch: 'main',
894
- token: process.env.GITHUB_TOKEN!, // contents: write, pull requests: write
895
- },
896
- });
897
-
898
- export const GET = handler;
899
- export const POST = handler;
900
- export const OPTIONS = handler;
901
- ```
902
-
903
- Environment on the preview deployment:
904
-
905
- ```bash
906
- LIVELOCALE=preview # server: watermark and serve the API in a production build
907
- NEXT_PUBLIC_LIVELOCALE=preview # client bundle: run the scanner and clipboard guards
908
- LIVELOCALE_TOKEN=<long random> # required; the Studio asks for it once per browser session
909
- GITHUB_TOKEN=<fine-grained PAT> # Contents and Pull requests: "Read and write" on the repository
910
- ```
911
-
912
- The app must also let the Studio embed it. Next.js evaluates `headers()` in `next.config` at
913
- **build** time, so a `frame-ancestors` value that lists the Studio origin has to be present when the
914
- preview is built, not only at run time.
915
-
916
- For a Vite app, the Vite plugin mirrors the server's `LIVELOCALE` flag into the page, so
917
- `LIVELOCALE=preview` alone is enough; no `NEXT_PUBLIC_` variable is needed there. Preview mode
918
- runs on the Vite dev server only. For a static `vite build` served by Apache, Nginx or a CDN, use
919
- the share link instead; see [Share link on any host](#-share-link-on-any-host).
920
-
921
- Then open the Studio with `?app=https://<preview-url>` (or `studio --app`). It shows a
922
- **PREVIEW** badge, asks for the access token, and the Changes panel switches to branch and
923
- pull-request actions. Code-reference scanning is off in this mode (there is no checkout), so the
924
- Locale Directory lists keys from the locale files only.
925
-
926
- Check a preview deployment before handing it to anyone:
927
-
928
- ```bash
929
- npx livelocale doctor --app https://<preview-url> --token <LIVELOCALE_TOKEN>
930
- ```
931
-
932
- The server verifies its GitHub token when the Studio connects: a token that cannot write contents
933
- or open pull requests, a missing base branch, or an unknown repository is reported in a banner
934
- in the Studio and by `doctor`, before anyone loses an edit to a failed save.
935
-
936
- Behaviour worth knowing:
937
-
938
- - **Edits stay visible.** The deployment keeps serving the text it was built with until the
939
- branch is rebuilt, so the Studio re-applies this session's saved values on reload and navigation.
940
- - **Concurrent saves are safe.** Saves to one file are queued, and a save that loses a race with
941
- another server instance or another Studio re-reads the file and retries.
942
- - **Discard closes the pull request.** Resetting the branch to the base leaves the pull request
943
- with no changes, and GitHub closes it.
944
- - **Every save is a commit, and most hosts build every commit.** To save build minutes, skip builds
945
- for commits that only touch locale files; the Studio already shows saved edits. On Vercel, set
946
- _Project Settings → Git → Ignored Build Step_ to
947
- `git diff --quiet HEAD^ HEAD -- . ':(exclude)locales'`.
948
- - **Hosting login walls block the Studio.** Vercel's Deployment Protection (on by default for
949
- previews) sends the iframe and the API calls to a Vercel login page. Turn off Vercel
950
- Authentication for the preview deployments you edit; `doctor --app` reports it.
951
-
952
- ### What preview mode does not do
953
-
954
- - It never writes to the deployment's filesystem; the GitHub branch is the only store.
955
- - It does not replace authentication for the app itself. The token only guards the mutation API.
956
- - It is not a production feature. Do not set `LIVELOCALE=preview` on a production deployment.
957
-
958
- ## 🛑 Safety switches
959
-
960
- Everything runs only when `NODE_ENV=development`, or when preview mode is switched on explicitly.
961
- Two more guards exist for teams:
962
-
963
- - **Kill switch.** `LIVELOCALE=off` disables watermarking, the mutation API, and the client scanner.
964
- Client bundles read `NEXT_PUBLIC_LIVELOCALE=off` instead, because Next.js only inlines public
965
- variables.
966
- - **Deployed-platform guard.** If the server detects Vercel (preview or production), Netlify,
967
- Kubernetes, Render, Fly.io, Railway, or Heroku while `NODE_ENV` is still `development`, the tool
968
- switches itself off and logs one warning. Local `vercel dev` and `netlify dev` are unaffected.
969
- Set `LIVELOCALE=force` to override on purpose.
970
-
971
- ## 🪄 When the watermark bites
972
-
973
- In development every translated string starts with invisible characters. Most code never notices,
974
- but anything that treats the string as data will:
975
-
976
- | Symptom | Cause | Fix |
977
- | --------------------------------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------- |
978
- | `t('yes') === 'Yes'` is false | equality on a watermarked string | `unwrap(t('yes')) === 'Yes'` |
979
- | `maxLength` or `.length` checks are off by a few | invisible prefix counts as characters | `unwrap(value).length` |
980
- | `<title>`, `aria-label`, `alt`, `placeholder` show odd spacing in dev tools | attributes do not strip the prefix | pass `unwrap(...)` to attributes, or leave it: it is dev-only |
981
- | Screenshot or snapshot tests differ between dev and prod | watermarks only exist in development | run those tests with `LIVELOCALE=off` |
982
- | A value saved from a form contains zero-width characters | input sanitizers were bypassed (custom components) | call `sanitizeZeroWidth` before submitting |
983
-
984
- `unwrap` and `sanitizeZeroWidth` are exported from both `livelocale/client` and
985
- `livelocale/server`. `unwrap` is deep: it cleans strings inside arrays and objects and never
986
- mutates its input.
987
-
988
- ## 🏢 Running in team and enterprise setups
989
-
990
- The defaults assume `localhost:3000` for the app and `localhost:3010` for the Studio. Anything else
991
- needs one of the knobs below.
992
-
993
- **App on another port or hostname.** Start the Studio with `--app https://app.dev.local:3000`, or
994
- open the Studio with `?app=<origin>`.
995
-
996
- **Studio not on localhost (GitHub Codespaces, Gitpod, a shared dev box).** The mutation API only
997
- accepts writes from localhost origins unless told otherwise. Either pass `allowedOrigins` to
998
- `createLiveLocaleHandler`, or set an environment variable in the app's dev process:
999
-
1000
- ```bash
1001
- LIVELOCALE_ALLOWED_ORIGINS="https://abc-3010.app.github.dev,https://studio.dev.local"
1002
- ```
1003
-
1004
- **Content-Security-Policy blocks the iframe.** If your app sends `frame-ancestors 'none'` or
1005
- `X-Frame-Options: DENY` in development, the Studio cannot embed it. Relax the header only when
1006
- `NODE_ENV === 'development'`, for example by allowing `frame-ancestors 'self' http://localhost:3010`.
1007
-
1008
- **Auth middleware redirects the API.** Exclude `/api/livelocale` from your middleware matcher so the
1009
- Studio can reach the mutation route without a session:
1010
-
1011
- ```typescript
1012
- export const config = { matcher: ['/((?!api/livelocale).*)'] };
1013
- ```
1014
-
1015
- **Not sure which of these applies?** Run `npx livelocale doctor --app <origin>`; it names
1016
- the exact file and header involved.
1017
-
1018
- **Namespaced hooks.** The key scanner reads the namespace from `useTranslations('ns')`,
1019
- `getTranslations('ns')`, `useTranslation('ns')`, `wrapNextIntl(…, 'ns')`, and
1020
- `wrapTranslationEngine(…, { keyPrefix: 'ns' })` in each file, and prefixes the keys it finds. It
1021
- does not follow translator functions passed between files or chosen at run time.
1022
-
1023
- **Monorepos.** `localesPath` is resolved relative to the process working directory of the app, so
1024
- point it at the package that owns the locale files, for example `path.resolve('./src/locales')`.
1025
-
1026
- ---
114
+ ## Docs
1027
115
 
1028
- ## 🔁 Migrating from next-i18n-lens
116
+ - [Get started](https://livelocale.js.org/docs/): Next.js, Vite + React, or any static host.
117
+ - [Next.js, step by step](https://livelocale.js.org/docs/start/nextjs.html): what each step should show.
118
+ - [Share links](https://livelocale.js.org/docs/guides/share-links.html): fixes from the deployed site, no account and no install.
119
+ - [Catch untranslated text in CI](https://livelocale.js.org/docs/guides/ci-check.html): missing keys, placeholder drift and hardcoded text.
120
+ - [CLI reference](https://livelocale.js.org/docs/reference/cli/index.html): every command and flag.
121
+ - [Versioning and stability](https://livelocale.js.org/docs/reference/stability.html): what semver covers, and the versions CI tests.
1029
122
 
1030
- LiveLocale was published as `next-i18n-lens` up to 0.7.0. Version 0.8.0 renames everything, with no
1031
- aliases for the old names:
123
+ ## Contributing
1032
124
 
1033
- 1. `npm uninstall next-i18n-lens && npm install livelocale`, and import from `livelocale/client`,
1034
- `livelocale/server`, `livelocale/react` and `livelocale/vite`.
1035
- 2. `I18nLensProvider` → `LiveLocaleProvider`, `createI18nLensHandler` → `createLiveLocaleHandler`,
1036
- `i18nLensVite` → `liveLocaleVite`, `getLensMode` / `isLensEnabled` → `getLiveLocaleMode` /
1037
- `isLiveLocaleEnabled`; types `I18nLensHandler` → `LiveLocaleHandler` and `LensMode` →
1038
- `LiveLocaleMode` (`ClientInitializer` is gone).
1039
- 3. Move the route file: `app/api/i18n-lens/mutate/route.ts` → `app/api/livelocale/route.ts`
1040
- (Pages Router: `pages/api/i18n-lens/mutate.ts` → `pages/api/livelocale.ts`), or run
1041
- `npx livelocale init` again.
1042
- 4. Rename environment variables: `I18N_LENS` → `LIVELOCALE`, `NEXT_PUBLIC_I18N_LENS` →
1043
- `NEXT_PUBLIC_LIVELOCALE`, `I18N_LENS_TOKEN` → `LIVELOCALE_TOKEN`, `I18N_LENS_ALLOWED_ORIGINS` →
1044
- `LIVELOCALE_ALLOWED_ORIGINS`. Old names are ignored, so a forgotten kill switch stops working:
1045
- check your hosting settings.
1046
- 5. The CLI is `npx livelocale` (`doctor`, `check`, `studio`, `init`, `migrate`).
125
+ - [Contributing guide](CONTRIBUTING.md): setup, checks and changesets.
126
+ - [Security policy](SECURITY.md): report a vulnerability privately.
127
+ - [Code of Conduct](CODE_OF_CONDUCT.md): how we work together.
1047
128
 
1048
- ## ⚖️ License
129
+ ## License
1049
130
 
1050
131
  MIT License. See [LICENSE](LICENSE) for more details.