From e4ca27a7e11d4b49f44a341d87a288f74772b0f1 Mon Sep 17 00:00:00 2001
From: Dominik Hebeler <dominik@hebeler.club>
Date: Thu, 13 Jul 2023 10:54:29 +0200
Subject: [PATCH] smarter language detection

---
 pass/app.js              | 14 ++++++++++----
 pass/app/Langdetector.js | 36 +++++++++++++++++++++++++++---------
 pass/package-lock.json   | 24 ++++++++++++++++++++++++
 pass/package.json        |  3 ++-
 4 files changed, 63 insertions(+), 14 deletions(-)

diff --git a/pass/app.js b/pass/app.js
index 6b8e705..f524bb6 100644
--- a/pass/app.js
+++ b/pass/app.js
@@ -15,9 +15,10 @@ const i18nextmiddleware = require("i18next-http-middleware");
 const mglangdetector = require("./app/Langdetector");
 const langdetector = new i18nextmiddleware.LanguageDetector();
 langdetector.addDetector(mglangdetector);
+
 let ns = [];
-readdirSync(path.join(__dirname, "lang", "de")).forEach((fileName) => {
-  const joinedPath = path.join(path.join(__dirname, "lang", "de"), fileName);
+readdirSync(path.join(__dirname, "lang", "en")).forEach((fileName) => {
+  const joinedPath = path.join(path.join(__dirname, "lang", "en"), fileName);
   if (!lstatSync(joinedPath).isDirectory()) {
     ns.push(fileName.replace(".json", ""));
   }
@@ -36,7 +37,12 @@ i18next
     debug: false,
     // Lang Detector Options
     detection: {
-      order: ["mg_detection"],
+      order: ["cookie", "mg_detection"],
+      lookupCookie: "web_setting_m",
+      lookupFromPathIndex: 0,
+      convertDetectedLanguage: (lng) => {
+        return lng.replace("_", "-");
+      }
     },
     // FS Backend Options
     backend: {
@@ -45,7 +51,7 @@ i18next
     },
     ns: ns,
     defaultNS: "index",
-    fallbackLng: "de",
+    fallbackLng: "en",
     initImmediate: false,
     preload: readdirSync(path.join(__dirname, "lang")).filter((fileName) => {
       const joinedPath = path.join(path.join(__dirname, "lang"), fileName);
diff --git a/pass/app/Langdetector.js b/pass/app/Langdetector.js
index 3ca040a..04bc7b6 100644
--- a/pass/app/Langdetector.js
+++ b/pass/app/Langdetector.js
@@ -1,19 +1,37 @@
 module.exports = {
     name: 'mg_detection',
     lookup: (req, res, options) => {
-        let language = 'de';
-
-        if (req.hostname === "metager.org") {
-            language = 'en';
+        // Cookie is checked at this point
+        // Next detection in order is the request path
+        let path = req.path.replace(/^\/+/, "").replace(/\/+$/, "").split("/");
+        let path_matches = path[0].match(/^([a-z]{2})-([A-Z]{2})/);
+        if (path.length > 0 && path_matches) {
+            let path_tool = require("path");
+            let fs = require("fs");
+            let lang_folder = path_tool.join(__dirname, "../lang", path[0]);
+            // Check if translation exists for full locale
+            if (fs.existsSync(lang_folder)) {
+                return path[0];
+            }
+            // Check if translation exists for language part of locale
+            lang_folder = path_tool.join(__dirname, "../lang", path_matches[1]);
+            if (fs.existsSync(lang_folder)) {
+                return path[0];
+            }
         }
 
-        let localePathMatches = req.path.match(/^\/(en|de)(\-[a-zA-Z]{2})?/);
-        if (localePathMatches) {
-            language = localePathMatches[1];
+        // If the path does not match we'll try to guess a locale based on Accept-Language header
+        let acceptLanguage = require("accept-language");
+        acceptLanguage.languages([null, "de-DE", "de-AT", "de-CH", "de", "en-GB", "en-UK", "en-IE", "en-US", "en", "es-ES", "es-MX", "es"]);
+        let guessed_lang = acceptLanguage.get(req.headers["accept-language"]);
+        if (guessed_lang) {
+            return guessed_lang;
         }
 
-        if (language !== "en" && req.path.match(/^\/(ie|uk)/)) {
-            language = "en";
+        // If all prior checks failed we'll use the default depending on the current domain
+        let language = 'en';
+        if (req.hostname === "metager.de") {
+            language = 'de';
         }
         return language;
     },
diff --git a/pass/package-lock.json b/pass/package-lock.json
index c542ff3..2aefae8 100644
--- a/pass/package-lock.json
+++ b/pass/package-lock.json
@@ -9,6 +9,7 @@
       "version": "0.0.0",
       "dependencies": {
         "@paypal/paypal-js": "^5.1.1",
+        "accept-language": "^3.0.18",
         "blind-signatures": "^1.0.7",
         "browserify-middleware": "^8.1.1",
         "concat-stream": "^2.0.0",
@@ -581,6 +582,15 @@
       "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
       "dev": true
     },
+    "node_modules/accept-language": {
+      "version": "3.0.18",
+      "resolved": "https://registry.npmjs.org/accept-language/-/accept-language-3.0.18.tgz",
+      "integrity": "sha512-sUofgqBPzgfcF20sPoBYGQ1IhQLt2LSkxTnlQSuLF3n5gPEqd5AimbvOvHEi0T1kLMiGVqPWzI5a9OteBRth3A==",
+      "dependencies": {
+        "bcp47": "^1.1.2",
+        "stable": "^0.1.6"
+      }
+    },
     "node_modules/accepts": {
       "version": "1.3.8",
       "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -929,6 +939,14 @@
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
       "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
     },
+    "node_modules/bcp47": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/bcp47/-/bcp47-1.1.2.tgz",
+      "integrity": "sha512-JnkkL4GUpOvvanH9AZPX38CxhiLsXMBicBY2IAtqiVN8YulGDQybUydWA4W6yAMtw6iShtw+8HEF6cfrTHU+UQ==",
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
     "node_modules/bcrypt-pbkdf": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
@@ -6757,6 +6775,12 @@
       "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==",
       "optional": true
     },
+    "node_modules/stable": {
+      "version": "0.1.8",
+      "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz",
+      "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==",
+      "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility"
+    },
     "node_modules/standard-as-callback": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
diff --git a/pass/package.json b/pass/package.json
index 9423e28..3d0c449 100644
--- a/pass/package.json
+++ b/pass/package.json
@@ -8,6 +8,7 @@
   },
   "dependencies": {
     "@paypal/paypal-js": "^5.1.1",
+    "accept-language": "^3.0.18",
     "blind-signatures": "^1.0.7",
     "browserify-middleware": "^8.1.1",
     "concat-stream": "^2.0.0",
@@ -44,4 +45,4 @@
   "devDependencies": {
     "nodemon": "^2.0.20"
   }
-}
\ No newline at end of file
+}
-- 
GitLab