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/react/index.cjs
CHANGED
|
@@ -154,6 +154,15 @@ function stripWatermark(text) {
|
|
|
154
154
|
function sanitizeZeroWidth(text) {
|
|
155
155
|
return text.replace(ZW_RE, "");
|
|
156
156
|
}
|
|
157
|
+
function removeWatermarks(text) {
|
|
158
|
+
return text.replace(WATERMARK_RE, "");
|
|
159
|
+
}
|
|
160
|
+
function containsWatermark(text) {
|
|
161
|
+
WATERMARK_RE.lastIndex = 0;
|
|
162
|
+
const found = WATERMARK_RE.test(text);
|
|
163
|
+
WATERMARK_RE.lastIndex = 0;
|
|
164
|
+
return found;
|
|
165
|
+
}
|
|
157
166
|
function hasWatermark(text) {
|
|
158
167
|
return text.startsWith(DELIM);
|
|
159
168
|
}
|
|
@@ -169,14 +178,137 @@ function unwrap(value) {
|
|
|
169
178
|
}
|
|
170
179
|
return value;
|
|
171
180
|
}
|
|
172
|
-
var DELIM, BIT_0, BIT_1, ZW_RE;
|
|
181
|
+
var DELIM, BIT_0, BIT_1, WATERMARK_DELIMITER, ZW_RE, WATERMARK_RE;
|
|
173
182
|
var init_watermark = __esm({
|
|
174
183
|
"src/client/watermark.ts"() {
|
|
175
184
|
"use strict";
|
|
176
|
-
DELIM = "\
|
|
185
|
+
DELIM = "\u200C";
|
|
177
186
|
BIT_0 = "\u200B";
|
|
178
|
-
BIT_1 = "\
|
|
187
|
+
BIT_1 = "\u200D";
|
|
188
|
+
WATERMARK_DELIMITER = DELIM;
|
|
179
189
|
ZW_RE = /[\u200B\u200C\u200D]/g;
|
|
190
|
+
WATERMARK_RE = /\u200C(?:[\u200B\u200D]{8})+\u200C/g;
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// src/client/iframe-bridge.ts
|
|
195
|
+
function toHttpOrigin(value) {
|
|
196
|
+
if (!value) return null;
|
|
197
|
+
try {
|
|
198
|
+
const url = new URL(value);
|
|
199
|
+
return url.protocol === "http:" || url.protocol === "https:" ? url.origin : null;
|
|
200
|
+
} catch {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function readSameOriginParent(win) {
|
|
205
|
+
try {
|
|
206
|
+
const parent = win.parent;
|
|
207
|
+
if (!parent || parent === win) return null;
|
|
208
|
+
const own = toHttpOrigin(win.location.origin);
|
|
209
|
+
if (!own) return null;
|
|
210
|
+
return parent.location.origin === own ? own : null;
|
|
211
|
+
} catch {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function resolveStudioOrigin(search, storage, sameOriginParent) {
|
|
216
|
+
const fromParent = toHttpOrigin(sameOriginParent);
|
|
217
|
+
if (fromParent) return fromParent;
|
|
218
|
+
const fromQuery = toHttpOrigin(new URLSearchParams(search).get("livelocale-origin"));
|
|
219
|
+
if (fromQuery) {
|
|
220
|
+
try {
|
|
221
|
+
storage?.setItem(STUDIO_ORIGIN_STORAGE_KEY, fromQuery);
|
|
222
|
+
} catch {
|
|
223
|
+
}
|
|
224
|
+
return fromQuery;
|
|
225
|
+
}
|
|
226
|
+
let remembered = null;
|
|
227
|
+
try {
|
|
228
|
+
remembered = toHttpOrigin(storage?.getItem(STUDIO_ORIGIN_STORAGE_KEY));
|
|
229
|
+
} catch {
|
|
230
|
+
}
|
|
231
|
+
return remembered ?? DEFAULT_STUDIO_ORIGIN;
|
|
232
|
+
}
|
|
233
|
+
var DEFAULT_STUDIO_ORIGIN, STUDIO_ORIGIN_STORAGE_KEY, MAX_VISIBLE_TEXTS, MAX_VISIBLE_TEXT_LENGTH, KEY_PATTERN, IframeBridge;
|
|
234
|
+
var init_iframe_bridge = __esm({
|
|
235
|
+
"src/client/iframe-bridge.ts"() {
|
|
236
|
+
"use strict";
|
|
237
|
+
DEFAULT_STUDIO_ORIGIN = "http://localhost:3010";
|
|
238
|
+
STUDIO_ORIGIN_STORAGE_KEY = "livelocale-studio-origin";
|
|
239
|
+
MAX_VISIBLE_TEXTS = 100;
|
|
240
|
+
MAX_VISIBLE_TEXT_LENGTH = 200;
|
|
241
|
+
KEY_PATTERN = /^[a-zA-Z0-9._-]+$/;
|
|
242
|
+
IframeBridge = class {
|
|
243
|
+
allowedOrigin;
|
|
244
|
+
constructor(allowedOrigin) {
|
|
245
|
+
this.allowedOrigin = allowedOrigin;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Sends a message from the iframe client to the parent Studio window.
|
|
249
|
+
*/
|
|
250
|
+
sendToParent(type, payload) {
|
|
251
|
+
if (typeof window === "undefined" || !window.parent || window.parent === window) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (type === "ELEMENT_SELECTED") {
|
|
255
|
+
const p = payload;
|
|
256
|
+
if (!p || typeof p.key !== "string" || p.key.trim() === "" || !/^[a-zA-Z0-9._-]+$/.test(p.key) || typeof p.currentValue !== "string") {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (type === "VISIBLE_KEYS_CHANGED") {
|
|
261
|
+
if (!Array.isArray(payload)) return;
|
|
262
|
+
const validKeys = payload.filter(
|
|
263
|
+
(k) => typeof k === "string" && /^[a-zA-Z0-9._-]+$/.test(k)
|
|
264
|
+
);
|
|
265
|
+
payload = validKeys;
|
|
266
|
+
}
|
|
267
|
+
if (type === "VISIBLE_TEXTS_CHANGED") {
|
|
268
|
+
if (!Array.isArray(payload)) return;
|
|
269
|
+
const texts = [];
|
|
270
|
+
for (const entry of payload) {
|
|
271
|
+
if (texts.length >= MAX_VISIBLE_TEXTS) break;
|
|
272
|
+
if (!entry || typeof entry !== "object") continue;
|
|
273
|
+
const { key, text } = entry;
|
|
274
|
+
if (typeof key !== "string" || !KEY_PATTERN.test(key) || typeof text !== "string") {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
texts.push({ key, text: text.slice(0, MAX_VISIBLE_TEXT_LENGTH) });
|
|
278
|
+
}
|
|
279
|
+
payload = texts;
|
|
280
|
+
}
|
|
281
|
+
const message = {
|
|
282
|
+
source: "livelocale-client",
|
|
283
|
+
type,
|
|
284
|
+
payload
|
|
285
|
+
};
|
|
286
|
+
window.parent.postMessage(message, this.allowedOrigin);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Sets up a listener for messages originating from the parent Studio dashboard.
|
|
290
|
+
*/
|
|
291
|
+
listenToParent(callback) {
|
|
292
|
+
if (typeof window === "undefined") {
|
|
293
|
+
return () => {
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
const listener = (event) => {
|
|
297
|
+
if (event.origin !== this.allowedOrigin) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (event.data?.source !== "livelocale-studio") {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const { type, payload } = event.data;
|
|
304
|
+
callback(type, payload);
|
|
305
|
+
};
|
|
306
|
+
window.addEventListener("message", listener);
|
|
307
|
+
return () => {
|
|
308
|
+
window.removeEventListener("message", listener);
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
};
|
|
180
312
|
}
|
|
181
313
|
});
|
|
182
314
|
|
|
@@ -204,6 +336,7 @@ var init_dom_interceptor = __esm({
|
|
|
204
336
|
"use strict";
|
|
205
337
|
init_watermark();
|
|
206
338
|
init_env();
|
|
339
|
+
init_iframe_bridge();
|
|
207
340
|
DOMInterceptor = class {
|
|
208
341
|
onElementSelected;
|
|
209
342
|
onVisibleKeysChanged;
|
|
@@ -238,6 +371,35 @@ var init_dom_interceptor = __esm({
|
|
|
238
371
|
});
|
|
239
372
|
return Array.from(keys);
|
|
240
373
|
}
|
|
374
|
+
/**
|
|
375
|
+
* Returns one `{ key, text }` per translation key on the page, in document
|
|
376
|
+
* order, with the text it shows now (watermark removed, trimmed, cut to
|
|
377
|
+
* `MAX_VISIBLE_TEXT_LENGTH`). The first element carrying a key wins; its
|
|
378
|
+
* text is the first descendant text node whose watermark names that key.
|
|
379
|
+
* Elements without such a node, and blank texts, are skipped. At most
|
|
380
|
+
* `limit` entries are returned.
|
|
381
|
+
*/
|
|
382
|
+
getVisibleTexts(limit = MAX_VISIBLE_TEXTS) {
|
|
383
|
+
const texts = [];
|
|
384
|
+
const seen = /* @__PURE__ */ new Set();
|
|
385
|
+
const elements = document.querySelectorAll("[data-i18n-key]");
|
|
386
|
+
for (const el of elements) {
|
|
387
|
+
if (texts.length >= limit) break;
|
|
388
|
+
const key = el.getAttribute("data-i18n-key");
|
|
389
|
+
if (!key || seen.has(key)) continue;
|
|
390
|
+
seen.add(key);
|
|
391
|
+
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null);
|
|
392
|
+
let node;
|
|
393
|
+
while ((node = walker.nextNode()) !== null) {
|
|
394
|
+
const decoded = decodeKey(node.nodeValue ?? "");
|
|
395
|
+
if (decoded?.key !== key) continue;
|
|
396
|
+
const text = decoded.cleanText.trim().slice(0, MAX_VISIBLE_TEXT_LENGTH);
|
|
397
|
+
if (text) texts.push({ key, text });
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return texts;
|
|
402
|
+
}
|
|
241
403
|
/**
|
|
242
404
|
* Initializes the DOM event interceptors in development mode.
|
|
243
405
|
* Also launches the watermark scanner and MutationObserver.
|
|
@@ -408,7 +570,7 @@ var init_dom_interceptor = __esm({
|
|
|
408
570
|
}
|
|
409
571
|
processTextNode(node) {
|
|
410
572
|
const raw = node.nodeValue ?? "";
|
|
411
|
-
if (!raw.includes(
|
|
573
|
+
if (!raw.includes(WATERMARK_DELIMITER)) return;
|
|
412
574
|
const decoded = decodeKey(raw);
|
|
413
575
|
if (!decoded) return;
|
|
414
576
|
const parent = node.parentElement;
|
|
@@ -481,10 +643,10 @@ var init_dom_interceptor = __esm({
|
|
|
481
643
|
Object.defineProperty(proto, "value", {
|
|
482
644
|
get() {
|
|
483
645
|
const raw = original.get.call(this);
|
|
484
|
-
return
|
|
646
|
+
return removeWatermarks(raw);
|
|
485
647
|
},
|
|
486
648
|
set(val) {
|
|
487
|
-
const clean = typeof val === "string" ?
|
|
649
|
+
const clean = typeof val === "string" ? removeWatermarks(val) : val;
|
|
488
650
|
original.set.call(this, clean);
|
|
489
651
|
},
|
|
490
652
|
configurable: true
|
|
@@ -495,7 +657,7 @@ var init_dom_interceptor = __esm({
|
|
|
495
657
|
if (!form) return;
|
|
496
658
|
const inputs = form.querySelectorAll("input, textarea");
|
|
497
659
|
inputs.forEach((input) => {
|
|
498
|
-
const cleaned =
|
|
660
|
+
const cleaned = removeWatermarks(input.value);
|
|
499
661
|
if (cleaned !== input.value) {
|
|
500
662
|
const proto = input instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
501
663
|
const descriptor = Object.getOwnPropertyDescriptor(proto, "value");
|
|
@@ -586,17 +748,17 @@ var init_dom_interceptor = __esm({
|
|
|
586
748
|
};
|
|
587
749
|
handleCopy = (event) => {
|
|
588
750
|
const selection = window.getSelection()?.toString() || "";
|
|
589
|
-
if (
|
|
590
|
-
const clean =
|
|
751
|
+
if (containsWatermark(selection)) {
|
|
752
|
+
const clean = removeWatermarks(selection);
|
|
591
753
|
event.clipboardData?.setData("text/plain", clean);
|
|
592
754
|
event.preventDefault();
|
|
593
755
|
}
|
|
594
756
|
};
|
|
595
757
|
handlePaste = (event) => {
|
|
596
758
|
const text = event.clipboardData?.getData("text/plain") || "";
|
|
597
|
-
if (
|
|
759
|
+
if (containsWatermark(text)) {
|
|
598
760
|
event.preventDefault();
|
|
599
|
-
const cleaned =
|
|
761
|
+
const cleaned = removeWatermarks(text);
|
|
600
762
|
const targetInput = event.target;
|
|
601
763
|
if (targetInput && "value" in targetInput) {
|
|
602
764
|
const proto = targetInput instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
@@ -617,70 +779,6 @@ var init_dom_interceptor = __esm({
|
|
|
617
779
|
}
|
|
618
780
|
});
|
|
619
781
|
|
|
620
|
-
// src/client/iframe-bridge.ts
|
|
621
|
-
var IframeBridge;
|
|
622
|
-
var init_iframe_bridge = __esm({
|
|
623
|
-
"src/client/iframe-bridge.ts"() {
|
|
624
|
-
"use strict";
|
|
625
|
-
IframeBridge = class {
|
|
626
|
-
allowedOrigin;
|
|
627
|
-
constructor(allowedOrigin) {
|
|
628
|
-
this.allowedOrigin = allowedOrigin;
|
|
629
|
-
}
|
|
630
|
-
/**
|
|
631
|
-
* Sends a message from the iframe client to the parent Studio window.
|
|
632
|
-
*/
|
|
633
|
-
sendToParent(type, payload) {
|
|
634
|
-
if (typeof window === "undefined" || !window.parent || window.parent === window) {
|
|
635
|
-
return;
|
|
636
|
-
}
|
|
637
|
-
if (type === "ELEMENT_SELECTED") {
|
|
638
|
-
const p = payload;
|
|
639
|
-
if (!p || typeof p.key !== "string" || p.key.trim() === "" || !/^[a-zA-Z0-9._-]+$/.test(p.key) || typeof p.currentValue !== "string") {
|
|
640
|
-
return;
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
if (type === "VISIBLE_KEYS_CHANGED") {
|
|
644
|
-
if (!Array.isArray(payload)) return;
|
|
645
|
-
const validKeys = payload.filter(
|
|
646
|
-
(k) => typeof k === "string" && /^[a-zA-Z0-9._-]+$/.test(k)
|
|
647
|
-
);
|
|
648
|
-
payload = validKeys;
|
|
649
|
-
}
|
|
650
|
-
const message = {
|
|
651
|
-
source: "livelocale-client",
|
|
652
|
-
type,
|
|
653
|
-
payload
|
|
654
|
-
};
|
|
655
|
-
window.parent.postMessage(message, this.allowedOrigin);
|
|
656
|
-
}
|
|
657
|
-
/**
|
|
658
|
-
* Sets up a listener for messages originating from the parent Studio dashboard.
|
|
659
|
-
*/
|
|
660
|
-
listenToParent(callback) {
|
|
661
|
-
if (typeof window === "undefined") {
|
|
662
|
-
return () => {
|
|
663
|
-
};
|
|
664
|
-
}
|
|
665
|
-
const listener = (event) => {
|
|
666
|
-
if (event.origin !== this.allowedOrigin) {
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
|
-
if (event.data?.source !== "livelocale-studio") {
|
|
670
|
-
return;
|
|
671
|
-
}
|
|
672
|
-
const { type, payload } = event.data;
|
|
673
|
-
callback(type, payload);
|
|
674
|
-
};
|
|
675
|
-
window.addEventListener("message", listener);
|
|
676
|
-
return () => {
|
|
677
|
-
window.removeEventListener("message", listener);
|
|
678
|
-
};
|
|
679
|
-
}
|
|
680
|
-
};
|
|
681
|
-
}
|
|
682
|
-
});
|
|
683
|
-
|
|
684
782
|
// src/client/wrap.ts
|
|
685
783
|
function extractKey(arg, keyPrefix) {
|
|
686
784
|
if (typeof arg === "string") {
|
|
@@ -799,6 +897,7 @@ __export(client_exports, {
|
|
|
799
897
|
decodeKey: () => decodeKey,
|
|
800
898
|
encodeKey: () => encodeKey,
|
|
801
899
|
hasWatermark: () => hasWatermark,
|
|
900
|
+
removeWatermarks: () => removeWatermarks,
|
|
802
901
|
sanitizeZeroWidth: () => sanitizeZeroWidth,
|
|
803
902
|
stripWatermark: () => stripWatermark,
|
|
804
903
|
unwrap: () => unwrap,
|
|
@@ -850,8 +949,16 @@ var init_client = __esm({
|
|
|
850
949
|
init_wrap();
|
|
851
950
|
previewNodes = /* @__PURE__ */ new Map();
|
|
852
951
|
if (typeof window !== "undefined" && isLiveLocaleEnabled() && window.parent !== window) {
|
|
853
|
-
|
|
854
|
-
|
|
952
|
+
let storage = null;
|
|
953
|
+
try {
|
|
954
|
+
storage = window.sessionStorage;
|
|
955
|
+
} catch {
|
|
956
|
+
}
|
|
957
|
+
const allowedOrigin = resolveStudioOrigin(
|
|
958
|
+
window.location.search,
|
|
959
|
+
storage,
|
|
960
|
+
readSameOriginParent(window)
|
|
961
|
+
);
|
|
855
962
|
const bridge = new IframeBridge(allowedOrigin);
|
|
856
963
|
const interceptor = new DOMInterceptor(
|
|
857
964
|
(payload) => {
|
|
@@ -859,6 +966,7 @@ var init_client = __esm({
|
|
|
859
966
|
},
|
|
860
967
|
(keys) => {
|
|
861
968
|
bridge.sendToParent("VISIBLE_KEYS_CHANGED", keys);
|
|
969
|
+
bridge.sendToParent("VISIBLE_TEXTS_CHANGED", interceptor.getVisibleTexts());
|
|
862
970
|
}
|
|
863
971
|
);
|
|
864
972
|
interceptor.init();
|