@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.
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/bookreader",
3
- "version": "5.0.0-97",
3
+ "version": "5.0.0-99",
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.0",
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: null,
150
+ archiveAnalytics: {},
151
151
  /** @type {Partial<import('../plugins/plugin.autoplay.js').AutoplayPlugin['options'>]}*/
152
- autoplay: null,
152
+ autoplay: {},
153
153
  /** @type {Partial<import('../plugins/plugin.chapters.js').ChaptersPlugin['options']>} */
154
- chapters: null,
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: null,
158
+ iiif: {},
157
159
  /** @type {Partial<import('../plugins/plugin.resume.js').ResumePlugin['options']>} */
158
- resume: null,
160
+ resume: {},
159
161
  /** @type {Partial<import('../plugins/search/plugin.search.js').SearchPlugin['options']>} */
160
- search: null,
162
+ search: {},
161
163
  /** @type {Partial<import('../plugins/plugin.text_selection.js').TextSelectionPlugin['options']>} */
162
- textSelection: null,
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: null,
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 = jQuery.extend(true, {}, BookReader.defaultOptions, overrides, BookReader.optionOverrides);
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 [
@@ -961,7 +1005,8 @@ BookReader.prototype.zoom = function(direction) {
961
1005
  } else {
962
1006
  this.activeMode.zoom('out');
963
1007
  }
964
- 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);
965
1010
  return;
966
1011
  };
967
1012
 
@@ -1208,7 +1253,8 @@ BookReader.prototype.switchMode = function(
1208
1253
  const eventName = mode + 'PageViewSelected';
1209
1254
  this.trigger(BookReader.eventNames[eventName]);
1210
1255
 
1211
- 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);
1212
1258
  };
1213
1259
 
1214
1260
  BookReader.prototype.updateBrClasses = function() {
@@ -1280,7 +1326,8 @@ BookReader.prototype.enterFullscreen = async function(bindKeyboardControls = tru
1280
1326
  }
1281
1327
  this.jumpToIndex(currentIndex);
1282
1328
 
1283
- 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);
1284
1331
  // Add "?view=theater"
1285
1332
  this.trigger(BookReader.eventNames.fragmentChange);
1286
1333
  // trigger event here, so that animations,
@@ -1326,7 +1373,8 @@ BookReader.prototype.exitFullScreen = async function () {
1326
1373
  await this.activeMode.mode1UpLit.updateComplete;
1327
1374
  }
1328
1375
 
1329
- 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);
1330
1378
  // Remove "?view=theater"
1331
1379
  this.trigger(BookReader.eventNames.fragmentChange);
1332
1380
  this.refs.$br.removeClass('BRfullscreenAnimation');
Binary file
@@ -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
 
@@ -45,6 +45,7 @@
45
45
  // but they appear the same in the UI.
