@internetarchive/bookreader 5.0.0-98 → 5.0.0-99

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/bookreader",
3
- "version": "5.0.0-98",
3
+ "version": "5.0.0-99",
4
4
  "description": "The Internet Archive BookReader.",
5
5
  "type": "module",
6
6
  "files": [
package/src/BookReader.js CHANGED
@@ -1005,7 +1005,8 @@ BookReader.prototype.zoom = function(direction) {
1005
1005
  } else {
1006
1006
  this.activeMode.zoom('out');
1007
1007
  }
1008
- this.plugins.textSelection?.stopPageFlip(this.refs.$brContainer);
1008
+ this.plugins.textSelection?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
1009
+ this.plugins.translate?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
1009
1010
  return;
1010
1011
  };
1011
1012
 
@@ -1252,7 +1253,8 @@ BookReader.prototype.switchMode = function(
1252
1253
  const eventName = mode + 'PageViewSelected';
1253
1254
  this.trigger(BookReader.eventNames[eventName]);
1254
1255
 
1255
- this.plugins.textSelection?.stopPageFlip(this.refs.$brContainer);
1256
+ this.plugins.textSelection?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
1257
+ this.plugins.translate?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
1256
1258
  };
1257
1259
 
1258
1260
  BookReader.prototype.updateBrClasses = function() {
@@ -1324,7 +1326,8 @@ BookReader.prototype.enterFullscreen = async function(bindKeyboardControls = tru
1324
1326
  }
1325
1327
  this.jumpToIndex(currentIndex);
1326
1328
 
1327
- this.plugins.textSelection?.stopPageFlip(this.refs.$brContainer);
1329
+ this.plugins.textSelection?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
1330
+ this.plugins.translate?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
1328
1331
  // Add "?view=theater"
1329
1332
  this.trigger(BookReader.eventNames.fragmentChange);
1330
1333
  // trigger event here, so that animations,
@@ -1370,7 +1373,8 @@ BookReader.prototype.exitFullScreen = async function () {
1370
1373
  await this.activeMode.mode1UpLit.updateComplete;
1371
1374
  }
1372
1375
 
1373
- this.plugins.textSelection?.stopPageFlip(this.refs.$brContainer);
1376
+ this.plugins.textSelection?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
1377
+ this.plugins.translate?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
1374
1378
  // Remove "?view=theater"
1375
1379
  this.trigger(BookReader.eventNames.fragmentChange);
1376
1380
  this.refs.$br.removeClass('BRfullscreenAnimation');
@@ -91,16 +91,8 @@
91
91
  }
92
92
  }
93
93
 
94
- svg.BRPageLayer {
95
- position: absolute;
96
- top: 0;
97
- left: 0;
98
- right: 0;
99
- bottom: 0;
100
- }
101
-
102
94
  // Hides page layers during page flip animation
103
- .BRpageFlipping .BRPageLayer {
95
+ .BRpageFlipping .BRtextLayer {
104
96
  display: none;
105
97
  }
106
98
 
@@ -54,6 +54,21 @@
54
54
  }
55
55
  }
56
56
 
57
+ // Style URI TextFragments, eg #:~:text=example
58
+ .BRtextLayer ::target-text {
59
+ // Similar colour to the default one used in Safari, Firefox. Note Chrome uses a purple colour
60
+ background-color: hsla(45, 80%, 66%, 0.6);
61
+ color: transparent;
62
+ }
63
+
64
+ .BRtranslateLayer ::selection {
65
+ background: hsla(210, 74%, 62%, 0.4);
66
+ }
67
+
68
+ .BRtranslateLayer ::-moz-selection {
69
+ background: hsla(210, 74%, 62%, 0.4);
70
+ }
71
+
57
72
  .BRparagraphElement br {
58
73
  visibility: hidden;
59
74
  }
@@ -130,8 +145,7 @@
130
145
  }
131
146
 
132
147
  .BRtextLayer.showingTranslation {
133
- visibility: hidden;
134
- pointer-events: none;
148
+ display: none;
135
149
  }
136
150
 
