Skip to content

Commit

Permalink
Fix: Auto language detection #2122 (#2148)
Browse files Browse the repository at this point in the history
* Fix: Auto language detection #2122

* add LanguageService and AdditionalLanguageJsController

* hidden swagger
  • Loading branch information
Ludy87 authored Nov 3, 2024
1 parent 68c9601 commit 1a19024
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 143 deletions.
Original file line number Diff line number Diff line change
@@ -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<String> 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<String> 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();
}
}
41 changes: 41 additions & 0 deletions src/main/java/stirling/software/SPDF/service/LanguageService.java
Original file line number Diff line number Diff line change
@@ -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<String> getSupportedLanguages() {
List<String> 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;
}
}
137 changes: 67 additions & 70 deletions src/main/resources/static/js/languageSelection.js
Original file line number Diff line number Diff line change
@@ -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);
}
}

function initLanguageSettings() {
document.addEventListener("DOMContentLoaded", function () {
setLanguageForDropdown(".lang_dropdown-item");

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"));
const defaultLocale = getStoredOrDefaultLocale();
checkUserLanguage(defaultLocale);

const dropdownItems = document.querySelectorAll(".lang_dropdown-item");
dropdownItems.forEach(item => {
item.classList.toggle("active", item.dataset.bsLanguageCode === defaultLocale);
});
});
}

if (items.length <= 2) {
if (
element.previousElementSibling &&
element.previousElementSibling.classList.contains("col-lg-2") &&
element.previousElementSibling.classList.contains("nav-item-separator")
) {
element.previousElementSibling.remove();
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();
24 changes: 15 additions & 9 deletions src/main/resources/templates/fragments/navbar.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<div th:fragment="navbar" class="mx-auto">
<script th:src="@{'/js/languageSelection.js'}"></script>
<script th:src="@{'/js/additionalLanguageCode.js'}"></script>
<script th:inline="javascript">
// Initializing the scripts
initLanguageSettings();
removeElements();
</script>
<script th:inline="javascript">
const currentVersion = /*[[${@appVersion}]]*/ '';
const noFavourites = /*[[#{noFavourites}]]*/ '';
Expand Down Expand Up @@ -36,7 +42,7 @@
<div class="container-fluid">
<div class="row">
<!-- Page tools menu items -->
<div class="col-lg-2 col-sm-6 py px-xl-1 px-2">
<div class="navbar-item col-lg-2 col-sm-6 py px-xl-1 px-2">
<h6 class="menu-title" th:text="#{navbar.sections.organize}"></h6>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('compress-pdf', 'zoom_in_map', 'home.compressPdfs.title', 'home.compressPdfs.desc', 'compressPdfs.tags', 'advance')}">
Expand Down Expand Up @@ -73,7 +79,7 @@ <h6 class="menu-title" th:text="#{navbar.sections.organize}"></h6>
</div>
</div>
<!-- Convert to PDF menu items -->
<div class="col-lg-2 col-sm-6 py px-xl-1 px-2">
<div class="navbar-item col-lg-2 col-sm-6 py px-xl-1 px-2">
<h6 class="menu-title" th:text="#{navbar.sections.convertTo}"></h6>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('img-to-pdf', 'image', 'home.imageToPdf.title', 'home.imageToPdf.desc', 'imageToPdf.tags', 'image')}">
Expand All @@ -95,7 +101,7 @@ <h6 class="menu-title" th:text="#{navbar.sections.convertTo}"></h6>
</div>
</div>
<!-- Convert from PDF menu items -->
<div class="col-lg-2 col-sm-6 py px-xl-1 px-2">
<div class="navbar-item col-lg-2 col-sm-6 py px-xl-1 px-2">
<h6 class="menu-title" th:text="#{navbar.sections.convertFrom}"></h6>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-img', 'image', 'home.pdfToImage.title', 'home.pdfToImage.desc', 'pdfToImage.tags', 'image')}">
Expand Down Expand Up @@ -126,7 +132,7 @@ <h6 class="menu-title" th:text="#{navbar.sections.convertFrom}"></h6>
</div>
</div>
<!-- Security menu items -->
<div class="col-lg-2 col-sm-6 py px-xl-1 px-2">
<div class="navbar-item col-lg-2 col-sm-6 py px-xl-1 px-2">
<h6 class="menu-title" th:text="#{navbar.sections.security}"></h6>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('sign', 'signature', 'home.sign.title', 'home.sign.desc', 'sign.tags', 'sign')}">
Expand Down Expand Up @@ -160,7 +166,7 @@ <h6 class="menu-title" th:text="#{navbar.sections.security}"></h6>
</div>
</div>
<!-- View & Edit menu items -->
<div class="col-lg-2 col-sm-6 py px-xl-1 px-2">
<div class="navbar-item col-lg-2 col-sm-6 py px-xl-1 px-2">
<h6 class="menu-title" th:text="#{navbar.sections.edit}"></h6>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('view-pdf', 'menu_book', 'home.viewPdf.title', 'home.viewPdf.desc', 'viewPdf.tags', 'other')}">
Expand Down Expand Up @@ -196,14 +202,14 @@ <h6 class="menu-title" th:text="#{navbar.sections.edit}"></h6>
th:replace="~{fragments/navbarEntry :: navbarEntry ('get-info-on-pdf', 'info', 'home.getPdfInfo.title', 'home.getPdfInfo.desc', 'getPdfInfo.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('remove-image-pdf','remove_selection', 'home.removeImagePdf.title', 'home.removeImagePdf.desc', 'removeImagePdf.tags', 'other')}">
th:replace="~{fragments/navbarEntry :: navbarEntry ('remove-image-pdf','remove_selection', 'home.removeImagePdf.title', 'home.removeImagePdf.desc', 'removeImagePdf.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('replace-and-invert-color-pdf','format_color_fill', 'replace-color.title', 'home.replaceColorPdf.desc', 'replaceColorPdf.tags', 'other')}">
th:replace="~{fragments/navbarEntry :: navbarEntry ('replace-and-invert-color-pdf','format_color_fill', 'replace-color.title', 'home.replaceColorPdf.desc', 'replaceColorPdf.tags', 'other')}">
</div>
</div>
<!-- Advance menu items -->
<div class="col-lg-2 col-sm-6 py px-xl-1 px-2">
<div class="navbar-item col-lg-2 col-sm-6 py px-xl-1 px-2">
<h6 class="menu-title" th:text="#{navbar.sections.advance}"></h6>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('multi-tool', 'construction', 'home.multiTool.title', 'home.multiTool.desc', 'multiTool.tags', 'advance')}">
Expand All @@ -230,7 +236,7 @@ <h6 class="menu-title" th:text="#{navbar.sections.advance}"></h6>
th:replace="~{fragments/navbarEntry :: navbarEntry ('split-pdf-by-sections', 'grid_on', 'home.split-by-sections.title', 'home.split-by-sections.desc', 'split-by-sections.tags', 'advance')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('split-pdf-by-chapters', 'book', 'home.splitPdfByChapters.title', 'home.splitPdfByChapters.desc', 'splitPdfByChapters.tags', 'advance')}">
th:replace="~{fragments/navbarEntry :: navbarEntry ('split-pdf-by-chapters', 'book', 'home.splitPdfByChapters.title', 'home.splitPdfByChapters.desc', 'splitPdfByChapters.tags', 'advance')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('split-by-size-or-count', 'vertical_split', 'home.autoSizeSplitPDF.title', 'home.autoSizeSplitPDF.desc', 'autoSizeSplitPDF.tags', 'advance')}">
Expand Down
Loading

0 comments on commit 1a19024

Please sign in to comment.