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.js
CHANGED
|
@@ -142,6 +142,15 @@ function stripWatermark(text) {
|
|
|
142
142
|
function sanitizeZeroWidth(text) {
|
|
143
143
|
return text.replace(ZW_RE, "");
|
|
144
144
|
}
|
|
145
|
+
function removeWatermarks(text) {
|
|
146
|
+
return text.replace(WATERMARK_RE, "");
|
|
147
|
+
}
|
|
148
|
+
function containsWatermark(text) {
|
|
149
|
+
WATERMARK_RE.lastIndex = 0;
|
|
150
|
+
const found = WATERMARK_RE.test(text);
|
|
151
|
+
WATERMARK_RE.lastIndex = 0;
|
|
152
|
+
return found;
|
|
153
|
+
}
|
|
145
154
|
function hasWatermark(text) {
|
|
146
155
|
return text.startsWith(DELIM);
|
|
147
156
|
}
|
|
@@ -157,14 +166,137 @@ function unwrap(value) {
|
|
|
157
166
|
}
|
|
158
167
|
return value;
|
|
159
168
|
}
|
|
160
|
-
var DELIM, BIT_0, BIT_1, ZW_RE;
|
|
169
|
+
var DELIM, BIT_0, BIT_1, WATERMARK_DELIMITER, ZW_RE, WATERMARK_RE;
|
|
161
170
|
var init_watermark = __esm({
|
|
162
171
|
"src/client/watermark.ts"() {
|
|
163
172
|
"use strict";
|
|
164
|
-
DELIM = "\
|
|
173
|
+
DELIM = "\u200C";
|
|
165
174
|
BIT_0 = "\u200B";
|
|
166
|
-
BIT_1 = "\
|
|
175
|
+
BIT_1 = "\u200D";
|
|
176
|
+
WATERMARK_DELIMITER = DELIM;
|
|
167
177
|
ZW_RE = /[\u200B\u200C\u200D]/g;
|
|
178
|
+
WATERMARK_RE = /\u200C(?:[\u200B\u200D]{8})+\u200C/g;
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// src/client/iframe-bridge.ts
|
|
183
|
+
function toHttpOrigin(value) {
|
|
184
|
+
if (!value) return null;
|
|
185
|
+
try {
|
|
186
|
+
const url = new URL(value);
|
|
187
|
+
return url.protocol === "http:" || url.protocol === "https:" ? url.origin : null;
|
|
188
|
+
} catch {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function readSameOriginParent(win) {
|
|
193
|
+
try {
|
|
194
|
+
const parent = win.parent;
|
|
195
|
+
if (!parent || parent === win) return null;
|
|
196
|
+
const own = toHttpOrigin(win.location.origin);
|
|
197
|
+
if (!own) return null;
|
|
198
|
+
return parent.location.origin === own ? own : null;
|
|
199
|
+
} catch {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function resolveStudioOrigin(search, storage, sameOriginParent) {
|
|
204
|
+
const fromParent = toHttpOrigin(sameOriginParent);
|
|
205
|
+
if (fromParent) return fromParent;
|
|
206
|
+
const fromQuery = toHttpOrigin(new URLSearchParams(search).get("livelocale-origin"));
|
|
207
|
+
if (fromQuery) {
|
|
208
|
+
try {
|
|
209
|
+
storage?.setItem(STUDIO_ORIGIN_STORAGE_KEY, fromQuery);
|
|
210
|
+
} catch {
|
|
211
|
+
}
|
|
212
|
+
return fromQuery;
|
|
213
|
+
}
|
|
214
|
+
let remembered = null;
|
|
215
|
+
try {
|
|
216
|
+
remembered = toHttpOrigin(storage?.getItem(STUDIO_ORIGIN_STORAGE_KEY));
|
|
217
|
+
} catch {
|
|
218
|
+
}
|
|
219
|
+
return remembered ?? DEFAULT_STUDIO_ORIGIN;
|
|
220
|
+
}
|
|
221
|
+
var DEFAULT_STUDIO_ORIGIN, STUDIO_ORIGIN_STORAGE_KEY, MAX_VISIBLE_TEXTS, MAX_VISIBLE_TEXT_LENGTH, KEY_PATTERN, IframeBridge;
|
|
222
|
+
var init_iframe_bridge = __esm({
|
|
223
|
+
"src/client/iframe-bridge.ts"() {
|
|
224
|
+
"use strict";
|
|
225
|
+
DEFAULT_STUDIO_ORIGIN = "http://localhost:3010";
|
|
226
|
+
STUDIO_ORIGIN_STORAGE_KEY = "livelocale-studio-origin";
|
|
227
|
+
MAX_VISIBLE_TEXTS = 100;
|
|
228
|
+
MAX_VISIBLE_TEXT_LENGTH = 200;
|
|
229
|
+
KEY_PATTERN = /^[a-zA-Z0-9._-]+$/;
|
|
230
|
+
IframeBridge = class {
|
|
231
|
+
allowedOrigin;
|
|
232
|
+
constructor(allowedOrigin) {
|
|
233
|
+
this.allowedOrigin = allowedOrigin;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Sends a message from the iframe client to the parent Studio window.
|
|
237
|
+
*/
|
|
238
|
+
sendToParent(type, payload) {
|
|
239
|
+
if (typeof window === "undefined" || !window.parent || window.parent === window) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (type === "ELEMENT_SELECTED") {
|
|
243
|
+
const p = payload;
|
|
244
|
+
if (!p || typeof p.key !== "string" || p.key.trim() === "" || !/^[a-zA-Z0-9._-]+$/.test(p.key) || typeof p.currentValue !== "string") {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (type === "VISIBLE_KEYS_CHANGED") {
|
|
249
|
+
if (!Array.isArray(payload)) return;
|
|
250
|
+
const validKeys = payload.filter(
|
|
251
|
+
(k) => typeof k === "string" && /^[a-zA-Z0-9._-]+$/.test(k)
|
|
252
|
+
);
|
|
253
|
+
payload = validKeys;
|
|
254
|
+
}
|
|
255
|
+
if (type === "VISIBLE_TEXTS_CHANGED") {
|
|
256
|
+
if (!Array.isArray(payload)) return;
|
|
257
|
+
const texts = [];
|
|
258
|
+
for (const entry of payload) {
|
|
259
|
+
if (texts.length >= MAX_VISIBLE_TEXTS) break;
|
|
260
|
+
if (!entry || typeof entry !== "object") continue;
|
|
261
|
+
const { key, text } = entry;
|
|
262
|
+
if (typeof key !== "string" || !KEY_PATTERN.test(key) || typeof text !== "string") {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
texts.push({ key, text: text.slice(0, MAX_VISIBLE_TEXT_LENGTH) });
|
|
266
|
+
}
|
|
267
|
+
payload = texts;
|
|
268
|
+
}
|
|
269
|
+
const message = {
|
|
270
|
+
source: "livelocale-client",
|
|
271
|
+
type,
|
|
272
|
+
payload
|
|
273
|
+
};
|
|
274
|
+
window.parent.postMessage(message, this.allowedOrigin);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Sets up a listener for messages originating from the parent Studio dashboard.
|
|
278
|
+
*/
|
|
279
|
+
listenToParent(callback) {
|
|
280
|
+
if (typeof window === "undefined") {
|
|
281
|
+
return () => {
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
const listener = (event) => {
|
|
285
|
+
if (event.origin !== this.allowedOrigin) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (event.data?.source !== "livelocale-studio") {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const { type, payload } = event.data;
|
|
292
|
+
callback(type, payload);
|
|
293
|
+
};
|
|
294
|
+
window.addEventListener("message", listener);
|
|
295
|
+
return () => {
|
|
296
|
+
window.removeEventListener("message", listener);
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
};
|
|
168
300
|
}
|
|
169
301
|
});
|
|
170
302
|
|
|
@@ -192,6 +324,7 @@ var init_dom_interceptor = __esm({
|
|
|
192
324
|
"use strict";
|
|
193
325
|
init_watermark();
|
|
194
326
|
init_env();
|
|
327
|
+
init_iframe_bridge();
|
|
195
328
|
DOMInterceptor = class {
|
|
196
329
|
onElementSelected;
|
|
197
330
|
onVisibleKeysChanged;
|
|
@@ -226,6 +359,35 @@ var init_dom_interceptor = __esm({
|
|
|
226
359
|
});
|
|
227
360
|
return Array.from(keys);
|
|
228
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* Returns one `{ key, text }` per translation key on the page, in document
|
|
364
|
+
* order, with the text it shows now (watermark removed, trimmed, cut to
|
|
365
|
+
* `MAX_VISIBLE_TEXT_LENGTH`). The first element carrying a key wins; its
|
|
366
|
+
* text is the first descendant text node whose watermark names that key.
|
|
367
|
+
* Elements without such a node, and blank texts, are skipped. At most
|
|
368
|
+
* `limit` entries are returned.
|
|
369
|
+
*/
|
|
370
|
+
getVisibleTexts(limit = MAX_VISIBLE_TEXTS) {
|
|
371
|
+
const texts = [];
|
|
372
|
+
const seen = /* @__PURE__ */ new Set();
|
|
373
|
+
const elements = document.querySelectorAll("[data-i18n-key]");
|
|
374
|
+
for (const el of elements) {
|
|
375
|
+
if (texts.length >= limit) break;
|
|
376
|
+
const key = el.getAttribute("data-i18n-key");
|
|
377
|
+
if (!key || seen.has(key)) continue;
|
|
378
|
+
seen.add(key);
|
|
379
|
+
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null);
|
|
380
|
+
let node;
|
|
381
|
+
while ((node = walker.nextNode()) !== null) {
|
|
382
|
+
const decoded = decodeKey(node.nodeValue ?? "");
|
|
383
|
+
if (decoded?.key !== key) continue;
|
|
384
|
+
const text = decoded.cleanText.trim().slice(0, MAX_VISIBLE_TEXT_LENGTH);
|
|
385
|
+
if (text) texts.push({ key, text });
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return texts;
|
|
390
|
+
}
|
|
229
391
|
/**
|
|
230
392
|
* Initializes the DOM event interceptors in development mode.
|
|
231
393
|
* Also launches the watermark scanner and MutationObserver.
|
|
@@ -396,7 +558,7 @@ var init_dom_interceptor = __esm({
|
|
|
396
558
|
}
|
|
397
559
|
processTextNode(node) {
|
|
398
560
|
const raw = node.nodeValue ?? "";
|
|
399
|
-
if (!raw.includes(
|
|
561
|
+
if (!raw.includes(WATERMARK_DELIMITER)) return;
|
|
400
562
|
const decoded = decodeKey(raw);
|
|
401
563
|
if (!decoded) return;
|
|
402
564
|
const parent = node.parentElement;
|
|
@@ -469,10 +631,10 @@ var init_dom_interceptor = __esm({
|
|
|
469
631
|
Object.defineProperty(proto, "value", {
|
|
470
632
|
get() {
|
|
471
633
|
const raw = original.get.call(this);
|
|
472
|
-
return
|
|
634
|
+
return removeWatermarks(raw);
|
|
473
635
|
},
|
|
474
636
|
set(val) {
|
|
475
|
-
const clean = typeof val === "string" ?
|
|
637
|
+
const clean = typeof val === "string" ? removeWatermarks(val) : val;
|
|
476
638
|
original.set.call(this, clean);
|
|
477
639
|
},
|
|
478
640
|
configurable: true
|
|
@@ -483,7 +645,7 @@ var init_dom_interceptor = __esm({
|
|
|
483
645
|
if (!form) return;
|
|
484
646
|
const inputs = form.querySelectorAll("input, textarea");
|
|
485
647
|
inputs.forEach((input) => {
|
|
486
|
-
const cleaned =
|
|
648
|
+
const cleaned = removeWatermarks(input.value);
|
|
487
649
|
if (cleaned !== input.value) {
|
|
488
650
|
const proto = input instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
489
651
|
const descriptor = Object.getOwnPropertyDescriptor(proto, "value");
|
|
@@ -574,17 +736,17 @@ var init_dom_interceptor = __esm({
|
|
|
574
736
|
};
|
|
575
737
|
handleCopy = (event) => {
|
|
576
738
|
const selection = window.getSelection()?.toString() || "";
|
|
577
|
-
if (
|
|
578
|
-
const clean =
|
|
739
|
+
if (containsWatermark(selection)) {
|
|
740
|
+
const clean = removeWatermarks(selection);
|
|
579
741
|
event.clipboardData?.setData("text/plain", clean);
|
|
580
742
|
event.preventDefault();
|
|
581
743
|
}
|
|
582
744
|
};
|
|
583
745
|
handlePaste = (event) => {
|
|
584
746
|
const text = event.clipboardData?.getData("text/plain") || "";
|
|
585
|
-
if (
|
|
747
|
+
if (containsWatermark(text)) {
|
|
586
748
|
event.preventDefault();
|
|
587
|
-
const cleaned =
|
|
749
|
+
const cleaned = removeWatermarks(text);
|
|
588
750
|
const targetInput = event.target;
|
|
589
751
|
if (targetInput && "value" in targetInput) {
|
|
590
752
|
const proto = targetInput instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
@@ -605,70 +767,6 @@ var init_dom_interceptor = __esm({
|
|
|
605
767
|
}
|
|
606
768
|
});
|
|
607
769
|
|
|
608
|
-
// src/client/iframe-bridge.ts
|
|
609
|
-
var IframeBridge;
|
|
610
|
-
var init_iframe_bridge = __esm({
|
|
611
|
-
"src/client/iframe-bridge.ts"() {
|
|
612
|
-
"use strict";
|
|
613
|
-
IframeBridge = class {
|
|
614
|
-
allowedOrigin;
|
|
615
|
-
constructor(allowedOrigin) {
|
|
616
|
-
this.allowedOrigin = allowedOrigin;
|
|
617
|
-
}
|
|
618
|
-
/**
|
|
619
|
-
* Sends a message from the iframe client to the parent Studio window.
|
|
620
|
-
*/
|
|
621
|
-
sendToParent(type, payload) {
|
|
622
|
-
if (typeof window === "undefined" || !window.parent || window.parent === window) {
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
625
|
-
if (type === "ELEMENT_SELECTED") {
|
|
626
|
-
const p = payload;
|
|
627
|
-
if (!p || typeof p.key !== "string" || p.key.trim() === "" || !/^[a-zA-Z0-9._-]+$/.test(p.key) || typeof p.currentValue !== "string") {
|
|
628
|
-
return;
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
if (type === "VISIBLE_KEYS_CHANGED") {
|
|
632
|
-
if (!Array.isArray(payload)) return;
|
|
633
|
-
const validKeys = payload.filter(
|
|
634
|
-
(k) => typeof k === "string" && /^[a-zA-Z0-9._-]+$/.test(k)
|
|
635
|
-
);
|
|
636
|
-
payload = validKeys;
|
|
637
|
-
}
|
|
638
|
-
const message = {
|
|
639
|
-
source: "livelocale-client",
|
|
640
|
-
type,
|
|
641
|
-
payload
|
|
642
|
-
};
|
|
643
|
-
window.parent.postMessage(message, this.allowedOrigin);
|
|
644
|
-
}
|
|
645
|
-
/**
|
|
646
|
-
* Sets up a listener for messages originating from the parent Studio dashboard.
|
|
647
|
-
*/
|
|
648
|
-
listenToParent(callback) {
|
|
649
|
-
if (typeof window === "undefined") {
|
|
650
|
-
return () => {
|
|
651
|
-
};
|
|
652
|
-
}
|
|
653
|
-
const listener = (event) => {
|
|
654
|
-
if (event.origin !== this.allowedOrigin) {
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
if (event.data?.source !== "livelocale-studio") {
|
|
658
|
-
return;
|
|
659
|
-
}
|
|
660
|
-
const { type, payload } = event.data;
|
|
661
|
-
callback(type, payload);
|
|
662
|
-
};
|
|
663
|
-
window.addEventListener("message", listener);
|
|
664
|
-
return () => {
|
|
665
|
-
window.removeEventListener("message", listener);
|
|
666
|
-
};
|
|
667
|
-
}
|
|
668
|
-
};
|
|
669
|
-
}
|
|
670
|
-
});
|
|
671
|
-
|
|
672
770
|
// src/client/wrap.ts
|
|
673
771
|
function extractKey(arg, keyPrefix) {
|
|
674
772
|
if (typeof arg === "string") {
|
|
@@ -787,6 +885,7 @@ __export(client_exports, {
|
|
|
787
885
|
decodeKey: () => decodeKey,
|
|
788
886
|
encodeKey: () => encodeKey,
|
|
789
887
|
hasWatermark: () => hasWatermark,
|
|
888
|
+
removeWatermarks: () => removeWatermarks,
|
|
790
889
|
sanitizeZeroWidth: () => sanitizeZeroWidth,
|
|
791
890
|
stripWatermark: () => stripWatermark,
|
|
792
891
|
unwrap: () => unwrap,
|
|
@@ -838,8 +937,16 @@ var init_client = __esm({
|
|
|
838
937
|
init_wrap();
|
|
839
938
|
previewNodes = /* @__PURE__ */ new Map();
|
|
840
939
|
if (typeof window !== "undefined" && isLiveLocaleEnabled() && window.parent !== window) {
|
|
841
|
-
|
|
842
|
-
|
|
940
|
+
let storage = null;
|
|
941
|
+
try {
|
|
942
|
+
storage = window.sessionStorage;
|
|
943
|
+
} catch {
|
|
944
|
+
}
|
|
945
|
+
const allowedOrigin = resolveStudioOrigin(
|
|
946
|
+
window.location.search,
|
|
947
|
+
storage,
|
|
948
|
+
readSameOriginParent(window)
|
|
949
|
+
);
|
|
843
950
|
const bridge = new IframeBridge(allowedOrigin);
|
|
844
951
|
const interceptor = new DOMInterceptor(
|
|
845
952
|
(payload) => {
|
|
@@ -847,6 +954,7 @@ var init_client = __esm({
|
|
|
847
954
|
},
|
|
848
955
|
(keys) => {
|
|
849
956
|
bridge.sendToParent("VISIBLE_KEYS_CHANGED", keys);
|
|
957
|
+
bridge.sendToParent("VISIBLE_TEXTS_CHANGED", interceptor.getVisibleTexts());
|
|
850
958
|
}
|
|
851
959
|
);
|
|
852
960
|
interceptor.init();
|