137
151
  .BRtranslateLayer .BRparagraphElement.BRtranslateHidden {
@@ -1,10 +1,10 @@
1
1
  //@ts-check
2
2
  import { createDIVPageLayer } from '../BookReader/PageContainer.js';
3
- import { SelectionObserver } from '../BookReader/utils/SelectionObserver.js';
4
3
  import { BookReaderPlugin } from '../BookReaderPlugin.js';
5
4
  import { applyVariables } from '../util/strings.js';
6
5
  import { Cache } from '../util/cache.js';
7
6
  import { toISO6391 } from './tts/utils.js';
7
+ import { TextSelectionManager } from '../util/TextSelectionManager.js';
8
8
  /** @typedef {import('../util/strings.js').StringWithVars} StringWithVars */
9
9
  /** @typedef {import('../BookReader/PageContainer.js').PageContainer} PageContainer */
10
10
 
@@ -20,7 +20,7 @@ export class TextSelectionPlugin extends BookReaderPlugin {
20
20
  singlePageDjvuXmlUrl: null,
21
21
  /** Whether to fetch the XML as a jsonp */
22
22
  jsonp: false,
23
- /** Mox words tha can be selected when the text layer is protected */
23
+ /** Mox words that can be selected when the text layer is protected */
24
24
  maxProtectedWords: 200,
25
25
  }
26
26
 
@@ -46,7 +46,7 @@ export class TextSelectionPlugin extends BookReaderPlugin {
46
46
  // now we do make that assumption.
47
47
  /** Whether the book is right-to-left */
48
48
  this.rtl = this.br.pageProgression === 'rl';
49
- this.selectionObserver = new SelectionObserver('.BRtextLayer', this._onSelectionChange);
49
+ this.textSelectionManager = new TextSelectionManager('.BRtextLayer', this.br, {selectionElement: ['.BRwordElement', '.BRspace']}, this.options.maxProtectedWords);
50
50
  }
51
51
 
52
52
  /** @override */
@@ -54,72 +54,9 @@ export class TextSelectionPlugin extends BookReaderPlugin {
54
54
  if (!this.options.enabled) return;
55
55
 
56
56
  this.loadData();
57
-
58
- this.selectionObserver.attach();
59
- new SelectionObserver('.BRtextLayer', (selectEvent) => {
60
- // Track how often selection is used
61
- if (selectEvent == 'started') {
62
- this.br.plugins.archiveAnalytics?.sendEvent('BookReader', 'SelectStart');
63
-
64
- // Set a class on the page to avoid hiding it when zooming/etc
65
- this.br.refs.$br.find('.BRpagecontainer--hasSelection').removeClass('BRpagecontainer--hasSelection');
66
- $(window.getSelection().anchorNode).closest('.BRpagecontainer').addClass('BRpagecontainer--hasSelection');
67
- }
68
- }).attach();
69
-
70
- if (this.br.protected) {
71
- document.addEventListener('selectionchange', this._limitSelection);
72
- // Prevent right clicking when selected text
73
- $(document.body).on('contextmenu dragstart copy', (e) => {
74
- const selection = document.getSelection();
75
- if (selection?.toString()) {
76
- const intersectsTextLayer = $('.BRtextLayer')
77
- .toArray()
78
- .some(el => selection.containsNode(el, true));
79
- if (intersectsTextLayer) {
80
- e.preventDefault();
81
- return false;
82
- }
83
- }
84
- });
85
- }
57
+ this.textSelectionManager.init();
86
58
  }
87
59
 
88
- _limitSelection = () => {
89
- const selection = window.getSelection();
90
- if (!selection.rangeCount) return;
91
-
92
- const range = selection.getRangeAt(0);
93
-
94
- // Check if range.startContainer is inside the sub-tree of .BRContainer
95
- const startInBr = !!range.startContainer.parentElement.closest('.BRcontainer');
96
- const endInBr = !!range.endContainer.parentElement.closest('.BRcontainer');
97
- if (!startInBr && !endInBr) return;
98
- if (!startInBr || !endInBr) {
99
- // weird case, just clear the selection
100
- selection.removeAllRanges();
101
- return;
102
- }
103
-
104
- // Find the last allowed word in the selection
105
- const lastAllowedWord = genAt(
106
- genFilter(
107
- walkBetweenNodes(range.startContainer, range.endContainer),
108
- (node) => node.classList?.contains('BRwordElement'),
109
- ),
110
- this.options.maxProtectedWords - 1,
111
- );
112
-
113
- if (!lastAllowedWord || range.endContainer.parentNode == lastAllowedWord) return;
114
-
115
- const newRange = document.createRange();
116
- newRange.setStart(range.startContainer, range.startOffset);
117
- newRange.setEnd(lastAllowedWord.firstChild, lastAllowedWord.textContent.length);
118
-
119
- selection.removeAllRanges();
120
- selection.addRange(newRange);
121
- };
122
-
123
60
  /**
124
61
  * @override
125
62
  * @param {PageContainer} pageContainer
@@ -134,20 +71,6 @@ export class TextSelectionPlugin extends BookReaderPlugin {
134
71
  return pageContainer;
135
72
  }
136
73
 
137
- /**
138
- * @param {'started' | 'cleared'} type
139
- * @param {HTMLElement} target
140
- */
141
- _onSelectionChange = (type, target) => {
142
- if (type === 'started') {
143
- this.textSelectingMode(target);
144
- } else if (type === 'cleared') {
145
- this.defaultMode(target);
146
- } else {
147
- throw new Error(`Unknown type ${type}`);
148
- }
149
- }
150
-
151
74
  loadData() {
152
75
  // Only fetch the full djvu xml if the single page url isn't there
153
76
  if (this.options.singlePageDjvuXmlUrl) return;
@@ -204,92 +127,6 @@ export class TextSelectionPlugin extends BookReaderPlugin {
204
127
  }
205
128
  }
206
129
 
207
- /**
208
- * Intercept copied text to remove any styling applied to it
209
- * @param {JQuery} $container
210
- */
211
- interceptCopy($container) {
212
- $container[0].addEventListener('copy', (event) => {
213
- const selection = document.getSelection();
214
- event.clipboardData.setData('text/plain', selection.toString());
215
- event.preventDefault();
216
- });
217
- }
218
-
219
- /**
220
- * Applies mouse events when in default mode
221
- * @param {HTMLElement} textLayer
222
- */
223
- defaultMode(textLayer) {
224
- const $pageContainer = $(textLayer).closest('.BRpagecontainer');
225
- textLayer.style.pointerEvents = "none";
226
- $pageContainer.find("img").css("pointer-events", "auto");
227
-
228
- $(textLayer).off(".textSelectPluginHandler");
229
- const startedMouseDown = this.mouseIsDown;
230
- let skipNextMouseup = this.mouseIsDown;
231
- if (startedMouseDown) {
232
- textLayer.style.pointerEvents = "auto";
233
- }
234
-
235
- // Need to stop propagation to prevent DragScrollable from
236
- // blocking selection
237
- $(textLayer).on("mousedown.textSelectPluginHandler", (event) => {
238
- this.mouseIsDown = true;
239
- if ($(event.target).is(".BRwordElement, .BRspace")) {
240
- event.stopPropagation();
241
- }
242
- });
243
-
244
- $(textLayer).on("mouseup.textSelectPluginHandler", (event) => {
245
- this.mouseIsDown = false;
246
- textLayer.style.pointerEvents = "none";
247
- if (skipNextMouseup) {
248
- skipNextMouseup = false;
249
- event.stopPropagation();
250
- }
251
- });
252
- }
253
-
254
- /**
255
- * This mode is active while there is a selection on the given textLayer
256
- * @param {HTMLElement} textLayer
257
- */
258
- textSelectingMode(textLayer) {
259
- const $pageContainer = $(textLayer).closest('.BRpagecontainer');
260
- // Make text layer consume all events
261
- textLayer.style.pointerEvents = "all";
262
- // Block img from getting long-press to save while selecting
263
- $pageContainer.find("img").css("pointer-events", "none");
264
-
265
- $(textLayer).off(".textSelectPluginHandler");
266
-
267
- $(textLayer).on('mousedown.textSelectPluginHandler', (event) => {
268
- this.mouseIsDown = true;
269
- event.stopPropagation();
270
- });
271
-
272
- // Prevent page flip on click
273
- $(textLayer).on('mouseup.textSelectPluginHandler', (event) => {
274
- this.mouseIsDown = false;
275
- event.stopPropagation();
276
- });
277
- }
278
-
279
- /**
280
- * Initializes text selection modes if there is a text layer on the page
281
- * @param {JQuery} $container
282
- */
283
- stopPageFlip($container) {
284
- /** @type {JQuery<HTMLElement>} */
285
- const $textLayer = $container.find('.BRtextLayer');
286
- if (!$textLayer.length) return;
287
- $textLayer.each((i, s) => this.defaultMode(s));
288
- if (!this.br.protected) {
289
- this.interceptCopy($container);
290
- }
291
- }
292
-
293
130
  /**
294
131
  * @param {PageContainer} pageContainer
295
132
  */
@@ -344,7 +181,7 @@ export class TextSelectionPlugin extends BookReaderPlugin {
344
181
  textLayer.appendChild(paragEl);
345
182
  }
346
183
  $container.append(textLayer);
347
- this.stopPageFlip($container);
184
+ this.textSelectionManager.stopPageFlip($container);
348
185
  this.br.trigger('textLayerRendered', {
349
186
  pageIndex,
350
187
  pageContainer,
@@ -3,27 +3,6 @@ import { Cache } from '../../util/cache.js';
3
3
  import { BatchTranslator } from '@internetarchive/bergamot-translator/translator.js';
4
4
  import { toISO6391 } from '../tts/utils.js';
5
5
 
6
- export const langs = /** @type {{[lang: string]: string}} */ {
7
- "bg": "Bulgarian",
8
- "ca": "Catalan",
9
- "cs": "Czech",
10
- "nl": "Dutch",
11
- "en": "English",
12
- "et": "Estonian",
13
- "de": "German",
14
- "fr": "French",
15
- "is": "Icelandic",
16
- "it": "Italian",
17
- "nb": "Norwegian Bokmål",
18
- "nn": "Norwegian Nynorsk",
19
- "fa": "Persian",
20
- "pl": "Polish",
21
- "pt": "Portuguese",
22
- "ru": "Russian",
23
- "es": "Spanish",
24
- "uk": "Ukrainian",
25
- };
26
-
27
6
  export class TranslationManager {
28
7
  /** @type {Cache<{index: string, response: string}>} */
29
8
  alreadyTranslated = new Cache(100);
@@ -167,4 +146,17 @@ export class TranslationManager {
167
146
 
168
147
  return promise;
169
148
  }
149
+
150
+ /**
151
+ * Checks and updates the status of a model that is currently being retreived from IA
152
+ * @param {string} fromLanguage
153
+ * @param {string} toLanguage
154
+ * @returns {Promise<boolean>}
155
+ */
156
+ getTranslationModel = async(fromLanguage, toLanguage) => {
157
+ return this.translator.backing.getTranslationModel({from: fromLanguage, to: toLanguage})
158
+ .catch((err) => {
159
+ console.error(`An error occurred while trying to retreive the ${fromLanguage}-${toLanguage} model`);
160
+ });
161
+ }
170
162
  }
@@ -2,9 +2,11 @@
2
2
  import { html, LitElement } from 'lit';
3
3
  import { BookReaderPlugin } from '../../BookReaderPlugin.js';
4
4
  import { customElement, property } from 'lit/decorators.js';
5
- import { langs, TranslationManager } from "./TranslationManager.js";
5
+ import { TranslationManager } from "./TranslationManager.js";
6
6
  import { toISO6391 } from '../tts/utils.js';
7
7
  import { sortBy } from '../../../src/BookReader/utils.js';
8
+ import { TextSelectionManager } from '../../../src/util/TextSelectionManager.js';
9
+ import '@internetarchive/ia-activity-indicator/ia-activity-indicator.js';
8
10
 
9
11
  // @ts-ignore
10
12
  const BookReader = /** @type {typeof import('@/src/BookReader.js').default} */(window.BookReader);
@@ -34,7 +36,7 @@ export class TranslatePlugin extends BookReaderPlugin {
34
36
  * Current language code that is being translated From. Defaults to EN currently
35
37
  * @type {!string}
36
38
  */
37
- langFromCode
39
+ langFromCode;
38
40
 
39
41
  /**
40
42
  * Current language code that is being translated To
@@ -53,6 +55,12 @@ export class TranslatePlugin extends BookReaderPlugin {
53
55
  */
54
56
  userToggleTranslate;
55
57
 
58
+ /**
59
+ * @type {boolean} loadingModel - Shows loading animation while downloading lang model
60
+ */
61
+ loadingModel = true;
62
+
63
+
56
64
  async init() {
57
65
  const currentLanguage = toISO6391(this.br.options.bookLanguage.replace(/[.,/#!$%^&*;:{}=\-_`~()]/g, ""));
58
66
  this.langFromCode = currentLanguage ?? "en";
@@ -176,28 +184,15 @@ export class TranslatePlugin extends BookReaderPlugin {
176
184
  "width": $(originalParagraphStyle).css("width"),
177
185
  "font-size": fontSize,
178
186
  });
179
-
180
- // Note: We'll likely want to switch to using the same logic as
181
- // TextSelectionPlugin's selection, which allows for e.g. click-to-flip
182
- // to work simultaneously with text selection.
183
- translatedParagraph.addEventListener('mousedown', (e) => {
184
- e.stopPropagation();
185
- e.stopImmediatePropagation();
186
- });
187
-
188
- translatedParagraph.addEventListener('mouseup', (e) => {
189
- e.stopPropagation();
190
- e.stopImmediatePropagation();
191
- });
192
-
193
- translatedParagraph.addEventListener('dragstart', (e) =>{
194
- e.preventDefault();
195
- });
196
187
  pageTranslationLayer.append(translatedParagraph);
197
188
  }
198
189
 
199
190
  if (paragraph.textContent.length !== 0) {
200
191
  const pagePriority = parseFloat(pageIndex) + priority + pidx;
192
+ this.translationManager.getTranslationModel(this.langFromCode, this.langToCode).then(() => {
193
+ this._panel.loadingModel = false;
194
+ this.loadingModel = false;
195
+ });
201
196
  const translatedText = await this.translationManager.getTranslation(this.langFromCode, this.langToCode, pageIndex, pidx, paragraph.textContent, pagePriority);
202
197
  // prevent duplicate spans from appearing if exists
203
198
  translatedParagraph.firstElementChild?.remove();
@@ -216,6 +211,8 @@ export class TranslatePlugin extends BookReaderPlugin {
216
211
  }
217
212
  }
218
213
  });
214
+
215
+ this.textSelectionManager?.stopPageFlip(this.br.refs.$brContainer);
219
216
  await Promise.all(paragraphTranslationPromises);
220
217
  this.br.trigger('translateLayerRendered', {
221
218
  leafIndex: pageIndex,
@@ -246,6 +243,7 @@ export class TranslatePlugin extends BookReaderPlugin {
246
243
 
247
244
  clearAllTranslations() {
248
245
  document.querySelectorAll('.BRtranslateLayer').forEach(el => el.remove());
246
+ document.querySelectorAll('.showingTranslation').forEach(el => el.classList.remove('showingTranslation'));
249
247
  }
250
248
 
251
249
  /**
@@ -290,16 +288,21 @@ export class TranslatePlugin extends BookReaderPlugin {
290
288
 
291
289
  // Update the from language
292
290
  this.langFromCode = selectedLangFrom;
291
+ this._panel.requestUpdate();
293
292
 
294
293
  // Add 'From' language to 'To' list if not already present
295
294
  if (!this.translationManager.toLanguages.some(lang => lang.code === selectedLangFrom)) {
296
- this.translationManager.toLanguages.push({ code: selectedLangFrom, name: langs[selectedLangFrom] });
295
+ this.translationManager.toLanguages.push({
296
+ code: selectedLangFrom,
297
+ name: this.translationManager.fromLanguages.find((entry) => entry.code == selectedLangFrom).name,
298
+ });
297
299
  }
298
300
 
299
301
  // Update the 'To' languages list and set the correct 'To' language
300
302
  this._panel.toLanguages = this.translationManager.toLanguages;
301
303
 
302
304
  console.log(this.langFromCode, this.langToCode);
305
+ this._render();
303
306
  if (this.langFromCode !== this.langToCode) {
304
307
  this.translateActivePageContainerElements();
305
308
  }
@@ -308,18 +311,27 @@ export class TranslatePlugin extends BookReaderPlugin {
308
311
  handleToLangChange = async (e) => {
309
312
  this.clearAllTranslations();
310
313
  this.langToCode = e.detail.value;
314
+ this._render();
311
315
  this.translateActivePageContainerElements();
312
316
  }
313
317
 
314
318
  handleToggleTranslation = async () => {
315
319
  this.userToggleTranslate = !this.userToggleTranslate;
316
320
  this.translationManager.active = this.userToggleTranslate;
321
+ // Init textSelectionManager only after the translation is active
322
+ if (!this.textSelectionManager) {
323
+ this.textSelectionManager = new TextSelectionManager('.BRtranslateLayer', this.br, {selectionElement: [".BRlineElement"]}, 1);
324
+ this.textSelectionManager.init();
325
+ }
326
+ this._render();
317
327
  if (!this.userToggleTranslate) {
318
328
  this.clearAllTranslations();
319
329
  this.br.trigger('translationDisabled', { });
330
+ this.textSelectionManager.detach();
320
331
  } else {
321
332
  this.br.trigger('translationEnabled', { });
322
333
  this.translateActivePageContainerElements();
334
+ this.textSelectionManager.attach();
323
335
  }
324
336
  }
325
337
 
@@ -336,6 +348,10 @@ export class TranslatePlugin extends BookReaderPlugin {
336
348
  this._panel = e.target;
337
349
  this._panel.fromLanguages = this.translationManager.fromLanguages;
338
350
  this._panel.toLanguages = this.translationManager.toLanguages;
351
+ this._panel.userTranslationActive = this.userToggleTranslate;
352
+ this._panel.detectedToLang = this.langToCode;
353
+ this._panel.detectedFromLang = this.langFromCode;
354
+ this._panel.loadingModel = this.loadingModel;
339
355
  }
340
356
  }"
341
357
  @langFromChanged="${this.handleFromLangChange}"
@@ -344,9 +360,10 @@ export class TranslatePlugin extends BookReaderPlugin {
344
360
  .fromLanguages="${this.translationManager.fromLanguages}"
345
361
  .toLanguages="${this.translationManager.toLanguages}"
346
362
  .disclaimerMessage="${this.options.panelDisclaimerText}"
347
- .userTranslationActive=${false}
363
+ .userTranslationActive=${this.userToggleTranslate}
348
364
  .detectedFromLang=${this.langFromCode}
349
365
  .detectedToLang=${this.langToCode}
366
+ .loadingModel=${this.loadingModel}
350
367
  class="translate-panel"
351
368
  />`,
352
369
  };
@@ -364,6 +381,7 @@ export class BrTranslatePanel extends LitElement {
364
381
  @property({ type: Boolean }) userTranslationActive = false;
365
382
  @property({ type: String }) detectedFromLang = '';
366
383
  @property({ type: String }) detectedToLang = '';
384
+ @property({ type: Boolean }) loadingModel;
367
385
 
368
386
  /** @override */
369
387
  createRenderRoot() {
@@ -425,6 +443,10 @@ export class BrTranslatePanel extends LitElement {
425
443
  <div class="footer" id="status" style="margin-top:5%">
426
444
  ${this._statusWarning()}
427
445
  </div>
446
+
447
+ <div class="lang-models-loading">
448
+ ${this._languageModelStatus()}
449
+ </div>
428
450
  </div>`;
429
451
  }
430
452
  _onLangFromChange(event) {
@@ -439,6 +461,7 @@ export class BrTranslatePanel extends LitElement {
439
461
  if (this._getSelectedLang('to') !== this._getSelectedLang('from')) {
440
462
  this.prevSelectedLang = this._getSelectedLang('from');
441
463
  }
464
+ this.loadingModel = true;
442
465
  this.detectedFromLang = event.target.value;
443
466
  }
444
467
 
@@ -454,6 +477,7 @@ export class BrTranslatePanel extends LitElement {
454
477
  if (this._getSelectedLang('from') !== event.target.value) {
455
478
  this.prevSelectedLang = this._getSelectedLang('from');
456
479
  }
480
+ this.loadingModel = true;
457
481
  this.detectedToLang = event.target.value;
458
482
  }
459
483
 
@@ -485,5 +509,17 @@ export class BrTranslatePanel extends LitElement {
485
509
  }
486
510
  return "";
487
511
  }
488
- }
489
512
 
513
+ _languageModelStatus() {
514
+ if (this.userTranslationActive) {
515
+ if (this.loadingModel) {
516
+ return html`
517
+ <ia-activity-indicator mode="processing" style="display:block; width: 40px; height: 40px; margin: 0 auto;"></ia-activity-indicator>
518
+ <p>Downloading language model</p>
519
+ `;
520
+ }
521
+ return html`<p>Language model loaded</p>`;
522
+ }
523
+ return "";
524
+ }
525
+ }