@@ -82,22 +82,20 @@ function deepForIn(object, fn) {
82
82
} ;
83
83
forIn ( object , iteratee ) ;
84
84
}
85
- export function encodeMessages ( messagesObject ) {
85
+ function encodeMessages ( messagesObject , locale ) {
86
86
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 ( ' | ' ) ) ;
101
99
} ) ;
102
100
return messages ;
103
101
}
@@ -156,6 +154,7 @@ class LiveTranslatorManager {
156
154
_indicator ;
157
155
_box ;
158
156
_wrapper ;
157
+ _cache = { } ;
159
158
constructor ( options ) {
160
159
this . _enabled = false ;
161
160
this . _options = options ;
@@ -190,7 +189,11 @@ class LiveTranslatorManager {
190
189
this . _box . classList . add ( 'live-translator-box' ) ;
191
190
this . _wrapper . appendChild ( this . _box ) ;
192
191
// 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
+ }
194
197
// initialize decode & render
195
198
const throttler = throttle ( ( ) => this . render ( ) , this . _options . refreshRate || 50 ) ;
196
199
const observer = new MutationObserver ( throttler ) ;
@@ -208,6 +211,9 @@ class LiveTranslatorManager {
208
211
get root ( ) {
209
212
return this . _options . root || document . documentElement ;
210
213
}
214
+ get i18n ( ) {
215
+ return this . _options . i18n . global || this . _options . i18n ;
216
+ }
211
217
toggle ( enable ) {
212
218
if ( enable !== undefined ) {
213
219
this . _enabled = enable ;
@@ -221,28 +227,26 @@ class LiveTranslatorManager {
221
227
console . log ( `%c Live Translator ${ this . _enabled ? 'ON' : 'OFF' } ` , 'background: #222; color: #bada55' ) ;
222
228
}
223
229
render ( ) {
224
- this . _box . style . display = 'none' ;
225
- document .
226
- querySelectorAll ( '.live-translator-badge-container' ) .
227
- forEach ( ( elem ) => {
228
- elem . remove ( ) ;
229
- } ) ;
230
230
this . _indicator . style . background = this . _enabled ? 'lightgreen' : 'red' ;
231
231
if ( ! this . _enabled ) {
232
232
return ;
233
233
}
234
+ const newCache = { } ;
234
235
const re = new RegExp ( ZeroWidthEncoder . PATTERN , 'gm' ) ;
235
236
const queue = [ this . root ] ;
236
237
while ( queue . length > 0 ) {
237
238
const node = queue . pop ( ) ;
238
239
const badges = [ ] ;
240
+ let cacheKeyParts = [ ] ;
239
241
if ( node instanceof Text ) {
240
242
const matches = node . textContent . match ( re ) ;
241
243
for ( const match of matches ?? [ ] ) {
242
244
const meta = JSON . parse ( ZeroWidthEncoder . decode ( match ) ) ;
243
245
const badge = createBadge ( meta , this . _options , node ) ;
244
246
badge . addEventListener ( 'mouseenter' , ( ) => this . showBox ( node ) ) ;
247
+ badge . addEventListener ( 'mouseleave' , ( ) => this . hideBox ( ) ) ;
245
248
badges . push ( badge ) ;
249
+ cacheKeyParts . push ( meta . path ) ;
246
250
}
247
251
}
248
252
const attributes = ( node . attributes ? [ ...node . attributes ] : [ ] )
@@ -253,7 +257,9 @@ class LiveTranslatorManager {
253
257
const meta = JSON . parse ( ZeroWidthEncoder . decode ( m ) ) ;
254
258
const badge = createBadge ( meta , this . _options , node , attribute . name ) ;
255
259
badge . addEventListener ( 'mouseenter' , ( ) => this . showBox ( node , true ) ) ;
260
+ badge . addEventListener ( 'mouseleave' , ( ) => this . hideBox ( ) ) ;
256
261
badges . push ( badge ) ;
262
+ cacheKeyParts . push ( meta . path ) ;
257
263
}
258
264
}
259
265
if ( badges . length ) {
@@ -264,13 +270,15 @@ class LiveTranslatorManager {
264
270
const clientRect = getBoundingClientRect ( node ) ;
265
271
position . top = clientRect . top + window . scrollY ;
266
272
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 ;
268
275
}
269
276
else {
270
277
const clientRect = node . getClientRects ( ) [ 0 ] ;
271
278
position . top = clientRect . top + clientRect . height - 10 + window . scrollY ;
272
279
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 ;
274
282
}
275
283
if ( ! isVisible ) {
276
284
continue ;
@@ -280,19 +288,32 @@ class LiveTranslatorManager {
280
288
// console.warn('Could not get bounding box for', node);
281
289
continue ;
282
290
}
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 ] ;
290
307
}
291
308
}
292
309
for ( const child of node . childNodes ) {
293
310
queue . push ( child ) ;
294
311
}
295
312
}
313
+ for ( const elem of Object . values ( this . _cache ) ) {
314
+ elem . remove ( ) ;
315
+ }
316
+ this . _cache = newCache ;
296
317
}
297
318
showBox ( node , attribute = false ) {
298
319
const rect = ! attribute ? getBoundingClientRect ( node ) : node . getClientRects ( ) [ 0 ] ;
@@ -312,6 +333,9 @@ class LiveTranslatorManager {
312
333
this . _box . style . height = rect . height + 2 * padding + 'px' ;
313
334
this . _box . style . display = 'block' ;
314
335
}
336
+ hideBox ( ) {
337
+ this . _box . style . display = 'none' ;
338
+ }
315
339
}
316
340
const createBadge = ( meta , options , node , attribute ) => {
317
341
const badge = document . createElement ( 'a' ) ;
0 commit comments