@internetarchive/bookreader 5.0.0-97 → 5.0.0-98
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 +8 -9
- package/BookReader/BookReader.js +1 -1
- package/BookReader/BookReader.js.map +1 -1
- package/BookReader/bergamot-translator-worker.js +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 +1 -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 +45 -1
- package/src/assets/silence.mp3 +0 -0
- package/src/css/_BRsearch.scss +1 -0
- package/src/css/_TextSelection.scss +7 -9
- package/src/plugins/plugin.text_selection.js +9 -0
- package/src/plugins/translate/TranslationManager.js +6 -3
- package/src/plugins/translate/plugin.translate.js +133 -58
- 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
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@internetarchive/bookreader",
|
|
3
|
-
"version": "5.0.0-
|
|
3
|
+
"version": "5.0.0-98",
|
|
4
4
|
"description": "The Internet Archive BookReader.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"homepage": "https://github.com/internetarchive/bookreader#readme",
|
|
32
32
|
"private": false,
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@internetarchive/bergamot-translator": "0.4.9-ia.
|
|
34
|
+
"@internetarchive/bergamot-translator": "^0.4.9-ia.1",
|
|
35
35
|
"@internetarchive/ia-activity-indicator": "^0.0.4",
|
|
36
36
|
"@internetarchive/ia-item-navigator": "^2.1.2",
|
|
37
37
|
"@internetarchive/icon-bookmark": "^1.3.4",
|
|
@@ -147,21 +147,25 @@ export const DEFAULT_OPTIONS = {
|
|
|
147
147
|
**/
|
|
148
148
|
plugins: {
|
|
149
149
|
/** @type {Partial<import('../plugins/plugin.archive_analytics.js').ArchiveAnalyticsPlugin['options'>]}*/
|
|
150
|
-
archiveAnalytics:
|
|
150
|
+
archiveAnalytics: {},
|
|
151
151
|
/** @type {Partial<import('../plugins/plugin.autoplay.js').AutoplayPlugin['options'>]}*/
|
|
152
|
-
autoplay:
|
|
152
|
+
autoplay: {},
|
|
153
153
|
/** @type {Partial<import('../plugins/plugin.chapters.js').ChaptersPlugin['options']>} */
|
|
154
|
-
chapters:
|
|
154
|
+
chapters: {},
|
|
155
|
+
/** @type {Partial<import('../plugins/plugin.experiments.js').ExperimentsPlugin['options']>} */
|
|
156
|
+
experiments: {},
|
|
155
157
|
/** @type {Partial<import('../plugins/plugin.iiif.js').IiifPlugin['options']>} */
|
|
156
|
-
iiif:
|
|
158
|
+
iiif: {},
|
|
157
159
|
/** @type {Partial<import('../plugins/plugin.resume.js').ResumePlugin['options']>} */
|
|
158
|
-
resume:
|
|
160
|
+
resume: {},
|
|
159
161
|
/** @type {Partial<import('../plugins/search/plugin.search.js').SearchPlugin['options']>} */
|
|
160
|
-
search:
|
|
162
|
+
search: {},
|
|
161
163
|
/** @type {Partial<import('../plugins/plugin.text_selection.js').TextSelectionPlugin['options']>} */
|
|
162
|
-
textSelection:
|
|
164
|
+
textSelection: {},
|
|
165
|
+
/** @type {Partial<import('../plugins/translate/plugin.translate.js').TranslatePlugin['options']>} */
|
|
166
|
+
translate: {},
|
|
163
167
|
/** @type {Partial<import('../plugins/tts/plugin.tts.js').TtsPlugin['options']>} */
|
|
164
|
-
tts:
|
|
168
|
+
tts: {},
|
|
165
169
|
},
|
|
166
170
|
|
|
167
171
|
/**
|
package/src/BookReader.js
CHANGED
|
@@ -49,10 +49,47 @@ import { NAMED_REDUCE_SETS } from './BookReader/ReduceSet.js';
|
|
|
49
49
|
* @constructor
|
|
50
50
|
*/
|
|
51
51
|
export default function BookReader(overrides = {}) {
|
|
52
|
-
const options =
|
|
52
|
+
const options = BookReader.extendOptions({}, BookReader.defaultOptions, overrides, BookReader.optionOverrides);
|
|
53
53
|
this.setup(options);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Extend the default options for BookReader
|
|
58
|
+
* Accepts any number of option objects and merges them in order.
|
|
59
|
+
*
|
|
60
|
+
* Does a shallow merge of everything except plugin options. These are individually merged.
|
|
61
|
+
* @param {...Partial<BookReaderOptions>} newOptions
|
|
62
|
+
*/
|
|
63
|
+
BookReader.extendOptions = function(...newOptions) {
|
|
64
|
+
if (newOptions.length <= 1) {
|
|
65
|
+
return newOptions[0];
|
|
66
|
+
}
|
|
67
|
+
/** @type {Array<keyof BookReaderOptions>} */
|
|
68
|
+
const shallowOverrides = ['onePage', 'twoPage', 'vars'];
|
|
69
|
+
/** @type {Array<keyof BookReaderOptions>} */
|
|
70
|
+
const mapOverrides = ['plugins', 'controls'];
|
|
71
|
+
|
|
72
|
+
const result = newOptions.shift();
|
|
73
|
+
for (const opts of newOptions) {
|
|
74
|
+
for (const [key, value] of Object.entries(opts)) {
|
|
75
|
+
if (shallowOverrides.includes(key)) {
|
|
76
|
+
result[key] ||= {};
|
|
77
|
+
result[key] = Object.assign(result[key], value);
|
|
78
|
+
} else if (mapOverrides.includes(key)) {
|
|
79
|
+
result[key] ||= {};
|
|
80
|
+
for (const [name, mapValue] of Object.entries(value)) {
|
|
81
|
+
result[key][name] ||= {};
|
|
82
|
+
Object.assign(result[key][name], mapValue);
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
result[key] = value;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return result;
|
|
91
|
+
};
|
|
92
|
+
|
|
56
93
|
BookReader.version = PACKAGE_JSON.version;
|
|
57
94
|
|
|
58
95
|
// Mode constants
|
|
@@ -72,12 +109,16 @@ BookReader.PLUGINS = {
|
|
|
72
109
|
autoplay: null,
|
|
73
110
|
/** @type {typeof import('./plugins/plugin.chapters.js').ChaptersPlugin | null}*/
|
|
74
111
|
chapters: null,
|
|
112
|
+
/** @type {typeof import('./plugins/plugin.experiments.js').ExperimentsPlugin | null}*/
|
|
113
|
+
experiments: null,
|
|
75
114
|
/** @type {typeof import('./plugins/plugin.resume.js').ResumePlugin | null}*/
|
|
76
115
|
resume: null,
|
|
77
116
|
/** @type {typeof import('./plugins/search/plugin.search.js').SearchPlugin | null}*/
|
|
78
117
|
search: null,
|
|
79
118
|
/** @type {typeof import('./plugins/plugin.text_selection.js').TextSelectionPlugin | null}*/
|
|
80
119
|
textSelection: null,
|
|
120
|
+
/** @type {typeof import('./plugins/translate/plugin.translate.js').TranslatePlugin | null}*/
|
|
121
|
+
translate: null,
|
|
81
122
|
/** @type {typeof import('./plugins/tts/plugin.tts.js').TtsPlugin | null}*/
|
|
82
123
|
tts: null,
|
|
83
124
|
};
|
|
@@ -138,9 +179,11 @@ BookReader.prototype.setup = function(options) {
|
|
|
138
179
|
archiveAnalytics: BookReader.PLUGINS.archiveAnalytics ? new BookReader.PLUGINS.archiveAnalytics(this) : null,
|
|
139
180
|
autoplay: BookReader.PLUGINS.autoplay ? new BookReader.PLUGINS.autoplay(this) : null,
|
|
140
181
|
chapters: BookReader.PLUGINS.chapters ? new BookReader.PLUGINS.chapters(this) : null,
|
|
182
|
+
experiments: BookReader.PLUGINS.experiments ? new BookReader.PLUGINS.experiments(this) : null,
|
|
141
183
|
search: BookReader.PLUGINS.search ? new BookReader.PLUGINS.search(this) : null,
|
|
142
184
|
resume: BookReader.PLUGINS.resume ? new BookReader.PLUGINS.resume(this) : null,
|
|
143
185
|
textSelection: BookReader.PLUGINS.textSelection ? new BookReader.PLUGINS.textSelection(this) : null,
|
|
186
|
+
translate: BookReader.PLUGINS.translate ? new BookReader.PLUGINS.translate(this) : null,
|
|
144
187
|
tts: BookReader.PLUGINS.tts ? new BookReader.PLUGINS.tts(this) : null,
|
|
145
188
|
};
|
|
146
189
|
|
|
@@ -365,6 +408,7 @@ BookReader.prototype.getActivePageContainerElements = function() {
|
|
|
365
408
|
* Get the HTML Elements for the rendered page. Note there can be more than one, since
|
|
366
409
|
* (at least as of writing) different modes can maintain different caches.
|
|
367
410
|
* @param {PageIndex} pageIndex
|
|
411
|
+
* @returns {HTMLElement[]}
|
|
368
412
|
*/
|
|
369
413
|
BookReader.prototype.getActivePageContainerElementsForIndex = function(pageIndex) {
|
|
370
414
|
return [
|
|
Binary file
|
package/src/css/_BRsearch.scss
CHANGED
|
@@ -88,21 +88,20 @@
|
|
|
88
88
|
// These are Microsoft Edge specific fixed to make some of the
|
|
89
89
|
// browsers features work well. These are for the in-place
|
|
90
90
|
// translation.
|
|
91
|
+
.BRtextLayer:has([_istranslated="1"], msreadoutspan) {
|
|
92
|
+
mix-blend-mode: normal;
|
|
93
|
+
}
|
|
94
|
+
|
|
91
95
|
.BRwordElement, .BRspace {
|
|
92
96
|
&[_istranslated="1"], &[_msttexthash] {
|
|
93
|
-
background-color: #e4dccd;
|
|
94
97
|
color: black;
|
|
95
98
|
letter-spacing: unset !important;
|
|
96
|
-
background: #ccbfa7;
|
|
97
99
|
}
|
|
98
100
|
}
|
|
99
101
|
|
|
100
|
-
.BRlineElement font[_mstmutation="1"] {
|
|
101
|
-
background: #ccbfa7;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
102
|
.BRlineElement:has([_istranslated="1"], [_msttexthash]) {
|
|
105
|
-
background
|
|
103
|
+
background: rgba(248, 237, 192, 0.8);
|
|
104
|
+
backdrop-filter: blur(8px);
|
|
106
105
|
color: black;
|
|
107
106
|
text-align: justify;
|
|
108
107
|
width: inherit;
|
|
@@ -112,14 +111,13 @@
|
|
|
112
111
|
}
|
|
113
112
|
|
|
114
113
|
.BRlineElement[_msttexthash] {
|
|
115
|
-
background: #ccbfa7;
|
|
116
114
|
word-spacing: unset !important;
|
|
117
115
|
}
|
|
118
116
|
|
|
119
117
|
.BRtranslateLayer .BRparagraphElement {
|
|
120
118
|
pointer-events: auto;
|
|
121
119
|
overflow-y: auto;
|
|
122
|
-
background: rgba(248, 237, 192, 0.
|
|
120
|
+
background: rgba(248, 237, 192, 0.8);
|
|
123
121
|
backdrop-filter: blur(8px);
|
|
124
122
|
color:black;
|
|
125
123
|
line-height: 1em;
|
|
@@ -4,6 +4,7 @@ import { SelectionObserver } from '../BookReader/utils/SelectionObserver.js';
|
|
|
4
4
|
import { BookReaderPlugin } from '../BookReaderPlugin.js';
|
|
5
5
|
import { applyVariables } from '../util/strings.js';
|
|
6
6
|
import { Cache } from '../util/cache.js';
|
|
7
|
+
import { toISO6391 } from './tts/utils.js';
|
|
7
8
|
/** @typedef {import('../util/strings.js').StringWithVars} StringWithVars */
|
|
8
9
|
/** @typedef {import('../BookReader/PageContainer.js').PageContainer} PageContainer */
|
|
9
10
|
|
|
@@ -314,6 +315,10 @@ export class TextSelectionPlugin extends BookReaderPlugin {
|
|
|
314
315
|
const ratioW = parseFloat(pageContainer.$container[0].style.width) / pageContainer.page.width;
|
|
315
316
|
const ratioH = parseFloat(pageContainer.$container[0].style.height) / pageContainer.page.height;
|
|
316
317
|
textLayer.style.transform = `scale(${ratioW}, ${ratioH})`;
|
|
318
|
+
const bookLangCode = toISO6391(this.br.options.bookLanguage);
|
|
319
|
+
if (bookLangCode) {
|
|
320
|
+
textLayer.setAttribute("lang", bookLangCode);
|
|
321
|
+
}
|
|
317
322
|
textLayer.setAttribute("dir", this.rtl ? "rtl" : "ltr");
|
|
318
323
|
|
|
319
324
|
const ocrParagraphs = $(XMLpage).find("PARAGRAPH[coords]").toArray();
|
|
@@ -353,6 +358,10 @@ export class TextSelectionPlugin extends BookReaderPlugin {
|
|
|
353
358
|
renderParagraph(ocrParagraph) {
|
|
354
359
|
const paragEl = document.createElement('p');
|
|
355
360
|
paragEl.classList.add('BRparagraphElement');
|
|
361
|
+
if (ocrParagraph.getAttribute("x-role")) {
|
|
362
|
+
paragEl.classList.add('ocr-role-header-footer');
|
|
363
|
+
paragEl.ariaHidden = "true";
|
|
364
|
+
}
|
|
356
365
|
const [paragLeft, paragBottom, paragRight, paragTop] = $(ocrParagraph).attr("coords").split(",").map(parseFloat);
|
|
357
366
|
const wordHeightArr = [];
|
|
358
367
|
const lines = $(ocrParagraph).find("LINE[coords]").toArray();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import { Cache } from '../../util/cache.js';
|
|
3
3
|
import { BatchTranslator } from '@internetarchive/bergamot-translator/translator.js';
|
|
4
|
+
import { toISO6391 } from '../tts/utils.js';
|
|
4
5
|
|
|
5
6
|
export const langs = /** @type {{[lang: string]: string}} */ {
|
|
6
7
|
"bg": "Bulgarian",
|
|
@@ -68,7 +69,7 @@ export class TranslationManager {
|
|
|
68
69
|
this._initResolve = resolve;
|
|
69
70
|
this._initReject = reject;
|
|
70
71
|
});
|
|
71
|
-
const registryUrl = "https://cors.archive.org/cors/mozilla-translate-models/";
|
|
72
|
+
const registryUrl = "https://cors.archive.org/cors/mozilla-translate-models/firefox_models/";
|
|
72
73
|
const registryJson = await fetch(registryUrl + "registry.json").then(r => r.json());
|
|
73
74
|
for (const language of Object.values(registryJson)) {
|
|
74
75
|
for (const file of Object.values(language)) {
|
|
@@ -97,10 +98,10 @@ export class TranslationManager {
|
|
|
97
98
|
// List of dev models found here https://github.com/mozilla/firefox-translations-models/tree/main/models/base
|
|
98
99
|
// There are also differences between the model types in the repo above here: https://github.com/mozilla/firefox-translations-models?tab=readme-ov-file#firefox-translations-models
|
|
99
100
|
if (firstLang !== "en") {
|
|
100
|
-
this.fromLanguages.push({code: firstLang, name:
|
|
101
|
+
this.fromLanguages.push({code: firstLang, name: toISO6391(firstLang, true), type: "prod"});
|
|
101
102
|
}
|
|
102
103
|
if (secondLang !== "en") {
|
|
103
|
-
this.toLanguages.push({code: secondLang, name:
|
|
104
|
+
this.toLanguages.push({code: secondLang, name: toISO6391(secondLang, true), type: "prod"});
|
|
104
105
|
}
|
|
105
106
|
}
|
|
106
107
|
this._initResolve([this.modelRegistry]);
|
|
@@ -160,6 +161,8 @@ export class TranslationManager {
|
|
|
160
161
|
}).then((resp) => {
|
|
161
162
|
const response = resp;
|
|
162
163
|
this.currentlyTranslating[key].resolve(response.target.text);
|
|
164
|
+
this.alreadyTranslated.add({index: key, response: response.target.text});
|
|
165
|
+
delete this.currentlyTranslating[key];
|
|
163
166
|
});
|
|
164
167
|
|
|
165
168
|
return promise;
|
|
@@ -3,6 +3,8 @@ import { html, LitElement } from 'lit';
|
|
|
3
3
|
import { BookReaderPlugin } from '../../BookReaderPlugin.js';
|
|
4
4
|
import { customElement, property } from 'lit/decorators.js';
|
|
5
5
|
import { langs, TranslationManager } from "./TranslationManager.js";
|
|
6
|
+
import { toISO6391 } from '../tts/utils.js';
|
|
7
|
+
import { sortBy } from '../../../src/BookReader/utils.js';
|
|
6
8
|
|
|
7
9
|
// @ts-ignore
|
|
8
10
|
const BookReader = /** @type {typeof import('@/src/BookReader.js').default} */(window.BookReader);
|
|
@@ -11,6 +13,8 @@ export class TranslatePlugin extends BookReaderPlugin {
|
|
|
11
13
|
|
|
12
14
|
options = {
|
|
13
15
|
enabled: true,
|
|
16
|
+
|
|
17
|
+
/** @type {string | import('lit').TemplateResult} */
|
|
14
18
|
panelDisclaimerText: "Translations are in alpha",
|
|
15
19
|
}
|
|
16
20
|
|
|
@@ -27,10 +31,10 @@ export class TranslatePlugin extends BookReaderPlugin {
|
|
|
27
31
|
toLanguages = [];
|
|
28
32
|
|
|
29
33
|
/**
|
|
30
|
-
* Current language code that is being translated From
|
|
34
|
+
* Current language code that is being translated From. Defaults to EN currently
|
|
31
35
|
* @type {!string}
|
|
32
36
|
*/
|
|
33
|
-
langFromCode
|
|
37
|
+
langFromCode
|
|
34
38
|
|
|
35
39
|
/**
|
|
36
40
|
* Current language code that is being translated To
|
|
@@ -43,7 +47,16 @@ export class TranslatePlugin extends BookReaderPlugin {
|
|
|
43
47
|
*/
|
|
44
48
|
_panel;
|
|
45
49
|
|
|
50
|
+
/**
|
|
51
|
+
* @type {boolean} userToggleTranslate - Checks if user has initiated translation
|
|
52
|
+
* Should synchronize with the state of TranslationManager's active state
|
|
53
|
+
*/
|
|
54
|
+
userToggleTranslate;
|
|
55
|
+
|
|
46
56
|
async init() {
|
|
57
|
+
const currentLanguage = toISO6391(this.br.options.bookLanguage.replace(/[.,/#!$%^&*;:{}=\-_`~()]/g, ""));
|
|
58
|
+
this.langFromCode = currentLanguage ?? "en";
|
|
59
|
+
|
|
47
60
|
if (!this.options.enabled) {
|
|
48
61
|
return;
|
|
49
62
|
}
|
|
@@ -84,10 +97,8 @@ export class TranslatePlugin extends BookReaderPlugin {
|
|
|
84
97
|
await this.translationManager.initWorker();
|
|
85
98
|
// Note await above lets _render function properly, since it gives the browser
|
|
86
99
|
// time to render the rest of bookreader, which _render depends on
|
|
87
|
-
this._render();
|
|
88
|
-
|
|
89
100
|
this.langToCode = this.translationManager.toLanguages[0].code;
|
|
90
|
-
|
|
101
|
+
this._render();
|
|
91
102
|
}
|
|
92
103
|
|
|
93
104
|
/** @param {HTMLElement} page*/
|
|
@@ -114,7 +125,8 @@ export class TranslatePlugin extends BookReaderPlugin {
|
|
|
114
125
|
|
|
115
126
|
/** @param {HTMLElement} page */
|
|
116
127
|
async translateRenderedLayer(page, priority) {
|
|
117
|
-
if
|
|
128
|
+
// Do not run translation if in thumbnail mode or if user did not initiate transations
|
|
129
|
+
if (this.br.mode == this.br.constModeThumb || !this.userToggleTranslate || this.langFromCode == this.langToCode) {
|
|
118
130
|
return;
|
|
119
131
|
}
|
|
120
132
|
|
|
@@ -123,7 +135,7 @@ export class TranslatePlugin extends BookReaderPlugin {
|
|
|
123
135
|
let pageTranslationLayer;
|
|
124
136
|
if (!page.querySelector('.BRPageLayer.BRtranslateLayer')) {
|
|
125
137
|
pageTranslationLayer = document.createElement('div');
|
|
126
|
-
pageTranslationLayer.classList.add('BRPageLayer', 'BRtranslateLayer');
|
|
138
|
+
pageTranslationLayer.classList.add('BRPageLayer', 'BRtranslateLayer', 'BRtranslateLayerLoading');
|
|
127
139
|
pageTranslationLayer.setAttribute('lang', `${this.langToCode}`);
|
|
128
140
|
page.prepend(pageTranslationLayer);
|
|
129
141
|
} else {
|
|
@@ -141,7 +153,7 @@ export class TranslatePlugin extends BookReaderPlugin {
|
|
|
141
153
|
});
|
|
142
154
|
const paragraphs = this.getParagraphsOnPage(page);
|
|
143
155
|
|
|
144
|
-
paragraphs.
|
|
156
|
+
const paragraphTranslationPromises = paragraphs.map(async (paragraph, pidx) => {
|
|
145
157
|
let translatedParagraph = page.querySelector(`[data-translate-index='${pageIndex}-${pidx}']`);
|
|
146
158
|
if (!translatedParagraph) {
|
|
147
159
|
translatedParagraph = document.createElement('p');
|
|
@@ -149,6 +161,11 @@ export class TranslatePlugin extends BookReaderPlugin {
|
|
|
149
161
|
translatedParagraph.setAttribute('data-translate-index', `${pageIndex}-${pidx}`);
|
|
150
162
|
translatedParagraph.className = 'BRparagraphElement';
|
|
151
163
|
const originalParagraphStyle = paragraphs[pidx];
|
|
164
|
+
// check text selection paragraphs for header/footer roles
|
|
165
|
+
if (paragraph.classList.contains('ocr-role-header-footer')) {
|
|
166
|
+
translatedParagraph.ariaHidden = "true";
|
|
167
|
+
translatedParagraph.classList.add('ocr-role-header-footer');
|
|
168
|
+
}
|
|
152
169
|
const fontSize = `${parseInt($(originalParagraphStyle).css("font-size"))}px`;
|
|
153
170
|
|
|
154
171
|
$(translatedParagraph).css({
|
|
@@ -199,6 +216,32 @@ export class TranslatePlugin extends BookReaderPlugin {
|
|
|
199
216
|
}
|
|
200
217
|
}
|
|
201
218
|
});
|
|
219
|
+
await Promise.all(paragraphTranslationPromises);
|
|
220
|
+
this.br.trigger('translateLayerRendered', {
|
|
221
|
+
leafIndex: pageIndex,
|
|
222
|
+
translateLayer: pageTranslationLayer,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Get the translation layers for a specific leaf index.
|
|
228
|
+
* @param {number} leafIndex
|
|
229
|
+
* @returns {Promise<HTMLElement[]>}
|
|
230
|
+
*/
|
|
231
|
+
async getTranslateLayers(leafIndex) {
|
|
232
|
+
const pageContainerElements = this.br.getActivePageContainerElementsForIndex(leafIndex);
|
|
233
|
+
const translateLayer = $(pageContainerElements).filter(`[data-index='${leafIndex}']`).find('.BRtranslateLayer');
|
|
234
|
+
if (translateLayer.length) return translateLayer.toArray();
|
|
235
|
+
|
|
236
|
+
return new Promise((res, rej) => {
|
|
237
|
+
const handler = async (_, extraParams) => {
|
|
238
|
+
if (extraParams.leafIndex == leafIndex) {
|
|
239
|
+
this.br.off('translateLayerRendered', handler); // remember to detach translateLayer
|
|
240
|
+
res([extraParams.translateLayer]);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
this.br.on('translateLayerRendered', handler);
|
|
244
|
+
});
|
|
202
245
|
}
|
|
203
246
|
|
|
204
247
|
clearAllTranslations() {
|
|
@@ -268,9 +311,20 @@ export class TranslatePlugin extends BookReaderPlugin {
|
|
|
268
311
|
this.translateActivePageContainerElements();
|
|
269
312
|
}
|
|
270
313
|
|
|
314
|
+
handleToggleTranslation = async () => {
|
|
315
|
+
this.userToggleTranslate = !this.userToggleTranslate;
|
|
316
|
+
this.translationManager.active = this.userToggleTranslate;
|
|
317
|
+
if (!this.userToggleTranslate) {
|
|
318
|
+
this.clearAllTranslations();
|
|
319
|
+
this.br.trigger('translationDisabled', { });
|
|
320
|
+
} else {
|
|
321
|
+
this.br.trigger('translationEnabled', { });
|
|
322
|
+
this.translateActivePageContainerElements();
|
|
323
|
+
}
|
|
324
|
+
}
|
|
271
325
|
|
|
272
326
|
/**
|
|
273
|
-
* Update
|
|
327
|
+
* Update translation side menu
|
|
274
328
|
*/
|
|
275
329
|
_render() {
|
|
276
330
|
this.br.shell.menuProviders['translate'] = {
|
|
@@ -286,9 +340,13 @@ export class TranslatePlugin extends BookReaderPlugin {
|
|
|
286
340
|
}"
|
|
287
341
|
@langFromChanged="${this.handleFromLangChange}"
|
|
288
342
|
@langToChanged="${this.handleToLangChange}"
|
|
343
|
+
@toggleTranslation="${this.handleToggleTranslation}"
|
|
289
344
|
.fromLanguages="${this.translationManager.fromLanguages}"
|
|
290
345
|
.toLanguages="${this.translationManager.toLanguages}"
|
|
291
346
|
.disclaimerMessage="${this.options.panelDisclaimerText}"
|
|
347
|
+
.userTranslationActive=${false}
|
|
348
|
+
.detectedFromLang=${this.langFromCode}
|
|
349
|
+
.detectedToLang=${this.langToCode}
|
|
292
350
|
class="translate-panel"
|
|
293
351
|
/>`,
|
|
294
352
|
};
|
|
@@ -303,6 +361,9 @@ export class BrTranslatePanel extends LitElement {
|
|
|
303
361
|
@property({ type: Array }) toLanguages = []; // List of obj {code, name}
|
|
304
362
|
@property({ type: String }) prevSelectedLang = ''; // Tracks the previous selected language for the "To" dropdown
|
|
305
363
|
@property({ type: String }) disclaimerMessage = '';
|
|
364
|
+
@property({ type: Boolean }) userTranslationActive = false;
|
|
365
|
+
@property({ type: String }) detectedFromLang = '';
|
|
366
|
+
@property({ type: String }) detectedToLang = '';
|
|
306
367
|
|
|
307
368
|
/** @override */
|
|
308
369
|
createRenderRoot() {
|
|
@@ -316,42 +377,56 @@ export class BrTranslatePanel extends LitElement {
|
|
|
316
377
|
}
|
|
317
378
|
|
|
318
379
|
render() {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
380
|
+
return html`<div class="app" style="margin-top: 5%;padding-right: 5px;">
|
|
381
|
+
<div
|
|
382
|
+
class="disclaimer"
|
|
383
|
+
id="disclaimerMessage"
|
|
384
|
+
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);"
|
|
385
|
+
>${this.disclaimerMessage}</div>
|
|
386
|
+
|
|
387
|
+
<div class="panel panel--to" style="padding: 0 10px;">
|
|
325
388
|
<label>
|
|
326
|
-
|
|
327
|
-
<select id="lang-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
389
|
+
<span style="font-size: 12px;color: #ccc;">Translate To</span>
|
|
390
|
+
<select id="lang-to" name="to" class="lang-select" style="display:block; width:100%;" @change="${this._onLangToChange}">
|
|
391
|
+
${sortBy(this.toLanguages, ((lang) => lang.name.toLowerCase()
|
|
392
|
+
)).map((lang) => {
|
|
393
|
+
return html`<option value="${lang.code}"
|
|
394
|
+
?selected=${lang.code == this.detectedToLang}
|
|
395
|
+
> ${lang.name ? lang.name : lang.code} </option>`;
|
|
396
|
+
})
|
|
397
|
+
}
|
|
331
398
|
</select>
|
|
332
399
|
</label>
|
|
333
400
|
</div>
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
401
|
+
|
|
402
|
+
<div class="panel panel--start" style="text-align: right;padding: 0 10px;/*! font-size: 18px; */margin-top: 10px;">
|
|
403
|
+
<button class="start-translation-brn" @click="${this._toggleTranslation}">
|
|
404
|
+
${this.userTranslationActive ? "Stop Translating" : "Translate"}
|
|
405
|
+
</button>
|
|
406
|
+
</div>
|
|
407
|
+
|
|
408
|
+
<div class="panel panel--from" style="font-size: 12px;color: #ccc;text-align: center;padding: 8px 10px;">
|
|
409
|
+
<details style="display: contents">
|
|
410
|
+
<summary style="text-decoration: underline white; cursor:pointer; display:inline-block">
|
|
411
|
+
<i>
|
|
412
|
+
Source: ${this._getLangName(this.detectedFromLang)} ${this.prevSelectedLang ? "" : "(detected)"}
|
|
413
|
+
</i> Change
|
|
414
|
+
</summary>
|
|
415
|
+
<select id="lang-from" name="from" class="lang-select" value=${this.detectedFromLang} @change="${this._onLangFromChange}" style="width:65%; margin-top: 3%; margin-bottom: 3%">
|
|
416
|
+
${sortBy(this.fromLanguages, ((lang) => lang.name.toLowerCase()
|
|
417
|
+
)).map((lang) => {
|
|
418
|
+
return html`<option value="${lang.code}"
|
|
419
|
+
?selected=${lang.code == this.detectedFromLang}
|
|
420
|
+
>${lang.name ? lang.name : lang.code} </option>`;
|
|
421
|
+
})
|
|
422
|
+
}
|
|
341
423
|
</select>
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
${this._getLangName(this.prevSelectedLang)}
|
|
346
|
-
</button>`
|
|
347
|
-
: ''}
|
|
424
|
+
</details>
|
|
425
|
+
<div class="footer" id="status" style="margin-top:5%">
|
|
426
|
+
${this._statusWarning()}
|
|
348
427
|
</div>
|
|
349
|
-
<div class="footer" id="status"></div>
|
|
350
|
-
<br/>
|
|
351
|
-
<div class="disclaimer" id="disclaimerMessage"> ${this.disclaimerMessage} </div>
|
|
352
428
|
</div>`;
|
|
353
429
|
}
|
|
354
|
-
|
|
355
430
|
_onLangFromChange(event) {
|
|
356
431
|
const langFromChangedEvent = new CustomEvent('langFromChanged', {
|
|
357
432
|
detail: { value: event.target.value },
|
|
@@ -364,6 +439,7 @@ export class BrTranslatePanel extends LitElement {
|
|
|
364
439
|
if (this._getSelectedLang('to') !== this._getSelectedLang('from')) {
|
|
365
440
|
this.prevSelectedLang = this._getSelectedLang('from');
|
|
366
441
|
}
|
|
442
|
+
this.detectedFromLang = event.target.value;
|
|
367
443
|
}
|
|
368
444
|
|
|
369
445
|
_onLangToChange(event) {
|
|
@@ -378,30 +454,11 @@ export class BrTranslatePanel extends LitElement {
|
|
|
378
454
|
if (this._getSelectedLang('from') !== event.target.value) {
|
|
379
455
|
this.prevSelectedLang = this._getSelectedLang('from');
|
|
380
456
|
}
|
|
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
|
-
}
|
|
457
|
+
this.detectedToLang = event.target.value;
|
|
402
458
|
}
|
|
403
459
|
|
|
404
460
|
_getSelectedLang(type) {
|
|
461
|
+
/** @type {HTMLSelectElement} */
|
|
405
462
|
const dropdown = this.querySelector(`#lang-${type}`);
|
|
406
463
|
return dropdown ? dropdown.value : '';
|
|
407
464
|
}
|
|
@@ -410,5 +467,23 @@ export class BrTranslatePanel extends LitElement {
|
|
|
410
467
|
const lang = [...this.fromLanguages, ...this.toLanguages].find(lang => lang.code === code);
|
|
411
468
|
return lang ? lang.name : '';
|
|
412
469
|
}
|
|
470
|
+
|
|
471
|
+
_toggleTranslation(event) {
|
|
472
|
+
const toggleTranslateEvent = new CustomEvent('toggleTranslation', {
|
|
473
|
+
detail: {value: event.target.value},
|
|
474
|
+
bubbles: true,
|
|
475
|
+
composed:true,
|
|
476
|
+
});
|
|
477
|
+
this.userTranslationActive = !this.userTranslationActive;
|
|
478
|
+
this.dispatchEvent(toggleTranslateEvent);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// TODO: Hardcoded warning message for now but should add more statuses
|
|
482
|
+
_statusWarning() {
|
|
483
|
+
if (this.detectedFromLang == this.detectedToLang) {
|
|
484
|
+
return "Translate To language is the same as the Source language";
|
|
485
|
+
}
|
|
486
|
+
return "";
|
|
487
|
+
}
|
|
413
488
|
}
|
|
414
489
|
|
|
@@ -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
|
/**
|