From 1daf4fa23a86492d4c4fce13669db60ede37b782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20Slez=C3=A1k?= Date: Wed, 27 Mar 2024 08:45:40 +0100 Subject: [PATCH] Do not use Cockpit manifest for storing supported languages --- .github/workflows/weblate-merge-po.yml | 2 +- web/cspell.json | 1 + ...update-manifest.py => update-languages.py} | 35 ++++++-------- .../l10n/InstallerLocaleSwitcher.jsx | 7 ++- .../l10n/InstallerLocaleSwitcher.test.jsx | 15 ++---- web/src/context/installerL10n.jsx | 3 +- web/src/context/installerL10n.test.jsx | 18 +++---- web/src/languages.json | 12 +++++ web/src/lib/webpack-manifests-handler.js | 48 ------------------- web/src/manifest.json | 14 +----- web/tsconfig.json | 1 + web/webpack.config.js | 11 ----- 12 files changed, 46 insertions(+), 121 deletions(-) rename web/share/{update-manifest.py => update-languages.py} (86%) create mode 100644 web/src/languages.json delete mode 100644 web/src/lib/webpack-manifests-handler.js diff --git a/.github/workflows/weblate-merge-po.yml b/.github/workflows/weblate-merge-po.yml index 7d13aeae97..8735946f72 100644 --- a/.github/workflows/weblate-merge-po.yml +++ b/.github/workflows/weblate-merge-po.yml @@ -92,7 +92,7 @@ jobs: if: steps.check_changes.outputs.po_updated == 'true' working-directory: ./agama run: | - web/share/update-manifest.py web/src/manifest.json + web/share/update-languages.py web/src/languages.json # use a unique branch to avoid possible conflicts with already existing branches git checkout -b "po_merge_${GITHUB_RUN_ID}" git commit -a -m "Update web PO files"$'\n\n'"Agama-weblate commit: `git -C ../agama-weblate rev-parse HEAD`" diff --git a/web/cspell.json b/web/cspell.json index 3a2944fcda..5a6b767489 100644 --- a/web/cspell.json +++ b/web/cspell.json @@ -3,6 +3,7 @@ "language": "en", "allowCompoundWords": false, "ignorePaths": [ + "src/languages.json", "src/lib/cockpit.js", "src/lib/cockpit-po-plugin.js", "src/manifest.json", diff --git a/web/share/update-manifest.py b/web/share/update-languages.py similarity index 86% rename from web/share/update-manifest.py rename to web/share/update-languages.py index 6976a4e3ec..a85323ed22 100755 --- a/web/share/update-manifest.py +++ b/web/share/update-languages.py @@ -23,10 +23,8 @@ from langtable import language_name from pathlib import Path import json -import re import subprocess - class Locale: language: str territory: str @@ -76,20 +74,16 @@ def language(self): return self.path.stem -class Manifest: - """ This class takes care of updating the manifest file""" +class Languages: + """ This class takes care of generating the supported languages file""" def __init__(self, path: Path): self.path = path - self.__read__() - - def __read__(self): - with open(self.path) as file: - self.content = json.load(file) + self.content = dict() def update(self, po_files, lang2territory: str, threshold: int): """ - Updates the list of locales in the manifest file + Generate the list of supported locales It does not write the changes to file system. Use the write() function for that. @@ -124,32 +118,31 @@ def update(self, po_files, lang2territory: str, threshold: int): supported.append(locale) languages = [loc.language for loc in supported] - self.content["locales"] = dict() for locale in supported: include_territory = languages.count(locale.language) > 1 - self.content["locales"][locale.code()] = locale.name( - include_territory) + self.content[locale.code()] = locale.name(include_territory) def write(self): with open(self.path, "w+") as file: - json.dump(self.content, file, indent=4, ensure_ascii=False) + json.dump(self.content, file, indent=4, ensure_ascii=False, + sort_keys=True) -def update_manifest(args): +def update_languages(args): """Command to update the manifest.json file""" - manifest = Manifest(Path(args.manifest)) + languages = Languages(Path(args.file)) paths = [path for path in Path(args.po_directory).glob("*.po")] with open(args.territories) as file: lang2territory = json.load(file) - manifest.update(paths, lang2territory, args.threshold) - manifest.write() + languages.update(paths, lang2territory, args.threshold) + languages.write() if __name__ == "__main__": - parser = ArgumentParser(prog="locales.py") - parser.set_defaults(func=update_manifest) + parser = ArgumentParser(prog="update-languages.py") + parser.set_defaults(func=update_languages) parser.add_argument( - "manifest", type=str, help="Path to the manifest file", + "file", type=str, help="Path to the output file", ) parser.add_argument( "--po-directory", type=str, help="Directory containing the po files", diff --git a/web/src/components/l10n/InstallerLocaleSwitcher.jsx b/web/src/components/l10n/InstallerLocaleSwitcher.jsx index ad81c2ad7d..ac8054e362 100644 --- a/web/src/components/l10n/InstallerLocaleSwitcher.jsx +++ b/web/src/components/l10n/InstallerLocaleSwitcher.jsx @@ -26,12 +26,11 @@ import { FormSelect, FormSelectOption, Popover } from "@patternfly/react-core"; import { Icon } from "../layout"; import { _ } from "~/i18n"; import { useInstallerL10n } from "~/context/installerL10n"; -import cockpit from "~/lib/cockpit"; +import supportedLanguages from "~/languages.json"; export default function InstallerLocaleSwitcher() { const { language, changeLanguage } = useInstallerL10n(); const [selected, setSelected] = useState(null); - const languages = cockpit.manifests.agama?.locales || []; const onChange = useCallback((_event, value) => { setSelected(value); @@ -39,8 +38,8 @@ export default function InstallerLocaleSwitcher() { }, [setSelected, changeLanguage]); // sort by the language code to maintain consistent order - const options = Object.keys(languages).sort() - .map(id => ); + const options = Object.keys(supportedLanguages).sort() + .map(id => ); // TRANSLATORS: help text for the language selector in the sidebar, // %s will be replaced by the "Localization" page link diff --git a/web/src/components/l10n/InstallerLocaleSwitcher.test.jsx b/web/src/components/l10n/InstallerLocaleSwitcher.test.jsx index f51f3bdeaa..91e35cbbb6 100644 --- a/web/src/components/l10n/InstallerLocaleSwitcher.test.jsx +++ b/web/src/components/l10n/InstallerLocaleSwitcher.test.jsx @@ -27,17 +27,10 @@ import InstallerLocaleSwitcher from "./InstallerLocaleSwitcher"; const mockLanguage = "es-es"; let mockChangeLanguageFn; -jest.mock("~/lib/cockpit", () => ({ - gettext: term => term, - manifests: { - agama: { - locales: { - "de-de": "Deutsch", - "en-us": "English (US)", - "es-es": "Español" - } - } - } +jest.mock("~/languages.json", () => ({ + "de-de": "Deutsch", + "en-us": "English (US)", + "es-es": "Español" })); jest.mock("~/context/installerL10n", () => ({ diff --git a/web/src/context/installerL10n.jsx b/web/src/context/installerL10n.jsx index d73353d7ee..d035a6c7e9 100644 --- a/web/src/context/installerL10n.jsx +++ b/web/src/context/installerL10n.jsx @@ -27,6 +27,7 @@ import { useCancellablePromise, locationReload, setLocationSearch } from "~/util import cockpit from "../lib/cockpit"; import { useInstallerClient } from "./installer"; import agama from "~/agama"; +import supportedLanguages from "~/languages.json"; const L10nContext = React.createContext(null); @@ -154,7 +155,7 @@ function navigatorLanguages() { * @return {string|undefined} Undefined if none of the given languages is supported. */ function findSupportedLanguage(languages) { - const supported = Object.keys(cockpit.manifests.agama?.locales || {}); + const supported = Object.keys(supportedLanguages); for (const candidate of languages) { const [language, country] = candidate.split("-"); diff --git a/web/src/context/installerL10n.test.jsx b/web/src/context/installerL10n.test.jsx index bcf703a51d..3361c84b58 100644 --- a/web/src/context/installerL10n.test.jsx +++ b/web/src/context/installerL10n.test.jsx @@ -40,18 +40,14 @@ const client = { onDisconnect: jest.fn() }; +jest.mock("~/languages.json", () => ({ + "es-ar": "Español (Argentina)", + "cs-cz": "čeština", + "en-us": "English (US)", + "es-es": "Español" +})); + jest.mock("~/lib/cockpit", () => ({ - gettext: term => term, - manifests: { - agama: { - locales: { - "es-ar": "Español (Argentina)", - "cs-cz": "čeština", - "en-us": "English (US)", - "es-es": "Español" - } - } - }, spawn: jest.fn().mockResolvedValue() })); diff --git a/web/src/languages.json b/web/src/languages.json new file mode 100644 index 0000000000..ead0f2a4ae --- /dev/null +++ b/web/src/languages.json @@ -0,0 +1,12 @@ +{ + "ca-es": "Català", + "de-de": "Deutsch", + "en-us": "English", + "es-es": "Español", + "fr-fr": "Français", + "id-id": "Indonesia", + "ja-jp": "日本語", + "nl-nl": "Nederlands", + "sv-se": "Svenska", + "zh-Hans": "中文" +} \ No newline at end of file diff --git a/web/src/lib/webpack-manifests-handler.js b/web/src/lib/webpack-manifests-handler.js deleted file mode 100644 index 49be997c2a..0000000000 --- a/web/src/lib/webpack-manifests-handler.js +++ /dev/null @@ -1,48 +0,0 @@ -const fs = require("fs"); -const path = require("path"); - -const manifestFile = path.join(__dirname, "..", "manifest.json"); - -// this function is injected as a string into the resulting JS file -const updateAgamaManifest = (data) => { - if (typeof cockpit === "object" && cockpit.manifests) { - cockpit.manifests.agama = data; - } -}; - -// This function processes the webpack HTTP proxy request for manifests.js file. -// -// Patching the original JS code is difficult so rather inject code -// which rewrites the Agama manifest data with new content. -// -// @see https://github.com/http-party/node-http-proxy#modify-response -// -// @param proxyRes HTTP proxy resource -// @param req HTTP request -// @param res HTTP response -module.exports = function (proxyRes, req, res) { - // collect parts of the original response - const body = []; - - proxyRes.on("data", function (chunk) { - body.push(chunk); - }); - - proxyRes.on("end", function () { - // forward the original status code - res.statusCode = proxyRes.statusCode; - - // patch the response only on success otherwise there - // might be some unexpected content (HTML error page) - if (proxyRes.statusCode === 200 && fs.existsSync(manifestFile)) { - const manifest = fs.readFileSync(manifestFile); - // use an immediately-invoked function expression to inject the new - // manifest content - res.end(Buffer.concat(body).toString() + "((" + - updateAgamaManifest.toString() + ")(" + manifest + "));"); - } else { - // otherwise just return the original content - res.end(Buffer.concat(body).toString()); - } - }); -}; diff --git a/web/src/manifest.json b/web/src/manifest.json index 9c6a0332a6..2e2bd4cfff 100644 --- a/web/src/manifest.json +++ b/web/src/manifest.json @@ -7,17 +7,5 @@ "index": { "label": "Agama" } - }, - "locales": { - "en-us": "English", - "es-es": "Español", - "id-id": "Indonesia", - "fr-fr": "Français", - "sv-se": "Svenska", - "ca-es": "Català", - "ja-jp": "日本語", - "nl-nl": "Nederlands", - "zh-Hans": "中文", - "de-de": "Deutsch" } -} \ No newline at end of file +} diff --git a/web/tsconfig.json b/web/tsconfig.json index 3a117e098f..bde7ef8c06 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json @@ -5,6 +5,7 @@ "noEmit": true, "target": "esnext", "moduleResolution": "node", + "resolveJsonModule": true, "allowJs": true, "jsx": "react", "allowSyntheticDefaultImports": true, diff --git a/web/webpack.config.js b/web/webpack.config.js index 8fdda134b0..8449f0f168 100644 --- a/web/webpack.config.js +++ b/web/webpack.config.js @@ -15,7 +15,6 @@ const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); const webpack = require('webpack'); const po_handler = require("./src/lib/webpack-po-handler"); -const manifests_handler = require("./src/lib/webpack-manifests-handler"); /* A standard nodejs and webpack pattern */ const production = process.env.NODE_ENV === 'production'; @@ -112,16 +111,6 @@ module.exports = { // ignore SSL problems (self-signed certificate) secure: false, }, - // forward the manifests.js request and patch the response with the - // current Agama manifest from the ./src/manifest.json file - "/manifests.js": { - target: cockpitTarget + "/cockpit/@localhost/", - // ignore SSL problems (self-signed certificate) - secure: false, - // the response is modified by the onProxyRes handler - selfHandleResponse : true, - onProxyRes: manifests_handler, - }, }, // use https so Cockpit uses wss:// when connecting to the backend server: "https",