46
46
  .searchHiliteLayer, .ttsHiliteLayer {
47
47
  pointer-events: none;
48
+ z-index: 4;
48
49
 
49
50
  rect {
50
51
  // Note: Can't use fill-opacity ; safari inexplicably applies that to
@@ -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
  }
@@ -88,21 +103,20 @@
88
103
  // These are Microsoft Edge specific fixed to make some of the
89
104
  // browsers features work well. These are for the in-place
90
105
  // translation.
106
+ .BRtextLayer:has([_istranslated="1"], msreadoutspan) {
107
+ mix-blend-mode: normal;
108
+ }
109
+
91
110
  .BRwordElement, .BRspace {
92
111
  &[_istranslated="1"], &[_msttexthash] {
93
- background-color: #e4dccd;
94
112
  color: black;
95
113
  letter-spacing: unset !important;
96
- background: #ccbfa7;
97
114
  }
98
115
  }
99
116
 
100
- .BRlineElement font[_mstmutation="1"] {
101
- background: #ccbfa7;
102
- }
103
-
104
117
  .BRlineElement:has([_istranslated="1"], [_msttexthash]) {
105
- background-color: #e4dccd;
118
+ background: rgba(248, 237, 192, 0.8);
119
+ backdrop-filter: blur(8px);
106
120
  color: black;
107
121
  text-align: justify;
108
122
  width: inherit;
@@ -112,14 +126,13 @@
112
126
  }
113
127
 
114
128
  .BRlineElement[_msttexthash] {
115
- background: #ccbfa7;
116
129
  word-spacing: unset !important;
117
130
  }
118
131
 
119
132
  .BRtranslateLayer .BRparagraphElement {
120
133
  pointer-events: auto;
121
134
  overflow-y: auto;
122
- background: rgba(248, 237, 192, 0.5);
135
+ background: rgba(248, 237, 192, 0.8);
123
136
  backdrop-filter: blur(8px);
124
137
  color:black;
125
138
  line-height: 1em;
@@ -132,8 +145,7 @@
132
145
  }
133
146
 
134
147
  .BRtextLayer.showingTranslation {
135
- visibility: hidden;
136
- pointer-events: none;
148
+ display: none;
137
149
  }
138
150
 
139
151
  .BRtranslateLayer .BRparagraphElement.BRtranslateHidden {
@@ -1,9 +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';
6
+ import { toISO6391 } from './tts/utils.js';
7
+ import { TextSelectionManager } from '../util/TextSelectionManager.js';
7
8
  /** @typedef {import('../util/strings.js').StringWithVars} StringWithVars */
8
9
  /** @typedef {import('../BookReader/PageContainer.js').PageContainer} PageContainer */
9
10
 
@@ -19,7 +20,7 @@ export class TextSelectionPlugin extends BookReaderPlugin {
19
20
  singlePageDjvuXmlUrl: null,
20
21
  /** Whether to fetch the XML as a jsonp */
21
22
  jsonp: false,
22
- /** 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 */
23
24
  maxProtectedWords: 200,
24
25
  }
25
26
 
@@ -45,7 +46,7 @@ export class TextSelectionPlugin extends BookReaderPlugin {
45
46
  // now we do make that assumption.
46
47
  /** Whether the book is right-to-left */
47
48
  this.rtl = this.br.pageProgression === 'rl';
48
- this.selectionObserver = new SelectionObserver('.BRtextLayer', this._onSelectionChange);
49
+ this.textSelectionManager = new TextSelectionManager('.BRtextLayer', this.br, {selectionElement: ['.BRwordElement', '.BRspace']}, this.options.maxProtectedWords);
49
50
  }
50
51
 
51
52
  /** @override */
@@ -53,72 +54,9 @@ export class TextSelectionPlugin extends BookReaderPlugin {
53
54
  if (!this.options.enabled) return;
54
55
 
55
56
  this.loadData();
56
-
57
- this.selectionObserver.attach();
58
- new SelectionObserver('.BRtextLayer', (selectEvent) => {
59
- // Track how often selection is used
60
- if (selectEvent == 'started') {
61
- this.br.plugins.archiveAnalytics?.sendEvent('BookReader', 'SelectStart');
62
-
63
- // Set a class on the page to avoid hiding it when zooming/etc
64
- this.br.refs.$br.find('.BRpagecontainer--hasSelection').removeClass('BRpagecontainer--hasSelection');
65
- $(window.getSelection().anchorNode).closest('.BRpagecontainer').addClass('BRpagecontainer--hasSelection');
66
- }
67
- }).attach();
68
-
69
- if (this.br.protected) {
70
- document.addEventListener('selectionchange', this._limitSelection);
71
- // Prevent right clicking when selected text
72
- $(document.body).on('contextmenu dragstart copy', (e) => {
73
- const selection = document.getSelection();
74
- if (selection?.toString()) {
75
- const intersectsTextLayer = $('.BRtextLayer')
76
- .toArray()
77
- .some(el => selection.containsNode(el, true));
78
- if (intersectsTextLayer) {
79
- e.preventDefault();
80
- return false;
81
- }
82
- }
83
- });
84
- }
57
+ this.textSelectionManager.init();
85
58
  }
86
59
 
87
- _limitSelection = () => {
88
- const selection = window.getSelection();
89
- if (!selection.rangeCount) return;
90
-
91
- const range = selection.getRangeAt(0);
92
-
93
- // Check if range.startContainer is inside the sub-tree of .BRContainer
94
- const startInBr = !!range.startContainer.parentElement.closest('.BRcontainer');
95
- const endInBr = !!range.endContainer.parentElement.closest('.BRcontainer');
96
- if (!startInBr && !endInBr) return;
97
- if (!startInBr || !endInBr) {
98
- // weird case, just clear the selection
99
- selection.removeAllRanges();
100
- return;
101
- }
102
-
103
- // Find the last allowed word in the selection
104
- const lastAllowedWord = genAt(
105
- genFilter(
106
- walkBetweenNodes(range.startContainer, range.endContainer),
107
- (node) => node.classList?.contains('BRwordElement'),
108
- ),
109
- this.options.maxProtectedWords - 1,
110
- );
111
-
112
- if (!lastAllowedWord || range.endContainer.parentNode == lastAllowedWord) return;
113
-
114
- const newRange = document.createRange();
115
- newRange.setStart(range.startContainer, range.startOffset);
116
- newRange.setEnd(lastAllowedWord.firstChild, lastAllowedWord.textContent.length);
117
-
118
- selection.removeAllRanges();
119
- selection.addRange(newRange);
120
- };
121
-
122
60
  /**
123
61
  * @override
124
62
  * @param {PageContainer} pageContainer
@@ -133,20 +71,6 @@ export class TextSelectionPlugin extends BookReaderPlugin {
133
71
  return pageContainer;
134
72
  }
135
73
 
136
- /**
137
- * @param {'started' | 'cleared'} type
138
- * @param {HTMLElement} target
139
- */
140
- _onSelectionChange = (type, target) => {
141
- if (type === 'started') {
142
- this.textSelectingMode(target);
143
- } else if (type === 'cleared') {
144
- this.defaultMode(target);
145
- } else {
146
- throw new Error(`Unknown type ${type}`);
147
- }
148
- }
149
-
150
74
  loadData() {
151
75
  // Only fetch the full djvu xml if the single page url isn't there
152
76
  if (this.options.singlePageDjvuXmlUrl) return;
@@ -203,92 +127,6 @@ export class TextSelectionPlugin extends BookReaderPlugin {
203
127
  }
204
128
  }
205
129
 
206
- /**
207
- * Intercept copied text to remove any styling applied to it
208
- * @param {JQuery} $container
209
- */
210
- interceptCopy($container) {
211
- $container[0].addEventListener('copy', (event) => {
212
- const selection = document.getSelection();
213
- event.clipboardData.setData('text/plain', selection.toString());
214
- event.preventDefault();
215
- });
216
- }
217
-
218
- /**
219
- * Applies mouse events when in default mode
220
- * @param {HTMLElement} textLayer
221
- */
222
- defaultMode(textLayer) {
223
- const $pageContainer = $(textLayer).closest('.BRpagecontainer');
224
- textLayer.style.pointerEvents = "none";
225
- $pageContainer.find("img").css("pointer-events", "auto");
226
-
227
- $(textLayer).off(".textSelectPluginHandler");
228
- const startedMouseDown = this.mouseIsDown;
229
- let skipNextMouseup = this.mouseIsDown;
230
- if (startedMouseDown) {
231
- textLayer.style.pointerEvents = "auto";
232
- }
233
-
234
- // Need to stop propagation to prevent DragScrollable from
235
- // blocking selection
236
- $(textLayer).on("mousedown.textSelectPluginHandler", (event) => {
237
- this.mouseIsDown = true;
238
- if ($(event.target).is(".BRwordElement, .BRspace")) {
239
- event.stopPropagation();
240
- }
241
- });
242
-
243
- $(textLayer).on("mouseup.textSelectPluginHandler", (event) => {
244
- this.mouseIsDown = false;
245
- textLayer.style.pointerEvents = "none";
246
- if (skipNextMouseup) {
247
- skipNextMouseup = false;
248
- event.stopPropagation();
249
- }
250
- });
251
- }
252
-
253
- /**
254
- * This mode is active while there is a selection on the given textLayer
255
- * @param {HTMLElement} textLayer
256
- */
257
- textSelectingMode(textLayer) {
258
- const $pageContainer = $(textLayer).closest('.BRpagecontainer');
259
- // Make text layer consume all events
260
- textLayer.style.pointerEvents = "all";
261
- // Block img from getting long-press to save while selecting
262
- $pageContainer.find("img").css("pointer-events", "none");
263
-
264
- $(textLayer).off(".textSelectPluginHandler");
265
-
266
- $(textLayer).on('mousedown.textSelectPluginHandler', (event) => {
267
- this.mouseIsDown = true;
268
- event.stopPropagation();
269
- });
270
-
271
- // Prevent page flip on click
272
- $(textLayer).on('mouseup.textSelectPluginHandler', (event) => {
273
- this.mouseIsDown = false;
274
- event.stopPropagation();
275
- });
276
- }
277
-
278
- /**
279
- * Initializes text selection modes if there is a text layer on the page
280
- * @param {JQuery} $container
281
- */
282
- stopPageFlip($container) {
283
- /** @type {JQuery<HTMLElement>} */
284
- const $textLayer = $container.find('.BRtextLayer');
285
- if (!$textLayer.length) return;
286
- $textLayer.each((i, s) => this.defaultMode(s));
287
- if (!this.br.protected) {
288
- this.interceptCopy($container);
289
- }
290
- }
291
-
292
130
  /**
293
131
  * @param {PageContainer} pageContainer
294
132
  */
@@ -314,6 +152,10 @@ export class TextSelectionPlugin extends BookReaderPlugin {
314
152
  const ratioW = parseFloat(pageContainer.$container[0].style.width) / pageContainer.page.width;
315
153
  const ratioH = parseFloat(pageContainer.$container[0].style.height) / pageContainer.page.height;
316
154
  textLayer.style.transform = `scale(${ratioW}, ${ratioH})`;
155
+ const bookLangCode = toISO6391(this.br.options.bookLanguage);
156
+ if (bookLangCode) {
157
+ textLayer.setAttribute("lang", bookLangCode);
158
+ }
317
159
  textLayer.setAttribute("dir", this.rtl ? "rtl" : "ltr");
318
160
 
319
161
  const ocrParagraphs = $(XMLpage).find("PARAGRAPH[coords]").toArray();
@@ -339,7 +181,7 @@ export class TextSelectionPlugin extends BookReaderPlugin {
339
181
  textLayer.appendChild(paragEl);
340
182
  }
341
183
  $container.append(textLayer);
342
- this.stopPageFlip($container);
184
+ this.textSelectionManager.stopPageFlip($container);
343
185
  this.br.trigger('textLayerRendered', {
344
186
  pageIndex,
345
187
  pageContainer,
@@ -353,6 +195,10 @@ export class TextSelectionPlugin extends BookReaderPlugin {
353
195
  renderParagraph(ocrParagraph) {
354
196
  const paragEl = document.createElement('p');
355
197
  paragEl.classList.add('BRparagraphElement');
198
+ if (ocrParagraph.getAttribute("x-role")) {
199
+ paragEl.classList.add('ocr-role-header-footer');
200
+ paragEl.ariaHidden = "true";
201
+ }
356
202
  const [paragLeft, paragBottom, paragRight, paragTop] = $(ocrParagraph).attr("coords").split(",").map(parseFloat);
357
203
  const wordHeightArr = [];
358
204
  const lines = $(ocrParagraph).find("LINE[coords]").toArray();
@@ -1,27 +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
-
5
- export const langs = /** @type {{[lang: string]: string}} */ {
6
- "bg": "Bulgarian",
7
- "ca": "Catalan",
8
- "cs": "Czech",
9
- "nl": "Dutch",
10
- "en": "English",
11
- "et": "Estonian",
12
- "de": "German",
13
- "fr": "French",
14
- "is": "Icelandic",
15
- "it": "Italian",
16
- "nb": "Norwegian Bokmål",
17
- "nn": "Norwegian Nynorsk",
18
- "fa": "Persian",
19
- "pl": "Polish",
20
- "pt": "Portuguese",
21
- "ru": "Russian",
22
- "es": "Spanish",
23
- "uk": "Ukrainian",
24
- };
4
+ import { toISO6391 } from '../tts/utils.js';
25
5
 
26
6
  export class TranslationManager {
27
7
  /** @type {Cache<{index: string, response: string}>} */
@@ -68,7 +48,7 @@ export class TranslationManager {
68
48
  this._initResolve = resolve;
69
49
  this._initReject = reject;
70
50
  });
71
- const registryUrl = "https://cors.archive.org/cors/mozilla-translate-models/";
51
+ const registryUrl = "https://cors.archive.org/cors/mozilla-translate-models/firefox_models/";
72
52
  const registryJson = await fetch(registryUrl + "registry.json").then(r => r.json());
73
53
  for (const language of Object.values(registryJson)) {
74
54
  for (const file of Object.values(language)) {
@@ -97,10 +77,10 @@ export class TranslationManager {
97
77
  // List of dev models found here https://github.com/mozilla/firefox-translations-models/tree/main/models/base
98
78
  // 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
79
  if (firstLang !== "en") {
100
- this.fromLanguages.push({code: firstLang, name:langs[firstLang], type: "prod"});
80
+ this.fromLanguages.push({code: firstLang, name: toISO6391(firstLang, true), type: "prod"});
101
81
  }
102
82
  if (secondLang !== "en") {
103
- this.toLanguages.push({code: secondLang, name:langs[secondLang], type: "prod"});
83
+ this.toLanguages.push({code: secondLang, name: toISO6391(secondLang, true), type: "prod"});
104
84
  }
105
85
  }
106
86
  this._initResolve([this.modelRegistry]);
@@ -160,8 +140,23 @@ export class TranslationManager {
160
140
  }).then((resp) => {
161
141
  const response = resp;
162
142
  this.currentlyTranslating[key].resolve(response.target.text);
143
+ this.alreadyTranslated.add({index: key, response: response.target.text});
144
+ delete this.currentlyTranslating[key];
163
145
  });
164
146
 
165
147
  return promise;
166
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
+ }
167
162
  }