From 97c2ce9da02dfc3ee3a563342e48540c07c18a6f Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Tue, 30 Jan 2024 18:32:35 +0100 Subject: [PATCH] Ensure that `GenericL10n` works if the locale files cannot be loaded - Ensure that localization works in the GENERIC viewer, even if the necessary locale files cannot be loaded. This was the behaviour prior to the introduction of Fluent, and it seems worthwhile to keep that (especially since we already bundle the en-US strings anyway). - Let the `GenericL10n`-implementation use the *bundled* en-US strings directly when no language is provided. - Remove the `NullL10n`-implementation, and simply fallback to `GenericL10n`, to reduce the maintenance burden of viewer-components localization. - Indirectly, given the previous point, stop exporting `NullL10n` in the viewer-components since it's now removed. Note that it was never really intended to be used directly and only existed as a fallback. *Please note:* This doesn't affect the Firefox PDF Viewer, thanks to the use of import maps. --- examples/mobile-viewer/viewer.mjs | 2 +- gulpfile.mjs | 9 +-- test/unit/pdf_viewer.component_spec.js | 13 ---- test/unit/unit_test.html | 2 +- tsconfig.json | 2 +- web/annotation_editor_layer_builder.js | 7 ++- web/genericl10n.js | 65 ++++++++++++++----- web/l10n.js | 4 +- web/l10n_utils.js | 87 -------------------------- web/pdf_page_view.js | 9 ++- web/pdf_viewer.component.js | 2 - web/pdf_viewer.js | 9 ++- web/stubs.js | 18 ------ web/viewer-geckoview.html | 2 +- web/viewer.html | 2 +- 15 files changed, 78 insertions(+), 155 deletions(-) delete mode 100644 web/l10n_utils.js delete mode 100644 web/stubs.js diff --git a/examples/mobile-viewer/viewer.mjs b/examples/mobile-viewer/viewer.mjs index 32915ebe9cb82..fedd5d8e23ead 100644 --- a/examples/mobile-viewer/viewer.mjs +++ b/examples/mobile-viewer/viewer.mjs @@ -272,7 +272,7 @@ const PDFViewerApplication = { }); this.pdfLinkService = linkService; - this.l10n = pdfjsViewer.NullL10n; + this.l10n = new pdfjsViewer.GenericL10n(); const container = document.getElementById("viewerContainer"); const pdfViewer = new pdfjsViewer.PDFViewer({ diff --git a/gulpfile.mjs b/gulpfile.mjs index 697a81a461582..8112e8b155bcb 100644 --- a/gulpfile.mjs +++ b/gulpfile.mjs @@ -272,7 +272,7 @@ function createWebpackConfig( "web-com": "", "web-download_manager": "", "web-external_services": "", - "web-l10n_utils": "web/stubs.js", + "web-null_l10n": "", "web-pdf_attachment_viewer": "web/pdf_attachment_viewer.js", "web-pdf_cursor_tools": "web/pdf_cursor_tools.js", "web-pdf_document_properties": "web/pdf_document_properties.js", @@ -294,6 +294,7 @@ function createWebpackConfig( viewerAlias["web-com"] = "web/chromecom.js"; viewerAlias["web-download_manager"] = "web/download_manager.js"; viewerAlias["web-external_services"] = "web/chromecom.js"; + viewerAlias["web-null_l10n"] = "web/l10n.js"; viewerAlias["web-preferences"] = "web/chromecom.js"; viewerAlias["web-print_service"] = "web/pdf_print_service.js"; } else if (bundleDefines.GENERIC) { @@ -308,13 +309,12 @@ function createWebpackConfig( viewerAlias["web-com"] = "web/genericcom.js"; viewerAlias["web-download_manager"] = "web/download_manager.js"; viewerAlias["web-external_services"] = "web/genericcom.js"; - viewerAlias["web-l10n_utils"] = "web/l10n_utils.js"; + viewerAlias["web-null_l10n"] = "web/genericl10n.js"; viewerAlias["web-preferences"] = "web/genericcom.js"; viewerAlias["web-print_service"] = "web/pdf_print_service.js"; } else if (bundleDefines.MOZCENTRAL) { if (bundleDefines.GECKOVIEW) { const gvAlias = { - "web-l10n_utils": "web/stubs.js", "web-toolbar": "web/toolbar-geckoview.js", }; for (const key in viewerAlias) { @@ -324,6 +324,7 @@ function createWebpackConfig( viewerAlias["web-com"] = "web/firefoxcom.js"; viewerAlias["web-download_manager"] = "web/firefoxcom.js"; viewerAlias["web-external_services"] = "web/firefoxcom.js"; + viewerAlias["web-null_l10n"] = "web/l10n.js"; viewerAlias["web-preferences"] = "web/firefoxcom.js"; viewerAlias["web-print_service"] = "web/firefox_print_service.js"; } @@ -1616,7 +1617,7 @@ function buildLibHelper(bundleDefines, inputStream, outputDir) { "display-node_utils": "./node_utils.js", "fluent-bundle": "../../../node_modules/@fluent/bundle/esm/index.js", "fluent-dom": "../../../node_modules/@fluent/dom/esm/index.js", - "web-l10n_utils": "../web/l10n_utils.js", + "web-null_l10n": "../web/genericl10n.js", }, }; const licenseHeaderLibre = fs diff --git a/test/unit/pdf_viewer.component_spec.js b/test/unit/pdf_viewer.component_spec.js index cace29e03b5e8..1785a4959c256 100644 --- a/test/unit/pdf_viewer.component_spec.js +++ b/test/unit/pdf_viewer.component_spec.js @@ -30,8 +30,6 @@ import { AnnotationLayerBuilder } from "../../web/annotation_layer_builder.js"; import { DownloadManager } from "../../web/download_manager.js"; import { EventBus } from "../../web/event_utils.js"; import { GenericL10n } from "../../web/genericl10n.js"; -import { L10n } from "../../web/l10n.js"; -import { NullL10n } from "../../web/l10n_utils.js"; import { PDFHistory } from "../../web/pdf_history.js"; import { PDFPageView } from "../../web/pdf_page_view.js"; import { PDFScriptingManager } from "../../web/pdf_scripting_manager.component.js"; @@ -54,7 +52,6 @@ describe("pdfviewer_api", function () { FindState, GenericL10n, LinkTarget, - NullL10n, parseQueryString, PDFFindController, PDFHistory, @@ -73,14 +70,4 @@ describe("pdfviewer_api", function () { XfaLayerBuilder, }); }); - - it("checks that `NullL10n` implements all methods", function () { - const methods = Object.getOwnPropertyNames(NullL10n).sort(); - - const baseMethods = Object.getOwnPropertyNames(L10n.prototype) - .filter(m => m !== "constructor" && !m.startsWith("_")) - .sort(); - - expect(methods).toEqual(baseMethods); - }); }); diff --git a/test/unit/unit_test.html b/test/unit/unit_test.html index add51eb7c1676..d88bd5bfc8f94 100644 --- a/test/unit/unit_test.html +++ b/test/unit/unit_test.html @@ -30,7 +30,7 @@ "web-com": "../../web/genericcom.js", "web-download_manager": "../../web/download_manager.js", "web-external_services": "../../web/genericcom.js", - "web-l10n_utils": "../../web/l10n_utils.js", + "web-null_l10n": "../../web/genericl10n.js", "web-pdf_attachment_viewer": "../../web/pdf_attachment_viewer.js", "web-pdf_cursor_tools": "../../web/pdf_cursor_tools.js", "web-pdf_document_properties": "../../web/pdf_document_properties.js", diff --git a/tsconfig.json b/tsconfig.json index 402f13d23c042..4a48983e68e4d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,7 +16,7 @@ "display-node_utils": ["./src/display/node_utils"], "fluent-bundle": ["./node_modules/@fluent/bundle/esm/index.js"], "fluent-dom": ["./node_modules/@fluent/dom/esm/index.js"], - "web-l10n_utils": ["./web/l10n_utils"] + "web-null_l10n": ["../web/genericl10n.js"] } }, "files": ["src/pdf.js", "web/pdf_viewer.component.js"] diff --git a/web/annotation_editor_layer_builder.js b/web/annotation_editor_layer_builder.js index d4a2aa9fd6fe6..c841de6ac0f98 100644 --- a/web/annotation_editor_layer_builder.js +++ b/web/annotation_editor_layer_builder.js @@ -25,7 +25,7 @@ /** @typedef {import("../src/display/annotation_layer.js").AnnotationLayer} AnnotationLayer */ import { AnnotationEditorLayer } from "pdfjs-lib"; -import { NullL10n } from "web-l10n_utils"; +import { GenericL10n } from "web-null_l10n"; /** * @typedef {Object} AnnotationEditorLayerBuilderOptions @@ -55,7 +55,10 @@ class AnnotationEditorLayerBuilder { this.pageDiv = options.pageDiv; this.pdfPage = options.pdfPage; this.accessibilityManager = options.accessibilityManager; - this.l10n = options.l10n || NullL10n; + this.l10n = options.l10n; + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) { + this.l10n ||= new GenericL10n(); + } this.annotationEditorLayer = null; this.div = null; this._cancelled = false; diff --git a/web/genericl10n.js b/web/genericl10n.js index 7a350b50fec8f..431b1043fca5f 100644 --- a/web/genericl10n.js +++ b/web/genericl10n.js @@ -20,22 +20,34 @@ import { DOMLocalization } from "fluent-dom"; import { fetchData } from "pdfjs-lib"; import { L10n } from "./l10n.js"; +function createBundle(lang, text) { + const resource = new FluentResource(text); + const bundle = new FluentBundle(lang); + const errors = bundle.addResource(resource); + if (errors.length) { + console.error("L10n errors", errors); + } + return bundle; +} + /** * @implements {IL10n} */ class GenericL10n extends L10n { constructor(lang) { super({ lang }); - this._setL10n( - new DOMLocalization( - [], - GenericL10n.#generateBundles.bind( + + const generateBundles = !lang + ? GenericL10n.#generateBundlesFallback.bind( GenericL10n, - "en-us", this.getLanguage() ) - ) - ); + : GenericL10n.#generateBundles.bind( + GenericL10n, + "en-us", + this.getLanguage() + ); + this._setL10n(new DOMLocalization([], generateBundles)); } /** @@ -63,6 +75,9 @@ class GenericL10n extends L10n { if (bundle) { yield bundle; } + if (lang === "en-us") { + yield this.#createBundleFallback(lang); + } } } @@ -74,20 +89,36 @@ class GenericL10n extends L10n { const url = new URL(path, baseURL); const text = await fetchData(url, /* type = */ "text"); - const resource = new FluentResource(text); - const bundle = new FluentBundle(lang); - const errors = bundle.addResource(resource); - if (errors.length) { - console.error("L10n errors", errors); - } - return bundle; + return createBundle(lang, text); } static async #getPaths() { - const { href } = document.querySelector(`link[type="application/l10n"]`); - const paths = await fetchData(href, /* type = */ "json"); + try { + const { href } = document.querySelector(`link[type="application/l10n"]`); + const paths = await fetchData(href, /* type = */ "json"); + + return { baseURL: href.replace(/[^/]*$/, "") || "./", paths }; + } catch {} + return { baseURL: "./", paths: Object.create(null) }; + } + + static async *#generateBundlesFallback(lang) { + yield this.#createBundleFallback(lang); + } + + static async #createBundleFallback(lang) { + if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) { + throw new Error("Not implemented: #createBundleFallback"); + } + const text = + typeof PDFJSDev === "undefined" + ? await fetchData( + new URL(`./locale/${lang}/viewer.ftl`, window.location.href), + /* type = */ "text" + ) + : PDFJSDev.eval("DEFAULT_FTL"); - return { baseURL: href.replace(/[^/]*$/, "") || "./", paths }; + return createBundle(lang, text); } } diff --git a/web/l10n.js b/web/l10n.js index 14c09c389d496..7909e43ca31c3 100644 --- a/web/l10n.js +++ b/web/l10n.js @@ -117,4 +117,6 @@ class L10n { } } -export { L10n }; +const GenericL10n = null; + +export { GenericL10n, L10n }; diff --git a/web/l10n_utils.js b/web/l10n_utils.js deleted file mode 100644 index 2e493651eb106..0000000000000 --- a/web/l10n_utils.js +++ /dev/null @@ -1,87 +0,0 @@ -/* Copyright 2021 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. - */ - -/** @typedef {import("./interfaces").IL10n} IL10n */ - -import { fetchData, shadow } from "pdfjs-lib"; -import { FluentBundle, FluentResource } from "fluent-bundle"; -import { DOMLocalization } from "fluent-dom"; -import { L10n } from "./l10n.js"; - -/** - * @implements {IL10n} - */ -class ConstL10n extends L10n { - constructor(lang) { - super({ lang }); - this._setL10n( - new DOMLocalization([], ConstL10n.#generateBundles.bind(ConstL10n, lang)) - ); - } - - static async *#generateBundles(lang) { - const text = - typeof PDFJSDev === "undefined" - ? await fetchData( - new URL(`./locale/${lang}/viewer.ftl`, window.location.href), - /* type = */ "text" - ) - : PDFJSDev.eval("DEFAULT_FTL"); - - const resource = new FluentResource(text); - const bundle = new FluentBundle(lang); - const errors = bundle.addResource(resource); - if (errors.length) { - console.error("L10n errors", errors); - } - yield bundle; - } - - static get instance() { - return shadow(this, "instance", new ConstL10n("en-us")); - } -} - -/** - * No-op implementation of the localization service. - * @implements {IL10n} - */ -const NullL10n = { - getLanguage() { - return ConstL10n.instance.getLanguage(); - }, - - getDirection() { - return ConstL10n.instance.getDirection(); - }, - - async get(ids, args = null, fallback) { - return ConstL10n.instance.get(ids, args, fallback); - }, - - async translate(element) { - return ConstL10n.instance.translate(element); - }, - - pause() { - return ConstL10n.instance.pause(); - }, - - resume() { - return ConstL10n.instance.resume(); - }, -}; - -export { NullL10n }; diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js index 1df2f360e2a16..4e97897b5ca2e 100644 --- a/web/pdf_page_view.js +++ b/web/pdf_page_view.js @@ -43,7 +43,7 @@ import { AnnotationEditorLayerBuilder } from "./annotation_editor_layer_builder. import { AnnotationLayerBuilder } from "./annotation_layer_builder.js"; import { compatibilityParams } from "./app_options.js"; import { DrawLayerBuilder } from "./draw_layer_builder.js"; -import { NullL10n } from "web-l10n_utils"; +import { GenericL10n } from "web-null_l10n"; import { SimpleLinkService } from "./pdf_link_service.js"; import { StructTreeLayerBuilder } from "./struct_tree_layer_builder.js"; import { TextAccessibilityManager } from "./text_accessibility.js"; @@ -157,7 +157,10 @@ class PDFPageView { this.eventBus = options.eventBus; this.renderingQueue = options.renderingQueue; - this.l10n = options.l10n || NullL10n; + this.l10n = options.l10n; + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) { + this.l10n ||= new GenericL10n(); + } this.renderTask = null; this.resume = null; @@ -214,7 +217,7 @@ class PDFPageView { } // Ensure that Fluent is connected in e.g. the COMPONENTS build. - if (this.l10n === NullL10n) { + if (!options.l10n) { this.l10n.translate(this.div); } } diff --git a/web/pdf_viewer.component.js b/web/pdf_viewer.component.js index c10d4aeb6ecee..e3f9e8db8d24b 100644 --- a/web/pdf_viewer.component.js +++ b/web/pdf_viewer.component.js @@ -30,7 +30,6 @@ import { AnnotationLayerBuilder } from "./annotation_layer_builder.js"; import { DownloadManager } from "./download_manager.js"; import { EventBus } from "./event_utils.js"; import { GenericL10n } from "./genericl10n.js"; -import { NullL10n } from "./l10n_utils.js"; import { PDFHistory } from "./pdf_history.js"; import { PDFPageView } from "./pdf_page_view.js"; import { PDFScriptingManager } from "./pdf_scripting_manager.component.js"; @@ -54,7 +53,6 @@ export { FindState, GenericL10n, LinkTarget, - NullL10n, parseQueryString, PDFFindController, PDFHistory, diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index 54cf661407fbf..b1943dd9ca15f 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -63,7 +63,7 @@ import { VERTICAL_PADDING, watchScroll, } from "./ui_utils.js"; -import { NullL10n } from "web-l10n_utils"; +import { GenericL10n } from "web-null_l10n"; import { PDFPageView } from "./pdf_page_view.js"; import { PDFRenderingQueue } from "./pdf_rendering_queue.js"; import { SimpleLinkService } from "./pdf_link_service.js"; @@ -286,7 +286,10 @@ class PDFViewer { this.removePageBorders = options.removePageBorders || false; } this.maxCanvasPixels = options.maxCanvasPixels; - this.l10n = options.l10n || NullL10n; + this.l10n = options.l10n; + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) { + this.l10n ||= new GenericL10n(); + } this.#enablePermissions = options.enablePermissions || false; this.pageColors = options.pageColors || null; @@ -327,7 +330,7 @@ class PDFViewer { if ( (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) && - this.l10n === NullL10n + !options.l10n ) { // Ensure that Fluent is connected in e.g. the COMPONENTS build. this.l10n.translate(this.container); diff --git a/web/stubs.js b/web/stubs.js deleted file mode 100644 index 333ab1a5ece1d..0000000000000 --- a/web/stubs.js +++ /dev/null @@ -1,18 +0,0 @@ -/* Copyright 2023 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. - */ - -const NullL10n = null; - -export { NullL10n }; diff --git a/web/viewer-geckoview.html b/web/viewer-geckoview.html index 7076a436bf187..57e28e172d819 100644 --- a/web/viewer-geckoview.html +++ b/web/viewer-geckoview.html @@ -63,7 +63,7 @@ "web-com": "./genericcom.js", "web-download_manager": "./download_manager.js", "web-external_services": "./genericcom.js", - "web-l10n_utils": "./l10n_utils.js", + "web-null_l10n": "./genericl10n.js", "web-pdf_attachment_viewer": "./stubs-geckoview.js", "web-pdf_cursor_tools": "./stubs-geckoview.js", "web-pdf_document_properties": "./stubs-geckoview.js", diff --git a/web/viewer.html b/web/viewer.html index ffe19ed97a3e7..d65e56e63711c 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -72,7 +72,7 @@ "web-com": "./genericcom.js", "web-download_manager": "./download_manager.js", "web-external_services": "./genericcom.js", - "web-l10n_utils": "./l10n_utils.js", + "web-null_l10n": "./genericl10n.js", "web-pdf_attachment_viewer": "./pdf_attachment_viewer.js", "web-pdf_cursor_tools": "./pdf_cursor_tools.js", "web-pdf_document_properties": "./pdf_document_properties.js",