From 5438ce9b9826fabcdc1638223869dfb17925bd0c Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Wed, 3 May 2017 20:05:53 -0500 Subject: [PATCH] Wraps mozL10n to async calls; splits firefox and generic l10n libs. --- examples/mobile-viewer/viewer.html | 4 +- examples/mobile-viewer/viewer.js | 58 ++++--- extensions/firefox/tools/l10n.js | 29 +--- external/webL10n/l10n.js | 4 +- gulpfile.js | 11 -- web/annotation_layer_builder.js | 13 +- web/app.js | 200 +++++++++++++--------- web/chromecom.js | 4 + web/firefoxcom.js | 26 +++ web/genericcom.js | 5 + web/genericl10n.js | 51 ++++++ web/interfaces.js | 32 +++- web/password_prompt.js | 19 +- web/pdf_document_properties.js | 36 ++-- web/pdf_find_bar.js | 15 +- web/pdf_page_view.js | 8 +- web/pdf_print_service.js | 21 ++- web/pdf_sidebar.js | 20 ++- web/pdf_thumbnail_view.js | 45 +++-- web/pdf_thumbnail_viewer.js | 5 +- web/pdf_viewer.component.js | 3 + web/pdf_viewer.js | 12 +- web/toolbar.js | 47 +++-- web/ui_utils.js | 48 ++++-- web/viewer-snippet-chrome-extension.html | 1 - web/viewer-snippet-firefox-extension.html | 1 - web/viewer-snippet.html | 1 - web/viewer.html | 4 - 28 files changed, 463 insertions(+), 260 deletions(-) create mode 100644 web/genericl10n.js diff --git a/examples/mobile-viewer/viewer.html b/examples/mobile-viewer/viewer.html index 9c2eee1258e9c..be6f675eb552f 100644 --- a/examples/mobile-viewer/viewer.html +++ b/examples/mobile-viewer/viewer.html @@ -26,8 +26,6 @@ - - @@ -72,5 +70,7 @@

