Skip to content

Commit bab996a

Browse files
author
Sebi Nemeth
committed
move encode from loading to plugin install
add cache
1 parent cbc605b commit bab996a

File tree

5 files changed

+125
-77
lines changed

5 files changed

+125
-77
lines changed

dist/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ export type TranslationMeta = {
66
path: string;
77
};
88
type LiveTranslatorPluginOptions = {
9+
i18n: any;
910
translationLink: (meta: TranslationMeta) => string;
1011
persist?: boolean;
1112
root?: HTMLElement;
1213
refreshRate?: number;
1314
checkVisibility?: boolean;
1415
};
15-
export declare function encodeMessages(messagesObject: any): any;
1616
export declare const LiveTranslatorPlugin: {
1717
install(app: any, options: LiveTranslatorPluginOptions): void;
1818
};

dist/index.js

Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -82,22 +82,20 @@ function deepForIn(object, fn) {
8282
};
8383
forIn(object, iteratee);
8484
}
85-
export function encodeMessages(messagesObject) {
85+
function encodeMessages(messagesObject, locale) {
8686
const messages = cloneDeep(messagesObject);
87-
forIn(messages, (localeMessages, locale) => {
88-
deepForIn(localeMessages, (message, path) => {
89-
const parts = message.split('|').map(part => part.trim());
90-
for (let i = 0; i < parts.length; i++) {
91-
const meta = ZeroWidthEncoder.encode(JSON.stringify({
92-
locale,
93-
message,
94-
path,
95-
choice: i || undefined,
96-
}));
97-
parts[i] = meta + parts[i];
98-
}
99-
set(localeMessages, path, parts.join(' | '));
100-
});
87+
deepForIn(messages, (message, path) => {
88+
const parts = message.split('|').map(part => part.trim());
89+
for (let i = 0; i < parts.length; i++) {
90+
const meta = ZeroWidthEncoder.encode(JSON.stringify({
91+
locale,
92+
message,
93+
path,
94+
choice: i || undefined,
95+
}));
96+
parts[i] = meta + parts[i];
97+
}
98+
set(messages, path, parts.join(' | '));
10199
});
102100
return messages;
103101
}
@@ -156,6 +154,7 @@ class LiveTranslatorManager {
156154
_indicator;
157155
_box;
158156
_wrapper;
157+
_cache = {};
159158
constructor(options) {
160159
this._enabled = false;
161160
this._options = options;
@@ -190,7 +189,11 @@ class LiveTranslatorManager {
190189
this._box.classList.add('live-translator-box');
191190
this._wrapper.appendChild(this._box);
192191
// initialize encode
193-
// encode is moved to i18n.ts file
192+
for (const locale of this.i18n.availableLocales) {
193+
let messages = this.i18n.getLocaleMessage(locale);
194+
messages = encodeMessages(messages, locale);
195+
this.i18n.setLocaleMessage(locale, messages);
196+
}
194197
// initialize decode & render
195198
const throttler = throttle(() => this.render(), this._options.refreshRate || 50);
196199
const observer = new MutationObserver(throttler);
@@ -208,6 +211,9 @@ class LiveTranslatorManager {
208211
get root() {
209212
return this._options.root || document.documentElement;
210213
}
214+
get i18n() {
215+
return this._options.i18n.global || this._options.i18n;
216+
}
211217
toggle(enable) {
212218
if (enable !== undefined) {
213219
this._enabled = enable;
@@ -221,28 +227,26 @@ class LiveTranslatorManager {
221227
console.log(`%c Live Translator ${this._enabled ? 'ON' : 'OFF'} `, 'background: #222; color: #bada55');
222228
}
223229
render() {
224-
this._box.style.display = 'none';
225-
document.
226-
querySelectorAll('.live-translator-badge-container').
227-
forEach((elem) => {
228-
elem.remove();
229-
});
230230
this._indicator.style.background = this._enabled ? 'lightgreen' : 'red';
231231
if (!this._enabled) {
232232
return;
233233
}
234+
const newCache = {};
234235
const re = new RegExp(ZeroWidthEncoder.PATTERN, 'gm');
235236
const queue = [this.root];
236237
while (queue.length > 0) {
237238
const node = queue.pop();
238239
const badges = [];
240+
let cacheKeyParts = [];
239241
if (node instanceof Text) {
240242
const matches = node.textContent.match(re);
241243
for (const match of matches ?? []) {
242244
const meta = JSON.parse(ZeroWidthEncoder.decode(match));
243245
const badge = createBadge(meta, this._options, node);
244246
badge.addEventListener('mouseenter', () => this.showBox(node));
247+
badge.addEventListener('mouseleave', () => this.hideBox());
245248
badges.push(badge);
249+
cacheKeyParts.push(meta.path);
246250
}
247251
}
248252
const attributes = (node.attributes ? [...node.attributes] : [])
@@ -253,7 +257,9 @@ class LiveTranslatorManager {
253257
const meta = JSON.parse(ZeroWidthEncoder.decode(m));
254258
const badge = createBadge(meta, this._options, node, attribute.name);
255259
badge.addEventListener('mouseenter', () => this.showBox(node, true));
260+
badge.addEventListener('mouseleave', () => this.hideBox());
256261
badges.push(badge);
262+
cacheKeyParts.push(meta.path);
257263
}
258264
}
259265
if (badges.length) {
@@ -264,13 +270,15 @@ class LiveTranslatorManager {
264270
const clientRect = getBoundingClientRect(node);
265271
position.top = clientRect.top + window.scrollY;
266272
position.left = clientRect.left + window.screenX;
267-
isVisible = isVisible || node.parentElement.contains(document.elementFromPoint(clientRect.left + clientRect.width / 2, clientRect.top + clientRect.height / 2));
273+
const elemOnTop = document.elementFromPoint(clientRect.left + clientRect.width / 2, clientRect.top + clientRect.height / 2);
274+
isVisible = isVisible || node.parentElement.contains(elemOnTop) || elemOnTop === this._box;
268275
}
269276
else {
270277
const clientRect = node.getClientRects()[0];
271278
position.top = clientRect.top + clientRect.height - 10 + window.scrollY;
272279
position.left = clientRect.left + window.screenX;
273-
isVisible = isVisible || node.contains(document.elementFromPoint(clientRect.left + clientRect.width / 2, clientRect.top + clientRect.height / 2));
280+
const elemOnTop = document.elementFromPoint(clientRect.left + clientRect.width / 2, clientRect.top + clientRect.height / 2);
281+
isVisible = isVisible || node.contains(elemOnTop) || elemOnTop === this._box;
274282
}
275283
if (!isVisible) {
276284
continue;
@@ -280,19 +288,32 @@ class LiveTranslatorManager {
280288
// console.warn('Could not get bounding box for', node);
281289
continue;
282290
}
283-
const container = document.createElement('span');
284-
container.classList.add('live-translator-badge-container');
285-
container.style.top = position.top + 'px';
286-
container.style.left = position.left + 'px';
287-
this._wrapper.appendChild(container);
288-
for (const badge of badges) {
289-
container.appendChild(badge);
291+
cacheKeyParts.unshift(position.left, position.top);
292+
const cacheKey = cacheKeyParts.join(';');
293+
if (!(cacheKey in this._cache)) {
294+
const container = document.createElement('span');
295+
container.classList.add('live-translator-badge-container');
296+
container.style.top = position.top + 'px';
297+
container.style.left = position.left + 'px';
298+
this._wrapper.appendChild(container);
299+
for (const badge of badges) {
300+
container.appendChild(badge);
301+
}
302+
newCache[cacheKey] = container;
303+
}
304+
else {
305+
newCache[cacheKey] = this._cache[cacheKey];
306+
delete this._cache[cacheKey];
290307
}
291308
}
292309
for (const child of node.childNodes) {
293310
queue.push(child);
294311
}
295312
}
313+
for (const elem of Object.values(this._cache)) {
314+
elem.remove();
315+
}
316+
this._cache = newCache;
296317
}
297318
showBox(node, attribute = false) {
298319
const rect = !attribute ? getBoundingClientRect(node) : node.getClientRects()[0];
@@ -312,6 +333,9 @@ class LiveTranslatorManager {
312333
this._box.style.height = rect.height + 2 * padding + 'px';
313334
this._box.style.display = 'block';
314335
}
336+
hideBox() {
337+
this._box.style.display = 'none';
338+
}
315339
}
316340
const createBadge = (meta, options, node, attribute) => {
317341
const badge = document.createElement('a');

src/demo/i18n.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
import { createI18n } from "vue-i18n"
22
import en from './lang/en.json'
33
import hu from './lang/hu.json'
4-
import { encodeMessages } from ".."
54

65
export const i18n = createI18n({
76
legacy: false,
87
locale: 'en',
98
fallbackLocale: 'en',
10-
messages: encodeMessages({
11-
en,
12-
hu,
13-
}),
9+
messages: { en, hu },
1410
})

src/demo/main.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ const app = createApp(App)
88
app.use(i18n)
99

1010
app.use(LiveTranslatorPlugin, {
11+
i18n,
1112
translationLink(meta: TranslationMeta) {
1213
return `?meta=${encodeURIComponent(JSON.stringify(meta))}`
1314
},
1415
persist: true,
1516
root: document.getElementById('app'),
16-
refreshRate: 50,
17+
refreshRate: 100,
1718
checkVisibility: true,
1819
})
1920

0 commit comments

Comments
 (0)