livelocale 0.0.0-stage โ†’ 0.8.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,533 @@
1
- # Temporary Holding Version
1
+ # LiveLocale
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ <div align="center">
4
+ <p><strong>Click any text in your app. Fix the translation. Ship it as a pull request.</strong></p>
5
+ <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>
6
+
7
+ <p align="center">
8
+ <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>
9
+ <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>
10
+ <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>
11
+ <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>
12
+ </p>
13
+ </div>
14
+
15
+ ---
16
+
17
+ ## ๐Ÿ’ก How It Works: Zero-Width Unicode Watermarking
18
+
19
+ `livelocale` bridges your running application UI with your local JSON translation files using **Zero-Width Unicode Watermarking**.
20
+
21
+ 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.
22
+ 2. **DOM Scanning:** A lightweight dev-only browser listener scans the active DOM, decodes the watermarks, and dynamically hooks visual highlights onto translated elements.
23
+ 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.
24
+ 4. **Zero Production Footprint:** In production mode, all interceptors, listeners, and wrapper logic are bypassed completely, returning plain strings without overhead.
25
+
26
+ ---
27
+
28
+ ## โœจ Features
29
+
30
+ - ๐ŸŽฏ **In-Context Visual Editing:** Hover over any translated text to highlight it; click to instantly edit in the visual panel.
31
+ - ๐Ÿ”ด **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.
32
+ - ๐Ÿ”„ **Smart Fallback Placeholders:** Automatically renders translation placeholders from alternative languages for missing keys in development, keeping them visible and fully click-to-edit inline.
33
+ - ๐Ÿ“ **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.
34
+ - ๐Ÿงผ **Input & Form Sanitization:** Patches controlled inputs and intercepts clipboard actions to strip watermarks in development automatically.
35
+ - โšก **Atomic File Operations:** Prevents Next.js hot module replacement (HMR) reading corrupt half-written files via atomic temp-to-final writes.
36
+ - ๐Ÿ”’ **Secure Local Boundaries:** Restricts directory traversal, verifies origins, and supports deep schemas up to 30 levels of recursion.
37
+ - ๐Ÿค– **CLI Migration Tool:** Automatically parses and wraps standard `react-i18next` hooks with a single command.
38
+ - ๐Ÿ”ข **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.
39
+ - ๐Ÿงญ **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`.
40
+ - ๐Ÿงฉ **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.
41
+ - ๐ŸŒ **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.
42
+ - ๐Ÿงพ **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.
43
+ - ๐Ÿฉบ **Doctor:** `npx livelocale doctor` finds the usual integration mistakes before you open the Studio.
44
+ - โœ… **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.
45
+ - ๐Ÿ›‘ **Safety switches:** `LIVELOCALE=off` kill switch, and automatic shut-off on hosting platforms so a staging build can never leak watermarks.
46
+ - ๐Ÿงญ **App Router and Pages Router:** One handler works with both `route.ts` and `pages/api` signatures.
47
+ - ๐ŸŒ **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.
48
+
49
+ ---
50
+
51
+ ## โšก Quick Setup
52
+
53
+ ### 1. Install Dependency
54
+
55
+ ```bash
56
+ npm install livelocale
57
+ ```
58
+
59
+ ---
60
+
61
+ ### 2. Add the Client Listener
62
+
63
+ Mount the `LiveLocaleProvider` inside your root layout. This launches the dev-mode DOM scanners and intercepts clipboard copy/paste actions.
64
+
65
+ ```tsx
66
+ // app/layout.tsx
67
+ import { LiveLocaleProvider } from 'livelocale/react';
68
+
69
+ export default function RootLayout({ children }) {
70
+ return (
71
+ <html lang="en">
72
+ <body>
73
+ <LiveLocaleProvider />
74
+ {children}
75
+ </body>
76
+ </html>
77
+ );
78
+ }
79
+ ```
80
+
81
+ ---
82
+
83
+ ### 3. Initialize Server Mutation Handler
84
+
85
+ Initialize the server-side API endpoints automatically by running:
86
+
87
+ ```bash
88
+ npx livelocale init
89
+ ```
90
+
91
+ _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._
92
+
93
+ > [!NOTE]
94
+ > For manual setup, create `app/api/livelocale/route.ts` with:
95
+ >
96
+ > ```typescript
97
+ > import { createLiveLocaleHandler } from 'livelocale/server';
98
+ > import * as path from 'path';
99
+ >
100
+ > const handler = createLiveLocaleHandler({
101
+ > localesPath: path.resolve('./locales'),
102
+ > });
103
+ >
104
+ > export const GET = handler;
105
+ > export const POST = handler;
106
+ > export const OPTIONS = handler;
107
+ > ```
108
+ >
109
+ > For the **Pages Router**, create `pages/api/livelocale.ts` instead and default-export the
110
+ > same handler. It accepts the `(req, res)` signature automatically:
111
+ >
112
+ > ```typescript
113
+ > import { createLiveLocaleHandler } from 'livelocale/server';
114
+ > import * as path from 'path';
115
+ >
116
+ > export default createLiveLocaleHandler({ localesPath: path.resolve('./locales') });
117
+ > ```
118
+
119
+ ---
120
+
121
+ ### 4. Enable Key Watermarking
122
+
123
+ Wrap translation lookups to inject dev-only watermarks.
124
+
125
+ #### Option A: Server Components (Direct Loader)
126
+
127
+ Use `createTranslations` to load flat JSON files or directory-based namespaces:
128
+
129
+ ```tsx
130
+ import { createTranslations } from 'livelocale/server';
131
+
132
+ export default async function Page({ searchParams }) {
133
+ const { locale = 'en' } = await searchParams;
134
+ const t = createTranslations(locale, {
135
+ supportedLocales: ['en', 'ar', 'es'],
136
+ localesDir: './locales',
137
+ });
138
+
139
+ return <h1>{t.home?.welcome}</h1>;
140
+ }
141
+ ```
142
+
143
+ #### Option B: Client-side Hooks (react-i18next or next-intl)
144
+
145
+ Wrap hook return values with `wrapTranslationEngine`:
146
+
147
+ ```tsx
148
+ 'use client';
149
+ import { useTranslation } from 'react-i18next';
150
+ import { wrapTranslationEngine } from 'livelocale/client';
151
+
152
+ export default function Page() {
153
+ const { t: rawT } = useTranslation('auth');
154
+ const t = wrapTranslationEngine(rawT, { keyPrefix: 'auth' });
155
+
156
+ return <h1>{t('login.title')}</h1>;
157
+ }
158
+ ```
159
+
160
+ ---
161
+
162
+ ### 5. Launch the Visual Studio
163
+
164
+ Boot the editing studio next to your local development server:
165
+
166
+ ```bash
167
+ npx livelocale studio --port 3010 --app http://localhost:3000
168
+ ```
169
+
170
+ Open `http://localhost:3010` to view the Studio, loaded with your app. `--app` defaults to
171
+ `http://localhost:3000`; you can also switch apps without restarting by opening
172
+ `http://localhost:3010/?app=http://localhost:3001`.
173
+
174
+ ---
175
+
176
+ ### 6. Check your setup
177
+
178
+ ```bash
179
+ npx livelocale doctor --app http://localhost:3000
180
+ ```
181
+
182
+ The doctor verifies the route handler, the provider mount, middleware matchers, frame-blocking
183
+ headers, and locale JSON, then probes the running app. It exits non-zero on blocking problems, so
184
+ it can also run in CI.
185
+
186
+ ---
187
+
188
+ ## ๐Ÿ› ๏ธ CLI Reference
189
+
190
+ ### `init`
191
+
192
+ Generates API route handlers based on your router structure (App vs Pages Router) and configuration language (TypeScript vs JavaScript).
193
+
194
+ ```bash
195
+ npx livelocale init [--dir <path>]
196
+ ```
197
+
198
+ ### `studio`
199
+
200
+ Spins up a lightweight, CORS-enabled HTTP server serving the Visual Studio assets statically.
201
+
202
+ ```bash
203
+ npx livelocale studio [--port <number>] [--app <origin>]
204
+ ```
205
+
206
+ - `--port` โ€” port for the Studio itself (default `3010`).
207
+ - `--app` โ€” origin of the app being edited (default `http://localhost:3000`).
208
+
209
+ ### `doctor`
210
+
211
+ Inspects the project for the integration problems the Studio cannot recover from, and optionally
212
+ probes the running app.
213
+
214
+ ```bash
215
+ npx livelocale doctor [--dir <path>] [--locales <path>] [--app <origin>] [--token <token>]
216
+ ```
217
+
218
+ - `--locales` โ€” locales directory relative to the project (default `locales`).
219
+ - `--app` โ€” also check that the mutation API answers and that pages are embeddable.
220
+ - `--token` โ€” access token for a preview deployment (defaults to `LIVELOCALE_TOKEN`); with it the
221
+ doctor also reports the server mode and repository access problems.
222
+
223
+ ### `check`
224
+
225
+ A read-only consistency check of your translations, built for CI. It runs in any `NODE_ENV`.
226
+
227
+ ```bash
228
+ npx livelocale check [--dir <path>] [--locales <path>] [--source <locale>] [--no-unused] [--strict] [--json]
229
+ ```
230
+
231
+ | Finding | Level |
232
+ | ------------------------------------------------------------------------------- | ------- |
233
+ | Locale file is not valid JSON | problem |
234
+ | Broken ICU syntax (unbalanced braces, `plural` without `other`) | problem |
235
+ | Key used in code but missing from a locale | problem |
236
+ | Key in the source locale but missing from another locale | problem |
237
+ | Placeholders differ from the source locale (`{name}`, `{{count}}`, `%s`, `<b>`) | problem |
238
+ | Empty value | warning |
239
+ | Key never referenced in code | warning |
240
+
241
+ - `--source` โ€” locale the others are compared against (default `en`, else the first locale).
242
+ - `--no-unused` โ€” skip the unused-key report. Keys built at run time (``t(`status.${s}`)``) cannot be
243
+ detected, so review this list before deleting anything.
244
+ - `--strict` โ€” treat warnings as problems.
245
+ - `--json` โ€” print the full report for other tools.
246
+
247
+ The command exits with code 1 when there are problems. Code references resolve namespaces the same
248
+ way the Studio does, an object access such as `t.home` covers every key under `home`, and i18next
249
+ plural families (`items_one`, `items_other`, โ€ฆ) count as one key, so Arabic's six forms against
250
+ English's two are not reported.
251
+
252
+ ```yaml
253
+ # .github/workflows/i18n.yml
254
+ - run: npx livelocale check
255
+ ```
256
+
257
+ ### `migrate`
258
+
259
+ Performs static analysis to scan and automatically wrap existing `react-i18next` hooks with `wrapTranslationEngine`.
260
+
261
+ ```bash
262
+ npx livelocale migrate [--dir <path>] [--exclude <dirs>] [--dry-run]
263
+ ```
264
+
265
+ `migrate` needs the `typescript` package to parse your source. It is an optional dependency so
266
+ JavaScript-only projects are not forced to install it; `init` and `studio` never need it.
267
+
268
+ ---
269
+
270
+ ## โš™๏ธ Non-Next.js Integration (React + Vite SPA)
271
+
272
+ 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:
273
+
274
+ ```typescript
275
+ import { defineConfig } from 'vite';
276
+ import react from '@vitejs/plugin-react';
277
+ import { liveLocaleVite } from 'livelocale/vite';
278
+
279
+ export default defineConfig({
280
+ plugins: [
281
+ react(),
282
+ liveLocaleVite({
283
+ localesPath: './locales', // path to your locales directory
284
+ }),
285
+ ],
286
+ });
287
+ ```
288
+
289
+ ---
290
+
291
+ ## ๐Ÿงพ Reviewing and committing from the Studio
292
+
293
+ Every save writes to disk immediately. The **Changes** button in the Studio header opens a panel
294
+ that keeps those writes reviewable:
295
+
296
+ - **Locale files** lists what git sees as modified, added, or deleted under your locales directory,
297
+ with the current branch and an on-demand diff.
298
+ - **Commit** stages and commits _only_ the locales directory with the message you type. Other
299
+ work in your tree is never touched. Push when you are ready.
300
+ - **Discard** reverts the locales directory to the last commit (tracked files restored, new locale
301
+ files removed) after a confirmation.
302
+ - **This session** lists each edit you saved with its previous and new value. **Undo** writes the
303
+ previous value back through the normal save path.
304
+
305
+ Git integration needs the `git` command on PATH and the locales directory inside a repository. It
306
+ is on by default and can be turned off per handler:
307
+
308
+ ```typescript
309
+ createLiveLocaleHandler({ localesPath: path.resolve('./locales'), git: false });
310
+ ```
311
+
312
+ Rewrites keep your files diff-friendly: the existing indentation unit (tabs, 2 or 4 spaces) and
313
+ trailing newline are preserved.
314
+
315
+ ## ๐Ÿ”ข Editing plural forms
316
+
317
+ When a message is a single ICU `plural`, `select`, or `selectordinal` argument, optionally with text
318
+ around it, the editor replaces the textarea with one field per branch:
319
+
320
+ - Fields are the union of the branches already in the message (including explicit cases such as
321
+ `=0`) and the CLDR categories of the active locale, from `Intl.PluralRules`. English shows
322
+ `one` and `other`; Arabic shows `zero`, `one`, `two`, `few`, `many`, `other`; Japanese shows
323
+ `other` only. Required categories that are still empty are flagged.
324
+ - `#` stands for the number. Text before and after the argument is editable separately.
325
+ - Empty branches are dropped from the saved message, except `other`, which ICU requires.
326
+ - **Edit raw** switches back to the plain textarea at any time.
327
+
328
+ ## ๐Ÿงฉ Placeholder safety
329
+
330
+ Most production incidents caused by copy edits come from a placeholder that went missing. The
331
+ Studio and the API guard against that:
332
+
333
+ - The editor shows the placeholders found in the reference text as chips. A chip turns red when
334
+ your translation no longer contains it and amber when you introduce one the reference lacks.
335
+ - On save, the server compares the placeholder sets of the reference and the new value. A
336
+ mismatch answers `422 PLACEHOLDER_MISMATCH` and the Studio shows the reason with **Save
337
+ anyway** and **Keep editing**.
338
+ - Structurally broken ICU messages (unbalanced braces, a `plural`/`select` without an `other`
339
+ branch) answer `422 MESSAGE_SYNTAX` with the same override.
340
+
341
+ Recognised syntaxes: ICU/next-intl/react-intl `{name}`, `{count, plural, โ€ฆ}` (with apostrophe
342
+ quoting), i18next `{{name}}` and `$t(key)`, printf `%s` / `%1$s`, and rich-text tags such as
343
+ `<b>` or `<0>`. The reference is the source-language value for other locales, and the previous
344
+ value when you edit the source language itself.
345
+
346
+ For API clients: send `reference` with the POST to enable the check, and `force: true` to bypass
347
+ it. Without `reference` only the syntax check runs.
348
+
349
+ ## ๐ŸŒ Preview mode: editing without a checkout
350
+
351
+ Development mode needs a developer's machine. Preview mode lets a product manager, translator, or
352
+ reviewer edit copy against a **deployed preview** of the app, with changes going to a git branch
353
+ and a pull request instead of a local file. Nothing is written to the server's filesystem.
354
+
355
+ ### How it fits together
356
+
357
+ 1. The app is deployed from a branch (say `i18n/preview-edits`) with preview mode on.
358
+ 2. The Studio, hosted anywhere or run locally with `--app <preview-url>`, embeds that deployment.
359
+ 3. Each save calls the app's mutation API, which commits the change to `i18n/preview-edits`
360
+ through the GitHub API. The live preview in the iframe updates immediately; the deployment
361
+ itself updates when your platform rebuilds the branch.
362
+ 4. **Open pull request** in the Changes panel proposes the branch to `main`. Reviewers merge.
363
+
364
+ ### Server setup
365
+
366
+ ```typescript
367
+ // app/api/livelocale/route.ts
368
+ import { createLiveLocaleHandler } from 'livelocale/server';
369
+
370
+ const handler = createLiveLocaleHandler({
371
+ localesPath: 'locales', // repository-relative in GitHub mode
372
+ allowedOrigins: ['https://i18n-studio.example.com'], // where the Studio is served from
373
+ github: {
374
+ repo: 'acme/site',
375
+ branch: 'i18n/preview-edits',
376
+ baseBranch: 'main',
377
+ token: process.env.GITHUB_TOKEN!, // contents: write, pull requests: write
378
+ },
379
+ });
380
+
381
+ export const GET = handler;
382
+ export const POST = handler;
383
+ export const OPTIONS = handler;
384
+ ```
385
+
386
+ Environment on the preview deployment:
387
+
388
+ ```bash
389
+ LIVELOCALE=preview # server: watermark and serve the API in a production build
390
+ NEXT_PUBLIC_LIVELOCALE=preview # client bundle: run the scanner and clipboard guards
391
+ LIVELOCALE_TOKEN=<long random> # required; the Studio asks for it once per browser session
392
+ GITHUB_TOKEN=<fine-grained PAT> # Contents and Pull requests: "Read and write" on the repository
393
+ ```
394
+
395
+ The app must also let the Studio embed it. Next.js evaluates `headers()` in `next.config` at
396
+ **build** time, so a `frame-ancestors` value that lists the Studio origin has to be present when the
397
+ preview is built, not only at run time.
398
+
399
+ For a Vite app, the Vite plugin mirrors the server's `LIVELOCALE` flag into the page, so
400
+ `LIVELOCALE=preview` alone is enough; no `NEXT_PUBLIC_` variable is needed there. The plugin runs
401
+ on the Vite dev server only: a static `vite build` served by Apache, Nginx or a CDN has no server for
402
+ the Studio to save through, so preview mode does not work there yet.
403
+
404
+ Then open the Studio with `?app=https://<preview-url>` (or `studio --app`). It shows a
405
+ **PREVIEW** badge, asks for the access token, and the Changes panel switches to branch and
406
+ pull-request actions. Code-reference scanning is off in this mode (there is no checkout), so the
407
+ Locale Directory lists keys from the locale files only.
408
+
409
+ Check a preview deployment before handing it to anyone:
410
+
411
+ ```bash
412
+ npx livelocale doctor --app https://<preview-url> --token <LIVELOCALE_TOKEN>
413
+ ```
414
+
415
+ The server verifies its GitHub token when the Studio connects: a token that cannot write contents
416
+ or open pull requests, a missing base branch, or an unknown repository is reported in a banner
417
+ in the Studio and by `doctor`, before anyone loses an edit to a failed save.
418
+
419
+ Behaviour worth knowing:
420
+
421
+ - **Edits stay visible.** The deployment keeps serving the text it was built with until the
422
+ branch is rebuilt, so the Studio re-applies this session's saved values on reload and navigation.
423
+ - **Concurrent saves are safe.** Saves to one file are queued, and a save that loses a race with
424
+ another server instance or another Studio re-reads the file and retries.
425
+ - **Discard closes the pull request.** Resetting the branch to the base leaves the pull request
426
+ with no changes, and GitHub closes it.
427
+ - **Every save is a commit, and most hosts build every commit.** To save build minutes, skip builds
428
+ for commits that only touch locale files; the Studio already shows saved edits. On Vercel, set
429
+ _Project Settings โ†’ Git โ†’ Ignored Build Step_ to
430
+ `git diff --quiet HEAD^ HEAD -- . ':(exclude)locales'`.
431
+ - **Hosting login walls block the Studio.** Vercel's Deployment Protection (on by default for
432
+ previews) sends the iframe and the API calls to a Vercel login page. Turn off Vercel
433
+ Authentication for the preview deployments you edit; `doctor --app` reports it.
434
+
435
+ ### What preview mode does not do
436
+
437
+ - It never writes to the deployment's filesystem; the GitHub branch is the only store.
438
+ - It does not replace authentication for the app itself. The token only guards the mutation API.
439
+ - It is not a production feature. Do not set `LIVELOCALE=preview` on a production deployment.
440
+
441
+ ## ๐Ÿ›‘ Safety switches
442
+
443
+ Everything runs only when `NODE_ENV=development`, or when preview mode is switched on explicitly.
444
+ Two more guards exist for teams:
445
+
446
+ - **Kill switch.** `LIVELOCALE=off` disables watermarking, the mutation API, and the client scanner.
447
+ Client bundles read `NEXT_PUBLIC_LIVELOCALE=off` instead, because Next.js only inlines public
448
+ variables.
449
+ - **Deployed-platform guard.** If the server detects Vercel (preview or production), Netlify,
450
+ Kubernetes, Render, Fly.io, Railway, or Heroku while `NODE_ENV` is still `development`, the tool
451
+ switches itself off and logs one warning. Local `vercel dev` and `netlify dev` are unaffected.
452
+ Set `LIVELOCALE=force` to override on purpose.
453
+
454
+ ## ๐Ÿช„ When the watermark bites
455
+
456
+ In development every translated string starts with invisible characters. Most code never notices,
457
+ but anything that treats the string as data will:
458
+
459
+ | Symptom | Cause | Fix |
460
+ | --------------------------------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------- |
461
+ | `t('yes') === 'Yes'` is false | equality on a watermarked string | `unwrap(t('yes')) === 'Yes'` |
462
+ | `maxLength` or `.length` checks are off by a few | invisible prefix counts as characters | `unwrap(value).length` |
463
+ | `<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 |
464
+ | Screenshot or snapshot tests differ between dev and prod | watermarks only exist in development | run those tests with `LIVELOCALE=off` |
465
+ | A value saved from a form contains zero-width characters | input sanitizers were bypassed (custom components) | call `sanitizeZeroWidth` before submitting |
466
+
467
+ `unwrap` and `sanitizeZeroWidth` are exported from both `livelocale/client` and
468
+ `livelocale/server`. `unwrap` is deep: it cleans strings inside arrays and objects and never
469
+ mutates its input.
470
+
471
+ ## ๐Ÿข Running in team and enterprise setups
472
+
473
+ The defaults assume `localhost:3000` for the app and `localhost:3010` for the Studio. Anything else
474
+ needs one of the knobs below.
475
+
476
+ **App on another port or hostname.** Start the Studio with `--app https://app.dev.local:3000`, or
477
+ open the Studio with `?app=<origin>`.
478
+
479
+ **Studio not on localhost (GitHub Codespaces, Gitpod, a shared dev box).** The mutation API only
480
+ accepts writes from localhost origins unless told otherwise. Either pass `allowedOrigins` to
481
+ `createLiveLocaleHandler`, or set an environment variable in the app's dev process:
482
+
483
+ ```bash
484
+ LIVELOCALE_ALLOWED_ORIGINS="https://abc-3010.app.github.dev,https://studio.dev.local"
485
+ ```
486
+
487
+ **Content-Security-Policy blocks the iframe.** If your app sends `frame-ancestors 'none'` or
488
+ `X-Frame-Options: DENY` in development, the Studio cannot embed it. Relax the header only when
489
+ `NODE_ENV === 'development'`, for example by allowing `frame-ancestors 'self' http://localhost:3010`.
490
+
491
+ **Auth middleware redirects the API.** Exclude `/api/livelocale` from your middleware matcher so the
492
+ Studio can reach the mutation route without a session:
493
+
494
+ ```typescript
495
+ export const config = { matcher: ['/((?!api/livelocale).*)'] };
496
+ ```
497
+
498
+ **Not sure which of these applies?** Run `npx livelocale doctor --app <origin>`; it names
499
+ the exact file and header involved.
500
+
501
+ **Namespaced hooks.** The key scanner reads the namespace from `useTranslations('ns')`,
502
+ `getTranslations('ns')`, `useTranslation('ns')`, `wrapNextIntl(โ€ฆ, 'ns')`, and
503
+ `wrapTranslationEngine(โ€ฆ, { keyPrefix: 'ns' })` in each file, and prefixes the keys it finds. It
504
+ does not follow translator functions passed between files or chosen at run time.
505
+
506
+ **Monorepos.** `localesPath` is resolved relative to the process working directory of the app, so
507
+ point it at the package that owns the locale files, for example `path.resolve('./src/locales')`.
508
+
509
+ ---
510
+
511
+ ## ๐Ÿ” Migrating from next-i18n-lens
512
+
513
+ LiveLocale was published as `next-i18n-lens` up to 0.7.0. Version 0.8.0 renames everything, with no
514
+ aliases for the old names:
515
+
516
+ 1. `npm uninstall next-i18n-lens && npm install livelocale`, and import from `livelocale/client`,
517
+ `livelocale/server`, `livelocale/react` and `livelocale/vite`.
518
+ 2. `I18nLensProvider` โ†’ `LiveLocaleProvider`, `createI18nLensHandler` โ†’ `createLiveLocaleHandler`,
519
+ `i18nLensVite` โ†’ `liveLocaleVite`, `getLensMode` / `isLensEnabled` โ†’ `getLiveLocaleMode` /
520
+ `isLiveLocaleEnabled`; types `I18nLensHandler` โ†’ `LiveLocaleHandler` and `LensMode` โ†’
521
+ `LiveLocaleMode` (`ClientInitializer` is gone).
522
+ 3. Move the route file: `app/api/i18n-lens/mutate/route.ts` โ†’ `app/api/livelocale/route.ts`
523
+ (Pages Router: `pages/api/i18n-lens/mutate.ts` โ†’ `pages/api/livelocale.ts`), or run
524
+ `npx livelocale init` again.
525
+ 4. Rename environment variables: `I18N_LENS` โ†’ `LIVELOCALE`, `NEXT_PUBLIC_I18N_LENS` โ†’
526
+ `NEXT_PUBLIC_LIVELOCALE`, `I18N_LENS_TOKEN` โ†’ `LIVELOCALE_TOKEN`, `I18N_LENS_ALLOWED_ORIGINS` โ†’
527
+ `LIVELOCALE_ALLOWED_ORIGINS`. Old names are ignored, so a forgotten kill switch stops working:
528
+ check your hosting settings.
529
+ 5. The CLI is `npx livelocale` (`doctor`, `check`, `studio`, `init`, `migrate`).
530
+
531
+ ## โš–๏ธ License
532
+
533
+ MIT License. See [LICENSE](LICENSE) for more details.