From 1a1902496167de055aa7528d28ea734709f8875e Mon Sep 17 00:00:00 2001 From: Ludy Date: Sun, 3 Nov 2024 15:20:26 +0100 Subject: [PATCH] Fix: Auto language detection #2122 (#2148) * Fix: Auto language detection #2122 * add LanguageService and AdditionalLanguageJsController * hidden swagger --- .../api/AdditionalLanguageJsController.java | 65 +++++++++ .../SPDF/service/LanguageService.java | 41 ++++++ .../resources/static/js/languageSelection.js | 137 +++++++++--------- .../resources/templates/fragments/navbar.html | 24 +-- src/main/resources/templates/login.html | 72 +-------- 5 files changed, 196 insertions(+), 143 deletions(-) create mode 100644 src/main/java/stirling/software/SPDF/controller/api/AdditionalLanguageJsController.java create mode 100644 src/main/java/stirling/software/SPDF/service/LanguageService.java diff --git a/src/main/java/stirling/software/SPDF/controller/api/AdditionalLanguageJsController.java b/src/main/java/stirling/software/SPDF/controller/api/AdditionalLanguageJsController.java new file mode 100644 index 00000000..959e9ac4 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/api/AdditionalLanguageJsController.java @@ -0,0 +1,65 @@ +package stirling.software.SPDF.controller.api; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import io.swagger.v3.oas.annotations.Hidden; + +import jakarta.servlet.http.HttpServletResponse; +import stirling.software.SPDF.service.LanguageService; + +@RestController +@RequestMapping("/js") +public class AdditionalLanguageJsController { + + @Autowired private LanguageService languageService; + + @Hidden + @GetMapping(value = "/additionalLanguageCode.js", produces = "application/javascript") + public void generateAdditionalLanguageJs(HttpServletResponse response) throws IOException { + List supportedLanguages = languageService.getSupportedLanguages(); + + response.setContentType("application/javascript"); + PrintWriter writer = response.getWriter(); + + // Erstelle das JavaScript dynamisch + writer.println("const supportedLanguages = " + toJsonArray(supportedLanguages) + ";"); + + // Generiere die `getDetailedLanguageCode`-Funktion + writer.println( + """ + function getDetailedLanguageCode() { + const userLanguages = navigator.languages ? navigator.languages : [navigator.language]; + for (let lang of userLanguages) { + let matchedLang = supportedLanguages.find(supportedLang => supportedLang.startsWith(lang.replace('-', '_'))); + if (matchedLang) { + return matchedLang; + } + } + // Fallback + return "en_GB"; + } + """); + + writer.flush(); + } + + // Hilfsfunktion zum Konvertieren der Liste in ein JSON-Array + private String toJsonArray(List list) { + StringBuilder jsonArray = new StringBuilder("["); + for (int i = 0; i < list.size(); i++) { + jsonArray.append("\"").append(list.get(i)).append("\""); + if (i < list.size() - 1) { + jsonArray.append(","); + } + } + jsonArray.append("]"); + return jsonArray.toString(); + } +} diff --git a/src/main/java/stirling/software/SPDF/service/LanguageService.java b/src/main/java/stirling/software/SPDF/service/LanguageService.java new file mode 100644 index 00000000..2d4a47c6 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/service/LanguageService.java @@ -0,0 +1,41 @@ +package stirling.software.SPDF.service; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.stereotype.Service; + +@Service +public class LanguageService { + + private final PathMatchingResourcePatternResolver resourcePatternResolver = + new PathMatchingResourcePatternResolver(); + + public List getSupportedLanguages() { + List supportedLanguages = new ArrayList<>(); + + try { + Resource[] resources = + resourcePatternResolver.getResources("classpath*:messages_*.properties"); + for (Resource resource : resources) { + if (resource.exists() && resource.isReadable()) { + String filename = resource.getFilename(); + if (filename != null + && filename.startsWith("messages_") + && filename.endsWith(".properties")) { + String languageCode = + filename.replace("messages_", "").replace(".properties", ""); + supportedLanguages.add(languageCode); + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } + + return supportedLanguages; + } +} diff --git a/src/main/resources/static/js/languageSelection.js b/src/main/resources/static/js/languageSelection.js index 788a3420..383d23da 100644 --- a/src/main/resources/static/js/languageSelection.js +++ b/src/main/resources/static/js/languageSelection.js @@ -1,89 +1,86 @@ -document.addEventListener("DOMContentLoaded", function () { - setLanguageForDropdown(".lang_dropdown-item"); - - // Detect the browser's preferred language - let browserLang = navigator.language || navigator.userLanguage; - // Convert to a format consistent with your language codes (e.g., en-GB, fr-FR) - browserLang = browserLang.replace("-", "_"); - - // Check if the dropdown contains the browser's language - const dropdownLangExists = document.querySelector(`.lang_dropdown-item[data-language-code="${browserLang}"]`); - - // Set the default language to browser's language or 'en_GB' if not found in the dropdown - const defaultLocale = dropdownLangExists ? browserLang : "en_GB"; - const storedLocale = localStorage.getItem("languageCode") || defaultLocale; - - const dropdownItems = document.querySelectorAll(".lang_dropdown-item"); - - for (let i = 0; i < dropdownItems.length; i++) { - const item = dropdownItems[i]; - item.classList.remove("active"); - if (item.dataset.languageCode === storedLocale) { - item.classList.add("active"); - } - item.addEventListener("click", handleDropdownItemClick); - } -}); +function getStoredOrDefaultLocale() { + const storedLocale = localStorage.getItem("languageCode"); + return storedLocale || getDetailedLanguageCode(); +} function setLanguageForDropdown(dropdownClass) { - const defaultLocale = document.documentElement.getAttribute("data-language") || "en_GB"; - const storedLocale = localStorage.getItem("languageCode") || defaultLocale; + const storedLocale = getStoredOrDefaultLocale(); const dropdownItems = document.querySelectorAll(dropdownClass); - for (let i = 0; i < dropdownItems.length; i++) { - const item = dropdownItems[i]; - item.classList.remove("active"); - if (item.dataset.languageCode === storedLocale) { - item.classList.add("active"); - } - item.addEventListener("click", handleDropdownItemClick); - } + dropdownItems.forEach(item => { + item.classList.toggle("active", item.dataset.bsLanguageCode === storedLocale); + item.removeEventListener("click", handleDropdownItemClick); + item.addEventListener("click", handleDropdownItemClick); + }); +} + +function updateUrlWithLanguage(languageCode) { + const currentURL = new URL(window.location.href); + currentURL.searchParams.set('lang', languageCode); + window.location.href = currentURL.toString(); } function handleDropdownItemClick(event) { event.preventDefault(); - const languageCode = event.currentTarget.dataset.bsLanguageCode; // change this to event.currentTarget + const languageCode = event.currentTarget.dataset.bsLanguageCode; if (languageCode) { - localStorage.setItem("languageCode", languageCode); - - const currentUrl = window.location.href; - if (currentUrl.indexOf("?lang=") === -1 && currentUrl.indexOf("&lang=") === -1) { - window.location.href = currentUrl + "?lang=" + languageCode; - } else if (currentUrl.indexOf("&lang=") !== -1 && currentUrl.indexOf("?lang=") === -1) { - window.location.href = currentUrl.replace(/&lang=\w{2,}/, "&lang=" + languageCode); - } else { - window.location.href = currentUrl.replace(/\?lang=\w{2,}/, "?lang=" + languageCode); - } + localStorage.setItem("languageCode", languageCode); + updateUrlWithLanguage(languageCode); } else { - console.error("Language code is not set for this item."); // for debugging + console.error("Language code is not set for this item."); } } -document.addEventListener("DOMContentLoaded", function () { +function checkUserLanguage(defaultLocale) { + if (!localStorage.getItem("languageCode") || document.documentElement.getAttribute("data-language") != defaultLocale) { + localStorage.setItem("languageCode", defaultLocale); + updateUrlWithLanguage(defaultLocale); + } +} - document.querySelectorAll(".col-lg-2.col-sm-6").forEach((element) => { - const dropdownItems = element.querySelectorAll(".dropdown-item"); - const items = Array.from(dropdownItems).filter(item => !item.querySelector("hr.dropdown-divider")); +function initLanguageSettings() { + document.addEventListener("DOMContentLoaded", function () { + setLanguageForDropdown(".lang_dropdown-item"); - if (items.length <= 2) { - if ( - element.previousElementSibling && - element.previousElementSibling.classList.contains("col-lg-2") && - element.previousElementSibling.classList.contains("nav-item-separator") - ) { - element.previousElementSibling.remove(); + const defaultLocale = getStoredOrDefaultLocale(); + checkUserLanguage(defaultLocale); + + const dropdownItems = document.querySelectorAll(".lang_dropdown-item"); + dropdownItems.forEach(item => { + item.classList.toggle("active", item.dataset.bsLanguageCode === defaultLocale); + }); + }); +} + +function removeElements() { + document.addEventListener("DOMContentLoaded", function () { + document.querySelectorAll(".navbar-item").forEach((element) => { + const dropdownItems = element.querySelectorAll(".dropdown-item"); + const items = Array.from(dropdownItems).filter(item => !item.querySelector("hr.dropdown-divider")); + + if (items.length <= 2) { + if ( + element.previousElementSibling && + element.previousElementSibling.classList.contains("navbar-item") && + element.previousElementSibling.classList.contains("nav-item-separator") + ) { + element.previousElementSibling.remove(); + } + element.remove(); } - element.remove(); + }); + }); +} + +function sortLanguageDropdown() { + document.addEventListener("DOMContentLoaded", function () { + const dropdownMenu = document.querySelector('.dropdown-menu .dropdown-item.lang_dropdown-item').parentElement; + if (dropdownMenu) { + const items = Array.from(dropdownMenu.children).filter(child => child.matches('a')); + items.sort((a, b) => a.dataset.bsLanguageCode.localeCompare(b.dataset.bsLanguageCode)) + .forEach(node => dropdownMenu.appendChild(node)); } }); +} - //Sort languages by alphabet - const list = Array.from(document.querySelector('.dropdown-menu[aria-labelledby="languageDropdown"]').children).filter( - (child) => child.matches("a"), - ); - list - .sort(function (a, b) { - return a.textContent.toUpperCase().localeCompare(b.textContent.toUpperCase()); - }) - .forEach((node) => document.querySelector('.dropdown-menu[aria-labelledby="languageDropdown"]').appendChild(node)); -}); +sortLanguageDropdown(); diff --git a/src/main/resources/templates/fragments/navbar.html b/src/main/resources/templates/fragments/navbar.html index 5d58532f..4d82e445 100644 --- a/src/main/resources/templates/fragments/navbar.html +++ b/src/main/resources/templates/fragments/navbar.html @@ -1,5 +1,11 @@
+ + + @@ -29,86 +31,28 @@ }); document.addEventListener('DOMContentLoaded', function() { + const defaultLocale = getStoredOrDefaultLocale(); + checkUserLanguage(defaultLocale); - const defaultLocale = document.documentElement.getAttribute('data-language') || 'en_GB'; - const storedLocale = localStorage.getItem('languageCode') || defaultLocale; - - const currentURL = new URL(window.location.href); - const urlParams = currentURL.searchParams; - const currentLangParam = urlParams.get('lang') || defaultLocale; - - - if (defaultLocale !== storedLocale && currentLangParam !== storedLocale) { - urlParams.set('lang', storedLocale); - currentURL.search = urlParams.toString(); - window.location.href = currentURL.toString(); - return; - } - - const dropdown = document.getElementById('languageDropdown'); const dropdownItems = document.querySelectorAll('.lang_dropdown-item'); - let activeItem; + for (let i = 0; i < dropdownItems.length; i++) { const item = dropdownItems[i]; item.classList.remove('active'); - if (item.dataset.bsLanguageCode === storedLocale) { + if (item.dataset.bsLanguageCode === defaultLocale) { item.classList.add('active'); activeItem = item; } item.addEventListener('click', handleDropdownItemClick); } + const dropdown = document.getElementById('languageDropdown'); + if (activeItem) { dropdown.innerHTML = activeItem.innerHTML; // This will set the dropdown button's content to the active language's flag and name } - - // Additional functionality that was in your provided code: - - document.querySelectorAll('.nav-item.dropdown').forEach((element) => { - const dropdownMenu = element.querySelector(".dropdown-menu"); - if (dropdownMenu.id !== 'favoritesDropdown' && dropdownMenu.children.length <= 2 && dropdownMenu.querySelectorAll("hr.dropdown-divider").length === dropdownMenu.children.length) { - if (element.previousElementSibling && element.previousElementSibling.classList.contains('nav-item') && element.previousElementSibling.classList.contains('nav-item-separator')) { - element.previousElementSibling.remove(); - } - element.remove(); - } - }); - - // Sort languages by alphabet - const list = Array.from(document.querySelector('.dropdown-menu[aria-labelledby="languageDropdown"]').children).filter(child => child.matches('a')); - list.sort(function(a, b) { - var A = a.textContent.toUpperCase(); - var B = b.textContent.toUpperCase(); - return (A < B) ? -1 : (A > B) ? 1 : 0; - }).forEach(node => document.querySelector('.dropdown-menu[aria-labelledby="languageDropdown"]').appendChild(node)); }); - - function handleDropdownItemClick(event) { - event.preventDefault(); - const languageCode = event.currentTarget.dataset.bsLanguageCode; - const dropdown = document.getElementById('languageDropdown'); - - if (languageCode) { - localStorage.setItem('languageCode', languageCode); - const currentLang = document.documentElement.getAttribute('language'); - if (currentLang !== languageCode) { - console.log("currentLang", currentLang) - console.log("languageCode", languageCode) - const currentUrl = window.location.href; - if (currentUrl.indexOf("?lang=") === -1 && currentUrl.indexOf("&lang=") === -1) { - window.location.href = currentUrl + "?lang=" + languageCode; - } else if (currentUrl.indexOf("&lang=") !== -1 && currentUrl.indexOf("?lang=") === -1) { - window.location.href = currentUrl.replace(/&lang=\w{2,}/, "&lang=" + languageCode); - } else { - window.location.href = currentUrl.replace(/\?lang=\w{2,}/, "?lang=" + languageCode); - } - } - dropdown.innerHTML = event.currentTarget.innerHTML; // Update the dropdown button's content - } else { - console.error("Language code is not set for this item."); - } - }
favicon