@internetarchive/bookreader 5.0.0-97 → 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/BookReader/BookReader.css +23 -20
- package/BookReader/BookReader.js +1 -1
- package/BookReader/BookReader.js.map +1 -1
- package/BookReader/bergamot-translator-worker.js +1 -1
- package/BookReader/ia-bookreader-bundle.js +156 -156
- package/BookReader/ia-bookreader-bundle.js.map +1 -1
- package/BookReader/plugins/plugin.text_selection.js +1 -1
- package/BookReader/plugins/plugin.text_selection.js.map +1 -1
- package/BookReader/plugins/plugin.translate.js +135 -1
- package/BookReader/plugins/plugin.translate.js.map +1 -1
- package/BookReader/plugins/plugin.tts.js +1 -1
- package/BookReader/plugins/plugin.tts.js.map +1 -1
- package/BookReader/silence.mp3 +0 -0
- package/package.json +2 -2
- package/src/BookReader/options.js +12 -8
- package/src/BookReader.js +53 -5
- package/src/assets/silence.mp3 +0 -0
- package/src/css/_BRpages.scss +1 -9
- package/src/css/_BRsearch.scss +1 -0
- package/src/css/_TextSelection.scss +23 -11
- package/src/plugins/plugin.text_selection.js +14 -168
- package/src/plugins/translate/TranslationManager.js +19 -24
- package/src/plugins/translate/plugin.translate.js +189 -78
- package/src/plugins/tts/AbstractTTSEngine.js +3 -4
- package/src/plugins/tts/PageChunk.js +28 -9
- package/src/plugins/tts/WebTTSEngine.js +5 -7
- package/src/plugins/tts/plugin.tts.js +40 -4
- package/src/plugins/tts/utils.js +15 -5
- package/src/util/TextSelectionManager.js +282 -0
|
@@ -2,7 +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 {
|
|
5
|
+
import { TranslationManager } from "./TranslationManager.js";
|
|
6
|
+
import { toISO6391 } from '../tts/utils.js';
|
|
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';
|
|
6
10
|
|
|
7
11
|
// @ts-ignore
|
|
8
12
|
const BookReader = /** @type {typeof import('@/src/BookReader.js').default} */(window.BookReader);
|
|
@@ -11,6 +15,8 @@ export class TranslatePlugin extends BookReaderPlugin {
|
|
|
11
15
|
|
|
12
16
|
options = {
|
|
13
17
|
enabled: true,
|
|
18
|
+
|
|
19
|
+
/** @type {string | import('lit').TemplateResult} */
|
|
14
20
|
panelDisclaimerText: "Translations are in alpha",
|
|
15
21
|
}
|
|
16
22
|
|
|
@@ -27,10 +33,10 @@ export class TranslatePlugin extends BookReaderPlugin {
|
|
|
27
33
|
toLanguages = [];
|
|
28
34
|
|
|
29
35
|
/**
|
|
30
|
-
* Current language code that is being translated From
|
|
36
|
+
* Current language code that is being translated From. Defaults to EN currently
|
|
31
37
|
* @type {!string}
|
|
32
38
|
*/
|
|
33
|
-
langFromCode
|
|
39
|
+
langFromCode;
|
|
34
40
|
|
|
35
41
|
/**
|
|
36
42
|
* Current language code that is being translated To
|
|
@@ -43,7 +49,22 @@ export class TranslatePlugin extends BookReaderPlugin {
|
|
|
43
49
|
*/
|
|
44
50
|
_panel;
|
|
45
51
|
|
|
52
|
+
/**
|
|
53
|
+
* @type {boolean} userToggleTranslate - Checks if user has initiated translation
|
|
54
|
+
* Should synchronize with the state of TranslationManager's active state
|
|
55
|
+
*/
|
|
56
|
+
userToggleTranslate;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @type {boolean} loadingModel - Shows loading animation while downloading lang model
|
|
60
|
+
*/
|
|
61
|
+
loadingModel = true;
|
|
62
|
+
|
|
63
|
+
|
|
46
64
|
async init() {
|
|
65
|
+
const currentLanguage = toISO6391(this.br.options.bookLanguage.replace(/[.,/#!$%^&*;:{}=\-_`~()]/g, ""));
|
|
66
|
+
this.langFromCode = currentLanguage ?? "en";
|
|
67
|
+
|
|
47
68
|
if (!this.options.enabled) {
|
|
48
69
|
return;
|
|
49
70
|
}
|
|
@@ -84,10 +105,8 @@ export class TranslatePlugin extends BookReaderPlugin {
|
|
|
84
105
|
await this.translationManager.initWorker();
|
|
85
106
|
// Note await above lets _render function properly, since it gives the browser
|
|
86
107
|
// time to render the rest of bookreader, which _render depends on
|
|
87
|
-
this._render();
|
|
88
|
-
|
|
89
108
|
this.langToCode = this.translationManager.toLanguages[0].code;
|
|
90
|
-
|
|
109
|
+
this._render();
|
|
91
110
|
}
|
|
92
111
|
|
|
93
112
|
/** @param {HTMLElement} page*/
|
|
@@ -114,7 +133,8 @@ export class TranslatePlugin extends BookReaderPlugin {
|
|
|
114
133
|
|
|
115
134
|
/** @param {HTMLElement} page */
|
|
116
135
|
async translateRenderedLayer(page, priority) {
|
|
117
|
-
if
|
|
136
|
+
// Do not run translation if in thumbnail mode or if user did not initiate transations
|
|
137
|
+
if (this.br.mode == this.br.constModeThumb || !this.userToggleTranslate || this.langFromCode == this.langToCode) {
|
|
118
138
|
return;
|
|
119
139
|
}
|
|
120
140
|
|
|
@@ -123,7 +143,7 @@ export class TranslatePlugin extends BookReaderPlugin {
|
|
|
123
143
|
let pageTranslationLayer;
|
|
124
144
|
if (!page.querySelector('.BRPageLayer.BRtranslateLayer')) {
|
|
125
145
|
pageTranslationLayer = document.createElement('div');
|
|
126
|
-
pageTranslationLayer.classList.add('BRPageLayer', 'BRtranslateLayer');
|
|
146
|
+
pageTranslationLayer.classList.add('BRPageLayer', 'BRtranslateLayer', 'BRtranslateLayerLoading');
|
|
127
147
|
pageTranslationLayer.setAttribute('lang', `${this.langToCode}`);
|
|
128
148
|
page.prepend(pageTranslationLayer);
|
|
129
149
|
} else {
|
|
@@ -141,7 +161,7 @@ export class TranslatePlugin extends BookReaderPlugin {
|
|
|
141
161
|
});
|
|
142
162
|
const paragraphs = this.getParagraphsOnPage(page);
|
|
143
163
|
|
|
144
|
-
paragraphs.
|
|
164
|
+
const paragraphTranslationPromises = paragraphs.map(async (paragraph, pidx) => {
|
|
145
165
|
let translatedParagraph = page.querySelector(`[data-translate-index='${pageIndex}-${pidx}']`);
|
|
146
166
|
if (!translatedParagraph) {
|
|
147
167
|
translatedParagraph = document.createElement('p');
|
|
@@ -149,6 +169,11 @@ export class TranslatePlugin extends BookReaderPlugin {
|
|
|
149
169
|
translatedParagraph.setAttribute('data-translate-index', `${pageIndex}-${pidx}`);
|
|
150
170
|
translatedParagraph.className = 'BRparagraphElement';
|
|
151
171
|
const originalParagraphStyle = paragraphs[pidx];
|
|
172
|
+
// check text selection paragraphs for header/footer roles
|
|
173
|
+
if (paragraph.classList.contains('ocr-role-header-footer')) {
|
|
174
|
+
translatedParagraph.ariaHidden = "true";
|
|
175
|
+
translatedParagraph.classList.add('ocr-role-header-footer');
|
|
176
|
+
}
|
|
152
177
|
const fontSize = `${parseInt($(originalParagraphStyle).css("font-size"))}px`;
|
|
153
178
|
|
|
154
179
|
$(translatedParagraph).css({
|
|
@@ -159,28 +184,15 @@ export class TranslatePlugin extends BookReaderPlugin {
|
|
|
159
184
|
"width": $(originalParagraphStyle).css("width"),
|
|
160
185
|
"font-size": fontSize,
|
|
161
186
|
});
|
|
162
|
-
|
|
163
|
-
// Note: We'll likely want to switch to using the same logic as
|
|
164
|
-
// TextSelectionPlugin's selection, which allows for e.g. click-to-flip
|
|
165
|
-
// to work simultaneously with text selection.
|
|
166
|
-
translatedParagraph.addEventListener('mousedown', (e) => {
|
|
167
|
-
e.stopPropagation();
|
|
168
|
-
e.stopImmediatePropagation();
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
translatedParagraph.addEventListener('mouseup', (e) => {
|
|
172
|
-
e.stopPropagation();
|
|
173
|
-
e.stopImmediatePropagation();
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
translatedParagraph.addEventListener('dragstart', (e) =>{
|
|
177
|
-
e.preventDefault();
|
|
178
|
-
});
|
|
179
187
|
pageTranslationLayer.append(translatedParagraph);
|
|
180
188
|
}
|
|
181
189
|
|
|
182
190
|
if (paragraph.textContent.length !== 0) {
|
|
183
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
|
+
});
|
|
184
196
|
const translatedText = await this.translationManager.getTranslation(this.langFromCode, this.langToCode, pageIndex, pidx, paragraph.textContent, pagePriority);
|
|
185
197
|
// prevent duplicate spans from appearing if exists
|
|
186
198
|
translatedParagraph.firstElementChild?.remove();
|
|
@@ -199,10 +211,39 @@ export class TranslatePlugin extends BookReaderPlugin {
|
|
|
199
211
|
}
|
|
200
212
|
}
|
|
201
213
|
});
|
|
214
|
+
|
|
215
|
+
this.textSelectionManager?.stopPageFlip(this.br.refs.$brContainer);
|
|
216
|
+
await Promise.all(paragraphTranslationPromises);
|
|
217
|
+
this.br.trigger('translateLayerRendered', {
|
|
218
|
+
leafIndex: pageIndex,
|
|
219
|
+
translateLayer: pageTranslationLayer,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Get the translation layers for a specific leaf index.
|
|
225
|
+
* @param {number} leafIndex
|
|
226
|
+
* @returns {Promise<HTMLElement[]>}
|
|
227
|
+
*/
|
|
228
|
+
async getTranslateLayers(leafIndex) {
|
|
229
|
+
const pageContainerElements = this.br.getActivePageContainerElementsForIndex(leafIndex);
|
|
230
|
+
const translateLayer = $(pageContainerElements).filter(`[data-index='${leafIndex}']`).find('.BRtranslateLayer');
|
|
231
|
+
if (translateLayer.length) return translateLayer.toArray();
|
|
232
|
+
|
|
233
|
+
return new Promise((res, rej) => {
|
|
234
|
+
const handler = async (_, extraParams) => {
|
|
235
|
+
if (extraParams.leafIndex == leafIndex) {
|
|
236
|
+
this.br.off('translateLayerRendered', handler); // remember to detach translateLayer
|
|
237
|
+
res([extraParams.translateLayer]);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
this.br.on('translateLayerRendered', handler);
|
|
241
|
+
});
|
|
202
242
|
}
|
|
203
243
|
|
|
204
244
|
clearAllTranslations() {
|
|
205
245
|
document.querySelectorAll('.BRtranslateLayer').forEach(el => el.remove());
|
|
246
|
+
document.querySelectorAll('.showingTranslation').forEach(el => el.classList.remove('showingTranslation'));
|
|
206
247
|
}
|
|
207
248
|
|
|
208
249
|
/**
|
|
@@ -247,16 +288,21 @@ export class TranslatePlugin extends BookReaderPlugin {
|
|
|
247
288
|
|
|
248
289
|
// Update the from language
|
|
249
290
|
this.langFromCode = selectedLangFrom;
|
|
291
|
+
this._panel.requestUpdate();
|
|
250
292
|
|
|
251
293
|
// Add 'From' language to 'To' list if not already present
|
|
252
294
|
if (!this.translationManager.toLanguages.some(lang => lang.code === selectedLangFrom)) {
|
|
253
|
-
this.translationManager.toLanguages.push({
|
|
295
|
+
this.translationManager.toLanguages.push({
|
|
296
|
+
code: selectedLangFrom,
|
|
297
|
+
name: this.translationManager.fromLanguages.find((entry) => entry.code == selectedLangFrom).name,
|
|
298
|
+
});
|
|
254
299
|
}
|
|
255
300
|
|
|
256
301
|
// Update the 'To' languages list and set the correct 'To' language
|
|
257
302
|
this._panel.toLanguages = this.translationManager.toLanguages;
|
|
258
303
|
|
|
259
304
|
console.log(this.langFromCode, this.langToCode);
|
|
305
|
+
this._render();
|
|
260
306
|
if (this.langFromCode !== this.langToCode) {
|
|
261
307
|
this.translateActivePageContainerElements();
|
|
262
308
|
}
|
|
@@ -265,12 +311,32 @@ export class TranslatePlugin extends BookReaderPlugin {
|
|
|
265
311
|
handleToLangChange = async (e) => {
|
|
266
312
|
this.clearAllTranslations();
|
|
267
313
|
this.langToCode = e.detail.value;
|
|
314
|
+
this._render();
|
|
268
315
|
this.translateActivePageContainerElements();
|
|
269
316
|
}
|
|
270
317
|
|
|
318
|
+
handleToggleTranslation = async () => {
|
|
319
|
+
this.userToggleTranslate = !this.userToggleTranslate;
|
|
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();
|
|
327
|
+
if (!this.userToggleTranslate) {
|
|
328
|
+
this.clearAllTranslations();
|
|
329
|
+
this.br.trigger('translationDisabled', { });
|
|
330
|
+
this.textSelectionManager.detach();
|
|
331
|
+
} else {
|
|
332
|
+
this.br.trigger('translationEnabled', { });
|
|
333
|
+
this.translateActivePageContainerElements();
|
|
334
|
+
this.textSelectionManager.attach();
|
|
335
|
+
}
|
|
336
|
+
}
|
|
271
337
|
|
|
272
338
|
/**
|
|
273
|
-
* Update
|
|
339
|
+
* Update translation side menu
|
|
274
340
|
*/
|
|
275
341
|
_render() {
|
|
276
342
|
this.br.shell.menuProviders['translate'] = {
|
|
@@ -282,13 +348,22 @@ export class TranslatePlugin extends BookReaderPlugin {
|
|
|
282
348
|
this._panel = e.target;
|
|
283
349
|
this._panel.fromLanguages = this.translationManager.fromLanguages;
|
|
284
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;
|
|
285
355
|
}
|
|
286
356
|
}"
|
|
287
357
|
@langFromChanged="${this.handleFromLangChange}"
|
|
288
358
|
@langToChanged="${this.handleToLangChange}"
|
|
359
|
+
@toggleTranslation="${this.handleToggleTranslation}"
|
|
289
360
|
.fromLanguages="${this.translationManager.fromLanguages}"
|
|
290
361
|
.toLanguages="${this.translationManager.toLanguages}"
|
|
291
362
|
.disclaimerMessage="${this.options.panelDisclaimerText}"
|
|
363
|
+
.userTranslationActive=${this.userToggleTranslate}
|
|
364
|
+
.detectedFromLang=${this.langFromCode}
|
|
365
|
+
.detectedToLang=${this.langToCode}
|
|
366
|
+
.loadingModel=${this.loadingModel}
|
|
292
367
|
class="translate-panel"
|
|
293
368
|
/>`,
|
|
294
369
|
};
|
|
@@ -303,6 +378,10 @@ export class BrTranslatePanel extends LitElement {
|
|
|
303
378
|
@property({ type: Array }) toLanguages = []; // List of obj {code, name}
|
|
304
379
|
@property({ type: String }) prevSelectedLang = ''; // Tracks the previous selected language for the "To" dropdown
|
|
305
380
|
@property({ type: String }) disclaimerMessage = '';
|
|
381
|
+
@property({ type: Boolean }) userTranslationActive = false;
|
|
382
|
+
@property({ type: String }) detectedFromLang = '';
|
|
383
|
+
@property({ type: String }) detectedToLang = '';
|
|
384
|
+
@property({ type: Boolean }) loadingModel;
|
|
306
385
|
|
|
307
386
|
/** @override */
|
|
308
387
|
createRenderRoot() {
|
|
@@ -316,42 +395,60 @@ export class BrTranslatePanel extends LitElement {
|
|
|
316
395
|
}
|
|
317
396
|
|
|
318
397
|
render() {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
398
|
+
return html`<div class="app" style="margin-top: 5%;padding-right: 5px;">
|
|
399
|
+
<div
|
|
400
|
+
class="disclaimer"
|
|
401
|
+
id="disclaimerMessage"
|
|
402
|
+
style="background-color: rgba(255,255,255,0.1);padding: 10px;border-radius: 8px;font-size: 12px;margin-bottom: 10px;color: rgba(255,255,255, 0.9);"
|
|
403
|
+
>${this.disclaimerMessage}</div>
|
|
404
|
+
|
|
405
|
+
<div class="panel panel--to" style="padding: 0 10px;">
|
|
325
406
|
<label>
|
|
326
|
-
|
|
327
|
-
<select id="lang-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
407
|
+
<span style="font-size: 12px;color: #ccc;">Translate To</span>
|
|
408
|
+
<select id="lang-to" name="to" class="lang-select" style="display:block; width:100%;" @change="${this._onLangToChange}">
|
|
409
|
+
${sortBy(this.toLanguages, ((lang) => lang.name.toLowerCase()
|
|
410
|
+
)).map((lang) => {
|
|
411
|
+
return html`<option value="${lang.code}"
|
|
412
|
+
?selected=${lang.code == this.detectedToLang}
|
|
413
|
+
> ${lang.name ? lang.name : lang.code} </option>`;
|
|
414
|
+
})
|
|
415
|
+
}
|
|
331
416
|
</select>
|
|
332
417
|
</label>
|
|
333
418
|
</div>
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
419
|
+
|
|
420
|
+
<div class="panel panel--start" style="text-align: right;padding: 0 10px;/*! font-size: 18px; */margin-top: 10px;">
|
|
421
|
+
<button class="start-translation-brn" @click="${this._toggleTranslation}">
|
|
422
|
+
${this.userTranslationActive ? "Stop Translating" : "Translate"}
|
|
423
|
+
</button>
|
|
424
|
+
</div>
|
|
425
|
+
|
|
426
|
+
<div class="panel panel--from" style="font-size: 12px;color: #ccc;text-align: center;padding: 8px 10px;">
|
|
427
|
+
<details style="display: contents">
|
|
428
|
+
<summary style="text-decoration: underline white; cursor:pointer; display:inline-block">
|
|
429
|
+
<i>
|
|
430
|
+
Source: ${this._getLangName(this.detectedFromLang)} ${this.prevSelectedLang ? "" : "(detected)"}
|
|
431
|
+
</i> Change
|
|
432
|
+
</summary>
|
|
433
|
+
<select id="lang-from" name="from" class="lang-select" value=${this.detectedFromLang} @change="${this._onLangFromChange}" style="width:65%; margin-top: 3%; margin-bottom: 3%">
|
|
434
|
+
${sortBy(this.fromLanguages, ((lang) => lang.name.toLowerCase()
|
|
435
|
+
)).map((lang) => {
|
|
436
|
+
return html`<option value="${lang.code}"
|
|
437
|
+
?selected=${lang.code == this.detectedFromLang}
|
|
438
|
+
>${lang.name ? lang.name : lang.code} </option>`;
|
|
439
|
+
})
|
|
440
|
+
}
|
|
341
441
|
</select>
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
442
|
+
</details>
|
|
443
|
+
<div class="footer" id="status" style="margin-top:5%">
|
|
444
|
+
${this._statusWarning()}
|
|
445
|
+
</div>
|
|
446
|
+
|
|
447
|
+
<div class="lang-models-loading">
|
|
448
|
+
${this._languageModelStatus()}
|
|
348
449
|
</div>
|
|
349
|
-
<div class="footer" id="status"></div>
|
|
350
|
-
<br/>
|
|
351
|
-
<div class="disclaimer" id="disclaimerMessage"> ${this.disclaimerMessage} </div>
|
|
352
450
|
</div>`;
|
|
353
451
|
}
|
|
354
|
-
|
|
355
452
|
_onLangFromChange(event) {
|
|
356
453
|
const langFromChangedEvent = new CustomEvent('langFromChanged', {
|
|
357
454
|
detail: { value: event.target.value },
|
|
@@ -364,6 +461,8 @@ export class BrTranslatePanel extends LitElement {
|
|
|
364
461
|
if (this._getSelectedLang('to') !== this._getSelectedLang('from')) {
|
|
365
462
|
this.prevSelectedLang = this._getSelectedLang('from');
|
|
366
463
|
}
|
|
464
|
+
this.loadingModel = true;
|
|
465
|
+
this.detectedFromLang = event.target.value;
|
|
367
466
|
}
|
|
368
467
|
|
|
369
468
|
_onLangToChange(event) {
|
|
@@ -378,30 +477,12 @@ export class BrTranslatePanel extends LitElement {
|
|
|
378
477
|
if (this._getSelectedLang('from') !== event.target.value) {
|
|
379
478
|
this.prevSelectedLang = this._getSelectedLang('from');
|
|
380
479
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
_onPrevLangClick() {
|
|
384
|
-
const prevLang = this.prevSelectedLang;
|
|
385
|
-
if (prevLang == this._getSelectedLang('from')) {
|
|
386
|
-
console.log("_onPrevLangClick: will not change since prevLang is the same as the current 'To' language code");
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
this.prevSelectedLang = this._getSelectedLang('to'); // Update prevSelectedLang to current "To" value
|
|
390
|
-
const langToChangedEvent = new CustomEvent('langToChanged', {
|
|
391
|
-
detail: { value: prevLang },
|
|
392
|
-
bubbles: true,
|
|
393
|
-
composed: true,
|
|
394
|
-
});
|
|
395
|
-
this.dispatchEvent(langToChangedEvent);
|
|
396
|
-
|
|
397
|
-
// Update the "To" dropdown to the previous language
|
|
398
|
-
const toDropdown = this.querySelector('#lang-to');
|
|
399
|
-
if (toDropdown) {
|
|
400
|
-
toDropdown.value = prevLang;
|
|
401
|
-
}
|
|
480
|
+
this.loadingModel = true;
|
|
481
|
+
this.detectedToLang = event.target.value;
|
|
402
482
|
}
|
|
403
483
|
|
|
404
484
|
_getSelectedLang(type) {
|
|
485
|
+
/** @type {HTMLSelectElement} */
|
|
405
486
|
const dropdown = this.querySelector(`#lang-${type}`);
|
|
406
487
|
return dropdown ? dropdown.value : '';
|
|
407
488
|
}
|
|
@@ -410,5 +491,35 @@ export class BrTranslatePanel extends LitElement {
|
|
|
410
491
|
const lang = [...this.fromLanguages, ...this.toLanguages].find(lang => lang.code === code);
|
|
411
492
|
return lang ? lang.name : '';
|
|
412
493
|
}
|
|
413
|
-
}
|
|
414
494
|
|
|
495
|
+
_toggleTranslation(event) {
|
|
496
|
+
const toggleTranslateEvent = new CustomEvent('toggleTranslation', {
|
|
497
|
+
detail: {value: event.target.value},
|
|
498
|
+
bubbles: true,
|
|
499
|
+
composed:true,
|
|
500
|
+
});
|
|
501
|
+
this.userTranslationActive = !this.userTranslationActive;
|
|
502
|
+
this.dispatchEvent(toggleTranslateEvent);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// TODO: Hardcoded warning message for now but should add more statuses
|
|
506
|
+
_statusWarning() {
|
|
507
|
+
if (this.detectedFromLang == this.detectedToLang) {
|
|
508
|
+
return "Translate To language is the same as the Source language";
|
|
509
|
+
}
|
|
510
|
+
return "";
|
|
511
|
+
}
|
|
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
|
+
}
|
|
@@ -86,9 +86,8 @@ export default class AbstractTTSEngine {
|
|
|
86
86
|
|
|
87
87
|
this._chunkIterator = new PageChunkIterator(numLeafs, leafIndex, {
|
|
88
88
|
pageChunkUrl: this.opts.pageChunkUrl,
|
|
89
|
-
pageBufferSize:
|
|
89
|
+
pageBufferSize: 3,
|
|
90
90
|
});
|
|
91
|
-
|
|
92
91
|
this.step();
|
|
93
92
|
this.events.trigger('start');
|
|
94
93
|
}
|
|
@@ -205,8 +204,8 @@ export default class AbstractTTSEngine {
|
|
|
205
204
|
}
|
|
206
205
|
|
|
207
206
|
/** Convenience wrapper for {@see AbstractTTSEngine.getBestVoice} */
|
|
208
|
-
getBestVoice() {
|
|
209
|
-
return AbstractTTSEngine.getBestBookVoice(this.getVoices(), this.opts.bookLanguage);
|
|
207
|
+
getBestVoice(languageOverride) {
|
|
208
|
+
return AbstractTTSEngine.getBestBookVoice(this.getVoices(), languageOverride || this.opts.bookLanguage);
|
|
210
209
|
}
|
|
211
210
|
|
|
212
211
|
/**
|
|
@@ -23,15 +23,34 @@ export default class PageChunk {
|
|
|
23
23
|
* @return {Promise<PageChunk[]>}
|
|
24
24
|
*/
|
|
25
25
|
static async fetch(pageChunkUrl, leafIndex) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
if (window.br.plugins.translate?.translationManager.active) {
|
|
27
|
+
const translateLayers = await window.br.plugins.translate.getTranslateLayers(leafIndex);
|
|
28
|
+
const paragraphs = Array.from(translateLayers[0].childNodes);
|
|
29
|
+
|
|
30
|
+
const pageChunks = [];
|
|
31
|
+
for (const [idx, item] of paragraphs.entries()) {
|
|
32
|
+
// Should not read paragraphs w/ header or footer roles
|
|
33
|
+
if (!item.classList.contains('ocr-role-header-footer')) {
|
|
34
|
+
const translatedChunk = new PageChunk(leafIndex, idx, item.textContent, []);
|
|
35
|
+
pageChunks.push(translatedChunk);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (pageChunks.length === 0) {
|
|
39
|
+
const placeholder = new PageChunk(leafIndex, 0, "", []);
|
|
40
|
+
pageChunks.push(placeholder);
|
|
41
|
+
}
|
|
42
|
+
return pageChunks;
|
|
43
|
+
} else {
|
|
44
|
+
const chunks = await $.ajax({
|
|
45
|
+
type: 'GET',
|
|
46
|
+
url: applyVariables(pageChunkUrl, { pageIndex: leafIndex }),
|
|
47
|
+
cache: true,
|
|
48
|
+
xhrFields: {
|
|
49
|
+
withCredentials: window.br.protected,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
return PageChunk._fromTextWrapperResponse(leafIndex, chunks);
|
|
53
|
+
}
|
|
35
54
|
}
|
|
36
55
|
|
|
37
56
|
/**
|
|
@@ -30,7 +30,11 @@ export default class WebTTSEngine extends AbstractTTSEngine {
|
|
|
30
30
|
start(leafIndex, numLeafs) {
|
|
31
31
|
// Need to run in this function to capture user intent to start playing audio
|
|
32
32
|
if ('mediaSession' in navigator) {
|
|
33
|
-
|
|
33
|
+
/**
|
|
34
|
+
* According to https://developers.google.com/web/updates/2017/02/media-session#implementation_notes , it needs to be at least 5 seconds
|
|
35
|
+
* long to allow usage of the media sessions api
|
|
36
|
+
*/
|
|
37
|
+
const audio = new Audio(br.options.imagesBaseURL + '../silence.mp3');
|
|
34
38
|
audio.loop = true;
|
|
35
39
|
|
|
36
40
|
this.events.on('pause', () => audio.pause());
|
|
@@ -386,9 +390,3 @@ export class WebTTSSound {
|
|
|
386
390
|
}
|
|
387
391
|
}
|
|
388
392
|
}
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* According to https://developers.google.com/web/updates/2017/02/media-session#implementation_notes , it needs to be at least 5 seconds
|
|
392
|
-
* long to allow usage of the media sessions api
|
|
393
|
-
*/
|
|
394
|
-
const SILENCE_6S_MP3 = 'data:audio/mp3;base64,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';
|
|
@@ -165,13 +165,26 @@ export class TtsPlugin extends BookReaderPlugin {
|
|
|
165
165
|
|
|
166
166
|
const renderVoicesMenu = (voicesMenu) => {
|
|
167
167
|
voicesMenu.empty();
|
|
168
|
-
|
|
168
|
+
let bookLanguage = this.ttsEngine.opts.bookLanguage;
|
|
169
|
+
if (this.br.plugins.translate?.translationManager?.active) {
|
|
170
|
+
bookLanguage = this.br.plugins.translate.langFromCode;
|
|
171
|
+
}
|
|
172
|
+
|
|
169
173
|
const bookLanguages = this.ttsEngine.getVoices().filter(v => v.lang.startsWith(bookLanguage)).sort(voiceSortOrder);
|
|
170
174
|
const otherLanguages = this.ttsEngine.getVoices().filter(v => !v.lang.startsWith(bookLanguage)).sort(voiceSortOrder);
|
|
171
175
|
|
|
172
176
|
if (this.ttsEngine.getVoices().length > 1) {
|
|
173
|
-
|
|
174
|
-
|
|
177
|
+
if (this.br.plugins.translate?.translationManager?.active) {
|
|
178
|
+
// Separate out Other Languages when translation active, not sure if too much / unwieldy
|
|
179
|
+
const toLang = this.br.plugins.translate.langToCode;
|
|
180
|
+
const translatedLanguages = this.ttsEngine.getVoices().filter(v => v.lang.startsWith(toLang)).sort(voiceSortOrder);
|
|
181
|
+
voicesMenu.append($(`<optgroup label="Book Language, Translated From (${bookLanguage})"> ${renderVoiceOption(bookLanguages)} </optgroup>`));
|
|
182
|
+
voicesMenu.append($(`<optgroup label="Book Language, Translated To (${toLang})"> ${renderVoiceOption(translatedLanguages)} </optgroup>`));
|
|
183
|
+
voicesMenu.append($(`<optgroup label="Other Languages"> ${renderVoiceOption(otherLanguages.filter(v => !v.lang.startsWith(toLang)).sort(voiceSortOrder))}`));
|
|
184
|
+
} else {
|
|
185
|
+
voicesMenu.append($(`<optgroup label="Book Language (${bookLanguage})"> ${renderVoiceOption(bookLanguages)} </optgroup>`));
|
|
186
|
+
voicesMenu.append($(`<optgroup label="Other Languages"> ${renderVoiceOption(otherLanguages)} </optgroup>`));
|
|
187
|
+
}
|
|
175
188
|
|
|
176
189
|
voicesMenu.val(this.ttsEngine.voice.voiceURI);
|
|
177
190
|
voicesMenu.show();
|
|
@@ -185,6 +198,8 @@ export class TtsPlugin extends BookReaderPlugin {
|
|
|
185
198
|
voicesMenu.on("change", ev => this.ttsEngine.setVoice(voicesMenu.val()));
|
|
186
199
|
this.ttsEngine.events.on('pause resume start', () => this.updateState());
|
|
187
200
|
this.ttsEngine.events.on('voiceschanged', () => renderVoicesMenu(voicesMenu));
|
|
201
|
+
this.br.on('translationEnabled', () => renderVoicesMenu(voicesMenu));
|
|
202
|
+
this.br.on('translationDisabled', () => renderVoicesMenu(voicesMenu));
|
|
188
203
|
this.br.refs.$BRReadAloudToolbar.find('[name=play]').on("click", this.playPause.bind(this));
|
|
189
204
|
this.br.refs.$BRReadAloudToolbar.find('[name=advance]').on("click", this.jumpForward.bind(this));
|
|
190
205
|
this.br.refs.$BRReadAloudToolbar.find('[name=review]').on("click", this.jumpBackward.bind(this));
|
|
@@ -208,6 +223,12 @@ export class TtsPlugin extends BookReaderPlugin {
|
|
|
208
223
|
}
|
|
209
224
|
|
|
210
225
|
start(startTTSEngine = true) {
|
|
226
|
+
const checkVoice = this.br.plugins.translate?.translationManager?.active ? toISO6391(this.br.plugins.translate.langToCode) : "";
|
|
227
|
+
const bookVoice = this.ttsEngine.getBestVoice(checkVoice);
|
|
228
|
+
|
|
229
|
+
const voicesMenu = this.br.refs.$BRReadAloudToolbar.find('[name=playback-voice');
|
|
230
|
+
this.ttsEngine.setVoice(bookVoice.voiceURI);
|
|
231
|
+
voicesMenu.val(bookVoice.voiceURI);
|
|
211
232
|
if (this.br.constModeThumb == this.br.mode)
|
|
212
233
|
this.br.switchMode(this.br.constMode1up);
|
|
213
234
|
|
|
@@ -291,7 +312,22 @@ export class TtsPlugin extends BookReaderPlugin {
|
|
|
291
312
|
const pageIndex = chunk.leafIndex;
|
|
292
313
|
|
|
293
314
|
this.removeHilites();
|
|
294
|
-
|
|
315
|
+
if (this.br.plugins.translate?.translationManager.active) {
|
|
316
|
+
const pageContainers = this.br.getActivePageContainerElementsForIndex(pageIndex);
|
|
317
|
+
const paragraphIndex = chunk.chunkIndex;
|
|
318
|
+
pageContainers.forEach(container => {
|
|
319
|
+
const translateElement = container.querySelector('.BRtranslateLayer');
|
|
320
|
+
const containerChildren = Array.from(translateElement.childNodes);
|
|
321
|
+
const paragraphEle = containerChildren[paragraphIndex];
|
|
322
|
+
if (!paragraphEle) { return; }
|
|
323
|
+
const [pOffHeight, pOffTop, pOffWidth, pOffLeft] = [paragraphEle.offsetHeight, paragraphEle.offsetTop, paragraphEle.offsetWidth, paragraphEle.offsetLeft];
|
|
324
|
+
const boxes = {pageIndex: [
|
|
325
|
+
{l: pOffLeft, r: pOffLeft + pOffWidth, b: pOffTop + pOffHeight, t: pOffTop},
|
|
326
|
+
]};
|
|
327
|
+
renderBoxesInPageContainerLayer('ttsHiliteLayer', boxes.pageIndex, this.br.book.getPage(pageIndex), translateElement);
|
|
328
|
+
});
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
295
331
|
// group by index; currently only possible to have chunks on one page :/
|
|
296
332
|
this._ttsBoxesByIndex = {
|
|
297
333
|
[pageIndex]: chunk.lineRects.map(([l, b, r, t]) => ({l, r, b, t})),
|