livelocale 0.8.0 → 0.8.1
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/dist/client/index.cjs +56 -12
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +55 -12
- package/dist/client/index.js.map +1 -1
- package/dist/codemod/index.cjs +0 -0
- package/dist/codemod/index.js +0 -0
- package/dist/react/index.cjs +57 -14
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.js +57 -14
- package/dist/react/index.js.map +1 -1
- package/dist/server/index.cjs +8 -2
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +7 -2
- package/dist/server/index.js.map +1 -1
- package/dist/studio/assets/{index-D7ifnqy5.js → index-D6D5nPQI.js} +1 -1
- package/dist/studio/assets/{index-Zh0KtFUH.css → index-z0sbyt6N.css} +1 -1
- package/dist/studio/index.html +2 -2
- package/dist/{wrap-CsOtHp8r.d.cts → wrap-2qVxLN9Q.d.cts} +10 -18
- package/dist/{wrap-CsOtHp8r.d.ts → wrap-2qVxLN9Q.d.ts} +10 -18
- package/package.json +1 -1
package/dist/client/index.cjs
CHANGED
|
@@ -26,6 +26,7 @@ __export(client_exports, {
|
|
|
26
26
|
decodeKey: () => decodeKey,
|
|
27
27
|
encodeKey: () => encodeKey,
|
|
28
28
|
hasWatermark: () => hasWatermark,
|
|
29
|
+
removeWatermarks: () => removeWatermarks,
|
|
29
30
|
sanitizeZeroWidth: () => sanitizeZeroWidth,
|
|
30
31
|
stripWatermark: () => stripWatermark,
|
|
31
32
|
unwrap: () => unwrap,
|
|
@@ -35,10 +36,12 @@ __export(client_exports, {
|
|
|
35
36
|
module.exports = __toCommonJS(client_exports);
|
|
36
37
|
|
|
37
38
|
// src/client/watermark.ts
|
|
38
|
-
var DELIM = "\
|
|
39
|
+
var DELIM = "\u200C";
|
|
39
40
|
var BIT_0 = "\u200B";
|
|
40
|
-
var BIT_1 = "\
|
|
41
|
+
var BIT_1 = "\u200D";
|
|
42
|
+
var WATERMARK_DELIMITER = DELIM;
|
|
41
43
|
var ZW_RE = /[\u200B\u200C\u200D]/g;
|
|
44
|
+
var WATERMARK_RE = /\u200C(?:[\u200B\u200D]{8})+\u200C/g;
|
|
42
45
|
function encodeKey(text, key) {
|
|
43
46
|
const bytes = new TextEncoder().encode(key);
|
|
44
47
|
let bits = "";
|
|
@@ -85,6 +88,15 @@ function stripWatermark(text) {
|
|
|
85
88
|
function sanitizeZeroWidth(text) {
|
|
86
89
|
return text.replace(ZW_RE, "");
|
|
87
90
|
}
|
|
91
|
+
function removeWatermarks(text) {
|
|
92
|
+
return text.replace(WATERMARK_RE, "");
|
|
93
|
+
}
|
|
94
|
+
function containsWatermark(text) {
|
|
95
|
+
WATERMARK_RE.lastIndex = 0;
|
|
96
|
+
const found = WATERMARK_RE.test(text);
|
|
97
|
+
WATERMARK_RE.lastIndex = 0;
|
|
98
|
+
return found;
|
|
99
|
+
}
|
|
88
100
|
function hasWatermark(text) {
|
|
89
101
|
return text.startsWith(DELIM);
|
|
90
102
|
}
|
|
@@ -403,7 +415,7 @@ var DOMInterceptor = class {
|
|
|
403
415
|
}
|
|
404
416
|
processTextNode(node) {
|
|
405
417
|
const raw = node.nodeValue ?? "";
|
|
406
|
-
if (!raw.includes(
|
|
418
|
+
if (!raw.includes(WATERMARK_DELIMITER)) return;
|
|
407
419
|
const decoded = decodeKey(raw);
|
|
408
420
|
if (!decoded) return;
|
|
409
421
|
const parent = node.parentElement;
|
|
@@ -476,10 +488,10 @@ var DOMInterceptor = class {
|
|
|
476
488
|
Object.defineProperty(proto, "value", {
|
|
477
489
|
get() {
|
|
478
490
|
const raw = original.get.call(this);
|
|
479
|
-
return
|
|
491
|
+
return removeWatermarks(raw);
|
|
480
492
|
},
|
|
481
493
|
set(val) {
|
|
482
|
-
const clean = typeof val === "string" ?
|
|
494
|
+
const clean = typeof val === "string" ? removeWatermarks(val) : val;
|
|
483
495
|
original.set.call(this, clean);
|
|
484
496
|
},
|
|
485
497
|
configurable: true
|
|
@@ -490,7 +502,7 @@ var DOMInterceptor = class {
|
|
|
490
502
|
if (!form) return;
|
|
491
503
|
const inputs = form.querySelectorAll("input, textarea");
|
|
492
504
|
inputs.forEach((input) => {
|
|
493
|
-
const cleaned =
|
|
505
|
+
const cleaned = removeWatermarks(input.value);
|
|
494
506
|
if (cleaned !== input.value) {
|
|
495
507
|
const proto = input instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
496
508
|
const descriptor = Object.getOwnPropertyDescriptor(proto, "value");
|
|
@@ -581,17 +593,17 @@ var DOMInterceptor = class {
|
|
|
581
593
|
};
|
|
582
594
|
handleCopy = (event) => {
|
|
583
595
|
const selection = window.getSelection()?.toString() || "";
|
|
584
|
-
if (
|
|
585
|
-
const clean =
|
|
596
|
+
if (containsWatermark(selection)) {
|
|
597
|
+
const clean = removeWatermarks(selection);
|
|
586
598
|
event.clipboardData?.setData("text/plain", clean);
|
|
587
599
|
event.preventDefault();
|
|
588
600
|
}
|
|
589
601
|
};
|
|
590
602
|
handlePaste = (event) => {
|
|
591
603
|
const text = event.clipboardData?.getData("text/plain") || "";
|
|
592
|
-
if (
|
|
604
|
+
if (containsWatermark(text)) {
|
|
593
605
|
event.preventDefault();
|
|
594
|
-
const cleaned =
|
|
606
|
+
const cleaned = removeWatermarks(text);
|
|
595
607
|
const targetInput = event.target;
|
|
596
608
|
if (targetInput && "value" in targetInput) {
|
|
597
609
|
const proto = targetInput instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
@@ -611,6 +623,33 @@ var DOMInterceptor = class {
|
|
|
611
623
|
};
|
|
612
624
|
|
|
613
625
|
// src/client/iframe-bridge.ts
|
|
626
|
+
var DEFAULT_STUDIO_ORIGIN = "http://localhost:3010";
|
|
627
|
+
var STUDIO_ORIGIN_STORAGE_KEY = "livelocale-studio-origin";
|
|
628
|
+
function toHttpOrigin(value) {
|
|
629
|
+
if (!value) return null;
|
|
630
|
+
try {
|
|
631
|
+
const url = new URL(value);
|
|
632
|
+
return url.protocol === "http:" || url.protocol === "https:" ? url.origin : null;
|
|
633
|
+
} catch {
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
function resolveStudioOrigin(search, storage) {
|
|
638
|
+
const fromQuery = toHttpOrigin(new URLSearchParams(search).get("livelocale-origin"));
|
|
639
|
+
if (fromQuery) {
|
|
640
|
+
try {
|
|
641
|
+
storage?.setItem(STUDIO_ORIGIN_STORAGE_KEY, fromQuery);
|
|
642
|
+
} catch {
|
|
643
|
+
}
|
|
644
|
+
return fromQuery;
|
|
645
|
+
}
|
|
646
|
+
let remembered = null;
|
|
647
|
+
try {
|
|
648
|
+
remembered = toHttpOrigin(storage?.getItem(STUDIO_ORIGIN_STORAGE_KEY));
|
|
649
|
+
} catch {
|
|
650
|
+
}
|
|
651
|
+
return remembered ?? DEFAULT_STUDIO_ORIGIN;
|
|
652
|
+
}
|
|
614
653
|
var IframeBridge = class {
|
|
615
654
|
allowedOrigin;
|
|
616
655
|
constructor(allowedOrigin) {
|
|
@@ -804,8 +843,12 @@ function restorePreviews() {
|
|
|
804
843
|
previewNodes.clear();
|
|
805
844
|
}
|
|
806
845
|
if (typeof window !== "undefined" && isLiveLocaleEnabled() && window.parent !== window) {
|
|
807
|
-
|
|
808
|
-
|
|
846
|
+
let storage = null;
|
|
847
|
+
try {
|
|
848
|
+
storage = window.sessionStorage;
|
|
849
|
+
} catch {
|
|
850
|
+
}
|
|
851
|
+
const allowedOrigin = resolveStudioOrigin(window.location.search, storage);
|
|
809
852
|
const bridge = new IframeBridge(allowedOrigin);
|
|
810
853
|
const interceptor = new DOMInterceptor(
|
|
811
854
|
(payload) => {
|
|
@@ -844,6 +887,7 @@ if (typeof window !== "undefined" && isLiveLocaleEnabled() && window.parent !==
|
|
|
844
887
|
decodeKey,
|
|
845
888
|
encodeKey,
|
|
846
889
|
hasWatermark,
|
|
890
|
+
removeWatermarks,
|
|
847
891
|
sanitizeZeroWidth,
|
|
848
892
|
stripWatermark,
|
|
849
893
|
unwrap,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/client/index.ts","../../src/client/watermark.ts","../../src/core/env.ts","../../src/client/dom-interceptor.ts","../../src/client/iframe-bridge.ts","../../src/client/wrap.ts"],"sourcesContent":["'use client';\n\nimport { DOMInterceptor } from './dom-interceptor.js';\nimport { IframeBridge } 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} from './iframe-bridge.js';\nexport {\n encodeKey,\n decodeKey,\n stripWatermark,\n sanitizeZeroWidth,\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 const urlParams = new URLSearchParams(window.location.search);\n const allowedOrigin = urlParams.get('livelocale-origin') || 'http://localhost:3010';\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 }\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 * \\u200D – Zero-Width Joiner (ZWJ) → boundary start/end marker\n * \\u200B – Zero-Width Space (ZWS) → binary bit 0\n * \\u200C – Zero-Width Non-Joiner (ZWNJ) → binary bit 1\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 = '\\u200D'; // ZWJ – boundary delimiter\nconst BIT_0 = '\\u200B'; // ZWS – represents bit 0\nconst BIT_1 = '\\u200C'; // ZWNJ – represents bit 1\n\nconst ZW_RE = /[\\u200B\\u200C\\u200D]/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 * Use only for clipboard/form sanitization where no visible text should ever\n * contain ZW characters. Do NOT use on translation content destined for\n * locale files — prefer `stripWatermark`.\n */\nexport function sanitizeZeroWidth(text: string): string {\n return text.replace(ZW_RE, '');\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","import { decodeKey, stripWatermark, sanitizeZeroWidth } from './watermark.js';\nimport { isLiveLocaleEnabled } from '../core/env.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 * 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('\\u200D')) 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 sanitizeZeroWidth(raw);\n },\n set(val: string) {\n const clean = typeof val === 'string' ? sanitizeZeroWidth(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 = sanitizeZeroWidth(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 (\n selection.includes('\\u200D') ||\n selection.includes('\\u200B') ||\n selection.includes('\\u200C')\n ) {\n const clean = sanitizeZeroWidth(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 (text.includes('\\u200D') || text.includes('\\u200B') || text.includes('\\u200C')) {\n event.preventDefault();\n const cleaned = sanitizeZeroWidth(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","export type ClientMessageType = 'ELEMENT_SELECTED' | 'READY' | 'ERROR' | 'VISIBLE_KEYS_CHANGED';\nexport type StudioMessageType = 'APPLY_PREVIEW' | 'COMMIT_PREVIEW' | 'CLEAR_SELECTION';\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 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 { 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/dom-interceptor.ts","../../src/client/iframe-bridge.ts","../../src/client/wrap.ts"],"sourcesContent":["'use client';\n\nimport { DOMInterceptor } from './dom-interceptor.js';\nimport { IframeBridge, 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} 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(window.location.search, storage);\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 }\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","import {\n decodeKey,\n stripWatermark,\n removeWatermarks,\n containsWatermark,\n WATERMARK_DELIMITER,\n} from './watermark.js';\nimport { isLiveLocaleEnabled } from '../core/env.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 * 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","export type ClientMessageType = 'ELEMENT_SELECTED' | 'READY' | 'ERROR' | 'VISIBLE_KEYS_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 the Studio that embeds this page. The Studio passes it as\n * `?livelocale-origin=` on the iframe URL, but the app's own links drop that\n * parameter, so it is remembered for the tab; without it, messages after the\n * first navigation would go to the default origin and be discarded.\n */\nexport function resolveStudioOrigin(search: string, storage: Storage | null): string {\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\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 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 { 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.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { W as WrapOptions, d as decodeKey, e as encodeKey, h as hasWatermark, s as sanitizeZeroWidth, a as stripWatermark, u as unwrap, w as wrapNextIntl, b as wrapTranslationEngine } from '../wrap-
|
|
1
|
+
export { W as WrapOptions, d as decodeKey, e as encodeKey, h as hasWatermark, r as removeWatermarks, s as sanitizeZeroWidth, a as stripWatermark, u as unwrap, w as wrapNextIntl, b as wrapTranslationEngine } from '../wrap-2qVxLN9Q.cjs';
|
|
2
2
|
|
|
3
3
|
declare class DOMInterceptor {
|
|
4
4
|
private onElementSelected;
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { W as WrapOptions, d as decodeKey, e as encodeKey, h as hasWatermark, s as sanitizeZeroWidth, a as stripWatermark, u as unwrap, w as wrapNextIntl, b as wrapTranslationEngine } from '../wrap-
|
|
1
|
+
export { W as WrapOptions, d as decodeKey, e as encodeKey, h as hasWatermark, r as removeWatermarks, s as sanitizeZeroWidth, a as stripWatermark, u as unwrap, w as wrapNextIntl, b as wrapTranslationEngine } from '../wrap-2qVxLN9Q.js';
|
|
2
2
|
|
|
3
3
|
declare class DOMInterceptor {
|
|
4
4
|
private onElementSelected;
|
package/dist/client/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/client/watermark.ts
|
|
4
|
-
var DELIM = "\
|
|
4
|
+
var DELIM = "\u200C";
|
|
5
5
|
var BIT_0 = "\u200B";
|
|
6
|
-
var BIT_1 = "\
|
|
6
|
+
var BIT_1 = "\u200D";
|
|
7
|
+
var WATERMARK_DELIMITER = DELIM;
|
|
7
8
|
var ZW_RE = /[\u200B\u200C\u200D]/g;
|
|
9
|
+
var WATERMARK_RE = /\u200C(?:[\u200B\u200D]{8})+\u200C/g;
|
|
8
10
|
function encodeKey(text, key) {
|
|
9
11
|
const bytes = new TextEncoder().encode(key);
|
|
10
12
|
let bits = "";
|
|
@@ -51,6 +53,15 @@ function stripWatermark(text) {
|
|
|
51
53
|
function sanitizeZeroWidth(text) {
|
|
52
54
|
return text.replace(ZW_RE, "");
|
|
53
55
|
}
|
|
56
|
+
function removeWatermarks(text) {
|
|
57
|
+
return text.replace(WATERMARK_RE, "");
|
|
58
|
+
}
|
|
59
|
+
function containsWatermark(text) {
|
|
60
|
+
WATERMARK_RE.lastIndex = 0;
|
|
61
|
+
const found = WATERMARK_RE.test(text);
|
|
62
|
+
WATERMARK_RE.lastIndex = 0;
|
|
63
|
+
return found;
|
|
64
|
+
}
|
|
54
65
|
function hasWatermark(text) {
|
|
55
66
|
return text.startsWith(DELIM);
|
|
56
67
|
}
|
|
@@ -369,7 +380,7 @@ var DOMInterceptor = class {
|
|
|
369
380
|
}
|
|
370
381
|
processTextNode(node) {
|
|
371
382
|
const raw = node.nodeValue ?? "";
|
|
372
|
-
if (!raw.includes(
|
|
383
|
+
if (!raw.includes(WATERMARK_DELIMITER)) return;
|
|
373
384
|
const decoded = decodeKey(raw);
|
|
374
385
|
if (!decoded) return;
|
|
375
386
|
const parent = node.parentElement;
|
|
@@ -442,10 +453,10 @@ var DOMInterceptor = class {
|
|
|
442
453
|
Object.defineProperty(proto, "value", {
|
|
443
454
|
get() {
|
|
444
455
|
const raw = original.get.call(this);
|
|
445
|
-
return
|
|
456
|
+
return removeWatermarks(raw);
|
|
446
457
|
},
|
|
447
458
|
set(val) {
|
|
448
|
-
const clean = typeof val === "string" ?
|
|
459
|
+
const clean = typeof val === "string" ? removeWatermarks(val) : val;
|
|
449
460
|
original.set.call(this, clean);
|
|
450
461
|
},
|
|
451
462
|
configurable: true
|
|
@@ -456,7 +467,7 @@ var DOMInterceptor = class {
|
|
|
456
467
|
if (!form) return;
|
|
457
468
|
const inputs = form.querySelectorAll("input, textarea");
|
|
458
469
|
inputs.forEach((input) => {
|
|
459
|
-
const cleaned =
|
|
470
|
+
const cleaned = removeWatermarks(input.value);
|
|
460
471
|
if (cleaned !== input.value) {
|
|
461
472
|
const proto = input instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
462
473
|
const descriptor = Object.getOwnPropertyDescriptor(proto, "value");
|
|
@@ -547,17 +558,17 @@ var DOMInterceptor = class {
|
|
|
547
558
|
};
|
|
548
559
|
handleCopy = (event) => {
|
|
549
560
|
const selection = window.getSelection()?.toString() || "";
|
|
550
|
-
if (
|
|
551
|
-
const clean =
|
|
561
|
+
if (containsWatermark(selection)) {
|
|
562
|
+
const clean = removeWatermarks(selection);
|
|
552
563
|
event.clipboardData?.setData("text/plain", clean);
|
|
553
564
|
event.preventDefault();
|
|
554
565
|
}
|
|
555
566
|
};
|
|
556
567
|
handlePaste = (event) => {
|
|
557
568
|
const text = event.clipboardData?.getData("text/plain") || "";
|
|
558
|
-
if (
|
|
569
|
+
if (containsWatermark(text)) {
|
|
559
570
|
event.preventDefault();
|
|
560
|
-
const cleaned =
|
|
571
|
+
const cleaned = removeWatermarks(text);
|
|
561
572
|
const targetInput = event.target;
|
|
562
573
|
if (targetInput && "value" in targetInput) {
|
|
563
574
|
const proto = targetInput instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
@@ -577,6 +588,33 @@ var DOMInterceptor = class {
|
|
|
577
588
|
};
|
|
578
589
|
|
|
579
590
|
// src/client/iframe-bridge.ts
|
|
591
|
+
var DEFAULT_STUDIO_ORIGIN = "http://localhost:3010";
|
|
592
|
+
var STUDIO_ORIGIN_STORAGE_KEY = "livelocale-studio-origin";
|
|
593
|
+
function toHttpOrigin(value) {
|
|
594
|
+
if (!value) return null;
|
|
595
|
+
try {
|
|
596
|
+
const url = new URL(value);
|
|
597
|
+
return url.protocol === "http:" || url.protocol === "https:" ? url.origin : null;
|
|
598
|
+
} catch {
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
function resolveStudioOrigin(search, storage) {
|
|
603
|
+
const fromQuery = toHttpOrigin(new URLSearchParams(search).get("livelocale-origin"));
|
|
604
|
+
if (fromQuery) {
|
|
605
|
+
try {
|
|
606
|
+
storage?.setItem(STUDIO_ORIGIN_STORAGE_KEY, fromQuery);
|
|
607
|
+
} catch {
|
|
608
|
+
}
|
|
609
|
+
return fromQuery;
|
|
610
|
+
}
|
|
611
|
+
let remembered = null;
|
|
612
|
+
try {
|
|
613
|
+
remembered = toHttpOrigin(storage?.getItem(STUDIO_ORIGIN_STORAGE_KEY));
|
|
614
|
+
} catch {
|
|
615
|
+
}
|
|
616
|
+
return remembered ?? DEFAULT_STUDIO_ORIGIN;
|
|
617
|
+
}
|
|
580
618
|
var IframeBridge = class {
|
|
581
619
|
allowedOrigin;
|
|
582
620
|
constructor(allowedOrigin) {
|
|
@@ -770,8 +808,12 @@ function restorePreviews() {
|
|
|
770
808
|
previewNodes.clear();
|
|
771
809
|
}
|
|
772
810
|
if (typeof window !== "undefined" && isLiveLocaleEnabled() && window.parent !== window) {
|
|
773
|
-
|
|
774
|
-
|
|
811
|
+
let storage = null;
|
|
812
|
+
try {
|
|
813
|
+
storage = window.sessionStorage;
|
|
814
|
+
} catch {
|
|
815
|
+
}
|
|
816
|
+
const allowedOrigin = resolveStudioOrigin(window.location.search, storage);
|
|
775
817
|
const bridge = new IframeBridge(allowedOrigin);
|
|
776
818
|
const interceptor = new DOMInterceptor(
|
|
777
819
|
(payload) => {
|
|
@@ -809,6 +851,7 @@ export {
|
|
|
809
851
|
decodeKey,
|
|
810
852
|
encodeKey,
|
|
811
853
|
hasWatermark,
|
|
854
|
+
removeWatermarks,
|
|
812
855
|
sanitizeZeroWidth,
|
|
813
856
|
stripWatermark,
|
|
814
857
|
unwrap,
|