From 07fc40da5ab14bb999bd16b8d547c19d9d82d3a7 Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Wed, 9 Apr 2025 17:07:57 +0400 Subject: [PATCH] Translation works on external link blocker This comes at the cost of broken support for SeaMonkey (due to usage of import.meta in i18n.js) --- static/skin/i18n.js | 35 ++++++++++++++++++++++++- static/skin/viewer.js | 17 +----------- static/templates/captured_external.html | 5 ++-- test/server.cpp | 13 ++++----- 4 files changed, 45 insertions(+), 25 deletions(-) diff --git a/static/skin/i18n.js b/static/skin/i18n.js index ed92d6f3..94aee785 100644 --- a/static/skin/i18n.js +++ b/static/skin/i18n.js @@ -23,7 +23,8 @@ const Translations = { return; const errorMsg = `Error loading translations for language '${lang}': `; - this.promises[lang] = fetch(`./skin/i18n/${lang}.json`).then(async (resp) => { + const translationJsonUrl = import.meta.resolve(`./i18n/${lang}.json`); + this.promises[lang] = fetch(translationJsonUrl).then(async (resp) => { if ( resp.ok ) { this.data[lang] = JSON.parse(await resp.text()); } else { @@ -190,8 +191,40 @@ function initUILanguageSelector(activeLanguage, languageChangeCallback) { languageSelector.onchange = languageChangeCallback; } +function parseDom(html) { + const domParser = new DOMParser(); + return domParser.parseFromString(html, "text/html").documentElement; +} + +function translatePageInWindow(w) { + if ( w.KIWIX_RESPONSE_TEMPLATE && w.KIWIX_RESPONSE_DATA ) { + const template = parseDom(w.KIWIX_RESPONSE_TEMPLATE).textContent; + + // w.KIWIX_RESPONSE_DATA may belong to a different context and running + // I18n.render() on it directly won't work correctly + // because the type checks (obj.__proto__ == ???.prototype) in + // I18n.instantiateParameterizedMessages() will fail (String.prototype + // refers to different objects in different contexts). + // Work arround that issue by copying the object into our context. + const params = JSON.parse(JSON.stringify(w.KIWIX_RESPONSE_DATA)); + + const newHtml = I18n.render(template, params); + w.document.documentElement.innerHTML = parseDom(newHtml).innerHTML; + } +} + +function translateSelf() { + if ( window.KIWIX_RESPONSE_TEMPLATE && window.KIWIX_RESPONSE_DATA ) { + setUserLanguage(getUserLanguage(), () => { + translatePageInWindow(window) + }); + } +}; + window.$t = $t; window.getUserLanguage = getUserLanguage; window.setUserLanguage = setUserLanguage; window.initUILanguageSelector = initUILanguageSelector; +window.translatePageInWindow = translatePageInWindow; window.I18n = I18n; +window.addEventListener('load', translateSelf); diff --git a/static/skin/viewer.js b/static/skin/viewer.js index bbdbc74d..0b7a6787 100644 --- a/static/skin/viewer.js +++ b/static/skin/viewer.js @@ -262,22 +262,7 @@ function handle_location_hash_change() { } function translateErrorPageIfNeeded() { - const cw = contentIframe.contentWindow; - if ( cw.KIWIX_RESPONSE_TEMPLATE && cw.KIWIX_RESPONSE_DATA ) { - const template = htmlDecode(cw.KIWIX_RESPONSE_TEMPLATE); - - // cw.KIWIX_RESPONSE_DATA belongs to the iframe context and running - // I18n.render() on it directly in the top context doesn't work correctly - // because the type checks (obj.__proto__ == ???.prototype) in - // I18n.instantiateParameterizedMessages() always fail (String.prototype - // refers to different objects in different contexts). - // Work arround that issue by copying the object into our context. - const params = JSON.parse(JSON.stringify(cw.KIWIX_RESPONSE_DATA)); - - const html = I18n.render(template, params); - const htmlDoc = new DOMParser().parseFromString(html, "text/html"); - cw.document.documentElement.innerHTML = htmlDoc.documentElement.innerHTML; - } + translatePageInWindow(contentIframe.contentWindow); } function handle_content_url_change() { diff --git a/static/templates/captured_external.html b/static/templates/captured_external.html index 2e22b4bd..24379880 100644 --- a/static/templates/captured_external.html +++ b/static/templates/captured_external.html @@ -5,10 +5,11 @@ {{external_link_detected}} -{{#KIWIX_RESPONSE_DATA}} + {{/KIWIX_RESPONSE_DATA}} +
diff --git a/test/server.cpp b/test/server.cpp index d79c7d02..dfd5b81b 100644 --- a/test/server.cpp +++ b/test/server.cpp @@ -61,7 +61,7 @@ const ResourceCollection resources200Compressible{ { DYNAMIC_CONTENT, "/ROOT%23%3F/skin/error.css" }, { STATIC_CONTENT, "/ROOT%23%3F/skin/error.css?cacheid=b3fa90cf" }, { DYNAMIC_CONTENT, "/ROOT%23%3F/skin/i18n.js" }, - { STATIC_CONTENT, "/ROOT%23%3F/skin/i18n.js?cacheid=071abc9a" }, + { STATIC_CONTENT, "/ROOT%23%3F/skin/i18n.js?cacheid=e9a10ac1" }, { DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.css" }, { STATIC_CONTENT, "/ROOT%23%3F/skin/index.css?cacheid=ae79e41a" }, { DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.js" }, @@ -77,7 +77,7 @@ const ResourceCollection resources200Compressible{ { DYNAMIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css" }, { STATIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css?cacheid=80d56607" }, { DYNAMIC_CONTENT, "/ROOT%23%3F/skin/viewer.js" }, - { STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=d6f747f5" }, + { STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=7f05bf6c" }, { DYNAMIC_CONTENT, "/ROOT%23%3F/skin/fonts/Poppins.ttf" }, { STATIC_CONTENT, "/ROOT%23%3F/skin/fonts/Poppins.ttf?cacheid=af705837" }, { DYNAMIC_CONTENT, "/ROOT%23%3F/skin/fonts/Roboto.ttf" }, @@ -296,7 +296,7 @@ R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/kiwix.css?cacheid=3948b846" - + @@ -331,9 +331,9 @@ R"EXPECTEDRESULT( - + - + const blankPageUrl = root + "/skin/blank.html?cacheid=6b1fa032"; @@ -355,7 +355,8 @@ R"EXPECTEDRESULT( - window.KIWIX_RESPONSE_TEMPLATE = "<!DOCTYPE html>\n<html>\n <head>\n <meta charset="utf-8">\n <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />\n <title>{{external_link_detected}}</title>\n <link type="text/css" href="{{root}}/skin/error.css?cacheid=b3fa90cf" rel="Stylesheet" />\n{{#KIWIX_RESPONSE_DATA}} <script>\n window.KIWIX_RESPONSE_TEMPLATE = "{{KIWIX_RESPONSE_TEMPLATE}}";\n window.KIWIX_RESPONSE_DATA = {{{KIWIX_RESPONSE_DATA}}};\n </script>{{/KIWIX_RESPONSE_DATA}}\n </head>\n <body>\n <header>\n <img src="{{root}}/skin/blocklink.svg?cacheid=bd56b116"\n alt="Caution!"\n aria-label="Caution!"\n title="Caution!">\n </header>\n <section class="intro">\n <h1>{{external_link_detected}}</h1>\n <p>You are about to leave Kiwix's ZIM reader to go online to</p>\n <p><a href="{{source}}">{{ source }}</a></p>\n </section>\n <section class="advice">\n <p>The link you're trying to access is not part of your offline package and requires an internet connection.</p>\n <p>If you can go online, you can attempt to open the link.</p>\n <p>You can otherwise return to your ZIM's offline content by using your browser's back button.</p>\n </section>\n </body>\n</html>\n"; + + window.KIWIX_RESPONSE_TEMPLATE = "<!DOCTYPE html>\n<html>\n <head>\n <meta charset="utf-8">\n <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />\n <title>{{external_link_detected}}</title>\n <link type="text/css" href="{{root}}/skin/error.css?cacheid=b3fa90cf" rel="Stylesheet" />\n <script type="module" src="{{root}}/skin/i18n.js?cacheid=e9a10ac1"></script>\n <script>\n window.KIWIX_RESPONSE_TEMPLATE = "{{KIWIX_RESPONSE_TEMPLATE}}";\n window.KIWIX_RESPONSE_DATA = {{{KIWIX_RESPONSE_DATA}}};\n </script>\n </head>\n <body>\n <header>\n <img src="{{root}}/skin/blocklink.svg?cacheid=bd56b116"\n alt="Caution!"\n aria-label="Caution!"\n title="Caution!">\n </header>\n <section class="intro">\n <h1>{{external_link_detected}}</h1>\n <p>You are about to leave Kiwix's ZIM reader to go online to</p>\n <p><a href="{{source}}">{{ source }}</a></p>\n </section>\n <section class="advice">\n <p>The link you're trying to access is not part of your offline package and requires an internet connection.</p>\n <p>If you can go online, you can attempt to open the link.</p>\n <p>You can otherwise return to your ZIM's offline content by using your browser's back button.</p>\n </section>\n </body>\n</html>\n";