livelocale 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +230 -5
- package/dist/client/index.cjs +178 -70
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +36 -22
- package/dist/client/index.d.ts +36 -22
- package/dist/client/index.js +177 -70
- package/dist/client/index.js.map +1 -1
- package/dist/codemod/index.cjs +3057 -152
- package/dist/codemod/index.cjs.map +1 -1
- package/dist/codemod/index.js +3058 -153
- package/dist/codemod/index.js.map +1 -1
- package/dist/react/index.cjs +185 -77
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.js +185 -77
- package/dist/react/index.js.map +1 -1
- package/dist/server/index.cjs +108 -66
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +13 -3
- package/dist/server/index.d.ts +13 -3
- package/dist/server/index.js +107 -66
- package/dist/server/index.js.map +1 -1
- package/dist/studio/assets/index-Bvbtkry-.css +1 -0
- package/dist/studio/assets/index-DC_jRsox.js +13 -0
- package/dist/studio/assets/inter-latin-300-normal-BVlfKGgI.woff2 +0 -0
- package/dist/studio/assets/inter-latin-300-normal-i8F0SvXL.woff +0 -0
- package/dist/studio/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
- package/dist/studio/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
- package/dist/studio/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
- package/dist/studio/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
- package/dist/studio/assets/inter-latin-600-normal-CiBQ2DWP.woff +0 -0
- package/dist/studio/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
- package/dist/studio/assets/inter-latin-700-normal-BLAVimhd.woff +0 -0
- package/dist/studio/assets/inter-latin-700-normal-Yt3aPRUw.woff2 +0 -0
- package/dist/studio/assets/outfit-latin-400-normal-BGsTXAXT.woff2 +0 -0
- package/dist/studio/assets/outfit-latin-400-normal-DMwTpYkH.woff +0 -0
- package/dist/studio/assets/outfit-latin-500-normal-ClnHRwRh.woff +0 -0
- package/dist/studio/assets/outfit-latin-500-normal-DKnIMDSk.woff2 +0 -0
- package/dist/studio/assets/outfit-latin-600-normal-B7SfZ07L.woff2 +0 -0
- package/dist/studio/assets/outfit-latin-600-normal-BEfTtDA7.woff +0 -0
- package/dist/studio/assets/outfit-latin-700-normal-Cu9v6i1X.woff2 +0 -0
- package/dist/studio/assets/outfit-latin-700-normal-D4itBLBr.woff +0 -0
- package/dist/studio/index.html +3 -6
- package/dist/vite/index.cjs +739 -77
- package/dist/vite/index.cjs.map +1 -1
- package/dist/vite/index.d.cts +22 -2
- package/dist/vite/index.d.ts +22 -2
- package/dist/vite/index.js +738 -77
- package/dist/vite/index.js.map +1 -1
- 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 +4 -1
- package/dist/studio/assets/index-D7ifnqy5.js +0 -10
- package/dist/studio/assets/index-Zh0KtFUH.css +0 -2
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
|
}
|
|
@@ -147,6 +158,121 @@ function isLiveLocaleEnabled() {
|
|
|
147
158
|
return evaluate().mode !== "off";
|
|
148
159
|
}
|
|
149
160
|
|
|
161
|
+
// src/client/iframe-bridge.ts
|
|
162
|
+
var DEFAULT_STUDIO_ORIGIN = "http://localhost:3010";
|
|
163
|
+
var STUDIO_ORIGIN_STORAGE_KEY = "livelocale-studio-origin";
|
|
164
|
+
function toHttpOrigin(value) {
|
|
165
|
+
if (!value) return null;
|
|
166
|
+
try {
|
|
167
|
+
const url = new URL(value);
|
|
168
|
+
return url.protocol === "http:" || url.protocol === "https:" ? url.origin : null;
|
|
169
|
+
} catch {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function readSameOriginParent(win) {
|
|
174
|
+
try {
|
|
175
|
+
const parent = win.parent;
|
|
176
|
+
if (!parent || parent === win) return null;
|
|
177
|
+
const own = toHttpOrigin(win.location.origin);
|
|
178
|
+
if (!own) return null;
|
|
179
|
+
return parent.location.origin === own ? own : null;
|
|
180
|
+
} catch {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function resolveStudioOrigin(search, storage, sameOriginParent) {
|
|
185
|
+
const fromParent = toHttpOrigin(sameOriginParent);
|
|
186
|
+
if (fromParent) return fromParent;
|
|
187
|
+
const fromQuery = toHttpOrigin(new URLSearchParams(search).get("livelocale-origin"));
|
|
188
|
+
if (fromQuery) {
|
|
189
|
+
try {
|
|
190
|
+
storage?.setItem(STUDIO_ORIGIN_STORAGE_KEY, fromQuery);
|
|
191
|
+
} catch {
|
|
192
|
+
}
|
|
193
|
+
return fromQuery;
|
|
194
|
+
}
|
|
195
|
+
let remembered = null;
|
|
196
|
+
try {
|
|
197
|
+
remembered = toHttpOrigin(storage?.getItem(STUDIO_ORIGIN_STORAGE_KEY));
|
|
198
|
+
} catch {
|
|
199
|
+
}
|
|
200
|
+
return remembered ?? DEFAULT_STUDIO_ORIGIN;
|
|
201
|
+
}
|
|
202
|
+
var MAX_VISIBLE_TEXTS = 100;
|
|
203
|
+
var MAX_VISIBLE_TEXT_LENGTH = 200;
|
|
204
|
+
var KEY_PATTERN = /^[a-zA-Z0-9._-]+$/;
|
|
205
|
+
var IframeBridge = class {
|
|
206
|
+
allowedOrigin;
|
|
207
|
+
constructor(allowedOrigin) {
|
|
208
|
+
this.allowedOrigin = allowedOrigin;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Sends a message from the iframe client to the parent Studio window.
|
|
212
|
+
*/
|
|
213
|
+
sendToParent(type, payload) {
|
|
214
|
+
if (typeof window === "undefined" || !window.parent || window.parent === window) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (type === "ELEMENT_SELECTED") {
|
|
218
|
+
const p = payload;
|
|
219
|
+
if (!p || typeof p.key !== "string" || p.key.trim() === "" || !/^[a-zA-Z0-9._-]+$/.test(p.key) || typeof p.currentValue !== "string") {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (type === "VISIBLE_KEYS_CHANGED") {
|
|
224
|
+
if (!Array.isArray(payload)) return;
|
|
225
|
+
const validKeys = payload.filter(
|
|
226
|
+
(k) => typeof k === "string" && /^[a-zA-Z0-9._-]+$/.test(k)
|
|
227
|
+
);
|
|
228
|
+
payload = validKeys;
|
|
229
|
+
}
|
|
230
|
+
if (type === "VISIBLE_TEXTS_CHANGED") {
|
|
231
|
+
if (!Array.isArray(payload)) return;
|
|
232
|
+
const texts = [];
|
|
233
|
+
for (const entry of payload) {
|
|
234
|
+
if (texts.length >= MAX_VISIBLE_TEXTS) break;
|
|
235
|
+
if (!entry || typeof entry !== "object") continue;
|
|
236
|
+
const { key, text } = entry;
|
|
237
|
+
if (typeof key !== "string" || !KEY_PATTERN.test(key) || typeof text !== "string") {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
texts.push({ key, text: text.slice(0, MAX_VISIBLE_TEXT_LENGTH) });
|
|
241
|
+
}
|
|
242
|
+
payload = texts;
|
|
243
|
+
}
|
|
244
|
+
const message = {
|
|
245
|
+
source: "livelocale-client",
|
|
246
|
+
type,
|
|
247
|
+
payload
|
|
248
|
+
};
|
|
249
|
+
window.parent.postMessage(message, this.allowedOrigin);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Sets up a listener for messages originating from the parent Studio dashboard.
|
|
253
|
+
*/
|
|
254
|
+
listenToParent(callback) {
|
|
255
|
+
if (typeof window === "undefined") {
|
|
256
|
+
return () => {
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
const listener = (event) => {
|
|
260
|
+
if (event.origin !== this.allowedOrigin) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (event.data?.source !== "livelocale-studio") {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const { type, payload } = event.data;
|
|
267
|
+
callback(type, payload);
|
|
268
|
+
};
|
|
269
|
+
window.addEventListener("message", listener);
|
|
270
|
+
return () => {
|
|
271
|
+
window.removeEventListener("message", listener);
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
|
|
150
276
|
// src/client/dom-interceptor.ts
|
|
151
277
|
function debounce(fn, wait) {
|
|
152
278
|
let timer = null;
|
|
@@ -199,6 +325,35 @@ var DOMInterceptor = class {
|
|
|
199
325
|
});
|
|
200
326
|
return Array.from(keys);
|
|
201
327
|
}
|
|
328
|
+
/**
|
|
329
|
+
* Returns one `{ key, text }` per translation key on the page, in document
|
|
330
|
+
* order, with the text it shows now (watermark removed, trimmed, cut to
|
|
331
|
+
* `MAX_VISIBLE_TEXT_LENGTH`). The first element carrying a key wins; its
|
|
332
|
+
* text is the first descendant text node whose watermark names that key.
|
|
333
|
+
* Elements without such a node, and blank texts, are skipped. At most
|
|
334
|
+
* `limit` entries are returned.
|
|
335
|
+
*/
|
|
336
|
+
getVisibleTexts(limit = MAX_VISIBLE_TEXTS) {
|
|
337
|
+
const texts = [];
|
|
338
|
+
const seen = /* @__PURE__ */ new Set();
|
|
339
|
+
const elements = document.querySelectorAll("[data-i18n-key]");
|
|
340
|
+
for (const el of elements) {
|
|
341
|
+
if (texts.length >= limit) break;
|
|
342
|
+
const key = el.getAttribute("data-i18n-key");
|
|
343
|
+
if (!key || seen.has(key)) continue;
|
|
344
|
+
seen.add(key);
|
|
345
|
+
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null);
|
|
346
|
+
let node;
|
|
347
|
+
while ((node = walker.nextNode()) !== null) {
|
|
348
|
+
const decoded = decodeKey(node.nodeValue ?? "");
|
|
349
|
+
if (decoded?.key !== key) continue;
|
|
350
|
+
const text = decoded.cleanText.trim().slice(0, MAX_VISIBLE_TEXT_LENGTH);
|
|
351
|
+
if (text) texts.push({ key, text });
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return texts;
|
|
356
|
+
}
|
|
202
357
|
/**
|
|
203
358
|
* Initializes the DOM event interceptors in development mode.
|
|
204
359
|
* Also launches the watermark scanner and MutationObserver.
|
|
@@ -369,7 +524,7 @@ var DOMInterceptor = class {
|
|
|
369
524
|
}
|
|
370
525
|
processTextNode(node) {
|
|
371
526
|
const raw = node.nodeValue ?? "";
|
|
372
|
-
if (!raw.includes(
|
|
527
|
+
if (!raw.includes(WATERMARK_DELIMITER)) return;
|
|
373
528
|
const decoded = decodeKey(raw);
|
|
374
529
|
if (!decoded) return;
|
|
375
530
|
const parent = node.parentElement;
|
|
@@ -442,10 +597,10 @@ var DOMInterceptor = class {
|
|
|
442
597
|
Object.defineProperty(proto, "value", {
|
|
443
598
|
get() {
|
|
444
599
|
const raw = original.get.call(this);
|
|
445
|
-
return
|
|
600
|
+
return removeWatermarks(raw);
|
|
446
601
|
},
|
|
447
602
|
set(val) {
|
|
448
|
-
const clean = typeof val === "string" ?
|
|
603
|
+
const clean = typeof val === "string" ? removeWatermarks(val) : val;
|
|
449
604
|
original.set.call(this, clean);
|
|
450
605
|
},
|
|
451
606
|
configurable: true
|
|
@@ -456,7 +611,7 @@ var DOMInterceptor = class {
|
|
|
456
611
|
if (!form) return;
|
|
457
612
|
const inputs = form.querySelectorAll("input, textarea");
|
|
458
613
|
inputs.forEach((input) => {
|
|
459
|
-
const cleaned =
|
|
614
|
+
const cleaned = removeWatermarks(input.value);
|
|
460
615
|
if (cleaned !== input.value) {
|
|
461
616
|
const proto = input instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
462
617
|
const descriptor = Object.getOwnPropertyDescriptor(proto, "value");
|
|
@@ -547,17 +702,17 @@ var DOMInterceptor = class {
|
|
|
547
702
|
};
|
|
548
703
|
handleCopy = (event) => {
|
|
549
704
|
const selection = window.getSelection()?.toString() || "";
|
|
550
|
-
if (
|
|
551
|
-
const clean =
|
|
705
|
+
if (containsWatermark(selection)) {
|
|
706
|
+
const clean = removeWatermarks(selection);
|
|
552
707
|
event.clipboardData?.setData("text/plain", clean);
|
|
553
708
|
event.preventDefault();
|
|
554
709
|
}
|
|
555
710
|
};
|
|
556
711
|
handlePaste = (event) => {
|
|
557
712
|
const text = event.clipboardData?.getData("text/plain") || "";
|
|
558
|
-
if (
|
|
713
|
+
if (containsWatermark(text)) {
|
|
559
714
|
event.preventDefault();
|
|
560
|
-
const cleaned =
|
|
715
|
+
const cleaned = removeWatermarks(text);
|
|
561
716
|
const targetInput = event.target;
|
|
562
717
|
if (targetInput && "value" in targetInput) {
|
|
563
718
|
const proto = targetInput instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
@@ -576,64 +731,6 @@ var DOMInterceptor = class {
|
|
|
576
731
|
};
|
|
577
732
|
};
|
|
578
733
|
|
|
579
|
-
// src/client/iframe-bridge.ts
|
|
580
|
-
var IframeBridge = class {
|
|
581
|
-
allowedOrigin;
|
|
582
|
-
constructor(allowedOrigin) {
|
|
583
|
-
this.allowedOrigin = allowedOrigin;
|
|
584
|
-
}
|
|
585
|
-
/**
|
|
586
|
-
* Sends a message from the iframe client to the parent Studio window.
|
|
587
|
-
*/
|
|
588
|
-
sendToParent(type, payload) {
|
|
589
|
-
if (typeof window === "undefined" || !window.parent || window.parent === window) {
|
|
590
|
-
return;
|
|
591
|
-
}
|
|
592
|
-
if (type === "ELEMENT_SELECTED") {
|
|
593
|
-
const p = payload;
|
|
594
|
-
if (!p || typeof p.key !== "string" || p.key.trim() === "" || !/^[a-zA-Z0-9._-]+$/.test(p.key) || typeof p.currentValue !== "string") {
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
if (type === "VISIBLE_KEYS_CHANGED") {
|
|
599
|
-
if (!Array.isArray(payload)) return;
|
|
600
|
-
const validKeys = payload.filter(
|
|
601
|
-
(k) => typeof k === "string" && /^[a-zA-Z0-9._-]+$/.test(k)
|
|
602
|
-
);
|
|
603
|
-
payload = validKeys;
|
|
604
|
-
}
|
|
605
|
-
const message = {
|
|
606
|
-
source: "livelocale-client",
|
|
607
|
-
type,
|
|
608
|
-
payload
|
|
609
|
-
};
|
|
610
|
-
window.parent.postMessage(message, this.allowedOrigin);
|
|
611
|
-
}
|
|
612
|
-
/**
|
|
613
|
-
* Sets up a listener for messages originating from the parent Studio dashboard.
|
|
614
|
-
*/
|
|
615
|
-
listenToParent(callback) {
|
|
616
|
-
if (typeof window === "undefined") {
|
|
617
|
-
return () => {
|
|
618
|
-
};
|
|
619
|
-
}
|
|
620
|
-
const listener = (event) => {
|
|
621
|
-
if (event.origin !== this.allowedOrigin) {
|
|
622
|
-
return;
|
|
623
|
-
}
|
|
624
|
-
if (event.data?.source !== "livelocale-studio") {
|
|
625
|
-
return;
|
|
626
|
-
}
|
|
627
|
-
const { type, payload } = event.data;
|
|
628
|
-
callback(type, payload);
|
|
629
|
-
};
|
|
630
|
-
window.addEventListener("message", listener);
|
|
631
|
-
return () => {
|
|
632
|
-
window.removeEventListener("message", listener);
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
|
-
};
|
|
636
|
-
|
|
637
734
|
// src/client/wrap.ts
|
|
638
735
|
function extractKey(arg, keyPrefix) {
|
|
639
736
|
if (typeof arg === "string") {
|
|
@@ -770,8 +867,16 @@ function restorePreviews() {
|
|
|
770
867
|
previewNodes.clear();
|
|
771
868
|
}
|
|
772
869
|
if (typeof window !== "undefined" && isLiveLocaleEnabled() && window.parent !== window) {
|
|
773
|
-
|
|
774
|
-
|
|
870
|
+
let storage = null;
|
|
871
|
+
try {
|
|
872
|
+
storage = window.sessionStorage;
|
|
873
|
+
} catch {
|
|
874
|
+
}
|
|
875
|
+
const allowedOrigin = resolveStudioOrigin(
|
|
876
|
+
window.location.search,
|
|
877
|
+
storage,
|
|
878
|
+
readSameOriginParent(window)
|
|
879
|
+
);
|
|
775
880
|
const bridge = new IframeBridge(allowedOrigin);
|
|
776
881
|
const interceptor = new DOMInterceptor(
|
|
777
882
|
(payload) => {
|
|
@@ -779,6 +884,7 @@ if (typeof window !== "undefined" && isLiveLocaleEnabled() && window.parent !==
|
|
|
779
884
|
},
|
|
780
885
|
(keys) => {
|
|
781
886
|
bridge.sendToParent("VISIBLE_KEYS_CHANGED", keys);
|
|
887
|
+
bridge.sendToParent("VISIBLE_TEXTS_CHANGED", interceptor.getVisibleTexts());
|
|
782
888
|
}
|
|
783
889
|
);
|
|
784
890
|
interceptor.init();
|
|
@@ -809,6 +915,7 @@ export {
|
|
|
809
915
|
decodeKey,
|
|
810
916
|
encodeKey,
|
|
811
917
|
hasWatermark,
|
|
918
|
+
removeWatermarks,
|
|
812
919
|
sanitizeZeroWidth,
|
|
813
920
|
stripWatermark,
|
|
814
921
|
unwrap,
|