From 417b83bb4173d85f121d5208883abee5ec892314 Mon Sep 17 00:00:00 2001 From: koichixD <207069770+koichixD@users.noreply.github.com> Date: Mon, 9 Feb 2026 21:41:08 +0100 Subject: [PATCH] =?UTF-8?q?Spanisch=20&=20Franz=C3=B6sisch=20als=20UI=20Sp?= =?UTF-8?q?rache=20hinzugef=C3=BCgt=20~=20V=200.5.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/flags/es.svg | 4 + public/flags/fr.svg | 5 ++ src/App.jsx | 173 +++++++++++++++++++++++++++++++++++----- src/config/appConfig.js | 2 +- 4 files changed, 165 insertions(+), 19 deletions(-) create mode 100644 public/flags/es.svg create mode 100644 public/flags/fr.svg diff --git a/public/flags/es.svg b/public/flags/es.svg new file mode 100644 index 0000000..dbfd185 --- /dev/null +++ b/public/flags/es.svg @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/public/flags/fr.svg b/public/flags/fr.svg new file mode 100644 index 0000000..9812a25 --- /dev/null +++ b/public/flags/fr.svg @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/src/App.jsx b/src/App.jsx index e6cf06c..9d0bb41 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,4 +1,4 @@ -import React, { useMemo, useRef, useState } from "react"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import { convertFileSrc } from "@tauri-apps/api/core"; import { basename, join } from "@tauri-apps/api/path"; import { open } from "@tauri-apps/plugin-dialog"; @@ -38,6 +38,12 @@ const ammoOptions = [ const ALL_CATEGORY = "__ALL__"; const UNCATEGORIZED = "Nicht zugeordnet"; +const languageOptions = [ + { value: "de", label: "Deutsch", flagSrc: "/flags/de.svg", flagAlt: "DE" }, + { value: "en", label: "English", flagSrc: "/flags/us.svg", flagAlt: "US" }, + { value: "es", label: "Espanol", flagSrc: "/flags/es.svg", flagAlt: "ES" }, + { value: "fr", label: "Francais", flagSrc: "/flags/fr.svg", flagAlt: "FR" }, +]; const translations = { de: { allItems: "Alle Items", @@ -127,6 +133,94 @@ const translations = { imageAddError: ({ message }) => `Failed to add image: ${message}`, imageAdded: "Image added", }, + es: { + allItems: "Todos los items", + uncategorized: "Sin asignar", + categories: "Categorías", + category: "Categoría", + newCategoryPlaceholder: "Crear nueva categoría...", + searchPlaceholder: "Buscar item...", + inventoryOpen: "Abrir inventario", + inventoryLabel: "Inventario", + loadedFile: "Cargado", + chooseItemHint: "Elige una categoría a la izquierda y luego selecciona un item.", + applyChanges: "Aplicar cambios", + selectInventoryTitle: "Seleccionar carpeta de QS inventory", + itemsFileMissing: "No se encontró items.lua. Selecciona la carpeta qs-inventory.", + imagesMissing: "No se encontró la carpeta de imágenes: qs-inventory\\html\\images", + noItemsDetected: + "No se detectaron items. Asegúrate de que el archivo empiece con `ItemList = {` o `QBShared.Items = {`.", + loadedItems: ({ count }) => `Cargado: ${count} items`, + loadError: ({ message }) => `Error al cargar: ${message}`, + saved: "Guardado", + saveError: ({ message }) => `Error al guardar: ${message}`, + backupError: ({ message }) => `No se pudo crear la copia de seguridad: ${message}`, + language: "Idioma", + newItem: "Nuevo item", + template: "Plantilla", + create: "Crear", + cancel: "Cancelar", + itemId: "ID del item", + itemLabel: "Etiqueta", + idRequired: "Introduce un ID de item.", + idExists: ({ id }) => `El ID del item ya existe: ${id}`, + templateNormal: "Items normales", + templateNormalRare: "Items normales con rareza", + templateWeapon: "Armas", + templateFood: "Comida", + templateDrink: "Bebida", + selectTemplateTitle: "Seleccionar plantilla", + createItemTitle: "Editar nuevo item", + close: "Cerrar", + itemCreated: "Item guardado", + imageAdd: "Añadir imagen", + imageAddError: ({ message }) => `No se pudo añadir la imagen: ${message}`, + imageAdded: "Imagen añadida", + }, + fr: { + allItems: "Tous les items", + uncategorized: "Non attribué", + categories: "Catégories", + category: "Catégorie", + newCategoryPlaceholder: "Créer une nouvelle catégorie...", + searchPlaceholder: "Rechercher un item...", + inventoryOpen: "Ouvrir l'inventaire", + inventoryLabel: "Inventaire", + loadedFile: "Chargé", + chooseItemHint: "Choisis une catégorie à gauche puis sélectionne un item.", + applyChanges: "Appliquer les modifications", + selectInventoryTitle: "Sélectionner le dossier QS inventory", + itemsFileMissing: "items.lua introuvable. Sélectionne le dossier qs-inventory.", + imagesMissing: "Dossier d'images introuvable : qs-inventory\\html\\images", + noItemsDetected: + "Aucun item détecté. Vérifie que le fichier commence par `ItemList = {` ou `QBShared.Items = {`.", + loadedItems: ({ count }) => `Chargé : ${count} items`, + loadError: ({ message }) => `Erreur de chargement : ${message}`, + saved: "Enregistré", + saveError: ({ message }) => `Erreur d'enregistrement : ${message}`, + backupError: ({ message }) => `Échec de la sauvegarde : ${message}`, + language: "Langue", + newItem: "Nouvel item", + template: "Modèle", + create: "Créer", + cancel: "Annuler", + itemId: "ID de l'item", + itemLabel: "Libellé", + idRequired: "Veuillez saisir un ID d'item.", + idExists: ({ id }) => `L'ID de l'item existe déjà : ${id}`, + templateNormal: "Items normaux", + templateNormalRare: "Items normaux avec rareté", + templateWeapon: "Armes", + templateFood: "Nourriture", + templateDrink: "Boisson", + selectTemplateTitle: "Sélectionner un modèle", + createItemTitle: "Modifier le nouvel item", + close: "Fermer", + itemCreated: "Item enregistré", + imageAdd: "Ajouter une image", + imageAddError: ({ message }) => `Impossible d'ajouter l'image : ${message}`, + imageAdded: "Image ajoutée", + }, }; const itemTemplates = [ @@ -295,7 +389,9 @@ export default function App() { const [newItemDraft, setNewItemDraft] = useState(null); const [newItemTemplate, setNewItemTemplate] = useState(itemTemplates[0]?.key || "basic"); const [toastMessage, setToastMessage] = useState(""); + const [languageMenuOpen, setLanguageMenuOpen] = useState(false); const toastTimerRef = useRef(null); + const languageMenuRef = useRef(null); const t = (key, vars) => { const entry = translations[language]?.[key] ?? translations.en[key] ?? key; @@ -360,6 +456,24 @@ export default function App() { const selectedItem = useMemo(() => { return items.find((i) => i.id === selectedItemId) || null; }, [items, selectedItemId]); + const selectedLanguageOption = useMemo( + () => languageOptions.find((option) => option.value === language) || languageOptions[0], + [language] + ); + + useEffect(() => { + function handlePointerDown(event) { + if (!languageMenuOpen) return; + if (!languageMenuRef.current?.contains(event.target)) { + setLanguageMenuOpen(false); + } + } + + document.addEventListener("mousedown", handlePointerDown); + return () => { + document.removeEventListener("mousedown", handlePointerDown); + }; + }, [languageMenuOpen]); const selectedImageSrc = selectedItem ? getImageSrc(selectedItem) : null; @@ -904,27 +1018,50 @@ export default function App() { > {t("inventoryOpen")} -