livelocale 0.11.0-next.10 โ 0.11.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 +93 -2
- package/dist/client/index.cjs +14 -12
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.js +14 -12
- package/dist/client/index.js.map +1 -1
- package/dist/codemod/index.cjs +765 -312
- package/dist/codemod/index.cjs.map +1 -1
- package/dist/codemod/index.js +763 -310
- package/dist/codemod/index.js.map +1 -1
- package/dist/react/index.cjs +14 -12
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.js +14 -12
- package/dist/react/index.js.map +1 -1
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/studio/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/dist/studio/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/dist/studio/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/dist/studio/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/dist/studio/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
- package/dist/studio/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
- package/dist/studio/assets/ibm-plex-sans-arabic-arabic-400-normal-CyU-ddYS.woff2 +0 -0
- package/dist/studio/assets/ibm-plex-sans-arabic-arabic-400-normal-EA1o-u_Q.woff +0 -0
- package/dist/studio/assets/ibm-plex-sans-arabic-arabic-500-normal-C4MQITzh.woff2 +0 -0
- package/dist/studio/assets/ibm-plex-sans-arabic-arabic-500-normal-DPXcppbm.woff +0 -0
- package/dist/studio/assets/ibm-plex-sans-arabic-arabic-600-normal-0pRdybE_.woff2 +0 -0
- package/dist/studio/assets/ibm-plex-sans-arabic-arabic-600-normal-MJZ0liF5.woff +0 -0
- package/dist/studio/assets/ibm-plex-sans-arabic-arabic-700-normal-9rXc6cUL.woff +0 -0
- package/dist/studio/assets/ibm-plex-sans-arabic-arabic-700-normal-DrtBj6UE.woff2 +0 -0
- package/dist/studio/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
- package/dist/studio/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
- package/dist/studio/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
- package/dist/studio/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
- package/dist/studio/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
- package/dist/studio/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
- package/dist/studio/assets/index-BNCZW1xW.css +1 -0
- package/dist/studio/assets/index-DriSmNkY.js +14 -0
- package/dist/studio/assets/martian-mono-latin-500-normal-BWC8e-kx.woff2 +0 -0
- package/dist/studio/assets/martian-mono-latin-500-normal-BsOs1ffX.woff +0 -0
- package/dist/studio/assets/martian-mono-latin-700-normal-BEEYYU4J.woff2 +0 -0
- package/dist/studio/assets/martian-mono-latin-700-normal-CkDhVUqs.woff +0 -0
- package/dist/studio/fonts-licenses/ibm-plex-mono.txt +93 -0
- package/dist/studio/fonts-licenses/ibm-plex-sans-arabic.txt +93 -0
- package/dist/studio/fonts-licenses/ibm-plex-sans.txt +93 -0
- package/dist/studio/fonts-licenses/martian-mono.txt +93 -0
- package/dist/studio/index.html +3 -3
- package/dist/vite/index.cjs.map +1 -1
- package/dist/vite/index.js.map +1 -1
- package/package.json +12 -4
- package/dist/studio/assets/index-167fHSh2.js +0 -14
- package/dist/studio/assets/index-GgcTqdBQ.css +0 -1
- package/dist/studio/assets/inter-latin-300-normal-BVlfKGgI.woff2 +0 -0
- package/dist/studio/assets/inter-latin-300-normal-i8F0SvXL.woff +0 -0
- package/dist/studio/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
- package/dist/studio/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
- package/dist/studio/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
- package/dist/studio/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
- package/dist/studio/assets/inter-latin-600-normal-CiBQ2DWP.woff +0 -0
- package/dist/studio/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
- package/dist/studio/assets/inter-latin-700-normal-BLAVimhd.woff +0 -0
- package/dist/studio/assets/inter-latin-700-normal-Yt3aPRUw.woff2 +0 -0
- package/dist/studio/assets/outfit-latin-400-normal-BGsTXAXT.woff2 +0 -0
- package/dist/studio/assets/outfit-latin-400-normal-DMwTpYkH.woff +0 -0
- package/dist/studio/assets/outfit-latin-500-normal-ClnHRwRh.woff +0 -0
- package/dist/studio/assets/outfit-latin-500-normal-DKnIMDSk.woff2 +0 -0
- package/dist/studio/assets/outfit-latin-600-normal-B7SfZ07L.woff2 +0 -0
- package/dist/studio/assets/outfit-latin-600-normal-BEfTtDA7.woff +0 -0
- package/dist/studio/assets/outfit-latin-700-normal-Cu9v6i1X.woff2 +0 -0
- package/dist/studio/assets/outfit-latin-700-normal-D4itBLBr.woff +0 -0
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@
|
|
|
41
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
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
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.
|
|
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, and, with `--hardcoded`, text that never went through translation.
|
|
45
45
|
- ๐ **Safety switches:** `LIVELOCALE=off` kill switch, and automatic shut-off on hosting platforms so a staging build can never leak watermarks.
|
|
46
46
|
- ๐งญ **App Router and Pages Router:** One handler works with both `route.ts` and `pages/api` signatures.
|
|
47
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.
|
|
@@ -171,6 +171,17 @@ Open `http://localhost:3010` to view the Studio, loaded with your app. `--app` d
|
|
|
171
171
|
`http://localhost:3000`; you can also switch apps without restarting by opening
|
|
172
172
|
`http://localhost:3010/?app=http://localhost:3001`.
|
|
173
173
|
|
|
174
|
+
The Studio has the new LiveLocale look, in light and dark. It speaks English and Arabic; the share
|
|
175
|
+
link follows the browser language, and the switch is in the header. Share links also follow your
|
|
176
|
+
system's light or dark setting; the dev Studio starts dark until you pick System, Light or Dark.
|
|
177
|
+
Its fonts ship inside the package, so the Studio loads nothing from Google. Every control works by
|
|
178
|
+
keyboard, with a visible focus ring.
|
|
179
|
+
|
|
180
|
+
The look is the brand's "hunk" palette: amber fills for the patch, the primary button and the
|
|
181
|
+
edited row, with Martian Mono and IBM Plex type. The bundled fonts (Martian Mono, IBM Plex Sans,
|
|
182
|
+
IBM Plex Mono and IBM Plex Sans Arabic) are under the SIL Open Font License 1.1; each family's
|
|
183
|
+
licence ships in `dist/studio/fonts-licenses/`.
|
|
184
|
+
|
|
174
185
|
---
|
|
175
186
|
|
|
176
187
|
### 6. Check your setup
|
|
@@ -266,7 +277,7 @@ npx livelocale doctor [--dir <path>] [--locales <path>] [--app <url>] [--share]
|
|
|
266
277
|
A read-only consistency check of your translations, built for CI. It runs in any `NODE_ENV`.
|
|
267
278
|
|
|
268
279
|
```bash
|
|
269
|
-
npx livelocale check [--dir <path>] [--locales <path>] [--source <locale>] [--no-unused] [--strict] [--json]
|
|
280
|
+
npx livelocale check [--dir <path>] [--locales <path>] [--source <locale>] [--no-unused] [--strict] [--json] [--hardcoded] [--allow <word>]
|
|
270
281
|
```
|
|
271
282
|
|
|
272
283
|
| Finding | Level |
|
|
@@ -295,6 +306,86 @@ English's two are not reported.
|
|
|
295
306
|
- run: npx livelocale check
|
|
296
307
|
```
|
|
297
308
|
|
|
309
|
+
### Catch untranslated text before launch
|
|
310
|
+
|
|
311
|
+
`--hardcoded` adds three findings to `check`:
|
|
312
|
+
|
|
313
|
+
| Finding | Level |
|
|
314
|
+
| ----------------------------------------------------------------------------------------- | ------- |
|
|
315
|
+
| `hardcoded`: JSX text or a user-facing attribute that never went through translation | problem |
|
|
316
|
+
| `untranslated`: a translation identical to the source locale | warning |
|
|
317
|
+
| `wrong-script`: a translation mostly in Latin letters for a non-Latin locale (`ar`, `ja`) | warning |
|
|
318
|
+
|
|
319
|
+
Hardcoded text always fails the run. The other two are warnings and fail only with `--strict`.
|
|
320
|
+
Text is read from JSX children and from the attributes a user reads or hears (`alt`, `title`,
|
|
321
|
+
`placeholder`, `aria-label`, `label` and the other `aria-` text attributes); each finding prints
|
|
322
|
+
its file, line and column, and the source text shortened to 60 characters. Translation values are
|
|
323
|
+
never printed.
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
npx livelocale check --hardcoded
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
```yaml
|
|
330
|
+
# .github/workflows/i18n.yml
|
|
331
|
+
- run: npx livelocale check --hardcoded
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Without `--hardcoded`, `check` behaves as before, so an upgrade does not break an existing pipeline.
|
|
335
|
+
|
|
336
|
+
**Opting out.** Put `// livelocale-ignore-next-line` (or `{/* livelocale-ignore-next-line */}`
|
|
337
|
+
inside JSX) either on the line above the text or on the line above the element that holds it,
|
|
338
|
+
which is where Prettier leaves it for multi-line JSX:
|
|
339
|
+
|
|
340
|
+
```tsx
|
|
341
|
+
// livelocale-ignore-next-line
|
|
342
|
+
const label = <span>LiveLocale</span>;
|
|
343
|
+
|
|
344
|
+
<footer>
|
|
345
|
+
{/* livelocale-ignore-next-line */}
|
|
346
|
+
<img
|
|
347
|
+
src="/brand/livelocale-logo.svg"
|
|
348
|
+
alt="LiveLocale logo"
|
|
349
|
+
width={120}
|
|
350
|
+
height={32}
|
|
351
|
+
className="logo"
|
|
352
|
+
/>
|
|
353
|
+
</footer>;
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
A `// livelocale-ignore-file` comment at the top of a file skips the whole file. Brand names and
|
|
357
|
+
other words that are never translated go in a `livelocale.allow` file at the project root, one per
|
|
358
|
+
line; a `key:` line exempts a translation key from `untranslated` and `wrong-script`, for a value
|
|
359
|
+
that is the same in every language:
|
|
360
|
+
|
|
361
|
+
```text
|
|
362
|
+
# livelocale.allow
|
|
363
|
+
LiveLocale
|
|
364
|
+
GitHub
|
|
365
|
+
key:checkout.brand
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
`--allow <word>` adds a word or phrase for one run, and can be repeated. Allowed words are matched
|
|
369
|
+
as whole words, case-sensitively.
|
|
370
|
+
|
|
371
|
+
**What is skipped.** Test and story files (`*.test.*`, `*.spec.*`, `*.stories.*`, `*.cy.*`,
|
|
372
|
+
`*.e2e.*`), the folders `__tests__`, `__mocks__`, `__fixtures__`, `fixtures`, `.storybook`, `e2e`
|
|
373
|
+
and `cypress`, `.d.ts` files, folders the key scanner ignores (`node_modules`, build output) and
|
|
374
|
+
paths in `.gitignore`. Code-like strings are not reported: URLs, emails, paths, ids, `kebab-case`,
|
|
375
|
+
`snake_case`, `camelCase` and `CONSTANT` tokens, dotted keys, and shell commands. Elements such as
|
|
376
|
+
`<code>`, `<pre>`, `<kbd>`, `<script>`, `<style>` and `<Trans>` are not looked into.
|
|
377
|
+
|
|
378
|
+
**Limits.** Only JSX is checked: Vue, Svelte, Angular and HTML templates are not, and neither are
|
|
379
|
+
object literals such as page metadata. And text built at run time is not seen: a template string
|
|
380
|
+
with substitutions, a concatenation or a value from an API.
|
|
381
|
+
|
|
382
|
+
**Requirement.** The JSX scan uses your project's own `typescript`; without it the other checks
|
|
383
|
+
still run and the command exits with code 2. Install it with:
|
|
384
|
+
|
|
385
|
+
```bash
|
|
386
|
+
npm install -D typescript
|
|
387
|
+
```
|
|
388
|
+
|
|
298
389
|
### `pack`
|
|
299
390
|
|
|
300
391
|
Turns a finished static build into a share link: it writes the Studio, `manifest.json`, the locale
|
package/dist/client/index.cjs
CHANGED
|
@@ -480,9 +480,12 @@ var DOMInterceptor = class {
|
|
|
480
480
|
"white-space: nowrap",
|
|
481
481
|
"overflow: hidden",
|
|
482
482
|
"text-overflow: ellipsis",
|
|
483
|
-
"background:
|
|
484
|
-
|
|
485
|
-
"
|
|
483
|
+
"background: #0C0D0F",
|
|
484
|
+
// Night
|
|
485
|
+
"border: 1px solid #4FC3DC",
|
|
486
|
+
// Hunk
|
|
487
|
+
"color: #E8E4D9",
|
|
488
|
+
// Paper
|
|
486
489
|
"font-family: ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
487
490
|
"font-size: 10px",
|
|
488
491
|
"font-weight: 600",
|
|
@@ -490,7 +493,6 @@ var DOMInterceptor = class {
|
|
|
490
493
|
"padding: 3px 7px",
|
|
491
494
|
"border-radius: 4px",
|
|
492
495
|
"letter-spacing: 0.02em",
|
|
493
|
-
"backdrop-filter: blur(4px)",
|
|
494
496
|
"pointer-events: none"
|
|
495
497
|
].join("; ");
|
|
496
498
|
overlay.appendChild(badge);
|
|
@@ -522,16 +524,16 @@ var DOMInterceptor = class {
|
|
|
522
524
|
if (!this.overlay || !this.overlayBadge) return;
|
|
523
525
|
const key = this.hoveredElement?.getAttribute("data-i18n-key") || "";
|
|
524
526
|
if (this.isAltHeld) {
|
|
525
|
-
this.overlay.style.border = "2px solid #
|
|
526
|
-
this.overlay.style.boxShadow = "
|
|
527
|
-
this.overlayBadge.style.color = "#
|
|
528
|
-
this.overlayBadge.style.borderColor = "
|
|
527
|
+
this.overlay.style.border = "2px solid #4FC3DC";
|
|
528
|
+
this.overlay.style.boxShadow = "none";
|
|
529
|
+
this.overlayBadge.style.color = "#E8E4D9";
|
|
530
|
+
this.overlayBadge.style.borderColor = "#4FC3DC";
|
|
529
531
|
this.overlayBadge.textContent = `\u270E Click to edit: ${key}`;
|
|
530
532
|
} else {
|
|
531
|
-
this.overlay.style.border = "1.5px dashed
|
|
532
|
-
this.overlay.style.boxShadow = "
|
|
533
|
-
this.overlayBadge.style.color = "#
|
|
534
|
-
this.overlayBadge.style.borderColor = "
|
|
533
|
+
this.overlay.style.border = "1.5px dashed #4FC3DC";
|
|
534
|
+
this.overlay.style.boxShadow = "none";
|
|
535
|
+
this.overlayBadge.style.color = "#E8E4D9";
|
|
536
|
+
this.overlayBadge.style.borderColor = "#4FC3DC";
|
|
535
537
|
this.overlayBadge.textContent = `\u2325 Alt+Click to edit: ${key}`;
|
|
536
538
|
}
|
|
537
539
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/client/index.ts","../../src/client/watermark.ts","../../src/core/env.ts","../../src/client/iframe-bridge.ts","../../src/client/dom-interceptor.ts","../../src/client/wrap.ts"],"sourcesContent":["'use client';\n\nimport { DOMInterceptor } from './dom-interceptor.js';\nimport { IframeBridge, readSameOriginParent, resolveStudioOrigin } from './iframe-bridge.js';\nimport { decodeKey } from './watermark.js';\nimport { isLiveLocaleEnabled } from '../core/env.js';\n\nexport { DOMInterceptor } from './dom-interceptor.js';\nexport {\n IframeBridge,\n type TranslationPayload,\n type ClientMessageType,\n type StudioMessageType,\n type VisibleText,\n} from './iframe-bridge.js';\nexport {\n encodeKey,\n decodeKey,\n stripWatermark,\n sanitizeZeroWidth,\n removeWatermarks,\n hasWatermark,\n unwrap,\n} from './watermark.js';\n\n// wrapTranslationEngine lives in wrap.ts (no 'use client') so it can also be\n// imported by Server Components via 'livelocale/server'.\nexport { wrapTranslationEngine, wrapNextIntl, type WrapOptions } from './wrap.js';\n\n// โโโ Live preview support โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\n// Text nodes mutated by APPLY_PREVIEW, with their original value so the\n// preview can be reverted when the selection is cleared.\nconst previewNodes = new Map<Text, { key: string; original: string }>();\n\n/**\n * Applies a live preview value to every rendered element matching `key`\n * without touching the locale files. Only the visible text after the\n * watermark prefix is replaced, keeping the watermark (and thus the\n * data-i18n-key scanner) intact.\n */\nfunction applyPreview(key: string, value: string): void {\n const elements = document.querySelectorAll<HTMLElement>('[data-i18n-key]');\n elements.forEach((el) => {\n if (el.getAttribute('data-i18n-key') !== key) return;\n\n const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null);\n const node = walker.nextNode() as Text | null;\n if (!node) return;\n\n const raw = node.nodeValue ?? '';\n const decoded = decodeKey(raw);\n if (!decoded) return;\n\n // Preserve the watermark prefix, swap only the visible text\n const next = raw.slice(0, raw.length - decoded.cleanText.length) + value;\n // Skipping unchanged text avoids a mutation, so re-applying is idempotent.\n if (next === raw) return;\n if (!previewNodes.has(node)) {\n previewNodes.set(node, { key, original: raw });\n }\n node.nodeValue = next;\n });\n}\n\n/**\n * Makes a saved value the text that stays on the page. In development the\n * dev server re-renders from the edited file anyway; in preview mode the\n * deployment still serves the old text until it is rebuilt, so the Studio\n * re-sends saved values whenever the page reloads or changes.\n */\nfunction commitPreview(key: string, value: string): void {\n applyPreview(key, value);\n previewNodes.forEach((entry, node) => {\n if (entry.key === key) previewNodes.delete(node);\n });\n}\n\n/** Restores all previewed text nodes to their pre-preview values. */\nfunction restorePreviews(): void {\n previewNodes.forEach(({ original }, node) => {\n node.nodeValue = original;\n });\n previewNodes.clear();\n}\n\n// โโโ Auto-initialization โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\n// Auto-initialize the interceptor if running in the browser, in development mode,\n// AND only when the page is embedded inside the studio iframe (not accessed directly).\nif (\n typeof window !== 'undefined' &&\n isLiveLocaleEnabled() &&\n window.parent !== window // <-- iframe guard: only activate inside the studio\n) {\n let storage: Storage | null = null;\n try {\n storage = window.sessionStorage;\n } catch {}\n const allowedOrigin = resolveStudioOrigin(\n window.location.search,\n storage,\n readSameOriginParent(window)\n );\n\n const bridge = new IframeBridge(allowedOrigin);\n const interceptor = new DOMInterceptor(\n (payload) => {\n bridge.sendToParent('ELEMENT_SELECTED', payload);\n },\n (keys) => {\n bridge.sendToParent('VISIBLE_KEYS_CHANGED', keys);\n bridge.sendToParent('VISIBLE_TEXTS_CHANGED', interceptor.getVisibleTexts());\n }\n );\n\n interceptor.init();\n\n // Send a READY message to the parent studio\n bridge.sendToParent('READY', { url: window.location.pathname + window.location.search });\n\n // Listen to studio commands\n bridge.listenToParent((type, payload) => {\n if (type === 'APPLY_PREVIEW') {\n if (payload && typeof payload.key === 'string' && typeof payload.value === 'string') {\n applyPreview(payload.key, payload.value);\n }\n } else if (type === 'COMMIT_PREVIEW') {\n if (payload && typeof payload.key === 'string' && typeof payload.value === 'string') {\n commitPreview(payload.key, payload.value);\n }\n } else if (type === 'CLEAR_SELECTION') {\n // Revert any live preview, then re-trigger/reset interceptor highlight states\n restorePreviews();\n interceptor.destroy();\n interceptor.init();\n }\n });\n\n // Attach reference to window for developer inspection/diagnostics\n (window as any).__LIVELOCALE_CLIENT__ = {\n bridge,\n interceptor,\n };\n}\n","/**\n * Zero-Width Watermarking Encoder/Decoder\n *\n * Maps translation keys into invisible Unicode zero-width characters and\n * prepends them as a binary watermark onto the translation string.\n *\n * Delimiters:\n * \\u200C โ Zero-Width Non-Joiner (ZWNJ) โ boundary start/end marker\n * \\u200B โ Zero-Width Space (ZWS) โ binary bit 0\n * \\u200D โ Zero-Width Joiner (ZWJ) โ binary bit 1\n *\n * The delimiters touch the visible text, so they must be non-joining: a ZWJ\n * there is \"join-causing\" and makes the first Arabic letter take its connected\n * form, as if a hidden letter came before it. Bits never touch a letter.\n *\n * The watermark is strictly prepended so it survives downstream truncation\n * (e.g., text.substring()) that removes only from the right.\n */\n\nconst DELIM = '\\u200C'; // ZWNJ โ boundary delimiter (non-joining)\nconst BIT_0 = '\\u200B'; // ZWS โ represents bit 0\nconst BIT_1 = '\\u200D'; // ZWJ โ represents bit 1\n\n/** First character of every watermark; a cheap pre-check before decoding. */\nexport const WATERMARK_DELIMITER = DELIM;\n\nconst ZW_RE = /[\\u200B\\u200C\\u200D]/g;\n\n/** A complete watermark anywhere in a string: delimiter, whole bytes of bits, delimiter. */\nconst WATERMARK_RE = /\\u200C(?:[\\u200B\\u200D]{8})+\\u200C/g;\n\n/** Encodes a translation key as an invisible watermark prefix on `text`. */\nexport function encodeKey(text: string, key: string): string {\n const bytes = new TextEncoder().encode(key);\n let bits = '';\n for (const byte of bytes) {\n bits += byte.toString(2).padStart(8, '0');\n }\n\n let watermark = DELIM;\n for (const bit of bits) {\n watermark += bit === '0' ? BIT_0 : BIT_1;\n }\n watermark += DELIM;\n\n return watermark + text;\n}\n\n/**\n * Decodes a watermark prefix from `text`.\n *\n * Returns `{ key, cleanText }` where `cleanText` is the original visible\n * text with the watermark stripped. If no valid watermark is found,\n * returns `null`.\n */\nexport function decodeKey(text: string): { key: string; cleanText: string } | null {\n if (!text.startsWith(DELIM)) return null;\n\n const end = text.indexOf(DELIM, 1);\n if (end === -1) return null;\n\n const bitsStr = text.slice(1, end);\n // Validate: every char must be a known ZW bit\n for (const ch of bitsStr) {\n if (ch !== BIT_0 && ch !== BIT_1) return null;\n }\n\n if (bitsStr.length !== 0 && bitsStr.length % 8 !== 0) return null;\n\n const bytes: number[] = [];\n for (let i = 0; i < bitsStr.length; i += 8) {\n let byte = 0;\n for (let j = 0; j < 8; j++) {\n byte = (byte << 1) | (bitsStr[i + j] === BIT_1 ? 1 : 0);\n }\n bytes.push(byte);\n }\n\n try {\n const key = new TextDecoder().decode(new Uint8Array(bytes));\n const cleanText = text.slice(end + 1);\n return { key, cleanText };\n } catch {\n return null;\n }\n}\n\n/**\n * Strips a valid watermark *prefix* from `text`, leaving any legitimate\n * zero-width characters inside the visible text untouched.\n *\n * ZWNJ (\\u200C) and ZWSP (\\u200B) are valid orthography in Persian, Urdu,\n * Thai, and other scripts โ a blanket removal would silently corrupt real\n * translations. Only the decoded watermark prefix is removed.\n */\nexport function stripWatermark(text: string): string {\n const decoded = decodeKey(text);\n if (decoded) return decoded.cleanText;\n return text;\n}\n\n/**\n * Aggressively removes every zero-width character from `text`.\n *\n * This also removes ZWNJ and ZWSP that Persian, Urdu and Thai text needs, so\n * prefer `removeWatermarks` for user-visible text and `stripWatermark` for\n * translation content.\n */\nexport function sanitizeZeroWidth(text: string): string {\n return text.replace(ZW_RE, '');\n}\n\n/**\n * Removes every complete watermark from `text`, wherever it occurs, and keeps\n * all other zero-width characters. Persian and Urdu use ZWNJ inside words and\n * Thai uses ZWSP, so stripping every zero-width character would corrupt them.\n */\nexport function removeWatermarks(text: string): string {\n return text.replace(WATERMARK_RE, '');\n}\n\n/** True when `text` contains at least one complete watermark. */\nexport function containsWatermark(text: string): boolean {\n WATERMARK_RE.lastIndex = 0;\n const found = WATERMARK_RE.test(text);\n WATERMARK_RE.lastIndex = 0;\n return found;\n}\n\n/**\n * Returns true if `text` begins with a valid watermark prefix.\n */\nexport function hasWatermark(text: string): boolean {\n return text.startsWith(DELIM);\n}\n\n/**\n * Deeply strips watermark prefixes from a value: strings are cleaned with\n * `stripWatermark`, arrays and plain objects are rebuilt with cleaned members,\n * everything else is returned as-is. The input is never mutated.\n *\n * Use it wherever a translated string must be compared, measured, serialized\n * or handed to code that is not display-only, e.g. `unwrap(t('yes')) === 'Yes'`.\n */\nexport function unwrap<T>(value: T): T {\n if (typeof value === 'string') return stripWatermark(value) as T;\n if (Array.isArray(value)) return value.map((item) => unwrap(item)) as T;\n if (value !== null && typeof value === 'object') {\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(value as Record<string, unknown>)) {\n out[key] = unwrap((value as Record<string, unknown>)[key]);\n }\n return out as T;\n }\n return value;\n}\n","/**\n * Single source of truth for \"is LiveLocale active right now, and in which mode?\".\n *\n * Modes:\n * - `development`: NODE_ENV is `development` (the default, zero-config mode).\n * - `preview`: explicitly opted in with `LIVELOCALE=preview` (server) or\n * `NEXT_PUBLIC_LIVELOCALE=preview` (client bundles). Works in production builds so a\n * preview deployment can be edited through a hosted Studio. The mutation API then\n * requires a bearer token (see the router).\n * - `off`: everything else.\n *\n * Rules, in order:\n * 1. `LIVELOCALE=off|0|false` (or `NEXT_PUBLIC_LIVELOCALE=off`) always wins โ off.\n * 2. `LIVELOCALE=preview` / `NEXT_PUBLIC_LIVELOCALE=preview` โ preview.\n * 3. `NODE_ENV` must be `development`, otherwise off.\n * 4. `LIVELOCALE=force` skips rule 5.\n * 5. On the server, a known hosting platform (Vercel preview/production, Netlify,\n * Kubernetes, Render, Fly.io, Railway, Heroku) โ off, so a mis-configured staging\n * deploy cannot leak watermarks. A one-time warning is logged.\n *\n * `process.env.NODE_ENV` and `process.env.NEXT_PUBLIC_LIVELOCALE` are referenced as\n * literal member expressions so Next.js and Vite can inline them in browser bundles.\n */\n\nexport type LiveLocaleMode = 'off' | 'development' | 'preview';\n\nconst OFF_VALUES = new Set(['off', '0', 'false']);\n\nlet warnedDeployed = false;\n\n/** Test hook: forget that the deployed-platform warning was already printed. */\nexport function resetEnvWarnings(): void {\n warnedDeployed = false;\n}\n\nfunction readNodeEnv(): string | undefined {\n try {\n return process.env.NODE_ENV;\n } catch {\n return undefined;\n }\n}\n\n/** Set by the Vite plugin (and usable by any host) to mirror the server flag into the page. */\nexport const BROWSER_FLAG_GLOBAL = '__LIVELOCALE_FLAG__';\n\nfunction readPublicFlag(): string | undefined {\n let value: string | undefined;\n try {\n value = process.env.NEXT_PUBLIC_LIVELOCALE;\n } catch {\n value = undefined;\n }\n if (value) return value;\n try {\n const g = globalThis as any;\n const fromGlobal = g.window?.[BROWSER_FLAG_GLOBAL] ?? g[BROWSER_FLAG_GLOBAL];\n return typeof fromGlobal === 'string' ? fromGlobal : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction readEnv(name: string): string | undefined {\n if (typeof process === 'undefined' || !process.env) return undefined;\n const value = process.env[name];\n return value === '' ? undefined : value;\n}\n\n/** Returns a human-readable platform name when running on a known hosting platform. */\nexport function detectDeployedPlatform(): string | null {\n if (typeof window !== 'undefined') return null; // browser: nothing to detect\n const vercelEnv = readEnv('VERCEL_ENV');\n if (vercelEnv === 'production' || vercelEnv === 'preview')\n return `Vercel (VERCEL_ENV=${vercelEnv})`;\n const netlifyContext = readEnv('CONTEXT');\n if (readEnv('NETLIFY') === 'true' && netlifyContext && netlifyContext !== 'dev') {\n return `Netlify (CONTEXT=${netlifyContext})`;\n }\n if (readEnv('KUBERNETES_SERVICE_HOST')) return 'Kubernetes';\n if (readEnv('RENDER') === 'true') return 'Render';\n if (readEnv('FLY_APP_NAME')) return 'Fly.io';\n if (readEnv('RAILWAY_ENVIRONMENT')) return 'Railway';\n if (readEnv('DYNO')) return 'Heroku';\n return null;\n}\n\nfunction evaluate(): { mode: LiveLocaleMode; reason: string | null } {\n const flag = (readEnv('LIVELOCALE') ?? '').toLowerCase();\n const publicFlag = (readPublicFlag() ?? '').toLowerCase();\n\n if (OFF_VALUES.has(flag)) return { mode: 'off', reason: 'LIVELOCALE kill switch is set' };\n if (OFF_VALUES.has(publicFlag)) {\n return { mode: 'off', reason: 'NEXT_PUBLIC_LIVELOCALE kill switch is set' };\n }\n\n if (flag === 'preview' || publicFlag === 'preview') return { mode: 'preview', reason: null };\n\n const nodeEnv = readNodeEnv();\n if (nodeEnv !== 'development') {\n return {\n mode: 'off',\n reason: `NODE_ENV is \"${nodeEnv ?? 'undefined'}\" (must be \"development\", or set LIVELOCALE=preview)`,\n };\n }\n\n if (flag === 'force') return { mode: 'development', reason: null };\n\n const platform = detectDeployedPlatform();\n if (platform) {\n if (!warnedDeployed) {\n warnedDeployed = true;\n console.warn(\n `[livelocale] Disabled: deployed environment detected (${platform}) while NODE_ENV=development. ` +\n `Set LIVELOCALE=force to override, LIVELOCALE=preview for a hosted preview, or LIVELOCALE=off to silence this warning.`\n );\n }\n return { mode: 'off', reason: `deployed environment detected (${platform})` };\n }\n\n return { mode: 'development', reason: null };\n}\n\n/** The active mode: `off`, `development`, or `preview`. */\nexport function getLiveLocaleMode(): LiveLocaleMode {\n return evaluate().mode;\n}\n\n/**\n * Returns `null` when LiveLocale should be active, otherwise a short reason\n * suitable for error messages and logs.\n */\nexport function getDisabledReason(): string | null {\n return evaluate().reason;\n}\n\n/** True when watermarking, the mutation API, and the client scanner should run. */\nexport function isLiveLocaleEnabled(): boolean {\n return evaluate().mode !== 'off';\n}\n","export type ClientMessageType =\n | 'ELEMENT_SELECTED'\n | 'READY'\n | 'ERROR'\n | 'VISIBLE_KEYS_CHANGED'\n | 'VISIBLE_TEXTS_CHANGED';\nexport type StudioMessageType = 'APPLY_PREVIEW' | 'COMMIT_PREVIEW' | 'CLEAR_SELECTION';\n\n/** Where `livelocale studio` runs unless the Studio says otherwise. */\nexport const DEFAULT_STUDIO_ORIGIN = 'http://localhost:3010';\n\n/** Session key that keeps the Studio origin across in-app navigation within one tab. */\nexport const STUDIO_ORIGIN_STORAGE_KEY = 'livelocale-studio-origin';\n\nfunction toHttpOrigin(value: string | null | undefined): string | null {\n if (!value) return null;\n try {\n const url = new URL(value);\n return url.protocol === 'http:' || url.protocol === 'https:' ? url.origin : null;\n } catch {\n return null;\n }\n}\n\n/**\n * The origin of this page when its parent window is a different window with\n * the identical readable origin (a Studio served from the same host), else\n * `null`. Returns `toHttpOrigin(win.location.origin)` only when that is\n * non-null and equals `win.parent.location.origin`; a top-level page, a\n * missing parent, a cross-origin parent (whose `location` throws), a parent\n * on another origin and an opaque `'null'` origin all give `null`. Never throws.\n */\nexport function readSameOriginParent(win: Window): string | null {\n try {\n const parent = win.parent;\n if (!parent || parent === win) return null;\n const own = toHttpOrigin(win.location.origin);\n if (!own) return null;\n return parent.location.origin === own ? own : null;\n } catch {\n return null;\n }\n}\n\n/**\n * The origin of the Studio that embeds this page, in this order:\n *\n * 1. `sameOriginParent`, when it is an http(s) origin. `sendToParent` always\n * posts to `window.parent`, so a readable same-origin parent is the only\n * origin that can receive the messages; it wins over a stale query value or\n * remembered origin. This branch neither reads nor writes storage.\n * 2. `?livelocale-origin=` on the iframe URL, which the Studio passes. The\n * app's own links drop that parameter, so it is remembered for the tab;\n * without it, messages after the first navigation would go to the default\n * origin and be discarded.\n * 3. The origin remembered for the tab.\n * 4. `DEFAULT_STUDIO_ORIGIN`.\n */\nexport function resolveStudioOrigin(\n search: string,\n storage: Storage | null,\n sameOriginParent?: string | null\n): string {\n const fromParent = toHttpOrigin(sameOriginParent);\n if (fromParent) return fromParent;\n const fromQuery = toHttpOrigin(new URLSearchParams(search).get('livelocale-origin'));\n if (fromQuery) {\n try {\n storage?.setItem(STUDIO_ORIGIN_STORAGE_KEY, fromQuery);\n } catch {}\n return fromQuery;\n }\n let remembered: string | null = null;\n try {\n remembered = toHttpOrigin(storage?.getItem(STUDIO_ORIGIN_STORAGE_KEY));\n } catch {}\n return remembered ?? DEFAULT_STUDIO_ORIGIN;\n}\n\n/** A key on the page with the text it shows now; the Studio infers the page locale from these. */\nexport interface VisibleText {\n key: string;\n text: string;\n}\n\n/** Most visible key/text pairs one VISIBLE_TEXTS_CHANGED message carries. */\nexport const MAX_VISIBLE_TEXTS = 100;\n\n/** Longest text, in UTF-16 code units, one visible key/text pair carries. */\nexport const MAX_VISIBLE_TEXT_LENGTH = 200;\n\nconst KEY_PATTERN = /^[a-zA-Z0-9._-]+$/;\n\nexport interface TranslationPayload {\n key: string;\n fallbackValue: string;\n currentValue: string;\n}\n\nexport class IframeBridge {\n private allowedOrigin: string;\n\n constructor(allowedOrigin: string) {\n this.allowedOrigin = allowedOrigin;\n }\n\n /**\n * Sends a message from the iframe client to the parent Studio window.\n */\n sendToParent(type: ClientMessageType, payload?: any): void {\n if (typeof window === 'undefined' || !window.parent || window.parent === window) {\n // Not in an iframe context\n return;\n }\n\n // RULE CLT-006: PAYLOAD SANITIZATION\n if (type === 'ELEMENT_SELECTED') {\n const p = payload as TranslationPayload;\n if (\n !p ||\n typeof p.key !== 'string' ||\n p.key.trim() === '' ||\n !/^[a-zA-Z0-9._-]+$/.test(p.key) ||\n typeof p.currentValue !== 'string'\n ) {\n // Drop invalid payload silently\n return;\n }\n }\n\n if (type === 'VISIBLE_KEYS_CHANGED') {\n // Validate payload is an array of valid key strings\n if (!Array.isArray(payload)) return;\n const validKeys = (payload as unknown[]).filter(\n (k): k is string => typeof k === 'string' && /^[a-zA-Z0-9._-]+$/.test(k)\n );\n // Replace the payload with the sanitized array\n payload = validKeys;\n }\n\n if (type === 'VISIBLE_TEXTS_CHANGED') {\n if (!Array.isArray(payload)) return;\n const texts: VisibleText[] = [];\n for (const entry of payload as unknown[]) {\n if (texts.length >= MAX_VISIBLE_TEXTS) break;\n if (!entry || typeof entry !== 'object') continue;\n const { key, text } = entry as { key?: unknown; text?: unknown };\n if (typeof key !== 'string' || !KEY_PATTERN.test(key) || typeof text !== 'string') {\n continue;\n }\n texts.push({ key, text: text.slice(0, MAX_VISIBLE_TEXT_LENGTH) });\n }\n payload = texts;\n }\n\n const message = {\n source: 'livelocale-client',\n type,\n payload,\n };\n\n window.parent.postMessage(message, this.allowedOrigin);\n }\n\n /**\n * Sets up a listener for messages originating from the parent Studio dashboard.\n */\n listenToParent(callback: (type: StudioMessageType, payload: any) => void): () => void {\n if (typeof window === 'undefined') {\n return () => {};\n }\n\n const listener = (event: MessageEvent) => {\n // RULE CLT-005: MESSAGE ORIGIN VALIDATION\n if (event.origin !== this.allowedOrigin) {\n return;\n }\n if (event.data?.source !== 'livelocale-studio') {\n return;\n }\n\n const { type, payload } = event.data;\n callback(type, payload);\n };\n\n window.addEventListener('message', listener);\n\n // Return a function to clean up the event listener\n return () => {\n window.removeEventListener('message', listener);\n };\n }\n}\n","import {\n decodeKey,\n stripWatermark,\n removeWatermarks,\n containsWatermark,\n WATERMARK_DELIMITER,\n} from './watermark.js';\nimport { isLiveLocaleEnabled } from '../core/env.js';\nimport { MAX_VISIBLE_TEXTS, MAX_VISIBLE_TEXT_LENGTH, type VisibleText } from './iframe-bridge.js';\n\n// Debounce helper: delays executing fn until after wait ms have elapsed\n// since the last call. Returns a cancel function.\nfunction debounce<T extends (...args: any[]) => void>(fn: T, wait: number): T & { cancel(): void } {\n let timer: ReturnType<typeof setTimeout> | null = null;\n const debounced = (...args: Parameters<T>) => {\n if (timer !== null) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n fn(...args);\n }, wait);\n };\n debounced.cancel = () => {\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n };\n return debounced as T & { cancel(): void };\n}\n\nexport class DOMInterceptor {\n private onElementSelected: (payload: {\n key: string;\n fallbackValue: string;\n currentValue: string;\n }) => void;\n private onVisibleKeysChanged?: (keys: string[]) => void;\n\n private hoveredElement: HTMLElement | null = null;\n private observer: MutationObserver | null = null;\n private inputSanitizersAttached: boolean = false;\n\n // โโโ Floating Overlay โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n private overlay: HTMLDivElement | null = null;\n private overlayBadge: HTMLSpanElement | null = null;\n\n // โโโ Alt key state โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n private isAltHeld: boolean = false;\n\n // Debounced key-broadcast to avoid flooding the parent on rapid DOM mutations\n private broadcastVisibleKeys: (() => void) & { cancel(): void };\n\n constructor(\n onElementSelected: (payload: {\n key: string;\n fallbackValue: string;\n currentValue: string;\n }) => void,\n onVisibleKeysChanged?: (keys: string[]) => void\n ) {\n this.onElementSelected = onElementSelected;\n this.onVisibleKeysChanged = onVisibleKeysChanged;\n\n this.broadcastVisibleKeys = debounce(() => {\n if (this.onVisibleKeysChanged) {\n this.onVisibleKeysChanged(this.getActiveKeys());\n }\n }, 200);\n }\n\n /**\n * Returns the unique set of translation keys currently visible in the DOM.\n */\n getActiveKeys(): string[] {\n const elements = document.querySelectorAll<HTMLElement>('[data-i18n-key]');\n const keys = new Set<string>();\n elements.forEach((el) => {\n const key = el.getAttribute('data-i18n-key');\n if (key) keys.add(key);\n });\n return Array.from(keys);\n }\n\n /**\n * Returns one `{ key, text }` per translation key on the page, in document\n * order, with the text it shows now (watermark removed, trimmed, cut to\n * `MAX_VISIBLE_TEXT_LENGTH`). The first element carrying a key wins; its\n * text is the first descendant text node whose watermark names that key.\n * Elements without such a node, and blank texts, are skipped. At most\n * `limit` entries are returned.\n */\n getVisibleTexts(limit: number = MAX_VISIBLE_TEXTS): VisibleText[] {\n const texts: VisibleText[] = [];\n const seen = new Set<string>();\n const elements = document.querySelectorAll<HTMLElement>('[data-i18n-key]');\n for (const el of elements) {\n if (texts.length >= limit) break;\n const key = el.getAttribute('data-i18n-key');\n if (!key || seen.has(key)) continue;\n seen.add(key);\n const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null);\n let node: Text | null;\n while ((node = walker.nextNode() as Text | null) !== null) {\n const decoded = decodeKey(node.nodeValue ?? '');\n if (decoded?.key !== key) continue;\n const text = decoded.cleanText.trim().slice(0, MAX_VISIBLE_TEXT_LENGTH);\n if (text) texts.push({ key, text });\n break;\n }\n }\n return texts;\n }\n\n /**\n * Initializes the DOM event interceptors in development mode.\n * Also launches the watermark scanner and MutationObserver.\n */\n init(): void {\n // RULE CLT-004: INIT GUARD\n if (!isLiveLocaleEnabled()) {\n return;\n }\n\n if (typeof window === 'undefined') {\n return;\n }\n\n // Inject the floating highlighter overlay into the document body\n this.injectOverlay();\n\n // Attach listeners on window capture phase (RULE CLT-002)\n window.addEventListener('mouseover', this.handleMouseOver, true);\n window.addEventListener('mouseout', this.handleMouseOut, true);\n window.addEventListener('click', this.handleClick, true);\n window.addEventListener('copy', this.handleCopy, true);\n window.addEventListener('paste', this.handlePaste, true);\n window.addEventListener('scroll', this.handleScroll, true);\n window.addEventListener('resize', this.handleResize, false);\n window.addEventListener('keydown', this.handleKeyDown, true);\n window.addEventListener('keyup', this.handleKeyUp, true);\n\n // Phase 3: Scan existing DOM for watermarked text nodes\n this.scanSubtree(document.body);\n\n // Phase 3: Observe future DOM mutations for watermarked nodes\n this.attachMutationObserver();\n\n // Phase 3: Sanitize form inputs/submissions to strip ZW chars\n this.attachInputSanitizers();\n\n // Broadcast the initial set of visible keys to the studio sidebar\n this.broadcastVisibleKeys();\n }\n\n /**\n * Cleans up all attached event listeners, the MutationObserver,\n * the floating overlay, and the form-sanitization listeners.\n */\n destroy(): void {\n if (typeof window === 'undefined') {\n return;\n }\n window.removeEventListener('mouseover', this.handleMouseOver, true);\n window.removeEventListener('mouseout', this.handleMouseOut, true);\n window.removeEventListener('click', this.handleClick, true);\n window.removeEventListener('copy', this.handleCopy, true);\n window.removeEventListener('paste', this.handlePaste, true);\n window.removeEventListener('scroll', this.handleScroll, true);\n window.removeEventListener('resize', this.handleResize, false);\n window.removeEventListener('keydown', this.handleKeyDown, true);\n window.removeEventListener('keyup', this.handleKeyUp, true);\n window.removeEventListener('beforeinput', this.handleBeforeInput, true);\n\n // Detach form-submit sanitizer\n document.removeEventListener('submit', this.handleFormSubmit, true);\n\n if (this.observer) {\n this.observer.disconnect();\n this.observer = null;\n }\n\n this.inputSanitizersAttached = false;\n\n // Remove floating overlay from DOM\n if (this.overlay && this.overlay.parentNode) {\n this.overlay.parentNode.removeChild(this.overlay);\n }\n this.overlay = null;\n this.overlayBadge = null;\n this.hoveredElement = null;\n\n // Cancel any pending debounced broadcasts\n this.broadcastVisibleKeys.cancel();\n }\n\n // โโโ Floating Overlay โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\n /**\n * Creates and injects a single floating overlay div into the document body.\n * The overlay is positioned using fixed coordinates from getBoundingClientRect()\n * and uses pointer-events: none so it never blocks user interaction.\n */\n private injectOverlay(): void {\n if (this.overlay) return; // Guard against double injection\n\n const overlay = document.createElement('div');\n overlay.id = 'livelocale-highlighter-overlay';\n overlay.style.cssText = [\n 'position: fixed',\n 'top: 0',\n 'left: 0',\n 'width: 0',\n 'height: 0',\n 'pointer-events: none',\n 'z-index: 2147483647', // Maximum z-index\n 'box-sizing: border-box',\n 'opacity: 0',\n 'border-radius: 4px',\n 'transition: opacity 120ms ease, top 80ms ease, left 80ms ease, width 80ms ease, height 80ms ease, border-color 150ms ease',\n ].join('; ');\n\n const badge = document.createElement('span');\n badge.style.cssText = [\n 'position: absolute',\n 'top: -22px',\n 'left: 0',\n 'max-width: 280px',\n 'white-space: nowrap',\n 'overflow: hidden',\n 'text-overflow: ellipsis',\n 'background: rgba(15, 20, 40, 0.92)',\n 'border: 1px solid rgba(99, 102, 241, 0.5)',\n 'color: #a5b4fc',\n 'font-family: ui-monospace, SFMono-Regular, Menlo, monospace',\n 'font-size: 10px',\n 'font-weight: 600',\n 'line-height: 1',\n 'padding: 3px 7px',\n 'border-radius: 4px',\n 'letter-spacing: 0.02em',\n 'backdrop-filter: blur(4px)',\n 'pointer-events: none',\n ].join('; ');\n\n overlay.appendChild(badge);\n document.body.appendChild(overlay);\n\n this.overlay = overlay;\n this.overlayBadge = badge;\n }\n\n /**\n * Positions the floating overlay to surround the given element.\n * Uses getBoundingClientRect() for fixed-positioned geometry.\n */\n private positionOverlay(element: HTMLElement): void {\n if (!this.overlay) return;\n\n const rect = element.getBoundingClientRect();\n const computedStyle = window.getComputedStyle(element);\n const borderRadius = computedStyle.borderRadius || '4px';\n\n this.overlay.style.top = `${rect.top}px`;\n this.overlay.style.left = `${rect.left}px`;\n this.overlay.style.width = `${rect.width}px`;\n this.overlay.style.height = `${rect.height}px`;\n this.overlay.style.borderRadius = borderRadius;\n this.overlay.style.opacity = '1';\n\n this.updateOverlayStyle();\n }\n\n /**\n * Updates overlay border color and badge text based on current Alt key state.\n */\n private updateOverlayStyle(): void {\n if (!this.overlay || !this.overlayBadge) return;\n\n const key = this.hoveredElement?.getAttribute('data-i18n-key') || '';\n\n if (this.isAltHeld) {\n // Alt is held โ bright solid border signals \"click to edit\"\n this.overlay.style.border = '2px solid #3b82f6';\n this.overlay.style.boxShadow =\n '0 0 0 1px rgba(59, 130, 246, 0.3), inset 0 0 0 1px rgba(59, 130, 246, 0.1)';\n this.overlayBadge.style.color = '#93c5fd';\n this.overlayBadge.style.borderColor = 'rgba(59, 130, 246, 0.6)';\n this.overlayBadge.textContent = `โ Click to edit: ${key}`;\n } else {\n // Hover โ subtle dashed border signals \"Alt + Click to edit\"\n this.overlay.style.border = '1.5px dashed rgba(99, 102, 241, 0.65)';\n this.overlay.style.boxShadow = '0 0 0 1px rgba(99, 102, 241, 0.15)';\n this.overlayBadge.style.color = '#a5b4fc';\n this.overlayBadge.style.borderColor = 'rgba(99, 102, 241, 0.5)';\n this.overlayBadge.textContent = `โฅ Alt+Click to edit: ${key}`;\n }\n }\n\n /**\n * Hides the floating overlay without removing it from the DOM.\n */\n private hideOverlay(): void {\n if (!this.overlay) return;\n this.overlay.style.opacity = '0';\n }\n\n // โโโ Phase 3: Watermark DOM Scanner โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\n /**\n * Walks all text nodes inside `root` and decodes any watermark prefix.\n * When found, attaches data-i18n-key / data-i18n-template to the\n * immediate parent HTMLElement without mutating the text node itself\n * (hydration safety: RULE CLT-007).\n */\n private scanSubtree(root: Node): void {\n if (!root) return;\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null);\n\n let node: Text | null;\n while ((node = walker.nextNode() as Text | null) !== null) {\n this.processTextNode(node);\n }\n }\n\n private processTextNode(node: Text): void {\n const raw = node.nodeValue ?? '';\n if (!raw.includes(WATERMARK_DELIMITER)) return; // fast bail-out\n\n const decoded = decodeKey(raw);\n if (!decoded) return;\n\n const parent = node.parentElement;\n if (!parent) return;\n\n // RULE CLT-007: Do NOT strip the ZW chars from the text node โ\n // doing so would break React's reconciliation / hydration tree.\n // Only annotate the parent element.\n parent.setAttribute('data-i18n-key', decoded.key);\n\n // Use the clean (visible) text as the fallback template if not already set\n if (!parent.hasAttribute('data-i18n-template')) {\n parent.setAttribute('data-i18n-template', decoded.cleanText.trim());\n }\n }\n\n /**\n * Attaches a MutationObserver to catch newly rendered / streamed elements.\n */\n private attachMutationObserver(): void {\n if (this.observer) return;\n\n this.observer = new MutationObserver((mutations) => {\n let needsBroadcast = false;\n for (const mutation of mutations) {\n if (mutation.type === 'childList') {\n for (const added of mutation.addedNodes) {\n if (added.nodeType === Node.ELEMENT_NODE) {\n this.scanSubtree(added);\n needsBroadcast = true;\n } else if (added.nodeType === Node.TEXT_NODE) {\n this.processTextNode(added as Text);\n needsBroadcast = true;\n }\n }\n } else if (mutation.type === 'characterData') {\n if (mutation.target.nodeType === Node.TEXT_NODE) {\n this.processTextNode(mutation.target as Text);\n needsBroadcast = true;\n }\n }\n }\n if (needsBroadcast) {\n this.broadcastVisibleKeys();\n }\n });\n\n this.observer.observe(document.body, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n }\n\n // โโโ Phase 3: Input & Form Sanitization โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\n /**\n * Patches input value descriptors and attaches a form submit listener\n * to strip zero-width watermark characters before any data leaves the\n * browser (preventing backend schema validation rejections or storage\n * of invisible chars).\n */\n private attachInputSanitizers(): void {\n if (this.inputSanitizersAttached) return;\n this.inputSanitizersAttached = true;\n\n // Capture form submissions at the document level\n document.addEventListener('submit', this.handleFormSubmit, true);\n\n // Intercept `beforeinput` for direct keyboard input into inputs/textareas\n window.addEventListener('beforeinput', this.handleBeforeInput, true);\n\n // Patch HTMLInputElement.prototype.value getter/setter\n this.patchInputValueDescriptor(HTMLInputElement.prototype);\n this.patchInputValueDescriptor(HTMLTextAreaElement.prototype);\n }\n\n /**\n * Patches the `value` property descriptor of an input prototype so\n * that reads return sanitized strings even if the user somehow pastes\n * watermark characters.\n */\n private patchInputValueDescriptor(proto: HTMLInputElement | HTMLTextAreaElement): void {\n const original = Object.getOwnPropertyDescriptor(proto, 'value');\n if (!original || !original.get || !original.set) return;\n\n // Prevent double-patching\n if ((proto as any).__liveLocaleSanitized__) return;\n (proto as any).__liveLocaleSanitized__ = true;\n\n Object.defineProperty(proto, 'value', {\n get() {\n const raw: string = original.get!.call(this);\n return removeWatermarks(raw);\n },\n set(val: string) {\n const clean = typeof val === 'string' ? removeWatermarks(val) : val;\n original.set!.call(this, clean);\n },\n configurable: true,\n });\n }\n\n private handleFormSubmit = (event: Event): void => {\n const form = event.target as HTMLFormElement | null;\n if (!form) return;\n\n const inputs = form.querySelectorAll<HTMLInputElement | HTMLTextAreaElement>('input, textarea');\n inputs.forEach((input) => {\n const cleaned = removeWatermarks(input.value);\n if (cleaned !== input.value) {\n // Use the original setter to set the cleaned value directly\n const proto =\n input instanceof HTMLTextAreaElement\n ? HTMLTextAreaElement.prototype\n : HTMLInputElement.prototype;\n const descriptor = Object.getOwnPropertyDescriptor(proto, 'value');\n if (descriptor?.set) {\n descriptor.set.call(input, cleaned);\n }\n }\n });\n };\n\n private handleBeforeInput = (_event: InputEvent): void => {\n // No-op: direct keyboard input doesn't contain ZW characters.\n // This listener is reserved for future clipboard paste interception.\n };\n\n // โโโ Alt Key Tracking โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\n private handleKeyDown = (event: KeyboardEvent): void => {\n if (event.key === 'Alt') {\n this.isAltHeld = true;\n // Update the overlay style immediately if an element is hovered\n if (this.hoveredElement) {\n this.updateOverlayStyle();\n }\n }\n };\n\n private handleKeyUp = (event: KeyboardEvent): void => {\n if (event.key === 'Alt') {\n this.isAltHeld = false;\n // Revert to the \"hover hint\" style\n if (this.hoveredElement) {\n this.updateOverlayStyle();\n }\n }\n };\n\n // โโโ Viewport change handlers (reposition overlay on scroll / resize) โโโโโ\n\n private handleScroll = (): void => {\n if (this.hoveredElement) {\n this.positionOverlay(this.hoveredElement);\n }\n };\n\n private handleResize = (): void => {\n if (this.hoveredElement) {\n this.positionOverlay(this.hoveredElement);\n }\n };\n\n // โโโ Mouse / Click Handlers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\n private handleMouseOver = (event: MouseEvent): void => {\n const target = event.target as HTMLElement | null;\n if (!target || typeof target.closest !== 'function') return;\n\n const keyedElement = target.closest('[data-i18n-key]') as HTMLElement | null;\n if (!keyedElement) {\n // Moved onto a non-keyed element โ hide overlay\n if (this.hoveredElement) {\n this.hideOverlay();\n this.hoveredElement = null;\n }\n return;\n }\n\n // RULE CLT-003: ALWAYS CHECK data-i18n-key EXISTENCE\n const key = keyedElement.getAttribute('data-i18n-key');\n if (key === null) return;\n\n this.hoveredElement = keyedElement;\n this.positionOverlay(keyedElement);\n };\n\n private handleMouseOut = (event: MouseEvent): void => {\n const target = event.target as HTMLElement | null;\n if (!target || typeof target.closest !== 'function') return;\n\n const relatedTarget = event.relatedTarget as HTMLElement | null;\n\n // If we're moving into a child of the same keyed element, don't hide\n if (relatedTarget && this.hoveredElement && this.hoveredElement.contains(relatedTarget)) {\n return;\n }\n\n const keyedElement = target.closest('[data-i18n-key]') as HTMLElement | null;\n if (!keyedElement || keyedElement !== this.hoveredElement) return;\n\n this.hideOverlay();\n this.hoveredElement = null;\n };\n\n private handleClick = (event: MouseEvent): void => {\n // Feature 1: Only intercept clicks when Alt key is held.\n // Without Alt, let the event propagate naturally so links and buttons work.\n if (!event.altKey) return;\n\n const target = event.target as HTMLElement | null;\n if (!target || typeof target.closest !== 'function') return;\n\n const keyedElement = target.closest('[data-i18n-key]') as HTMLElement | null;\n if (!keyedElement) return;\n\n // RULE CLT-003: ALWAYS CHECK data-i18n-key EXISTENCE\n const key = keyedElement.getAttribute('data-i18n-key');\n if (key === null) return;\n\n // RULE CLT-002: EVENT LISTENERS IN CAPTURE PHASE\n event.preventDefault();\n event.stopPropagation();\n\n // Use innerText, stripping any invisible ZW chars before sending to studio\n const rawText = keyedElement.innerText || '';\n const cleanText = stripWatermark(rawText);\n\n const fallbackValue = keyedElement.getAttribute('data-i18n-template') || cleanText;\n const currentValue = cleanText;\n\n this.onElementSelected({\n key,\n fallbackValue,\n currentValue,\n });\n };\n\n private handleCopy = (event: ClipboardEvent): void => {\n const selection = window.getSelection()?.toString() || '';\n if (containsWatermark(selection)) {\n const clean = removeWatermarks(selection);\n event.clipboardData?.setData('text/plain', clean);\n event.preventDefault();\n }\n };\n\n private handlePaste = (event: ClipboardEvent): void => {\n const text = event.clipboardData?.getData('text/plain') || '';\n if (containsWatermark(text)) {\n event.preventDefault();\n const cleaned = removeWatermarks(text);\n const targetInput = event.target as HTMLInputElement | HTMLTextAreaElement | null;\n if (targetInput && 'value' in targetInput) {\n const proto =\n targetInput instanceof HTMLTextAreaElement\n ? HTMLTextAreaElement.prototype\n : HTMLInputElement.prototype;\n const nativeValueSetter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;\n\n if (nativeValueSetter) {\n const start = targetInput.selectionStart || 0;\n const end = targetInput.selectionEnd || 0;\n const val = targetInput.value;\n const cleanedValue = val.slice(0, start) + cleaned + val.slice(end);\n\n nativeValueSetter.call(targetInput, cleanedValue);\n targetInput.selectionStart = targetInput.selectionEnd = start + cleaned.length;\n\n // Dispatch bubbling input event so React virtual DOM state syncs\n targetInput.dispatchEvent(new Event('input', { bubbles: true }));\n }\n }\n }\n };\n}\n","import { encodeKey } from './watermark.js';\nimport { isLiveLocaleEnabled } from '../core/env.js';\n\nexport interface WrapOptions {\n /**\n * A dot-separated key prefix to prepend to all resolved key paths.\n *\n * Use this when wrapping a namespaced translation function (e.g.\n * react-i18next `useTranslation('home')` or next-intl `useTranslations('home')`)\n * so that the watermarked key matches the actual path in your locale JSON file.\n *\n * @example\n * const { t: rawT } = useTranslation('home');\n * const t = wrapTranslationEngine(rawT, { keyPrefix: 'home' });\n * // t('title') encodes as 'home.title' โ matches the JSON structure\n */\n keyPrefix?: string;\n\n /**\n * Optional fallback translations to use when the target key is missing.\n */\n fallback?: Record<string, any>;\n}\n\n/**\n * Helper that extracts a dot-notated key string from an argument (string or object descriptor).\n */\nfunction extractKey(arg: unknown, keyPrefix: string): string {\n if (typeof arg === 'string') {\n return keyPrefix ? `${keyPrefix}.${arg}` : arg;\n }\n if (arg && typeof arg === 'object') {\n const obj = arg as Record<string, any>;\n if (typeof obj.id === 'string' && obj.id.trim() !== '') {\n return keyPrefix ? `${keyPrefix}.${obj.id}` : obj.id;\n }\n if (typeof obj.key === 'string' && obj.key.trim() !== '') {\n return keyPrefix ? `${keyPrefix}.${obj.key}` : obj.key;\n }\n }\n return keyPrefix;\n}\n\n/**\n * Wraps any translation function or record (e.g. from next-intl, react-i18next,\n * FormatJS, or a plain locale object loaded server-side) with a JavaScript Proxy\n * that automatically encodes translation keys as invisible zero-width watermark\n * prefixes in development. In production the proxy is transparent โ the original\n * engine is returned as-is.\n */\nexport function wrapTranslationEngine<T extends object>(engine: T, options: WrapOptions = {}): T {\n if (!isLiveLocaleEnabled()) {\n return engine;\n }\n\n return buildProxy(engine, options.keyPrefix ?? '', options);\n}\n\n/**\n * Dedicated convenience wrapper for `next-intl` (App Router and Pages Router).\n * Automatically wraps `useTranslations(namespace)` or `getTranslations(namespace)`.\n *\n * @example\n * ```tsx\n * const t = wrapNextIntl(useTranslations('auth'), 'auth');\n * return <h1>{t('login.title')}</h1>;\n * ```\n */\nexport function wrapNextIntl<T extends object>(t: T, namespace?: string): T {\n return wrapTranslationEngine(t, { keyPrefix: namespace });\n}\n\nfunction getNestedProperty(obj: any, path: string): any {\n if (!obj || typeof obj !== 'object') return undefined;\n if (path in obj) {\n return obj[path];\n }\n const parts = path.split('.');\n let current = obj;\n for (const part of parts) {\n if (current === null || current === undefined || typeof current !== 'object') {\n return undefined;\n }\n current = current[part];\n }\n return current;\n}\n\nfunction buildProxy<T extends object>(target: T, keyPrefix: string, options: WrapOptions): T {\n return new Proxy(target, {\n get(obj, prop: string | symbol) {\n if (typeof prop !== 'string') {\n const val = Reflect.get(obj, prop);\n return typeof val === 'object' && val !== null\n ? buildProxy(val as object, keyPrefix, options)\n : val;\n }\n\n let val = Reflect.get(obj, prop);\n const fullKey = keyPrefix ? `${keyPrefix}.${prop}` : prop;\n\n // If val is missing, try looking it up in fallback\n const fallbackObj =\n options.fallback ??\n (typeof window !== 'undefined' ? (window as any).__livelocale_fallback__ : undefined);\n if (val === undefined && fallbackObj) {\n const fbVal = getNestedProperty(fallbackObj, fullKey);\n if (fbVal !== undefined) {\n val = fbVal;\n }\n }\n\n // Leaf string value โ encode it with its full key path\n if (typeof val === 'string') {\n return encodeKey(val, fullKey);\n }\n\n // Function (e.g. t() or t.raw() or t.rich() from next-intl) โ wrap the call result\n if (typeof val === 'function') {\n return (...args: unknown[]) => {\n const result = (val as (...args: unknown[]) => unknown).apply(obj, args);\n const calledKey = extractKey(args[0], fullKey);\n\n let resolvedValue = result;\n const isMissing =\n resolvedValue === undefined ||\n resolvedValue === null ||\n resolvedValue === '' ||\n (typeof resolvedValue === 'string' &&\n (resolvedValue === args[0] || resolvedValue === calledKey));\n\n if (isMissing && fallbackObj && calledKey) {\n const fbVal = getNestedProperty(fallbackObj, calledKey);\n if (fbVal !== undefined) {\n resolvedValue = fbVal;\n }\n }\n\n if (typeof resolvedValue === 'string' && calledKey) {\n return encodeKey(resolvedValue, calledKey);\n }\n return resolvedValue;\n };\n }\n\n // Nested object โ recurse with updated key path\n if (typeof val === 'object' && val !== null) {\n return buildProxy(val as object, fullKey, options);\n }\n\n return val;\n },\n\n // Allow the proxy to be called directly (e.g. t('key') or t('key', { name: 'value' }))\n apply(target: any, thisArg, args: unknown[]) {\n const result = Reflect.apply(target, thisArg, args);\n const calledKey = extractKey(args[0], keyPrefix);\n\n const fallbackObj =\n options.fallback ??\n (typeof window !== 'undefined' ? (window as any).__livelocale_fallback__ : undefined);\n let resolvedValue = result;\n const isMissing =\n resolvedValue === undefined ||\n resolvedValue === null ||\n resolvedValue === '' ||\n (typeof resolvedValue === 'string' &&\n (resolvedValue === args[0] || resolvedValue === calledKey));\n\n if (isMissing && fallbackObj && calledKey) {\n const fbVal = getNestedProperty(fallbackObj, calledKey);\n if (fbVal !== undefined) {\n resolvedValue = fbVal;\n }\n }\n\n if (typeof resolvedValue === 'string' && calledKey) {\n return encodeKey(resolvedValue, calledKey);\n }\n return resolvedValue;\n },\n });\n}\n"],"mappings":"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","names":["val","target"]}
|
|
1
|
+
{"version":3,"sources":["../../src/client/index.ts","../../src/client/watermark.ts","../../src/core/env.ts","../../src/client/iframe-bridge.ts","../../src/client/dom-interceptor.ts","../../src/client/wrap.ts"],"sourcesContent":["'use client';\n\nimport { DOMInterceptor } from './dom-interceptor.js';\nimport { IframeBridge, readSameOriginParent, resolveStudioOrigin } from './iframe-bridge.js';\nimport { decodeKey } from './watermark.js';\nimport { isLiveLocaleEnabled } from '../core/env.js';\n\nexport { DOMInterceptor } from './dom-interceptor.js';\nexport {\n IframeBridge,\n type TranslationPayload,\n type ClientMessageType,\n type StudioMessageType,\n type VisibleText,\n} from './iframe-bridge.js';\nexport {\n encodeKey,\n decodeKey,\n stripWatermark,\n sanitizeZeroWidth,\n removeWatermarks,\n hasWatermark,\n unwrap,\n} from './watermark.js';\n\n// wrapTranslationEngine lives in wrap.ts (no 'use client') so it can also be\n// imported by Server Components via 'livelocale/server'.\nexport { wrapTranslationEngine, wrapNextIntl, type WrapOptions } from './wrap.js';\n\n// โโโ Live preview support โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\n// Text nodes mutated by APPLY_PREVIEW, with their original value so the\n// preview can be reverted when the selection is cleared.\nconst previewNodes = new Map<Text, { key: string; original: string }>();\n\n/**\n * Applies a live preview value to every rendered element matching `key`\n * without touching the locale files. Only the visible text after the\n * watermark prefix is replaced, keeping the watermark (and thus the\n * data-i18n-key scanner) intact.\n */\nfunction applyPreview(key: string, value: string): void {\n const elements = document.querySelectorAll<HTMLElement>('[data-i18n-key]');\n elements.forEach((el) => {\n if (el.getAttribute('data-i18n-key') !== key) return;\n\n const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null);\n const node = walker.nextNode() as Text | null;\n if (!node) return;\n\n const raw = node.nodeValue ?? '';\n const decoded = decodeKey(raw);\n if (!decoded) return;\n\n // Preserve the watermark prefix, swap only the visible text\n const next = raw.slice(0, raw.length - decoded.cleanText.length) + value;\n // Skipping unchanged text avoids a mutation, so re-applying is idempotent.\n if (next === raw) return;\n if (!previewNodes.has(node)) {\n previewNodes.set(node, { key, original: raw });\n }\n node.nodeValue = next;\n });\n}\n\n/**\n * Makes a saved value the text that stays on the page. In development the\n * dev server re-renders from the edited file anyway; in preview mode the\n * deployment still serves the old text until it is rebuilt, so the Studio\n * re-sends saved values whenever the page reloads or changes.\n */\nfunction commitPreview(key: string, value: string): void {\n applyPreview(key, value);\n previewNodes.forEach((entry, node) => {\n if (entry.key === key) previewNodes.delete(node);\n });\n}\n\n/** Restores all previewed text nodes to their pre-preview values. */\nfunction restorePreviews(): void {\n previewNodes.forEach(({ original }, node) => {\n node.nodeValue = original;\n });\n previewNodes.clear();\n}\n\n// โโโ Auto-initialization โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\n// Auto-initialize the interceptor if running in the browser, in development mode,\n// AND only when the page is embedded inside the studio iframe (not accessed directly).\nif (\n typeof window !== 'undefined' &&\n isLiveLocaleEnabled() &&\n window.parent !== window // <-- iframe guard: only activate inside the studio\n) {\n let storage: Storage | null = null;\n try {\n storage = window.sessionStorage;\n } catch {}\n const allowedOrigin = resolveStudioOrigin(\n window.location.search,\n storage,\n readSameOriginParent(window)\n );\n\n const bridge = new IframeBridge(allowedOrigin);\n const interceptor = new DOMInterceptor(\n (payload) => {\n bridge.sendToParent('ELEMENT_SELECTED', payload);\n },\n (keys) => {\n bridge.sendToParent('VISIBLE_KEYS_CHANGED', keys);\n bridge.sendToParent('VISIBLE_TEXTS_CHANGED', interceptor.getVisibleTexts());\n }\n );\n\n interceptor.init();\n\n // Send a READY message to the parent studio\n bridge.sendToParent('READY', { url: window.location.pathname + window.location.search });\n\n // Listen to studio commands\n bridge.listenToParent((type, payload) => {\n if (type === 'APPLY_PREVIEW') {\n if (payload && typeof payload.key === 'string' && typeof payload.value === 'string') {\n applyPreview(payload.key, payload.value);\n }\n } else if (type === 'COMMIT_PREVIEW') {\n if (payload && typeof payload.key === 'string' && typeof payload.value === 'string') {\n commitPreview(payload.key, payload.value);\n }\n } else if (type === 'CLEAR_SELECTION') {\n // Revert any live preview, then re-trigger/reset interceptor highlight states\n restorePreviews();\n interceptor.destroy();\n interceptor.init();\n }\n });\n\n // Attach reference to window for developer inspection/diagnostics\n (window as any).__LIVELOCALE_CLIENT__ = {\n bridge,\n interceptor,\n };\n}\n","/**\n * Zero-Width Watermarking Encoder/Decoder\n *\n * Maps translation keys into invisible Unicode zero-width characters and\n * prepends them as a binary watermark onto the translation string.\n *\n * Delimiters:\n * \\u200C โ Zero-Width Non-Joiner (ZWNJ) โ boundary start/end marker\n * \\u200B โ Zero-Width Space (ZWS) โ binary bit 0\n * \\u200D โ Zero-Width Joiner (ZWJ) โ binary bit 1\n *\n * The delimiters touch the visible text, so they must be non-joining: a ZWJ\n * there is \"join-causing\" and makes the first Arabic letter take its connected\n * form, as if a hidden letter came before it. Bits never touch a letter.\n *\n * The watermark is strictly prepended so it survives downstream truncation\n * (e.g., text.substring()) that removes only from the right.\n */\n\nconst DELIM = '\\u200C'; // ZWNJ โ boundary delimiter (non-joining)\nconst BIT_0 = '\\u200B'; // ZWS โ represents bit 0\nconst BIT_1 = '\\u200D'; // ZWJ โ represents bit 1\n\n/** First character of every watermark; a cheap pre-check before decoding. */\nexport const WATERMARK_DELIMITER = DELIM;\n\nconst ZW_RE = /[\\u200B\\u200C\\u200D]/g;\n\n/** A complete watermark anywhere in a string: delimiter, whole bytes of bits, delimiter. */\nconst WATERMARK_RE = /\\u200C(?:[\\u200B\\u200D]{8})+\\u200C/g;\n\n/** Encodes a translation key as an invisible watermark prefix on `text`. */\nexport function encodeKey(text: string, key: string): string {\n const bytes = new TextEncoder().encode(key);\n let bits = '';\n for (const byte of bytes) {\n bits += byte.toString(2).padStart(8, '0');\n }\n\n let watermark = DELIM;\n for (const bit of bits) {\n watermark += bit === '0' ? BIT_0 : BIT_1;\n }\n watermark += DELIM;\n\n return watermark + text;\n}\n\n/**\n * Decodes a watermark prefix from `text`.\n *\n * Returns `{ key, cleanText }` where `cleanText` is the original visible\n * text with the watermark stripped. If no valid watermark is found,\n * returns `null`.\n */\nexport function decodeKey(text: string): { key: string; cleanText: string } | null {\n if (!text.startsWith(DELIM)) return null;\n\n const end = text.indexOf(DELIM, 1);\n if (end === -1) return null;\n\n const bitsStr = text.slice(1, end);\n // Validate: every char must be a known ZW bit\n for (const ch of bitsStr) {\n if (ch !== BIT_0 && ch !== BIT_1) return null;\n }\n\n if (bitsStr.length !== 0 && bitsStr.length % 8 !== 0) return null;\n\n const bytes: number[] = [];\n for (let i = 0; i < bitsStr.length; i += 8) {\n let byte = 0;\n for (let j = 0; j < 8; j++) {\n byte = (byte << 1) | (bitsStr[i + j] === BIT_1 ? 1 : 0);\n }\n bytes.push(byte);\n }\n\n try {\n const key = new TextDecoder().decode(new Uint8Array(bytes));\n const cleanText = text.slice(end + 1);\n return { key, cleanText };\n } catch {\n return null;\n }\n}\n\n/**\n * Strips a valid watermark *prefix* from `text`, leaving any legitimate\n * zero-width characters inside the visible text untouched.\n *\n * ZWNJ (\\u200C) and ZWSP (\\u200B) are valid orthography in Persian, Urdu,\n * Thai, and other scripts โ a blanket removal would silently corrupt real\n * translations. Only the decoded watermark prefix is removed.\n */\nexport function stripWatermark(text: string): string {\n const decoded = decodeKey(text);\n if (decoded) return decoded.cleanText;\n return text;\n}\n\n/**\n * Aggressively removes every zero-width character from `text`.\n *\n * This also removes ZWNJ and ZWSP that Persian, Urdu and Thai text needs, so\n * prefer `removeWatermarks` for user-visible text and `stripWatermark` for\n * translation content.\n */\nexport function sanitizeZeroWidth(text: string): string {\n return text.replace(ZW_RE, '');\n}\n\n/**\n * Removes every complete watermark from `text`, wherever it occurs, and keeps\n * all other zero-width characters. Persian and Urdu use ZWNJ inside words and\n * Thai uses ZWSP, so stripping every zero-width character would corrupt them.\n */\nexport function removeWatermarks(text: string): string {\n return text.replace(WATERMARK_RE, '');\n}\n\n/** True when `text` contains at least one complete watermark. */\nexport function containsWatermark(text: string): boolean {\n WATERMARK_RE.lastIndex = 0;\n const found = WATERMARK_RE.test(text);\n WATERMARK_RE.lastIndex = 0;\n return found;\n}\n\n/**\n * Returns true if `text` begins with a valid watermark prefix.\n */\nexport function hasWatermark(text: string): boolean {\n return text.startsWith(DELIM);\n}\n\n/**\n * Deeply strips watermark prefixes from a value: strings are cleaned with\n * `stripWatermark`, arrays and plain objects are rebuilt with cleaned members,\n * everything else is returned as-is. The input is never mutated.\n *\n * Use it wherever a translated string must be compared, measured, serialized\n * or handed to code that is not display-only, e.g. `unwrap(t('yes')) === 'Yes'`.\n */\nexport function unwrap<T>(value: T): T {\n if (typeof value === 'string') return stripWatermark(value) as T;\n if (Array.isArray(value)) return value.map((item) => unwrap(item)) as T;\n if (value !== null && typeof value === 'object') {\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(value as Record<string, unknown>)) {\n out[key] = unwrap((value as Record<string, unknown>)[key]);\n }\n return out as T;\n }\n return value;\n}\n","/**\n * Single source of truth for \"is LiveLocale active right now, and in which mode?\".\n *\n * Modes:\n * - `development`: NODE_ENV is `development` (the default, zero-config mode).\n * - `preview`: explicitly opted in with `LIVELOCALE=preview` (server) or\n * `NEXT_PUBLIC_LIVELOCALE=preview` (client bundles). Works in production builds so a\n * preview deployment can be edited through a hosted Studio. The mutation API then\n * requires a bearer token (see the router).\n * - `off`: everything else.\n *\n * Rules, in order:\n * 1. `LIVELOCALE=off|0|false` (or `NEXT_PUBLIC_LIVELOCALE=off`) always wins โ off.\n * 2. `LIVELOCALE=preview` / `NEXT_PUBLIC_LIVELOCALE=preview` โ preview.\n * 3. `NODE_ENV` must be `development`, otherwise off.\n * 4. `LIVELOCALE=force` skips rule 5.\n * 5. On the server, a known hosting platform (Vercel preview/production, Netlify,\n * Kubernetes, Render, Fly.io, Railway, Heroku) โ off, so a mis-configured staging\n * deploy cannot leak watermarks. A one-time warning is logged.\n *\n * `process.env.NODE_ENV` and `process.env.NEXT_PUBLIC_LIVELOCALE` are referenced as\n * literal member expressions so Next.js and Vite can inline them in browser bundles.\n */\n\nexport type LiveLocaleMode = 'off' | 'development' | 'preview';\n\nconst OFF_VALUES = new Set(['off', '0', 'false']);\n\nlet warnedDeployed = false;\n\n/** Test hook: forget that the deployed-platform warning was already printed. */\nexport function resetEnvWarnings(): void {\n warnedDeployed = false;\n}\n\nfunction readNodeEnv(): string | undefined {\n try {\n return process.env.NODE_ENV;\n } catch {\n return undefined;\n }\n}\n\n/** Set by the Vite plugin (and usable by any host) to mirror the server flag into the page. */\nexport const BROWSER_FLAG_GLOBAL = '__LIVELOCALE_FLAG__';\n\nfunction readPublicFlag(): string | undefined {\n let value: string | undefined;\n try {\n value = process.env.NEXT_PUBLIC_LIVELOCALE;\n } catch {\n value = undefined;\n }\n if (value) return value;\n try {\n const g = globalThis as any;\n const fromGlobal = g.window?.[BROWSER_FLAG_GLOBAL] ?? g[BROWSER_FLAG_GLOBAL];\n return typeof fromGlobal === 'string' ? fromGlobal : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction readEnv(name: string): string | undefined {\n if (typeof process === 'undefined' || !process.env) return undefined;\n const value = process.env[name];\n return value === '' ? undefined : value;\n}\n\n/** Returns a human-readable platform name when running on a known hosting platform. */\nexport function detectDeployedPlatform(): string | null {\n if (typeof window !== 'undefined') return null; // browser: nothing to detect\n const vercelEnv = readEnv('VERCEL_ENV');\n if (vercelEnv === 'production' || vercelEnv === 'preview')\n return `Vercel (VERCEL_ENV=${vercelEnv})`;\n const netlifyContext = readEnv('CONTEXT');\n if (readEnv('NETLIFY') === 'true' && netlifyContext && netlifyContext !== 'dev') {\n return `Netlify (CONTEXT=${netlifyContext})`;\n }\n if (readEnv('KUBERNETES_SERVICE_HOST')) return 'Kubernetes';\n if (readEnv('RENDER') === 'true') return 'Render';\n if (readEnv('FLY_APP_NAME')) return 'Fly.io';\n if (readEnv('RAILWAY_ENVIRONMENT')) return 'Railway';\n if (readEnv('DYNO')) return 'Heroku';\n return null;\n}\n\nfunction evaluate(): { mode: LiveLocaleMode; reason: string | null } {\n const flag = (readEnv('LIVELOCALE') ?? '').toLowerCase();\n const publicFlag = (readPublicFlag() ?? '').toLowerCase();\n\n if (OFF_VALUES.has(flag)) return { mode: 'off', reason: 'LIVELOCALE kill switch is set' };\n if (OFF_VALUES.has(publicFlag)) {\n return { mode: 'off', reason: 'NEXT_PUBLIC_LIVELOCALE kill switch is set' };\n }\n\n if (flag === 'preview' || publicFlag === 'preview') return { mode: 'preview', reason: null };\n\n const nodeEnv = readNodeEnv();\n if (nodeEnv !== 'development') {\n return {\n mode: 'off',\n reason: `NODE_ENV is \"${nodeEnv ?? 'undefined'}\" (must be \"development\", or set LIVELOCALE=preview)`,\n };\n }\n\n if (flag === 'force') return { mode: 'development', reason: null };\n\n const platform = detectDeployedPlatform();\n if (platform) {\n if (!warnedDeployed) {\n warnedDeployed = true;\n console.warn(\n `[livelocale] Disabled: deployed environment detected (${platform}) while NODE_ENV=development. ` +\n `Set LIVELOCALE=force to override, LIVELOCALE=preview for a hosted preview, or LIVELOCALE=off to silence this warning.`\n );\n }\n return { mode: 'off', reason: `deployed environment detected (${platform})` };\n }\n\n return { mode: 'development', reason: null };\n}\n\n/** The active mode: `off`, `development`, or `preview`. */\nexport function getLiveLocaleMode(): LiveLocaleMode {\n return evaluate().mode;\n}\n\n/**\n * Returns `null` when LiveLocale should be active, otherwise a short reason\n * suitable for error messages and logs.\n */\nexport function getDisabledReason(): string | null {\n return evaluate().reason;\n}\n\n/** True when watermarking, the mutation API, and the client scanner should run. */\nexport function isLiveLocaleEnabled(): boolean {\n return evaluate().mode !== 'off';\n}\n","export type ClientMessageType =\n | 'ELEMENT_SELECTED'\n | 'READY'\n | 'ERROR'\n | 'VISIBLE_KEYS_CHANGED'\n | 'VISIBLE_TEXTS_CHANGED';\nexport type StudioMessageType = 'APPLY_PREVIEW' | 'COMMIT_PREVIEW' | 'CLEAR_SELECTION';\n\n/** Where `livelocale studio` runs unless the Studio says otherwise. */\nexport const DEFAULT_STUDIO_ORIGIN = 'http://localhost:3010';\n\n/** Session key that keeps the Studio origin across in-app navigation within one tab. */\nexport const STUDIO_ORIGIN_STORAGE_KEY = 'livelocale-studio-origin';\n\nfunction toHttpOrigin(value: string | null | undefined): string | null {\n if (!value) return null;\n try {\n const url = new URL(value);\n return url.protocol === 'http:' || url.protocol === 'https:' ? url.origin : null;\n } catch {\n return null;\n }\n}\n\n/**\n * The origin of this page when its parent window is a different window with\n * the identical readable origin (a Studio served from the same host), else\n * `null`. Returns `toHttpOrigin(win.location.origin)` only when that is\n * non-null and equals `win.parent.location.origin`; a top-level page, a\n * missing parent, a cross-origin parent (whose `location` throws), a parent\n * on another origin and an opaque `'null'` origin all give `null`. Never throws.\n */\nexport function readSameOriginParent(win: Window): string | null {\n try {\n const parent = win.parent;\n if (!parent || parent === win) return null;\n const own = toHttpOrigin(win.location.origin);\n if (!own) return null;\n return parent.location.origin === own ? own : null;\n } catch {\n return null;\n }\n}\n\n/**\n * The origin of the Studio that embeds this page, in this order:\n *\n * 1. `sameOriginParent`, when it is an http(s) origin. `sendToParent` always\n * posts to `window.parent`, so a readable same-origin parent is the only\n * origin that can receive the messages; it wins over a stale query value or\n * remembered origin. This branch neither reads nor writes storage.\n * 2. `?livelocale-origin=` on the iframe URL, which the Studio passes. The\n * app's own links drop that parameter, so it is remembered for the tab;\n * without it, messages after the first navigation would go to the default\n * origin and be discarded.\n * 3. The origin remembered for the tab.\n * 4. `DEFAULT_STUDIO_ORIGIN`.\n */\nexport function resolveStudioOrigin(\n search: string,\n storage: Storage | null,\n sameOriginParent?: string | null\n): string {\n const fromParent = toHttpOrigin(sameOriginParent);\n if (fromParent) return fromParent;\n const fromQuery = toHttpOrigin(new URLSearchParams(search).get('livelocale-origin'));\n if (fromQuery) {\n try {\n storage?.setItem(STUDIO_ORIGIN_STORAGE_KEY, fromQuery);\n } catch {}\n return fromQuery;\n }\n let remembered: string | null = null;\n try {\n remembered = toHttpOrigin(storage?.getItem(STUDIO_ORIGIN_STORAGE_KEY));\n } catch {}\n return remembered ?? DEFAULT_STUDIO_ORIGIN;\n}\n\n/** A key on the page with the text it shows now; the Studio infers the page locale from these. */\nexport interface VisibleText {\n key: string;\n text: string;\n}\n\n/** Most visible key/text pairs one VISIBLE_TEXTS_CHANGED message carries. */\nexport const MAX_VISIBLE_TEXTS = 100;\n\n/** Longest text, in UTF-16 code units, one visible key/text pair carries. */\nexport const MAX_VISIBLE_TEXT_LENGTH = 200;\n\nconst KEY_PATTERN = /^[a-zA-Z0-9._-]+$/;\n\nexport interface TranslationPayload {\n key: string;\n fallbackValue: string;\n currentValue: string;\n}\n\nexport class IframeBridge {\n private allowedOrigin: string;\n\n constructor(allowedOrigin: string) {\n this.allowedOrigin = allowedOrigin;\n }\n\n /**\n * Sends a message from the iframe client to the parent Studio window.\n */\n sendToParent(type: ClientMessageType, payload?: any): void {\n if (typeof window === 'undefined' || !window.parent || window.parent === window) {\n // Not in an iframe context\n return;\n }\n\n // RULE CLT-006: PAYLOAD SANITIZATION\n if (type === 'ELEMENT_SELECTED') {\n const p = payload as TranslationPayload;\n if (\n !p ||\n typeof p.key !== 'string' ||\n p.key.trim() === '' ||\n !/^[a-zA-Z0-9._-]+$/.test(p.key) ||\n typeof p.currentValue !== 'string'\n ) {\n // Drop invalid payload silently\n return;\n }\n }\n\n if (type === 'VISIBLE_KEYS_CHANGED') {\n // Validate payload is an array of valid key strings\n if (!Array.isArray(payload)) return;\n const validKeys = (payload as unknown[]).filter(\n (k): k is string => typeof k === 'string' && /^[a-zA-Z0-9._-]+$/.test(k)\n );\n // Replace the payload with the sanitized array\n payload = validKeys;\n }\n\n if (type === 'VISIBLE_TEXTS_CHANGED') {\n if (!Array.isArray(payload)) return;\n const texts: VisibleText[] = [];\n for (const entry of payload as unknown[]) {\n if (texts.length >= MAX_VISIBLE_TEXTS) break;\n if (!entry || typeof entry !== 'object') continue;\n const { key, text } = entry as { key?: unknown; text?: unknown };\n if (typeof key !== 'string' || !KEY_PATTERN.test(key) || typeof text !== 'string') {\n continue;\n }\n texts.push({ key, text: text.slice(0, MAX_VISIBLE_TEXT_LENGTH) });\n }\n payload = texts;\n }\n\n const message = {\n source: 'livelocale-client',\n type,\n payload,\n };\n\n window.parent.postMessage(message, this.allowedOrigin);\n }\n\n /**\n * Sets up a listener for messages originating from the parent Studio dashboard.\n */\n listenToParent(callback: (type: StudioMessageType, payload: any) => void): () => void {\n if (typeof window === 'undefined') {\n return () => {};\n }\n\n const listener = (event: MessageEvent) => {\n // RULE CLT-005: MESSAGE ORIGIN VALIDATION\n if (event.origin !== this.allowedOrigin) {\n return;\n }\n if (event.data?.source !== 'livelocale-studio') {\n return;\n }\n\n const { type, payload } = event.data;\n callback(type, payload);\n };\n\n window.addEventListener('message', listener);\n\n // Return a function to clean up the event listener\n return () => {\n window.removeEventListener('message', listener);\n };\n }\n}\n","import {\n decodeKey,\n stripWatermark,\n removeWatermarks,\n containsWatermark,\n WATERMARK_DELIMITER,\n} from './watermark.js';\nimport { isLiveLocaleEnabled } from '../core/env.js';\nimport { MAX_VISIBLE_TEXTS, MAX_VISIBLE_TEXT_LENGTH, type VisibleText } from './iframe-bridge.js';\n\n// Debounce helper: delays executing fn until after wait ms have elapsed\n// since the last call. Returns a cancel function.\nfunction debounce<T extends (...args: any[]) => void>(fn: T, wait: number): T & { cancel(): void } {\n let timer: ReturnType<typeof setTimeout> | null = null;\n const debounced = (...args: Parameters<T>) => {\n if (timer !== null) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n fn(...args);\n }, wait);\n };\n debounced.cancel = () => {\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n };\n return debounced as T & { cancel(): void };\n}\n\nexport class DOMInterceptor {\n private onElementSelected: (payload: {\n key: string;\n fallbackValue: string;\n currentValue: string;\n }) => void;\n private onVisibleKeysChanged?: (keys: string[]) => void;\n\n private hoveredElement: HTMLElement | null = null;\n private observer: MutationObserver | null = null;\n private inputSanitizersAttached: boolean = false;\n\n // โโโ Floating Overlay โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n private overlay: HTMLDivElement | null = null;\n private overlayBadge: HTMLSpanElement | null = null;\n\n // โโโ Alt key state โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n private isAltHeld: boolean = false;\n\n // Debounced key-broadcast to avoid flooding the parent on rapid DOM mutations\n private broadcastVisibleKeys: (() => void) & { cancel(): void };\n\n constructor(\n onElementSelected: (payload: {\n key: string;\n fallbackValue: string;\n currentValue: string;\n }) => void,\n onVisibleKeysChanged?: (keys: string[]) => void\n ) {\n this.onElementSelected = onElementSelected;\n this.onVisibleKeysChanged = onVisibleKeysChanged;\n\n this.broadcastVisibleKeys = debounce(() => {\n if (this.onVisibleKeysChanged) {\n this.onVisibleKeysChanged(this.getActiveKeys());\n }\n }, 200);\n }\n\n /**\n * Returns the unique set of translation keys currently visible in the DOM.\n */\n getActiveKeys(): string[] {\n const elements = document.querySelectorAll<HTMLElement>('[data-i18n-key]');\n const keys = new Set<string>();\n elements.forEach((el) => {\n const key = el.getAttribute('data-i18n-key');\n if (key) keys.add(key);\n });\n return Array.from(keys);\n }\n\n /**\n * Returns one `{ key, text }` per translation key on the page, in document\n * order, with the text it shows now (watermark removed, trimmed, cut to\n * `MAX_VISIBLE_TEXT_LENGTH`). The first element carrying a key wins; its\n * text is the first descendant text node whose watermark names that key.\n * Elements without such a node, and blank texts, are skipped. At most\n * `limit` entries are returned.\n */\n getVisibleTexts(limit: number = MAX_VISIBLE_TEXTS): VisibleText[] {\n const texts: VisibleText[] = [];\n const seen = new Set<string>();\n const elements = document.querySelectorAll<HTMLElement>('[data-i18n-key]');\n for (const el of elements) {\n if (texts.length >= limit) break;\n const key = el.getAttribute('data-i18n-key');\n if (!key || seen.has(key)) continue;\n seen.add(key);\n const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null);\n let node: Text | null;\n while ((node = walker.nextNode() as Text | null) !== null) {\n const decoded = decodeKey(node.nodeValue ?? '');\n if (decoded?.key !== key) continue;\n const text = decoded.cleanText.trim().slice(0, MAX_VISIBLE_TEXT_LENGTH);\n if (text) texts.push({ key, text });\n break;\n }\n }\n return texts;\n }\n\n /**\n * Initializes the DOM event interceptors in development mode.\n * Also launches the watermark scanner and MutationObserver.\n */\n init(): void {\n // RULE CLT-004: INIT GUARD\n if (!isLiveLocaleEnabled()) {\n return;\n }\n\n if (typeof window === 'undefined') {\n return;\n }\n\n // Inject the floating highlighter overlay into the document body\n this.injectOverlay();\n\n // Attach listeners on window capture phase (RULE CLT-002)\n window.addEventListener('mouseover', this.handleMouseOver, true);\n window.addEventListener('mouseout', this.handleMouseOut, true);\n window.addEventListener('click', this.handleClick, true);\n window.addEventListener('copy', this.handleCopy, true);\n window.addEventListener('paste', this.handlePaste, true);\n window.addEventListener('scroll', this.handleScroll, true);\n window.addEventListener('resize', this.handleResize, false);\n window.addEventListener('keydown', this.handleKeyDown, true);\n window.addEventListener('keyup', this.handleKeyUp, true);\n\n // Phase 3: Scan existing DOM for watermarked text nodes\n this.scanSubtree(document.body);\n\n // Phase 3: Observe future DOM mutations for watermarked nodes\n this.attachMutationObserver();\n\n // Phase 3: Sanitize form inputs/submissions to strip ZW chars\n this.attachInputSanitizers();\n\n // Broadcast the initial set of visible keys to the studio sidebar\n this.broadcastVisibleKeys();\n }\n\n /**\n * Cleans up all attached event listeners, the MutationObserver,\n * the floating overlay, and the form-sanitization listeners.\n */\n destroy(): void {\n if (typeof window === 'undefined') {\n return;\n }\n window.removeEventListener('mouseover', this.handleMouseOver, true);\n window.removeEventListener('mouseout', this.handleMouseOut, true);\n window.removeEventListener('click', this.handleClick, true);\n window.removeEventListener('copy', this.handleCopy, true);\n window.removeEventListener('paste', this.handlePaste, true);\n window.removeEventListener('scroll', this.handleScroll, true);\n window.removeEventListener('resize', this.handleResize, false);\n window.removeEventListener('keydown', this.handleKeyDown, true);\n window.removeEventListener('keyup', this.handleKeyUp, true);\n window.removeEventListener('beforeinput', this.handleBeforeInput, true);\n\n // Detach form-submit sanitizer\n document.removeEventListener('submit', this.handleFormSubmit, true);\n\n if (this.observer) {\n this.observer.disconnect();\n this.observer = null;\n }\n\n this.inputSanitizersAttached = false;\n\n // Remove floating overlay from DOM\n if (this.overlay && this.overlay.parentNode) {\n this.overlay.parentNode.removeChild(this.overlay);\n }\n this.overlay = null;\n this.overlayBadge = null;\n this.hoveredElement = null;\n\n // Cancel any pending debounced broadcasts\n this.broadcastVisibleKeys.cancel();\n }\n\n // โโโ Floating Overlay โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\n /**\n * Creates and injects a single floating overlay div into the document body.\n * The overlay is positioned using fixed coordinates from getBoundingClientRect()\n * and uses pointer-events: none so it never blocks user interaction.\n */\n private injectOverlay(): void {\n if (this.overlay) return; // Guard against double injection\n\n const overlay = document.createElement('div');\n overlay.id = 'livelocale-highlighter-overlay';\n overlay.style.cssText = [\n 'position: fixed',\n 'top: 0',\n 'left: 0',\n 'width: 0',\n 'height: 0',\n 'pointer-events: none',\n 'z-index: 2147483647', // Maximum z-index\n 'box-sizing: border-box',\n 'opacity: 0',\n 'border-radius: 4px',\n 'transition: opacity 120ms ease, top 80ms ease, left 80ms ease, width 80ms ease, height 80ms ease, border-color 150ms ease',\n ].join('; ');\n\n const badge = document.createElement('span');\n badge.style.cssText = [\n 'position: absolute',\n 'top: -22px',\n 'left: 0',\n 'max-width: 280px',\n 'white-space: nowrap',\n 'overflow: hidden',\n 'text-overflow: ellipsis',\n 'background: #0C0D0F', // Night\n 'border: 1px solid #4FC3DC', // Hunk\n 'color: #E8E4D9', // Paper\n 'font-family: ui-monospace, SFMono-Regular, Menlo, monospace',\n 'font-size: 10px',\n 'font-weight: 600',\n 'line-height: 1',\n 'padding: 3px 7px',\n 'border-radius: 4px',\n 'letter-spacing: 0.02em',\n 'pointer-events: none',\n ].join('; ');\n\n overlay.appendChild(badge);\n document.body.appendChild(overlay);\n\n this.overlay = overlay;\n this.overlayBadge = badge;\n }\n\n /**\n * Positions the floating overlay to surround the given element.\n * Uses getBoundingClientRect() for fixed-positioned geometry.\n */\n private positionOverlay(element: HTMLElement): void {\n if (!this.overlay) return;\n\n const rect = element.getBoundingClientRect();\n const computedStyle = window.getComputedStyle(element);\n const borderRadius = computedStyle.borderRadius || '4px';\n\n this.overlay.style.top = `${rect.top}px`;\n this.overlay.style.left = `${rect.left}px`;\n this.overlay.style.width = `${rect.width}px`;\n this.overlay.style.height = `${rect.height}px`;\n this.overlay.style.borderRadius = borderRadius;\n this.overlay.style.opacity = '1';\n\n this.updateOverlayStyle();\n }\n\n /**\n * Updates overlay border color and badge text based on current Alt key state.\n */\n private updateOverlayStyle(): void {\n if (!this.overlay || !this.overlayBadge) return;\n\n const key = this.hoveredElement?.getAttribute('data-i18n-key') || '';\n\n if (this.isAltHeld) {\n // Alt is held โ solid Hunk border signals \"click to edit\"\n this.overlay.style.border = '2px solid #4FC3DC';\n this.overlay.style.boxShadow = 'none';\n this.overlayBadge.style.color = '#E8E4D9';\n this.overlayBadge.style.borderColor = '#4FC3DC';\n this.overlayBadge.textContent = `โ Click to edit: ${key}`;\n } else {\n // Hover โ dashed Hunk border signals \"Alt + Click to edit\"\n this.overlay.style.border = '1.5px dashed #4FC3DC';\n this.overlay.style.boxShadow = 'none';\n this.overlayBadge.style.color = '#E8E4D9';\n this.overlayBadge.style.borderColor = '#4FC3DC';\n this.overlayBadge.textContent = `โฅ Alt+Click to edit: ${key}`;\n }\n }\n\n /**\n * Hides the floating overlay without removing it from the DOM.\n */\n private hideOverlay(): void {\n if (!this.overlay) return;\n this.overlay.style.opacity = '0';\n }\n\n // โโโ Phase 3: Watermark DOM Scanner โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\n /**\n * Walks all text nodes inside `root` and decodes any watermark prefix.\n * When found, attaches data-i18n-key / data-i18n-template to the\n * immediate parent HTMLElement without mutating the text node itself\n * (hydration safety: RULE CLT-007).\n */\n private scanSubtree(root: Node): void {\n if (!root) return;\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null);\n\n let node: Text | null;\n while ((node = walker.nextNode() as Text | null) !== null) {\n this.processTextNode(node);\n }\n }\n\n private processTextNode(node: Text): void {\n const raw = node.nodeValue ?? '';\n if (!raw.includes(WATERMARK_DELIMITER)) return; // fast bail-out\n\n const decoded = decodeKey(raw);\n if (!decoded) return;\n\n const parent = node.parentElement;\n if (!parent) return;\n\n // RULE CLT-007: Do NOT strip the ZW chars from the text node โ\n // doing so would break React's reconciliation / hydration tree.\n // Only annotate the parent element.\n parent.setAttribute('data-i18n-key', decoded.key);\n\n // Use the clean (visible) text as the fallback template if not already set\n if (!parent.hasAttribute('data-i18n-template')) {\n parent.setAttribute('data-i18n-template', decoded.cleanText.trim());\n }\n }\n\n /**\n * Attaches a MutationObserver to catch newly rendered / streamed elements.\n */\n private attachMutationObserver(): void {\n if (this.observer) return;\n\n this.observer = new MutationObserver((mutations) => {\n let needsBroadcast = false;\n for (const mutation of mutations) {\n if (mutation.type === 'childList') {\n for (const added of mutation.addedNodes) {\n if (added.nodeType === Node.ELEMENT_NODE) {\n this.scanSubtree(added);\n needsBroadcast = true;\n } else if (added.nodeType === Node.TEXT_NODE) {\n this.processTextNode(added as Text);\n needsBroadcast = true;\n }\n }\n } else if (mutation.type === 'characterData') {\n if (mutation.target.nodeType === Node.TEXT_NODE) {\n this.processTextNode(mutation.target as Text);\n needsBroadcast = true;\n }\n }\n }\n if (needsBroadcast) {\n this.broadcastVisibleKeys();\n }\n });\n\n this.observer.observe(document.body, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n }\n\n // โโโ Phase 3: Input & Form Sanitization โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\n /**\n * Patches input value descriptors and attaches a form submit listener\n * to strip zero-width watermark characters before any data leaves the\n * browser (preventing backend schema validation rejections or storage\n * of invisible chars).\n */\n private attachInputSanitizers(): void {\n if (this.inputSanitizersAttached) return;\n this.inputSanitizersAttached = true;\n\n // Capture form submissions at the document level\n document.addEventListener('submit', this.handleFormSubmit, true);\n\n // Intercept `beforeinput` for direct keyboard input into inputs/textareas\n window.addEventListener('beforeinput', this.handleBeforeInput, true);\n\n // Patch HTMLInputElement.prototype.value getter/setter\n this.patchInputValueDescriptor(HTMLInputElement.prototype);\n this.patchInputValueDescriptor(HTMLTextAreaElement.prototype);\n }\n\n /**\n * Patches the `value` property descriptor of an input prototype so\n * that reads return sanitized strings even if the user somehow pastes\n * watermark characters.\n */\n private patchInputValueDescriptor(proto: HTMLInputElement | HTMLTextAreaElement): void {\n const original = Object.getOwnPropertyDescriptor(proto, 'value');\n if (!original || !original.get || !original.set) return;\n\n // Prevent double-patching\n if ((proto as any).__liveLocaleSanitized__) return;\n (proto as any).__liveLocaleSanitized__ = true;\n\n Object.defineProperty(proto, 'value', {\n get() {\n const raw: string = original.get!.call(this);\n return removeWatermarks(raw);\n },\n set(val: string) {\n const clean = typeof val === 'string' ? removeWatermarks(val) : val;\n original.set!.call(this, clean);\n },\n configurable: true,\n });\n }\n\n private handleFormSubmit = (event: Event): void => {\n const form = event.target as HTMLFormElement | null;\n if (!form) return;\n\n const inputs = form.querySelectorAll<HTMLInputElement | HTMLTextAreaElement>('input, textarea');\n inputs.forEach((input) => {\n const cleaned = removeWatermarks(input.value);\n if (cleaned !== input.value) {\n // Use the original setter to set the cleaned value directly\n const proto =\n input instanceof HTMLTextAreaElement\n ? HTMLTextAreaElement.prototype\n : HTMLInputElement.prototype;\n const descriptor = Object.getOwnPropertyDescriptor(proto, 'value');\n if (descriptor?.set) {\n descriptor.set.call(input, cleaned);\n }\n }\n });\n };\n\n private handleBeforeInput = (_event: InputEvent): void => {\n // No-op: direct keyboard input doesn't contain ZW characters.\n // This listener is reserved for future clipboard paste interception.\n };\n\n // โโโ Alt Key Tracking โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\n private handleKeyDown = (event: KeyboardEvent): void => {\n if (event.key === 'Alt') {\n this.isAltHeld = true;\n // Update the overlay style immediately if an element is hovered\n if (this.hoveredElement) {\n this.updateOverlayStyle();\n }\n }\n };\n\n private handleKeyUp = (event: KeyboardEvent): void => {\n if (event.key === 'Alt') {\n this.isAltHeld = false;\n // Revert to the \"hover hint\" style\n if (this.hoveredElement) {\n this.updateOverlayStyle();\n }\n }\n };\n\n // โโโ Viewport change handlers (reposition overlay on scroll / resize) โโโโโ\n\n private handleScroll = (): void => {\n if (this.hoveredElement) {\n this.positionOverlay(this.hoveredElement);\n }\n };\n\n private handleResize = (): void => {\n if (this.hoveredElement) {\n this.positionOverlay(this.hoveredElement);\n }\n };\n\n // โโโ Mouse / Click Handlers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\n private handleMouseOver = (event: MouseEvent): void => {\n const target = event.target as HTMLElement | null;\n if (!target || typeof target.closest !== 'function') return;\n\n const keyedElement = target.closest('[data-i18n-key]') as HTMLElement | null;\n if (!keyedElement) {\n // Moved onto a non-keyed element โ hide overlay\n if (this.hoveredElement) {\n this.hideOverlay();\n this.hoveredElement = null;\n }\n return;\n }\n\n // RULE CLT-003: ALWAYS CHECK data-i18n-key EXISTENCE\n const key = keyedElement.getAttribute('data-i18n-key');\n if (key === null) return;\n\n this.hoveredElement = keyedElement;\n this.positionOverlay(keyedElement);\n };\n\n private handleMouseOut = (event: MouseEvent): void => {\n const target = event.target as HTMLElement | null;\n if (!target || typeof target.closest !== 'function') return;\n\n const relatedTarget = event.relatedTarget as HTMLElement | null;\n\n // If we're moving into a child of the same keyed element, don't hide\n if (relatedTarget && this.hoveredElement && this.hoveredElement.contains(relatedTarget)) {\n return;\n }\n\n const keyedElement = target.closest('[data-i18n-key]') as HTMLElement | null;\n if (!keyedElement || keyedElement !== this.hoveredElement) return;\n\n this.hideOverlay();\n this.hoveredElement = null;\n };\n\n private handleClick = (event: MouseEvent): void => {\n // Feature 1: Only intercept clicks when Alt key is held.\n // Without Alt, let the event propagate naturally so links and buttons work.\n if (!event.altKey) return;\n\n const target = event.target as HTMLElement | null;\n if (!target || typeof target.closest !== 'function') return;\n\n const keyedElement = target.closest('[data-i18n-key]') as HTMLElement | null;\n if (!keyedElement) return;\n\n // RULE CLT-003: ALWAYS CHECK data-i18n-key EXISTENCE\n const key = keyedElement.getAttribute('data-i18n-key');\n if (key === null) return;\n\n // RULE CLT-002: EVENT LISTENERS IN CAPTURE PHASE\n event.preventDefault();\n event.stopPropagation();\n\n // Use innerText, stripping any invisible ZW chars before sending to studio\n const rawText = keyedElement.innerText || '';\n const cleanText = stripWatermark(rawText);\n\n const fallbackValue = keyedElement.getAttribute('data-i18n-template') || cleanText;\n const currentValue = cleanText;\n\n this.onElementSelected({\n key,\n fallbackValue,\n currentValue,\n });\n };\n\n private handleCopy = (event: ClipboardEvent): void => {\n const selection = window.getSelection()?.toString() || '';\n if (containsWatermark(selection)) {\n const clean = removeWatermarks(selection);\n event.clipboardData?.setData('text/plain', clean);\n event.preventDefault();\n }\n };\n\n private handlePaste = (event: ClipboardEvent): void => {\n const text = event.clipboardData?.getData('text/plain') || '';\n if (containsWatermark(text)) {\n event.preventDefault();\n const cleaned = removeWatermarks(text);\n const targetInput = event.target as HTMLInputElement | HTMLTextAreaElement | null;\n if (targetInput && 'value' in targetInput) {\n const proto =\n targetInput instanceof HTMLTextAreaElement\n ? HTMLTextAreaElement.prototype\n : HTMLInputElement.prototype;\n const nativeValueSetter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;\n\n if (nativeValueSetter) {\n const start = targetInput.selectionStart || 0;\n const end = targetInput.selectionEnd || 0;\n const val = targetInput.value;\n const cleanedValue = val.slice(0, start) + cleaned + val.slice(end);\n\n nativeValueSetter.call(targetInput, cleanedValue);\n targetInput.selectionStart = targetInput.selectionEnd = start + cleaned.length;\n\n // Dispatch bubbling input event so React virtual DOM state syncs\n targetInput.dispatchEvent(new Event('input', { bubbles: true }));\n }\n }\n }\n };\n}\n","import { encodeKey } from './watermark.js';\nimport { isLiveLocaleEnabled } from '../core/env.js';\n\nexport interface WrapOptions {\n /**\n * A dot-separated key prefix to prepend to all resolved key paths.\n *\n * Use this when wrapping a namespaced translation function (e.g.\n * react-i18next `useTranslation('home')` or next-intl `useTranslations('home')`)\n * so that the watermarked key matches the actual path in your locale JSON file.\n *\n * @example\n * const { t: rawT } = useTranslation('home');\n * const t = wrapTranslationEngine(rawT, { keyPrefix: 'home' });\n * // t('title') encodes as 'home.title' โ matches the JSON structure\n */\n keyPrefix?: string;\n\n /**\n * Optional fallback translations to use when the target key is missing.\n */\n fallback?: Record<string, any>;\n}\n\n/**\n * Helper that extracts a dot-notated key string from an argument (string or object descriptor).\n */\nfunction extractKey(arg: unknown, keyPrefix: string): string {\n if (typeof arg === 'string') {\n return keyPrefix ? `${keyPrefix}.${arg}` : arg;\n }\n if (arg && typeof arg === 'object') {\n const obj = arg as Record<string, any>;\n if (typeof obj.id === 'string' && obj.id.trim() !== '') {\n return keyPrefix ? `${keyPrefix}.${obj.id}` : obj.id;\n }\n if (typeof obj.key === 'string' && obj.key.trim() !== '') {\n return keyPrefix ? `${keyPrefix}.${obj.key}` : obj.key;\n }\n }\n return keyPrefix;\n}\n\n/**\n * Wraps any translation function or record (e.g. from next-intl, react-i18next,\n * FormatJS, or a plain locale object loaded server-side) with a JavaScript Proxy\n * that automatically encodes translation keys as invisible zero-width watermark\n * prefixes in development. In production the proxy is transparent โ the original\n * engine is returned as-is.\n */\nexport function wrapTranslationEngine<T extends object>(engine: T, options: WrapOptions = {}): T {\n if (!isLiveLocaleEnabled()) {\n return engine;\n }\n\n return buildProxy(engine, options.keyPrefix ?? '', options);\n}\n\n/**\n * Dedicated convenience wrapper for `next-intl` (App Router and Pages Router).\n * Automatically wraps `useTranslations(namespace)` or `getTranslations(namespace)`.\n *\n * @example\n * ```tsx\n * const t = wrapNextIntl(useTranslations('auth'), 'auth');\n * return <h1>{t('login.title')}</h1>;\n * ```\n */\nexport function wrapNextIntl<T extends object>(t: T, namespace?: string): T {\n return wrapTranslationEngine(t, { keyPrefix: namespace });\n}\n\nfunction getNestedProperty(obj: any, path: string): any {\n if (!obj || typeof obj !== 'object') return undefined;\n if (path in obj) {\n return obj[path];\n }\n const parts = path.split('.');\n let current = obj;\n for (const part of parts) {\n if (current === null || current === undefined || typeof current !== 'object') {\n return undefined;\n }\n current = current[part];\n }\n return current;\n}\n\nfunction buildProxy<T extends object>(target: T, keyPrefix: string, options: WrapOptions): T {\n return new Proxy(target, {\n get(obj, prop: string | symbol) {\n if (typeof prop !== 'string') {\n const val = Reflect.get(obj, prop);\n return typeof val === 'object' && val !== null\n ? buildProxy(val as object, keyPrefix, options)\n : val;\n }\n\n let val = Reflect.get(obj, prop);\n const fullKey = keyPrefix ? `${keyPrefix}.${prop}` : prop;\n\n // If val is missing, try looking it up in fallback\n const fallbackObj =\n options.fallback ??\n (typeof window !== 'undefined' ? (window as any).__livelocale_fallback__ : undefined);\n if (val === undefined && fallbackObj) {\n const fbVal = getNestedProperty(fallbackObj, fullKey);\n if (fbVal !== undefined) {\n val = fbVal;\n }\n }\n\n // Leaf string value โ encode it with its full key path\n if (typeof val === 'string') {\n return encodeKey(val, fullKey);\n }\n\n // Function (e.g. t() or t.raw() or t.rich() from next-intl) โ wrap the call result\n if (typeof val === 'function') {\n return (...args: unknown[]) => {\n const result = (val as (...args: unknown[]) => unknown).apply(obj, args);\n const calledKey = extractKey(args[0], fullKey);\n\n let resolvedValue = result;\n const isMissing =\n resolvedValue === undefined ||\n resolvedValue === null ||\n resolvedValue === '' ||\n (typeof resolvedValue === 'string' &&\n (resolvedValue === args[0] || resolvedValue === calledKey));\n\n if (isMissing && fallbackObj && calledKey) {\n const fbVal = getNestedProperty(fallbackObj, calledKey);\n if (fbVal !== undefined) {\n resolvedValue = fbVal;\n }\n }\n\n if (typeof resolvedValue === 'string' && calledKey) {\n return encodeKey(resolvedValue, calledKey);\n }\n return resolvedValue;\n };\n }\n\n // Nested object โ recurse with updated key path\n if (typeof val === 'object' && val !== null) {\n return buildProxy(val as object, fullKey, options);\n }\n\n return val;\n },\n\n // Allow the proxy to be called directly (e.g. t('key') or t('key', { name: 'value' }))\n apply(target: any, thisArg, args: unknown[]) {\n const result = Reflect.apply(target, thisArg, args);\n const calledKey = extractKey(args[0], keyPrefix);\n\n const fallbackObj =\n options.fallback ??\n (typeof window !== 'undefined' ? (window as any).__livelocale_fallback__ : undefined);\n let resolvedValue = result;\n const isMissing =\n resolvedValue === undefined ||\n resolvedValue === null ||\n resolvedValue === '' ||\n (typeof resolvedValue === 'string' &&\n (resolvedValue === args[0] || resolvedValue === calledKey));\n\n if (isMissing && fallbackObj && calledKey) {\n const fbVal = getNestedProperty(fallbackObj, calledKey);\n if (fbVal !== undefined) {\n resolvedValue = fbVal;\n }\n }\n\n if (typeof resolvedValue === 'string' && calledKey) {\n return encodeKey(resolvedValue, calledKey);\n }\n return resolvedValue;\n },\n });\n}\n"],"mappings":"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","names":["val","target"]}
|
package/dist/client/index.js
CHANGED
|
@@ -445,9 +445,12 @@ var DOMInterceptor = class {
|
|
|
445
445
|
"white-space: nowrap",
|
|
446
446
|
"overflow: hidden",
|
|
447
447
|
"text-overflow: ellipsis",
|
|
448
|
-
"background:
|
|
449
|
-
|
|
450
|
-
"
|
|
448
|
+
"background: #0C0D0F",
|
|
449
|
+
// Night
|
|
450
|
+
"border: 1px solid #4FC3DC",
|
|
451
|
+
// Hunk
|
|
452
|
+
"color: #E8E4D9",
|
|
453
|
+
// Paper
|
|
451
454
|
"font-family: ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
452
455
|
"font-size: 10px",
|
|
453
456
|
"font-weight: 600",
|
|
@@ -455,7 +458,6 @@ var DOMInterceptor = class {
|
|
|
455
458
|
"padding: 3px 7px",
|
|
456
459
|
"border-radius: 4px",
|
|
457
460
|
"letter-spacing: 0.02em",
|
|
458
|
-
"backdrop-filter: blur(4px)",
|
|
459
461
|
"pointer-events: none"
|
|
460
462
|
].join("; ");
|
|
461
463
|
overlay.appendChild(badge);
|
|
@@ -487,16 +489,16 @@ var DOMInterceptor = class {
|
|
|
487
489
|
if (!this.overlay || !this.overlayBadge) return;
|
|
488
490
|
const key = this.hoveredElement?.getAttribute("data-i18n-key") || "";
|
|
489
491
|
if (this.isAltHeld) {
|
|
490
|
-
this.overlay.style.border = "2px solid #
|
|
491
|
-
this.overlay.style.boxShadow = "
|
|
492
|
-
this.overlayBadge.style.color = "#
|
|
493
|
-
this.overlayBadge.style.borderColor = "
|
|
492
|
+
this.overlay.style.border = "2px solid #4FC3DC";
|
|
493
|
+
this.overlay.style.boxShadow = "none";
|
|
494
|
+
this.overlayBadge.style.color = "#E8E4D9";
|
|
495
|
+
this.overlayBadge.style.borderColor = "#4FC3DC";
|
|
494
496
|
this.overlayBadge.textContent = `\u270E Click to edit: ${key}`;
|
|
495
497
|
} else {
|
|
496
|
-
this.overlay.style.border = "1.5px dashed
|
|
497
|
-
this.overlay.style.boxShadow = "
|
|
498
|
-
this.overlayBadge.style.color = "#
|
|
499
|
-
this.overlayBadge.style.borderColor = "
|
|
498
|
+
this.overlay.style.border = "1.5px dashed #4FC3DC";
|
|
499
|
+
this.overlay.style.boxShadow = "none";
|
|
500
|
+
this.overlayBadge.style.color = "#E8E4D9";
|
|
501
|
+
this.overlayBadge.style.borderColor = "#4FC3DC";
|
|
500
502
|
this.overlayBadge.textContent = `\u2325 Alt+Click to edit: ${key}`;
|
|
501
503
|
}
|
|
502
504
|
}
|