+ + diff --git a/examples/mobile-viewer/viewer.js b/examples/mobile-viewer/viewer.js index 2c6cbb21f1fa1..5e734684a5da2 100644 --- a/examples/mobile-viewer/viewer.js +++ b/examples/mobile-viewer/viewer.js @@ -78,26 +78,28 @@ var PDFViewerApplication = { self.setTitleUsingMetadata(pdfDocument); }, function (exception) { var message = exception && exception.message; - var loadingErrorMessage = mozL10n.get('loading_error', null, - 'An error occurred while loading the PDF.'); + var l10n = self.l10n; + var loadingErrorMessage; if (exception instanceof PDFJS.InvalidPDFException) { // change error message also for other builds - loadingErrorMessage = mozL10n.get('invalid_file_error', null, + loadingErrorMessage = l10n.get('invalid_file_error', null, 'Invalid or corrupted PDF file.'); } else if (exception instanceof PDFJS.MissingPDFException) { // special message for missing PDFs - loadingErrorMessage = mozL10n.get('missing_file_error', null, + loadingErrorMessage = l10n.get('missing_file_error', null, 'Missing PDF file.'); } else if (exception instanceof PDFJS.UnexpectedResponseException) { - loadingErrorMessage = mozL10n.get('unexpected_response_error', null, + loadingErrorMessage = l10n.get('unexpected_response_error', null, 'Unexpected server response.'); + } else { + loadingErrorMessage = l10n.get('loading_error', null, + 'An error occurred while loading the PDF.'); } - var moreInfo = { - message: message - }; - self.error(loadingErrorMessage, moreInfo); + loadingErrorMessage.then(function (msg) { + self.error(msg, {message: message}); + }); self.loadingBar.hide(); }); }, @@ -186,28 +188,29 @@ var PDFViewerApplication = { }, error: function pdfViewError(message, moreInfo) { - var moreInfoText = mozL10n.get('error_version_info', + var l10n = this.l10n; + var moreInfoText = [l10n.get('error_version_info', {version: PDFJS.version || '?', build: PDFJS.build || '?'}, - 'PDF.js v{{version}} (build: {{build}})') + '\n'; + 'PDF.js v{{version}} (build: {{build}})')]; if (moreInfo) { - moreInfoText += - mozL10n.get('error_message', {message: moreInfo.message}, - 'Message: {{message}}'); + moreInfoText.push( + l10n.get('error_message', {message: moreInfo.message}, + 'Message: {{message}}')); if (moreInfo.stack) { - moreInfoText += '\n' + - mozL10n.get('error_stack', {stack: moreInfo.stack}, - 'Stack: {{stack}}'); + moreInfoText.push( + l10n.get('error_stack', {stack: moreInfo.stack}, + 'Stack: {{stack}}')); } else { if (moreInfo.filename) { - moreInfoText += '\n' + - mozL10n.get('error_file', {file: moreInfo.filename}, - 'File: {{file}}'); + moreInfoText.push( + l10n.get('error_file', {file: moreInfo.filename}, + 'File: {{file}}')); } if (moreInfo.lineNumber) { - moreInfoText += '\n' + - mozL10n.get('error_line', {line: moreInfo.lineNumber}, - 'Line: {{line}}'); + moreInfoText.push( + l10n.get('error_line', {line: moreInfo.lineNumber}, + 'Line: {{line}}')); } } } @@ -239,7 +242,9 @@ var PDFViewerApplication = { }; moreInfoButton.removeAttribute('hidden'); lessInfoButton.setAttribute('hidden', 'true'); - errorMoreInfo.value = moreInfoText; + Promise.all(moreInfoText).then(function (parts) { + errorMoreInfo.value = parts.join('\n'); + }); }, progress: function pdfViewProgress(level) { @@ -286,10 +291,13 @@ var PDFViewerApplication = { var linkService = new PDFJS.PDFLinkService(); this.pdfLinkService = linkService; + this.l10n = PDFJS.NullL10n; + var container = document.getElementById('viewerContainer'); var pdfViewer = new PDFJS.PDFViewer({ container: container, - linkService: linkService + linkService: linkService, + l10n: this.l10n, }); this.pdfViewer = pdfViewer; linkService.setViewer(pdfViewer); diff --git a/extensions/firefox/tools/l10n.js b/extensions/firefox/tools/l10n.js index 0659f0e8c1b5b..ddd5ac380f59d 100644 --- a/extensions/firefox/tools/l10n.js +++ b/extensions/firefox/tools/l10n.js @@ -94,27 +94,6 @@ } } - function translateDocument() { - gLanguage = gExternalLocalizerServices.getLocale(); - - translateFragment(); - - gReadyState = "complete"; - - // fire a 'localized' DOM event - var evtObject = document.createEvent("Event"); - evtObject.initEvent("localized", false, false); - evtObject.language = gLanguage; - window.dispatchEvent(evtObject); - } - - window.addEventListener("DOMContentLoaded", function() { - if (gExternalLocalizerServices) { - translateDocument(); - } - // ... else see setExternalLocalizerServices below - }); - // Public API document.mozL10n = { // get a localized string @@ -143,12 +122,8 @@ setExternalLocalizerServices(externalLocalizerServices) { gExternalLocalizerServices = externalLocalizerServices; - - // ... in case if we missed DOMContentLoaded above. - if (window.document.readyState === "interactive" || - window.document.readyState === "complete") { - translateDocument(); - } + gLanguage = gExternalLocalizerServices.getLocale(); + gReadyState = "complete"; }, // translate an element or document fragment diff --git a/external/webL10n/l10n.js b/external/webL10n/l10n.js index 2190c53850e00..19811cd2319ff 100644 --- a/external/webL10n/l10n.js +++ b/external/webL10n/l10n.js @@ -21,7 +21,8 @@ */ /* Additional modifications for PDF.js project: - - Disables language initialization on page loading; + - Disables language initialization on page loading. + - Disables document translation on page loading. - Removes consoleWarn and consoleLog and use console.log/warn directly. - Removes window._ assignment. - Remove compatibility code for OldIE. @@ -998,7 +999,6 @@ document.webL10n = (function(window, document, undefined) { loadLocale(lang, function() { if (callback) callback(); - translateFragment(); }); }, diff --git a/gulpfile.js b/gulpfile.js index 162dcc1b36050..64c722a2782c8 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -608,9 +608,6 @@ gulp.task('generic', ['buildnumber', 'locale'], function () { gulp.src(COMMON_WEB_FILES, {base: 'web/'}) .pipe(gulp.dest(GENERIC_DIR + 'web')), gulp.src('LICENSE').pipe(gulp.dest(GENERIC_DIR)), - gulp.src([ - 'external/webL10n/l10n.js' - ]).pipe(gulp.dest(GENERIC_DIR + 'web')), gulp.src([ 'web/locale/*/viewer.properties', 'web/locale/locale.properties' @@ -695,7 +692,6 @@ gulp.task('minified-pre', ['buildnumber', 'locale'], function () { gulp.task('minified-post', ['minified-pre'], function () { var viewerFiles = [ - 'external/webL10n/l10n.js', MINIFIED_DIR + BUILD_DIR + 'pdf.js', MINIFIED_DIR + '/web/viewer.js' ]; @@ -789,9 +785,6 @@ gulp.task('firefox-pre', ['buildnumber', 'locale'], function () { .pipe(gulp.dest(FIREFOX_BUILD_DIR)), gulp.src('LICENSE').pipe(gulp.dest(FIREFOX_BUILD_DIR)), - gulp.src(FIREFOX_EXTENSION_DIR + 'tools/l10n.js') - .pipe(gulp.dest(FIREFOX_BUILD_CONTENT_DIR + '/web')), - preprocessJS(FIREFOX_CONTENT_DIR + 'PdfStreamConverter.jsm', defines, true) .pipe(replace(/\bPDFJSSCRIPT_STREAM_CONVERTER_ID\b/g, FIREFOX_STREAM_CONVERTER_ID)) @@ -892,8 +885,6 @@ gulp.task('mozcentral-pre', ['buildnumber', 'locale'], function () { .pipe(replace(/\bPDFJSSCRIPT_COMMIT\b/g, commit)) .pipe(gulp.dest(MOZCENTRAL_EXTENSION_DIR)), gulp.src('LICENSE').pipe(gulp.dest(MOZCENTRAL_EXTENSION_DIR)), - gulp.src(FIREFOX_EXTENSION_DIR + 'tools/l10n.js') - .pipe(gulp.dest(MOZCENTRAL_CONTENT_DIR + '/web')), preprocessJS(FIREFOX_CONTENT_DIR + 'PdfJs.jsm', defines, true) .pipe(gulp.dest(MOZCENTRAL_CONTENT_DIR)), @@ -935,8 +926,6 @@ gulp.task('chromium-pre', ['buildnumber', 'locale'], function () { gulp.src(COMMON_WEB_FILES, {base: 'web/'}) .pipe(gulp.dest(CHROME_BUILD_CONTENT_DIR + 'web')), - gulp.src('external/webL10n/l10n.js') - .pipe(gulp.dest(CHROME_BUILD_CONTENT_DIR + 'web')), gulp.src([ 'web/locale/*/viewer.properties', 'web/locale/locale.properties' diff --git a/web/annotation_layer_builder.js b/web/annotation_layer_builder.js index 7086d283ed0fa..4c95711dbedc9 100644 --- a/web/annotation_layer_builder.js +++ b/web/annotation_layer_builder.js @@ -14,7 +14,7 @@ */ import { AnnotationLayer } from 'pdfjs-lib'; -import { mozL10n } from './ui_utils'; +import { NullL10n } from './ui_utils'; import { SimpleLinkService } from './pdf_link_service'; /** @@ -24,6 +24,7 @@ import { SimpleLinkService } from './pdf_link_service'; * @property {boolean} renderInteractiveForms * @property {IPDFLinkService} linkService * @property {DownloadManager} downloadManager + * @property {IL10n} l10n - Localization service. */ class AnnotationLayerBuilder { @@ -36,6 +37,7 @@ class AnnotationLayerBuilder { this.renderInteractiveForms = options.renderInteractiveForms; this.linkService = options.linkService; this.downloadManager = options.downloadManager; + this.l10n = options.l10n || NullL10n; this.div = null; } @@ -73,9 +75,7 @@ class AnnotationLayerBuilder { parameters.div = this.div; AnnotationLayer.render(parameters); - if (typeof mozL10n !== 'undefined') { - mozL10n.translate(this.div); - } + this.l10n.translate(this.div); } }); } @@ -96,15 +96,18 @@ class DefaultAnnotationLayerFactory { * @param {HTMLDivElement} pageDiv * @param {PDFPage} pdfPage * @param {boolean} renderInteractiveForms + * @param {IL10n} l10n * @returns {AnnotationLayerBuilder} */ createAnnotationLayerBuilder(pageDiv, pdfPage, - renderInteractiveForms = false) { + renderInteractiveForms = false, + l10n = NullL10n) { return new AnnotationLayerBuilder({ pageDiv, pdfPage, renderInteractiveForms, linkService: new SimpleLinkService(), + l10n, }); } } diff --git a/web/app.js b/web/app.js index 248d6e471f342..3397db70e9b73 100644 --- a/web/app.js +++ b/web/app.js @@ -15,8 +15,8 @@ /* globals PDFBug, Stats */ import { - animationStarted, DEFAULT_SCALE_VALUE, getPDFFileNameFromURL, localized, - MAX_SCALE, MIN_SCALE, mozL10n, noContextMenuHandler, normalizeWheelEventDelta, + animationStarted, DEFAULT_SCALE_VALUE, getPDFFileNameFromURL, MAX_SCALE, + MIN_SCALE, noContextMenuHandler, normalizeWheelEventDelta, parseQueryString, ProgressBar, RendererType, UNKNOWN_SCALE } from './ui_utils'; import { @@ -129,6 +129,8 @@ var PDFViewerApplication = { secondaryToolbar: null, /** @type {EventBus} */ eventBus: null, + /** @type {IL10n} */ + l10n: null, pageRotation: 0, isInitialViewSet: false, viewerPrefs: { @@ -155,6 +157,8 @@ var PDFViewerApplication = { this.appConfig = appConfig; return this._readPreferences().then(() => { + return this._initializeL10n(); + }).then(() => { return this._initializeViewerComponents(); }).then(() => { // Bind the various event handlers *after* the viewer has been @@ -162,13 +166,13 @@ var PDFViewerApplication = { this.bindEvents(); this.bindWindowEvents(); - if (typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) { - // For backwards compatibility, we dispatch the 'localized' event on - // the `eventBus` once the viewer has been initialized. - localized.then(() => { - this.eventBus.dispatch('localized'); - }); - } + // We can start UI localization now. + var appContainer = appConfig.appContainer || document.documentElement; + this.l10n.translate(appContainer).then(() => { + // Dispatch the 'localized' event on the `eventBus` once the viewer + // has been fully initialized and translated. + this.eventBus.dispatch('localized'); + }); if (this.isViewerEmbedded && !PDFJS.isExternalLinkTargetSet()) { // Prevent external links from "replacing" the viewer, @@ -256,6 +260,25 @@ var PDFViewerApplication = { ]).catch(function (reason) { }); }, + _initializeL10n() { + // Locale can be changed only when special debugging flags is present in + // the hash section of the URL, or development version of viewer is used. + // It is not possible to change locale for Firefox extension builds. + if (typeof PDFJSDev === 'undefined' || !PDFJSDev.test('PRODUCTION') || + (!PDFJSDev.test('FIREFOX || MOZCENTRAL') && + this.viewerPrefs['pdfBugEnabled'])) { + let hash = document.location.hash.substring(1); + let hashParams = parseQueryString(hash); + if ('locale' in hashParams) { + PDFJS.locale = hashParams['locale']; + } + } + this.l10n = this.externalServices.createL10n(); + return this.l10n.getDirection().then((dir) => { + document.getElementsByTagName('html')[0].dir = dir; + }); + }, + /** * @private */ @@ -290,6 +313,7 @@ var PDFViewerApplication = { linkService: pdfLinkService, downloadManager, renderer: this.viewerPrefs['renderer'], + l10n: this.l10n, enhanceTextSelection: this.viewerPrefs['enhanceTextSelection'], renderInteractiveForms: this.viewerPrefs['renderInteractiveForms'], enablePrintAutoRotate: this.viewerPrefs['enablePrintAutoRotate'], @@ -302,6 +326,7 @@ var PDFViewerApplication = { container: thumbnailContainer, renderingQueue: pdfRenderingQueue, linkService: pdfLinkService, + l10n: this.l10n }); pdfRenderingQueue.setThumbnailViewer(this.pdfThumbnailViewer); @@ -337,11 +362,11 @@ var PDFViewerApplication = { let findBarConfig = Object.create(appConfig.findBar); findBarConfig.findController = this.findController; findBarConfig.eventBus = eventBus; - this.findBar = new PDFFindBar(findBarConfig); + this.findBar = new PDFFindBar(findBarConfig, this.l10n); this.pdfDocumentProperties = new PDFDocumentProperties(appConfig.documentProperties, - this.overlayManager); + this.overlayManager, this.l10n); this.pdfCursorTools = new PDFCursorTools({ container, @@ -349,7 +374,8 @@ var PDFViewerApplication = { preferences: this.preferences, }); - this.toolbar = new Toolbar(appConfig.toolbar, container, eventBus); + this.toolbar = new Toolbar(appConfig.toolbar, container, eventBus, + this.l10n); this.secondaryToolbar = new SecondaryToolbar(appConfig.secondaryToolbar, container, eventBus); @@ -365,7 +391,7 @@ var PDFViewerApplication = { } this.passwordPrompt = new PasswordPrompt(appConfig.passwordOverlay, - this.overlayManager); + this.overlayManager, this.l10n); this.pdfOutlineViewer = new PDFOutlineViewer({ container: appConfig.sidebar.outlineView, @@ -385,7 +411,7 @@ var PDFViewerApplication = { sidebarConfig.pdfThumbnailViewer = this.pdfThumbnailViewer; sidebarConfig.pdfOutlineViewer = this.pdfOutlineViewer; sidebarConfig.eventBus = eventBus; - this.pdfSidebar = new PDFSidebar(sidebarConfig); + this.pdfSidebar = new PDFSidebar(sidebarConfig, this.l10n); this.pdfSidebar.onToggled = this.forceRendering.bind(this); resolve(undefined); @@ -507,8 +533,10 @@ var PDFViewerApplication = { PDFViewerApplication.open(file, args); }, onError(err) { - PDFViewerApplication.error(mozL10n.get('loading_error', null, - 'An error occurred while loading the PDF.'), err); + PDFViewerApplication.l10n.get('loading_error', null, + 'An error occurred while loading the PDF.').then((msg) => { + PDFViewerApplication.error(msg, err); + }); }, onProgress(loaded, total) { PDFViewerApplication.progress(loaded / total); @@ -659,24 +687,27 @@ var PDFViewerApplication = { this.load(pdfDocument, scale); }, (exception) => { let message = exception && exception.message; - let loadingErrorMessage = mozL10n.get('loading_error', null, - 'An error occurred while loading the PDF.'); - + let loadingErrorMessage; if (exception instanceof InvalidPDFException) { // change error message also for other builds - loadingErrorMessage = mozL10n.get('invalid_file_error', null, - 'Invalid or corrupted PDF file.'); + loadingErrorMessage = this.l10n.get('invalid_file_error', null, + 'Invalid or corrupted PDF file.'); } else if (exception instanceof MissingPDFException) { // special message for missing PDF's - loadingErrorMessage = mozL10n.get('missing_file_error', null, - 'Missing PDF file.'); + loadingErrorMessage = this.l10n.get('missing_file_error', null, + 'Missing PDF file.'); } else if (exception instanceof UnexpectedResponseException) { - loadingErrorMessage = mozL10n.get('unexpected_response_error', null, - 'Unexpected server response.'); + loadingErrorMessage = this.l10n.get('unexpected_response_error', null, + 'Unexpected server response.'); + } else { + loadingErrorMessage = this.l10n.get('loading_error', null, + 'An error occurred while loading the PDF.'); } - this.error(loadingErrorMessage, { message, }); - throw new Error(loadingErrorMessage); + return loadingErrorMessage.then((msg) => { + this.error(msg, { message, }); + throw new Error(msg); + }); }); }, @@ -744,27 +775,27 @@ var PDFViewerApplication = { * and optionally a 'stack' property. */ error: function pdfViewError(message, moreInfo) { - var moreInfoText = mozL10n.get('error_version_info', + let moreInfoText = [this.l10n.get('error_version_info', {version: version || '?', build: build || '?'}, - 'PDF.js v{{version}} (build: {{build}})') + '\n'; + 'PDF.js v{{version}} (build: {{build}})')]; if (moreInfo) { - moreInfoText += - mozL10n.get('error_message', {message: moreInfo.message}, - 'Message: {{message}}'); + moreInfoText.push( + this.l10n.get('error_message', {message: moreInfo.message}, + 'Message: {{message}}')); if (moreInfo.stack) { - moreInfoText += '\n' + - mozL10n.get('error_stack', {stack: moreInfo.stack}, - 'Stack: {{stack}}'); + moreInfoText.push( + this.l10n.get('error_stack', {stack: moreInfo.stack}, + 'Stack: {{stack}}')); } else { if (moreInfo.filename) { - moreInfoText += '\n' + - mozL10n.get('error_file', {file: moreInfo.filename}, - 'File: {{file}}'); + moreInfoText.push( + this.l10n.get('error_file', {file: moreInfo.filename}, + 'File: {{file}}')); } if (moreInfo.lineNumber) { - moreInfoText += '\n' + - mozL10n.get('error_line', {line: moreInfo.lineNumber}, - 'Line: {{line}}'); + moreInfoText.push( + this.l10n.get('error_line', {line: moreInfo.lineNumber}, + 'Line: {{line}}')); } } } @@ -802,7 +833,9 @@ var PDFViewerApplication = { closeButton.oncontextmenu = noContextMenuHandler; moreInfoButton.removeAttribute('hidden'); lessInfoButton.setAttribute('hidden', 'true'); - errorMoreInfo.value = moreInfoText; + Promise.all(moreInfoText).then((parts) => { + errorMoreInfo.value = parts.join('\n'); + }); } else { console.error(message + '\n' + moreInfoText); this.fallback(); @@ -1148,25 +1181,29 @@ var PDFViewerApplication = { } if (!this.supportsPrinting) { - var printMessage = mozL10n.get('printing_not_supported', null, - 'Warning: Printing is not fully supported by this browser.'); - this.error(printMessage); + this.l10n.get('printing_not_supported', null, + 'Warning: Printing is not fully supported by ' + + 'this browser.').then((printMessage) => { + this.error(printMessage); + }); return; } // The beforePrint is a sync method and we need to know layout before // returning from this method. Ensure that we can get sizes of the pages. if (!this.pdfViewer.pageViewsReady) { - var notReadyMessage = mozL10n.get('printing_not_ready', null, - 'Warning: The PDF is not fully loaded for printing.'); - window.alert(notReadyMessage); + this.l10n.get('printing_not_ready', null, + 'Warning: The PDF is not fully loaded for printing.'). + then((notReadyMessage) => { + window.alert(notReadyMessage); + }); return; } var pagesOverview = this.pdfViewer.getPagesOverview(); var printContainer = this.appConfig.printContainer; var printService = PDFPrintServiceFactory.instance.createPrintService( - this.pdfDocument, pagesOverview, printContainer); + this.pdfDocument, pagesOverview, printContainer, this.l10n); this.printService = printService; this.forceRendering(); @@ -1312,13 +1349,11 @@ if (typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) { } } catch (e) { var message = e && e.message; - var loadingErrorMessage = mozL10n.get('loading_error', null, - 'An error occurred while loading the PDF.'); - - var moreInfo = { - message, - }; - PDFViewerApplication.error(loadingErrorMessage, moreInfo); + PDFViewerApplication.l10n.get('loading_error', null, + 'An error occurred while loading the PDF.'). + then((loadingErrorMessage) => { + PDFViewerApplication.error(loadingErrorMessage, { message, }); + }); throw e; } }; @@ -1423,12 +1458,6 @@ function webViewerInitialized() { PDFJS.cMapPacked = false; } } - if (typeof PDFJSDev === 'undefined' || - !PDFJSDev.test('FIREFOX || MOZCENTRAL')) { - if ('locale' in hashParams) { - PDFJS.locale = hashParams['locale']; - } - } if ('textlayer' in hashParams) { switch (hashParams['textlayer']) { case 'off': @@ -1450,15 +1479,15 @@ function webViewerInitialized() { } } - if (typeof PDFJSDev === 'undefined' || - !PDFJSDev.test('FIREFOX || MOZCENTRAL')) { - mozL10n.setLanguage(PDFJS.locale); - } else { - if (!PDFViewerApplication.supportsDocumentFonts) { - PDFJS.disableFontFace = true; - console.warn(mozL10n.get('web_fonts_disabled', null, - 'Web fonts are disabled: unable to use embedded PDF fonts.')); - } + if (typeof PDFJSDev !== 'undefined' && + PDFJSDev.test('FIREFOX || MOZCENTRAL') && + !PDFViewerApplication.supportsDocumentFonts) { + PDFJS.disableFontFace = true; + PDFViewerApplication.l10n.get('web_fonts_disabled', null, + 'Web fonts are disabled: unable to use embedded PDF fonts.'). + then((msg) => { + console.warn(msg); + }); } if (!PDFViewerApplication.supportsPrinting) { @@ -1489,8 +1518,10 @@ function webViewerInitialized() { Promise.all(waitForBeforeOpening).then(function () { webViewerOpenFileViaURL(file); }).catch(function (reason) { - PDFViewerApplication.error(mozL10n.get('loading_error', null, - 'An error occurred while opening.'), reason); + PDFViewerApplication.l10n.get('loading_error', null, + 'An error occurred while opening.').then((msg) => { + PDFViewerApplication.error(msg, reason); + }); }); } @@ -1511,8 +1542,10 @@ if (typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) { xhr.responseType = 'arraybuffer'; xhr.send(); } catch (e) { - PDFViewerApplication.error(mozL10n.get('loading_error', null, - 'An error occurred while loading the PDF.'), e); + PDFViewerApplication.l10n.get('loading_error', null, + 'An error occurred while loading the PDF.').then((msg) => { + PDFViewerApplication.error(msg, e); + }); } return; } @@ -1563,8 +1596,10 @@ function webViewerPageRendered(e) { } if (pageView.error) { - PDFViewerApplication.error(mozL10n.get('rendering_error', null, - 'An error occurred while rendering the page.'), pageView.error); + PDFViewerApplication.l10n.get('rendering_error', null, + 'An error occurred while rendering the page.').then((msg) => { + PDFViewerApplication.error(msg, pageView.error); + }); } if (typeof PDFJSDev !== 'undefined' && @@ -1586,10 +1621,13 @@ function webViewerTextLayerRendered(e) { if (typeof PDFJSDev !== 'undefined' && PDFJSDev.test('FIREFOX || MOZCENTRAL') && e.numTextDivs > 0 && !PDFViewerApplication.supportsDocumentColors) { - console.error(mozL10n.get('document_colors_not_allowed', null, + PDFViewerApplication.l10n.get('document_colors_not_allowed', null, 'PDF documents are not allowed to use their own colors: ' + '\'Allow pages to choose their own colors\' ' + - 'is deactivated in the browser.')); + 'is deactivated in the browser.'). + then((msg) => { + console.error(msg); + }); PDFViewerApplication.fallback(); } } @@ -2194,10 +2232,6 @@ function webViewerKeyDown(evt) { } } -localized.then(function webViewerLocalized() { - document.getElementsByTagName('html')[0].dir = mozL10n.getDirection(); -}); - /* Abstract factory for the print service. */ var PDFPrintServiceFactory = { instance: { diff --git a/web/chromecom.js b/web/chromecom.js index 73bcea68b7095..95b6adf072d8d 100644 --- a/web/chromecom.js +++ b/web/chromecom.js @@ -17,6 +17,7 @@ import { DefaultExternalServices, PDFViewerApplication } from './app'; import { BasePreferences } from './preferences'; import { DownloadManager } from './download_manager'; +import { GenericL10n } from './genericl10n'; import { PDFJS } from 'pdfjs-lib'; if (typeof PDFJSDev === 'undefined' || !PDFJSDev.test('CHROME')) { @@ -350,6 +351,9 @@ ChromeExternalServices.createDownloadManager = function() { ChromeExternalServices.createPreferences = function() { return new ChromePreferences(); }; +ChromeExternalServices.createL10n = function () { + return new GenericL10n(navigator.language); +}; PDFViewerApplication.externalServices = ChromeExternalServices; export { diff --git a/web/firefoxcom.js b/web/firefoxcom.js index 7b3412eb918f9..0484f2c9c8010 100644 --- a/web/firefoxcom.js +++ b/web/firefoxcom.js @@ -13,6 +13,7 @@ * limitations under the License. */ +import '../extensions/firefox/tools/l10n'; import { createObjectURL, PDFDataRangeTransport, shadow } from 'pdfjs-lib'; import { BasePreferences } from './preferences'; import { PDFViewerApplication } from './app'; @@ -141,6 +142,25 @@ class FirefoxPreferences extends BasePreferences { } } +class MozL10n { + constructor(mozL10n) { + this.mozL10n = mozL10n; + } + + getDirection() { + return Promise.resolve(this.mozL10n.getDirection()); + } + + get(property, args, fallback) { + return Promise.resolve(this.mozL10n.get(property, args, fallback)); + } + + translate(element) { + this.mozL10n.translate(element); + return Promise.resolve(); + } +} + (function listenFindEvents() { var events = [ 'find', @@ -250,6 +270,12 @@ PDFViewerApplication.externalServices = { return new FirefoxPreferences(); }, + createL10n() { + var mozL10n = document.mozL10n; + // TODO refactor mozL10n.setExternalLocalizerServices + return new MozL10n(mozL10n); + }, + get supportsIntegratedFind() { var support = FirefoxCom.requestSync('supportsIntegratedFind'); return shadow(this, 'supportsIntegratedFind', support); diff --git a/web/genericcom.js b/web/genericcom.js index b1141c470f7ea..754f4f6e9bc63 100644 --- a/web/genericcom.js +++ b/web/genericcom.js @@ -16,6 +16,8 @@ import { DefaultExternalServices, PDFViewerApplication } from './app'; import { BasePreferences } from './preferences'; import { DownloadManager } from './download_manager'; +import { GenericL10n } from './genericl10n'; +import { PDFJS } from 'pdfjs-lib'; if (typeof PDFJSDev !== 'undefined' && !PDFJSDev.test('GENERIC')) { throw new Error('Module "pdfjs-web/genericcom" shall not be used outside ' + @@ -47,6 +49,9 @@ GenericExternalServices.createDownloadManager = function() { GenericExternalServices.createPreferences = function() { return new GenericPreferences(); }; +GenericExternalServices.createL10n = function () { + return new GenericL10n(PDFJS.locale); +}; PDFViewerApplication.externalServices = GenericExternalServices; export { diff --git a/web/genericl10n.js b/web/genericl10n.js new file mode 100644 index 0000000000000..cc3b7cc85d91c --- /dev/null +++ b/web/genericl10n.js @@ -0,0 +1,51 @@ +/* Copyright 2017 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '../external/webL10n/l10n'; + +var webL10n = document.webL10n; + +class GenericL10n { + constructor(lang) { + this._lang = lang; + this._ready = new Promise((resolve, reject) => { + webL10n.setLanguage(lang, () => { + resolve(webL10n); + }); + }); + } + + getDirection() { + return this._ready.then((l10n) => { + return l10n.getDirection(); + }); + } + + get(property, args, fallback) { + return this._ready.then((l10n) => { + return l10n.get(property, args, fallback); + }); + } + + translate(element) { + return this._ready.then((l10n) => { + return l10n.translate(element); + }); + } +} + +export { + GenericL10n, +}; diff --git a/web/interfaces.js b/web/interfaces.js index fe01288c7bc51..87aff84f994bc 100644 --- a/web/interfaces.js +++ b/web/interfaces.js @@ -118,9 +118,39 @@ class IPDFAnnotationLayerFactory { /** * @param {HTMLDivElement} pageDiv * @param {PDFPage} pdfPage + * @param {IL10n} l10n * @param {boolean} renderInteractiveForms * @returns {AnnotationLayerBuilder} */ createAnnotationLayerBuilder(pageDiv, pdfPage, - renderInteractiveForms = false) {} + renderInteractiveForms = false, + l10n = undefined) {} +} + +/** + * @interface + */ +class IL10n { + /** + * @returns {Promise} - Resolves to 'rtl' or 'ltr'. + */ + getDirection() {} + + /** + * Translates text identified by the key and adds/formats data using the args + * property bag. If the key was not found, translation falls back to the + * fallback text. + * @param {string} key + * @param {object} args + * @param {string} fallback + * @returns {Promise} + */ + get(key, args, fallback) { } + + /** + * Translates HTML element. + * @param {HTMLElement} element + * @returns {Promise} + */ + translate(element) { } } diff --git a/web/password_prompt.js b/web/password_prompt.js index 2e41abdbf9a5e..b0650e9632a61 100644 --- a/web/password_prompt.js +++ b/web/password_prompt.js @@ -13,7 +13,7 @@ * limitations under the License. */ -import { mozL10n } from './ui_utils'; +import { NullL10n } from './ui_utils'; import { PasswordResponses } from 'pdfjs-lib'; /** @@ -33,8 +33,9 @@ class PasswordPrompt { /** * @param {PasswordPromptOptions} options * @param {OverlayManager} overlayManager - Manager for the viewer overlays. + * @param {IL10n} l10n - Localization service. */ - constructor(options, overlayManager) { + constructor(options, overlayManager, l10n = NullL10n) { this.overlayName = options.overlayName; this.container = options.container; this.label = options.label; @@ -42,6 +43,7 @@ class PasswordPrompt { this.submitButton = options.submitButton; this.cancelButton = options.cancelButton; this.overlayManager = overlayManager; + this.l10n = l10n; this.updateCallback = null; this.reason = null; @@ -63,15 +65,18 @@ class PasswordPrompt { this.overlayManager.open(this.overlayName).then(() => { this.input.focus(); - var promptString = mozL10n.get('password_label', null, - 'Enter the password to open this PDF file.'); - + let promptString; if (this.reason === PasswordResponses.INCORRECT_PASSWORD) { - promptString = mozL10n.get('password_invalid', null, + promptString = this.l10n.get('password_invalid', null, 'Invalid password. Please try again.'); + } else { + promptString = this.l10n.get('password_label', null, + 'Enter the password to open this PDF file.'); } - this.label.textContent = promptString; + promptString.then((msg) => { + this.label.textContent = msg; + }); }); } diff --git a/web/pdf_document_properties.js b/web/pdf_document_properties.js index 970032bf9a20e..029994af760ef 100644 --- a/web/pdf_document_properties.js +++ b/web/pdf_document_properties.js @@ -13,7 +13,7 @@ * limitations under the License. */ -import { cloneObj, getPDFFileNameFromURL, mozL10n } from './ui_utils'; +import { cloneObj, getPDFFileNameFromURL, NullL10n } from './ui_utils'; import { createPromiseCapability } from 'pdfjs-lib'; const DEFAULT_FIELD_CONTENT = '-'; @@ -30,13 +30,15 @@ class PDFDocumentProperties { /** * @param {PDFDocumentPropertiesOptions} options * @param {OverlayManager} overlayManager - Manager for the viewer overlays. + * @param {IL10n} l10n - Localization service. */ constructor({ overlayName, fields, container, closeButton, }, - overlayManager) { + overlayManager, l10n = NullL10n) { this.overlayName = overlayName; this.fields = fields; this.container = container; this.overlayManager = overlayManager; + this.l10n = l10n; this._reset(); @@ -70,15 +72,23 @@ class PDFDocumentProperties { } // Get the document properties. this.pdfDocument.getMetadata().then(({ info, metadata, }) => { + return Promise.all([ + info, + metadata, + this._parseFileSize(this.maybeFileSize), + this._parseDate(info.CreationDate), + this._parseDate(info.ModDate) + ]); + }).then(([info, metadata, fileSize, creationDate, modificationDate]) => { freezeFieldData({ 'fileName': getPDFFileNameFromURL(this.url), - 'fileSize': this._parseFileSize(this.maybeFileSize), + 'fileSize': fileSize, 'title': info.Title, 'author': info.Author, 'subject': info.Subject, 'keywords': info.Keywords, - 'creationDate': this._parseDate(info.CreationDate), - 'modificationDate': this._parseDate(info.ModDate), + 'creationDate': creationDate, + 'modificationDate': modificationDate, 'creator': info.Creator, 'producer': info.Producer, 'version': info.PDFFormatVersion, @@ -90,8 +100,10 @@ class PDFDocumentProperties { // `this.setFileSize` wasn't called) or may be incorrectly set. return this.pdfDocument.getDownloadInfo(); }).then(({ length, }) => { + return this._parseFileSize(length); + }).then((fileSize) => { let data = cloneObj(this.fieldData); - data['fileSize'] = this._parseFileSize(length); + data['fileSize'] = fileSize; freezeFieldData(data); this._updateUI(); @@ -185,14 +197,14 @@ class PDFDocumentProperties { _parseFileSize(fileSize = 0) { let kb = fileSize / 1024; if (!kb) { - return; + return Promise.resolve(undefined); } else if (kb < 1024) { - return mozL10n.get('document_properties_kb', { + return this.l10n.get('document_properties_kb', { size_kb: (+kb.toPrecision(3)).toLocaleString(), size_b: fileSize.toLocaleString() }, '{{size_kb}} KB ({{size_b}} bytes)'); } - return mozL10n.get('document_properties_mb', { + return this.l10n.get('document_properties_mb', { size_mb: (+(kb / 1024).toPrecision(3)).toLocaleString(), size_b: fileSize.toLocaleString() }, '{{size_mb}} MB ({{size_b}} bytes)'); @@ -243,9 +255,9 @@ class PDFDocumentProperties { var date = new Date(Date.UTC(year, month, day, hours, minutes, seconds)); var dateString = date.toLocaleDateString(); var timeString = date.toLocaleTimeString(); - return mozL10n.get('document_properties_date_string', - { date: dateString, time: timeString }, - '{{date}}, {{time}}'); + return this.l10n.get('document_properties_date_string', + { date: dateString, time: timeString }, + '{{date}}, {{time}}'); } } diff --git a/web/pdf_find_bar.js b/web/pdf_find_bar.js index 812949d6525c1..c9908b807bfae 100644 --- a/web/pdf_find_bar.js +++ b/web/pdf_find_bar.js @@ -14,7 +14,7 @@ */ import { FindStates } from './pdf_find_controller'; -import { mozL10n } from './ui_utils'; +import { NullL10n } from './ui_utils'; /** * Creates a "search bar" given a set of DOM elements that act as controls @@ -23,7 +23,7 @@ import { mozL10n } from './ui_utils'; * is done by PDFFindController. */ class PDFFindBar { - constructor(options) { + constructor(options, l10n = NullL10n) { this.opened = false; this.bar = options.bar || null; @@ -38,6 +38,7 @@ class PDFFindBar { this.findNextButton = options.findNextButton || null; this.findController = options.findController || null; this.eventBus = options.eventBus; + this.l10n = l10n; if (this.findController === null) { throw new Error('PDFFindBar cannot be used without a ' + @@ -115,16 +116,16 @@ class PDFFindBar { break; case FindStates.FIND_NOTFOUND: - findMsg = mozL10n.get('find_not_found', null, 'Phrase not found'); + findMsg = this.l10n.get('find_not_found', null, 'Phrase not found'); notFound = true; break; case FindStates.FIND_WRAPPED: if (previous) { - findMsg = mozL10n.get('find_reached_top', null, + findMsg = this.l10n.get('find_reached_top', null, 'Reached top of document, continued from bottom'); } else { - findMsg = mozL10n.get('find_reached_bottom', null, + findMsg = this.l10n.get('find_reached_bottom', null, 'Reached end of document, continued from top'); } break; @@ -137,7 +138,9 @@ class PDFFindBar { } this.findField.setAttribute('data-status', status); - this.findMsg.textContent = findMsg; + Promise.resolve(findMsg).then((msg) => { + this.findMsg.textContent = msg; + }); this.updateResultsCount(matchCount); this._adjustWidth(); diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js index b5a2b0002f844..3e25b0a1183ba 100644 --- a/web/pdf_page_view.js +++ b/web/pdf_page_view.js @@ -14,8 +14,8 @@ */ import { - approximateFraction, CSS_UNITS, DEFAULT_SCALE, getOutputScale, RendererType, - roundToDivide + approximateFraction, CSS_UNITS, DEFAULT_SCALE, getOutputScale, NullL10n, + RendererType, roundToDivide } from './ui_utils'; import { createPromiseCapability, CustomStyle, PDFJS, RenderingCancelledException, @@ -41,6 +41,7 @@ const TEXT_LAYER_RENDER_DELAY = 200; // ms * @property {boolean} renderInteractiveForms - Turns on rendering of * interactive form elements. The default is `false`. * @property {string} renderer - 'canvas' or 'svg'. The default is 'canvas'. + * @property {IL10n} l10n - Localization service. */ /** @@ -71,6 +72,7 @@ class PDFPageView { this.textLayerFactory = options.textLayerFactory; this.annotationLayerFactory = options.annotationLayerFactory; this.renderer = options.renderer || RendererType.CANVAS; + this.l10n = options.l10n || NullL10n; this.paintTask = null; this.paintedViewportMap = new WeakMap(); @@ -467,7 +469,7 @@ class PDFPageView { if (!this.annotationLayer) { this.annotationLayer = this.annotationLayerFactory. createAnnotationLayerBuilder(div, pdfPage, - this.renderInteractiveForms); + this.renderInteractiveForms, this.l10n); } this.annotationLayer.render(this.viewport, 'display'); } diff --git a/web/pdf_print_service.js b/web/pdf_print_service.js index 9be414e004662..559d8be9bcd06 100644 --- a/web/pdf_print_service.js +++ b/web/pdf_print_service.js @@ -13,7 +13,7 @@ * limitations under the License. */ -import { CSS_UNITS, mozL10n } from './ui_utils'; +import { CSS_UNITS, NullL10n } from './ui_utils'; import { PDFPrintServiceFactory, PDFViewerApplication } from './app'; import { PDFJS } from 'pdfjs-lib'; @@ -57,10 +57,11 @@ function renderPage(activeServiceOnEntry, pdfDocument, pageNumber, size) { }); } -function PDFPrintService(pdfDocument, pagesOverview, printContainer) { +function PDFPrintService(pdfDocument, pagesOverview, printContainer, l10n) { this.pdfDocument = pdfDocument; this.pagesOverview = pagesOverview; this.printContainer = printContainer; + this.l10n = l10n || NullL10n; this.currentPage = -1; // The temporary canvas where renderPage paints one page at a time. this.scratchCanvas = document.createElement('canvas'); @@ -130,12 +131,12 @@ PDFPrintService.prototype = { var renderNextPage = (resolve, reject) => { this.throwIfInactive(); if (++this.currentPage >= pageCount) { - renderProgress(pageCount, pageCount); + renderProgress(pageCount, pageCount, this.l10n); resolve(); return; } var index = this.currentPage; - renderProgress(index, pageCount); + renderProgress(index, pageCount, this.l10n); renderPage(this, this.pdfDocument, index + 1, this.pagesOverview[index]) .then(this.useRenderedPage.bind(this)) .then(function () { @@ -255,14 +256,16 @@ function abort() { } } -function renderProgress(index, total) { +function renderProgress(index, total, l10n) { var progressContainer = document.getElementById('printServiceOverlay'); var progress = Math.round(100 * index / total); var progressBar = progressContainer.querySelector('progress'); var progressPerc = progressContainer.querySelector('.relative-progress'); progressBar.value = progress; - progressPerc.textContent = mozL10n.get('print_progress_percent', - { progress, }, progress + '%'); + l10n.get('print_progress_percent', { progress, }, progress + '%'). + then((msg) => { + progressPerc.textContent = msg; + }); } var hasAttachEvent = !!document.attachEvent; @@ -327,12 +330,12 @@ function ensureOverlay() { PDFPrintServiceFactory.instance = { supportsPrinting: true, - createPrintService(pdfDocument, pagesOverview, printContainer) { + createPrintService(pdfDocument, pagesOverview, printContainer, l10n) { if (activeService) { throw new Error('The print service is created and active.'); } activeService = new PDFPrintService(pdfDocument, pagesOverview, - printContainer); + printContainer, l10n); return activeService; } }; diff --git a/web/pdf_sidebar.js b/web/pdf_sidebar.js index ce6a96774a67b..9fac583887a0b 100644 --- a/web/pdf_sidebar.js +++ b/web/pdf_sidebar.js @@ -13,7 +13,7 @@ * limitations under the License. */ -import { mozL10n } from './ui_utils'; +import { NullL10n } from './ui_utils'; import { RenderingStates } from './pdf_rendering_queue'; const UI_NOTIFICATION_CLASS = 'pdfSidebarNotification'; @@ -56,8 +56,9 @@ const SidebarView = { class PDFSidebar { /** * @param {PDFSidebarOptions} options + * @param {IL10n} l10n - Localization service. */ - constructor(options) { + constructor(options, l10n = NullL10n) { this.isOpen = false; this.active = SidebarView.THUMBS; this.isInitialViewSet = false; @@ -87,6 +88,8 @@ class PDFSidebar { this.disableNotification = options.disableNotification || false; + this.l10n = l10n; + this._addEventListeners(); } @@ -310,8 +313,11 @@ class PDFSidebar { return; } - this.toggleButton.title = mozL10n.get('toggle_sidebar_notification.title', - null, 'Toggle Sidebar (document contains outline/attachments)'); + this.l10n.get('toggle_sidebar_notification.title', null, + 'Toggle Sidebar (document contains outline/attachments)'). + then((msg) => { + this.toggleButton.title = msg; + }); if (!this.isOpen) { // Only show the notification on the `toggleButton` if the sidebar is @@ -367,8 +373,10 @@ class PDFSidebar { removeNotification(SidebarView[view]); } - this.toggleButton.title = mozL10n.get('toggle_sidebar.title', null, - 'Toggle Sidebar'); + this.l10n.get('toggle_sidebar.title', null, 'Toggle Sidebar'). + then((msg) => { + this.toggleButton.title = msg; + }); } /** diff --git a/web/pdf_thumbnail_view.js b/web/pdf_thumbnail_view.js index 8230b2bb4961b..fb8ccc048b4b3 100644 --- a/web/pdf_thumbnail_view.js +++ b/web/pdf_thumbnail_view.js @@ -16,7 +16,7 @@ import { createPromiseCapability, RenderingCancelledException } from 'pdfjs-lib'; -import { getOutputScale, mozL10n } from './ui_utils'; +import { getOutputScale, NullL10n } from './ui_utils'; import { RenderingStates } from './pdf_rendering_queue'; var THUMBNAIL_WIDTH = 98; // px @@ -32,6 +32,7 @@ var THUMBNAIL_CANVAS_BORDER_WIDTH = 1; // px * @property {boolean} disableCanvasToImageConversion - (optional) Don't convert * the canvas thumbnails to images. This prevents `toDataURL` calls, * but increases the overall memory usage. The default value is false. + * @property {IL10n} l10n - Localization service. */ const TempImageFactory = (function TempImageFactoryClosure() { @@ -118,9 +119,14 @@ var PDFThumbnailView = (function PDFThumbnailViewClosure() { this.canvasHeight = (this.canvasWidth / this.pageRatio) | 0; this.scale = this.canvasWidth / this.pageWidth; + this.l10n = options.l10n || NullL10n; + var anchor = document.createElement('a'); anchor.href = linkService.getAnchorUrl('#page=' + id); - anchor.title = mozL10n.get('thumb_page_title', {page: id}, 'Page {{page}}'); + this.l10n.get('thumb_page_title', {page: id}, 'Page {{page}}'). + then((msg) => { + anchor.title = msg; + }); anchor.onclick = function stopNavigation() { linkService.page = id; return false; @@ -253,13 +259,14 @@ var PDFThumbnailView = (function PDFThumbnailViewClosure() { } var id = this.renderingId; var className = 'thumbnailImage'; - var ariaLabel = mozL10n.get('thumb_page_canvas', { page: this.pageId }, - 'Thumbnail of Page {{page}}'); if (this.disableCanvasToImageConversion) { this.canvas.id = id; this.canvas.className = className; - this.canvas.setAttribute('aria-label', ariaLabel); + this.l10n.get('thumb_page_canvas', { page: this.pageId }, + 'Thumbnail of Page {{page}}').then((msg) => { + this.canvas.setAttribute('aria-label', msg); + }); this.div.setAttribute('data-loaded', true); this.ring.appendChild(this.canvas); @@ -268,7 +275,11 @@ var PDFThumbnailView = (function PDFThumbnailViewClosure() { var image = document.createElement('img'); image.id = id; image.className = className; - image.setAttribute('aria-label', ariaLabel); + this.l10n.get('thumb_page_canvas', { page: this.pageId }, + 'Thumbnail of Page {{page}}'). + then((msg) => { + image.setAttribute('aria-label', msg); + }); image.style.width = this.canvasWidth + 'px'; image.style.height = this.canvasHeight + 'px'; @@ -409,19 +420,23 @@ var PDFThumbnailView = (function PDFThumbnailViewClosure() { setPageLabel: function PDFThumbnailView_setPageLabel(label) { this.pageLabel = (typeof label === 'string' ? label : null); - this.anchor.title = mozL10n.get('thumb_page_title', { page: this.pageId }, - 'Page {{page}}'); + this.l10n.get('thumb_page_title', { page: this.pageId }, 'Page {{page}}'). + then((msg) => { + this.anchor.title = msg; + }); if (this.renderingState !== RenderingStates.FINISHED) { return; } - var ariaLabel = mozL10n.get('thumb_page_canvas', { page: this.pageId }, - 'Thumbnail of Page {{page}}'); - if (this.image) { - this.image.setAttribute('aria-label', ariaLabel); - } else if (this.disableCanvasToImageConversion && this.canvas) { - this.canvas.setAttribute('aria-label', ariaLabel); - } + + this.l10n.get('thumb_page_canvas', { page: this.pageId }, + 'Thumbnail of Page {{page}}').then((ariaLabel) => { + if (this.image) { + this.image.setAttribute('aria-label', ariaLabel); + } else if (this.disableCanvasToImageConversion && this.canvas) { + this.canvas.setAttribute('aria-label', ariaLabel); + } + }); }, }; diff --git a/web/pdf_thumbnail_viewer.js b/web/pdf_thumbnail_viewer.js index fbc7f80e4f304..6839f7f3a8672 100644 --- a/web/pdf_thumbnail_viewer.js +++ b/web/pdf_thumbnail_viewer.js @@ -14,7 +14,7 @@ */ import { - getVisibleElements, scrollIntoView, watchScroll + getVisibleElements, NullL10n, scrollIntoView, watchScroll } from './ui_utils'; import { PDFThumbnailView } from './pdf_thumbnail_view'; @@ -26,6 +26,7 @@ var THUMBNAIL_SCROLL_MARGIN = -19; * elements. * @property {IPDFLinkService} linkService - The navigation/linking service. * @property {PDFRenderingQueue} renderingQueue - The rendering queue object. + * @property {IL10n} l10n - Localization service. */ /** @@ -42,6 +43,7 @@ var PDFThumbnailViewer = (function PDFThumbnailViewerClosure() { this.container = options.container; this.renderingQueue = options.renderingQueue; this.linkService = options.linkService; + this.l10n = options.l10n || NullL10n; this.scroll = watchScroll(this.container, this._scrollUpdated.bind(this)); this._resetView(); @@ -142,6 +144,7 @@ var PDFThumbnailViewer = (function PDFThumbnailViewerClosure() { linkService: this.linkService, renderingQueue: this.renderingQueue, disableCanvasToImageConversion: false, + l10n: this.l10n, }); this.thumbnails.push(thumbnail); } diff --git a/web/pdf_viewer.component.js b/web/pdf_viewer.component.js index 4fed1f884ef93..49d5a68286af5 100644 --- a/web/pdf_viewer.component.js +++ b/web/pdf_viewer.component.js @@ -25,6 +25,7 @@ var pdfjsWebPDFHistory = require('./pdf_history.js'); var pdfjsWebPDFFindController = require('./pdf_find_controller.js'); var pdfjsWebUIUtils = require('./ui_utils.js'); var pdfjsWebDownloadManager = require('./download_manager.js'); +var pdfjsWebGenericL10n = require('./genericl10n.js'); var PDFJS = pdfjsLib.PDFJS; @@ -44,5 +45,7 @@ PDFJS.EventBus = pdfjsWebUIUtils.EventBus; PDFJS.DownloadManager = pdfjsWebDownloadManager.DownloadManager; PDFJS.ProgressBar = pdfjsWebUIUtils.ProgressBar; +PDFJS.GenericL10n = pdfjsWebGenericL10n.GenericL10n; +PDFJS.NullL10n = pdfjsWebUIUtils.NullL10n; exports.PDFJS = PDFJS; diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index de0761c9fc2cd..d6cde0d181a8c 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -16,7 +16,7 @@ import { createPromiseCapability, PDFJS } from 'pdfjs-lib'; import { CSS_UNITS, DEFAULT_SCALE, DEFAULT_SCALE_VALUE, getVisibleElements, - MAX_AUTO_SCALE, RendererType, SCROLLBAR_PADDING, scrollIntoView, + MAX_AUTO_SCALE, NullL10n, RendererType, SCROLLBAR_PADDING, scrollIntoView, UNKNOWN_SCALE, VERTICAL_PADDING, watchScroll } from './ui_utils'; import { PDFRenderingQueue, RenderingStates } from './pdf_rendering_queue'; @@ -55,6 +55,7 @@ var DEFAULT_CACHE_SIZE = 10; * rotation of pages whose orientation differ from the first page upon * printing. The default is `false`. * @property {string} renderer - 'canvas' or 'svg'. The default is 'canvas'. + * @property {IL10n} l10n - Localization service. */ /** @@ -114,6 +115,7 @@ var PDFViewer = (function pdfViewer() { this.renderInteractiveForms = options.renderInteractiveForms || false; this.enablePrintAutoRotate = options.enablePrintAutoRotate || false; this.renderer = options.renderer || RendererType.CANVAS; + this.l10n = options.l10n || NullL10n; this.defaultRenderingQueue = !options.renderingQueue; if (this.defaultRenderingQueue) { @@ -368,6 +370,7 @@ var PDFViewer = (function pdfViewer() { enhanceTextSelection: this.enhanceTextSelection, renderInteractiveForms: this.renderInteractiveForms, renderer: this.renderer, + l10n: this.l10n, }); bindOnAfterAndBeforeDraw(pageView); this._pages.push(pageView); @@ -899,16 +902,19 @@ var PDFViewer = (function pdfViewer() { * @param {HTMLDivElement} pageDiv * @param {PDFPage} pdfPage * @param {boolean} renderInteractiveForms + * @param {IL10n} l10n * @returns {AnnotationLayerBuilder} */ createAnnotationLayerBuilder(pageDiv, pdfPage, - renderInteractiveForms = false) { + renderInteractiveForms = false, + l10n = NullL10n) { return new AnnotationLayerBuilder({ pageDiv, pdfPage, renderInteractiveForms, linkService: this.linkService, - downloadManager: this.downloadManager + downloadManager: this.downloadManager, + l10n, }); }, diff --git a/web/toolbar.js b/web/toolbar.js index 6ed95e91bee06..47212c3b24804 100644 --- a/web/toolbar.js +++ b/web/toolbar.js @@ -14,8 +14,8 @@ */ import { - animationStarted, DEFAULT_SCALE, DEFAULT_SCALE_VALUE, localized, MAX_SCALE, - MIN_SCALE, mozL10n, noContextMenuHandler + animationStarted, DEFAULT_SCALE, DEFAULT_SCALE_VALUE, MAX_SCALE, + MIN_SCALE, noContextMenuHandler, NullL10n } from './ui_utils'; var PAGE_NUMBER_LOADING_INDICATOR = 'visiblePageIsLoading'; @@ -55,11 +55,13 @@ var Toolbar = (function ToolbarClosure() { * @param {ToolbarOptions} options * @param {HTMLDivElement} mainContainer * @param {EventBus} eventBus + * @param {IL10n} l10n - Localization service. */ - function Toolbar(options, mainContainer, eventBus) { + function Toolbar(options, mainContainer, eventBus, l10n = NullL10n) { this.toolbar = options.container; this.mainContainer = mainContainer; this.eventBus = eventBus; + this.l10n = l10n; this.items = options; this._wasLocalized = false; @@ -160,7 +162,9 @@ var Toolbar = (function ToolbarClosure() { // Suppress context menus for some controls items.scaleSelect.oncontextmenu = noContextMenuHandler; - localized.then(this._localized.bind(this)); + eventBus.on('localized', (evt) => { + this._localized(); + }); }, _localized: function Toolbar_localized() { @@ -170,7 +174,12 @@ var Toolbar = (function ToolbarClosure() { }, _updateUIState: function Toolbar_updateUIState(resetNumPages) { - function selectScaleOption(value, scale) { + if (!this._wasLocalized) { + // Don't update UI state until we will localize the toolbar. + return; + } + + let selectScaleOption = (value, scale) => { var options = items.scaleSelect.options; var predefinedValueFound = false; for (var i = 0, ii = options.length; i < ii; i++) { @@ -184,17 +193,14 @@ var Toolbar = (function ToolbarClosure() { } if (!predefinedValueFound) { var customScale = Math.round(scale * 10000) / 100; - items.customScaleOption.textContent = - mozL10n.get('page_scale_percent', {scale: customScale}, - '{{scale}}%'); + this.l10n.get('page_scale_percent', {scale: customScale}, + '{{scale}}%'). + then((msg) => { + items.customScaleOption.textContent = msg; + }); items.customScaleOption.selected = true; } - } - - if (!this._wasLocalized) { - // Don't update UI state until we will localize the toolbar. - return; - } + }; var pageNumber = this.pageNumber; var scaleValue = (this.pageScaleValue || this.pageScale).toString(); @@ -208,16 +214,21 @@ var Toolbar = (function ToolbarClosure() { items.pageNumber.type = 'text'; } else { items.pageNumber.type = 'number'; - items.numPages.textContent = mozL10n.get('of_pages', - { pagesCount, }, 'of {{pagesCount}}'); + this.l10n.get('of_pages', { pagesCount, }, 'of {{pagesCount}}'). + then((msg) => { + items.numPages.textContent = msg; + }); } items.pageNumber.max = pagesCount; } if (this.hasPageLabels) { items.pageNumber.value = this.pageLabel; - items.numPages.textContent = mozL10n.get('page_of_pages', - { pageNumber, pagesCount, }, '({{pageNumber}} of {{pagesCount}})'); + this.l10n.get('page_of_pages', { pageNumber, pagesCount, }, + '({{pageNumber}} of {{pagesCount}})'). + then((msg) => { + items.numPages.textContent = msg; + }); } else { items.pageNumber.value = pageNumber; } diff --git a/web/ui_utils.js b/web/ui_utils.js index 4c22f331dba1c..2ed8a2f12fb89 100644 --- a/web/ui_utils.js +++ b/web/ui_utils.js @@ -30,8 +30,29 @@ var RendererType = { SVG: 'svg', }; -var mozL10n = typeof document !== 'undefined' ? - (document.mozL10n || document.webL10n) : undefined; +// Replaces {{arguments}} with their values. +function formatL10nValue(text, args) { + if (!args) { + return text; + } + return text.replace(/\{\{\s*(\w+)\s*\}\}/g, (all, name) => { + return (name in args ? args[name] : '{{' + name + '}}'); + }); +} + +/** + * No-op implemetation of the localization service. + * @implements {IL10n} + */ +var NullL10n = { + get(property, args, fallback) { + return Promise.resolve(formatL10nValue(fallback, args)); + }, + + translate(element) { + return Promise.resolve(); + } +}; /** * Disables fullscreen support, and by extension Presentation Mode, @@ -440,22 +461,14 @@ var animationStarted = new Promise(function (resolve) { }); /** - * Promise that is resolved when UI localization is finished. + * (deprecated) External localization service. */ -var localized = new Promise(function (resolve, reject) { - if (!mozL10n) { - // Resolve as localized even if mozL10n is not available. - resolve(); - return; - } - if (mozL10n.getReadyState() !== 'loading') { - resolve(); - return; - } - window.addEventListener('localized', function localized(evt) { - resolve(); - }); -}); +var mozL10n; + +/** + * (deprecated) Promise that is resolved when UI localization is finished. + */ +var localized = Promise.resolve(); /** * Simple event bus for an application. Listeners are attached using the @@ -595,6 +608,7 @@ export { cloneObj, RendererType, mozL10n, + NullL10n, EventBus, ProgressBar, getPDFFileNameFromURL, diff --git a/web/viewer-snippet-chrome-extension.html b/web/viewer-snippet-chrome-extension.html index ade1ceec785c5..dcf2ed59800c3 100644 --- a/web/viewer-snippet-chrome-extension.html +++ b/web/viewer-snippet-chrome-extension.html @@ -1,5 +1,4 @@ - diff --git a/web/viewer-snippet-firefox-extension.html b/web/viewer-snippet-firefox-extension.html index 5158e3995df13..b153febfa1d84 100644 --- a/web/viewer-snippet-firefox-extension.html +++ b/web/viewer-snippet-firefox-extension.html @@ -1,4 +1,3 @@ - diff --git a/web/viewer-snippet.html b/web/viewer-snippet.html index aed17b8fa0910..f0bea7080a8eb 100644 --- a/web/viewer-snippet.html +++ b/web/viewer-snippet.html @@ -1,4 +1,3 @@ - diff --git a/web/viewer.html b/web/viewer.html index e2a506becbdf6..d7e294055386d 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -48,10 +48,6 @@ - - - -