diff --git a/next-i18next.config.js b/next-i18next.config.js new file mode 100644 index 000000000..ae76b4355 --- /dev/null +++ b/next-i18next.config.js @@ -0,0 +1,10 @@ +module.exports = { + // https://www.i18next.com/overview/configuration-options#logging + debug: process.env.NODE_ENV === 'development', + i18n: { + defaultLocale: 'en', + locales: ['en', 'de'], + localeDetection: true, + }, + reloadOnPrerender: process.env.NODE_ENV === 'development', +}; diff --git a/next.config.js b/next.config.js index b91c26880..17decd099 100644 --- a/next.config.js +++ b/next.config.js @@ -1,5 +1,7 @@ const { env } = require('process'); +const { i18n } = require('./next-i18next.config'); + const withBundleAnalyzer = require('@next/bundle-analyzer')({ enabled: process.env.ANALYZE === 'true', }); @@ -13,4 +15,5 @@ module.exports = withBundleAnalyzer({ outputStandalone: true, }, output: 'standalone', + i18n, }); diff --git a/package.json b/package.json index 75482c3b5..c57cf4c88 100644 --- a/package.json +++ b/package.json @@ -53,8 +53,12 @@ "dockerode": "^3.3.2", "embla-carousel-react": "^7.0.0", "framer-motion": "^6.5.1", + "i18next": "^21.9.1", + "i18next-browser-languagedetector": "^6.1.5", + "i18next-http-backend": "^1.4.1", "js-file-download": "^0.4.12", "next": "12.1.6", + "next-i18next": "^11.3.0", "prism-react-renderer": "^1.3.5", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -87,5 +91,9 @@ "prettier": "^2.7.1", "typescript": "^4.7.4" }, + "resolutions": { + "@types/react": "17.0.2", + "@types/react-dom": "17.0.2" + }, "packageManager": "yarn@3.2.1" } diff --git a/public/imgs/flags/de.png b/public/imgs/flags/de.png new file mode 100644 index 000000000..c62776bf3 Binary files /dev/null and b/public/imgs/flags/de.png differ diff --git a/public/imgs/flags/en.png b/public/imgs/flags/en.png new file mode 100644 index 000000000..e6a6dc8be Binary files /dev/null and b/public/imgs/flags/en.png differ diff --git a/public/locales/de/common.json b/public/locales/de/common.json new file mode 100644 index 000000000..bcec9ac7f --- /dev/null +++ b/public/locales/de/common.json @@ -0,0 +1,6 @@ +{ + "actions": { + "save": "Speichern" + }, + "tip": "Tipp: " +} \ No newline at end of file diff --git a/public/locales/de/layout/add-service-app-shelf.json b/public/locales/de/layout/add-service-app-shelf.json new file mode 100644 index 000000000..d7a1ffb4c --- /dev/null +++ b/public/locales/de/layout/add-service-app-shelf.json @@ -0,0 +1,118 @@ +{ + "actionIcon": { + "tooltip": "Einen Service hinzufügen" + }, + "modal": { + "title": "Service hinzufügen", + "form": { + "validation": { + "invalidUrl": "Please enter a valid URL", + "noStatusCodeSelected": "Please select a status code" + } + }, + "tabs": { + "options": { + "title": "Optionen", + "form": { + "serviceName": { + "label": "Service Namen", + "placeholder": "Plex" + }, + "iconUrl": { + "label": "Icon URL" + }, + "serviceUrl": { + "label": "Service URL" + }, + "onClickUrl": { + "label": "URL bei einem Klick" + }, + "serviceType": { + "label": "Service Typ", + "defaultValue": "Andere", + "placeholder": "Wähle einen Typ aus" + }, + "category": { + "label": "Kategorie", + "placeholder": "Whle eine Kategorie oder erstelle eine neue", + "nothingFound": "Nichts gefunden", + "createLabel": "+ Erstelle {{query}}" + }, + "integrations": { + "apiKey": { + "label": "API Schlüssel", + "placeholder": "Dein API Schlüssel", + "validation": { + "noKey": "Invalider Schlüssel" + }, + "tip": { + "text": "Erhalte deinen API Schlüssel", + "link": "hier." + } + }, + "qBittorrent": { + "username": { + "label": "Benutzernamen", + "placeholder": "admin", + "validation": { + "invalidUsername": "Invalider Benutzername" + } + }, + "password": { + "label": "Passwort", + "placeholder": "adminadmin", + "validation": { + "invalidPassword": "Invalides Passwort" + } + } + }, + "deluge": { + "password": { + "label": "Passwort", + "placeholder": "password", + "validation": { + "invalidPassword": "Invalides Passwort" + } + } + }, + "transmission": { + "username": { + "label": "Benutzername", + "placeholder": "admin", + "validation": { + "invalidUsername": "Invalider Benutzername" + } + }, + "password": { + "label": "Passwort", + "placeholder": "adminadmin", + "validation": { + "invalidPassword": "Invalides Passwort" + } + } + } + } + } + }, + "advancedOptions": { + "title": "Weitere Optionen", + "form": { + "httpStatusCodes": { + "label": "HTTP Status Nummern", + "placeholder": "Wähle eine valide Status Nummer", + "clearButtonLabel": "Auswahl löschen", + "nothingFound": "Nichts gefunden" + }, + "openServiceInNewTab": { + "label": "Serivce in einem neuen Tab öffnen" + }, + "buttons": { + "submit": { + "content": "Service hinzufügen" + } + } + } + } + } + } + } \ No newline at end of file diff --git a/public/locales/de/layout/app-shelf-menu.json b/public/locales/de/layout/app-shelf-menu.json new file mode 100644 index 000000000..46e894272 --- /dev/null +++ b/public/locales/de/layout/app-shelf-menu.json @@ -0,0 +1,18 @@ +{ + "modal": { + "title": "Einen Service bearbeiten", + "buttons": { + "save": "Service speichern" + } + }, + "menu": { + "labels": { + "settings": "Einstellungen", + "dangerZone": "Gefahrenzone" + }, + "actions": { + "edit": "Bearbeiten", + "delete": "Löschen" + } + } +} \ No newline at end of file diff --git a/public/locales/de/layout/app-shelf.json b/public/locales/de/layout/app-shelf.json new file mode 100644 index 000000000..72788abb4 --- /dev/null +++ b/public/locales/de/layout/app-shelf.json @@ -0,0 +1,10 @@ +{ + "accordions": { + "downloads": { + "text": "Deine Downloads" + }, + "others": { + "text": "Andere" + } + } +} \ No newline at end of file diff --git a/public/locales/de/modules/calendar.json b/public/locales/de/modules/calendar.json new file mode 100644 index 000000000..e9337497f --- /dev/null +++ b/public/locales/de/modules/calendar.json @@ -0,0 +1,11 @@ +{ + "descriptor": { + "name": "Kalender", + "description": "Ein Kalender Modul welches bevorstehende Shows anzeigt. Es interagiert mit der API von Sonarr, Radarr, Readarr und Lidarr.", + "settings": { + "sundayStart": { + "label": "Wochenstart am Sonntag" + } + } + } +} \ No newline at end of file diff --git a/public/locales/de/modules/common-media-cards.json b/public/locales/de/modules/common-media-cards.json new file mode 100644 index 000000000..72313078a --- /dev/null +++ b/public/locales/de/modules/common-media-cards.json @@ -0,0 +1,6 @@ +{ + "buttons": { + "play": "Abspielen", + "request": "Anfragen" + } +} \ No newline at end of file diff --git a/public/locales/de/modules/dashdot.json b/public/locales/de/modules/dashdot.json new file mode 100644 index 000000000..d629b876f --- /dev/null +++ b/public/locales/de/modules/dashdot.json @@ -0,0 +1,32 @@ +{ + "card": { + "title": "Dash.", + "errors": { + "noService": "Kein Dash. Service gefunden. Bitte füge einen zu deinem Homarr Dashboard hinzu oder setze eine Dash. URL in den Modul-Optionen.", + "noInformation": "Informationen konnten nicht von Dash. geladen werden. Betriebst du die neuste Version?" + }, + "graphs": { + "storage": { + "title": "Speicher", + "label": "Speicher:" + }, + "network": { + "title": "Netzwerk", + "label": "Netzwerk:", + "metrics": { + "download": "Eingehend", + "upload": "Ausgehend" + } + }, + "cpu": { + "title": "CPU" + }, + "memory": { + "title": "RAM" + }, + "gpu": { + "title": "GPU" + } + } + } +} \ No newline at end of file diff --git a/public/locales/de/modules/docker-module.json b/public/locales/de/modules/docker-module.json new file mode 100644 index 000000000..495bacb08 --- /dev/null +++ b/public/locales/de/modules/docker-module.json @@ -0,0 +1,65 @@ +{ + "search": { + "placeholder": "Suche nach Conainer oder Image Namen" + }, + "table": { + "header": { + "name": "Name", + "image": "Image", + "ports": "Ports", + "state": "Status" + }, + "body": { + "portCollapse": "{{ports}} weitere" + }, + "states": { + "running": "Läuft", + "created": "Erstellt", + "stopped": "Gestopped", + "unknown": "Unbekannt" + } + }, + "actionBar": { + "addService": { + "title": "Service hinzufügen", + "message": "Service zu Homarr hinzufügen" + }, + "restart": { + "title": "Neustarten" + }, + "stop": { + "title": "Stoppen" + }, + "start": { + "title": "Starten" + }, + "refreshData": "Daten aktualisieren", + "addToHomarr": { + "title": "Zu Homarr hinzufügen" + }, + "remove": { + "title": "Entfernen" + } + }, + "messages": { + "successfullyExecuted": { + "title": "Container {{containerName}} {{action}}ed", + "message": "Your container was successfully {{action}}ed" + } + }, + "errors": { + "integrationFailed": { + "title": "Docker Integration schlug fehl", + "message": "Hast du vergessen, den Docker Socket zu verbinden?" + }, + "unknownError": { + "title": "Es ist ein Fehler aufgetreten" + }, + "oneServiceAtATime": { + "title": "Bitte füge nur einen Service zur Zeit hinzu." + } + }, + "actionIcon": { + "tooltip": "Docker" + } +} \ No newline at end of file diff --git a/public/locales/de/modules/overseerr.json b/public/locales/de/modules/overseerr.json new file mode 100644 index 000000000..99ce654f8 --- /dev/null +++ b/public/locales/de/modules/overseerr.json @@ -0,0 +1,26 @@ +{ + "popup": { + "item": { + "buttons": { + "askFor": "Fragen für {{title}}", + "cancel": "Abbrechen", + "request": "Anfragen" + }, + "alerts": { + "automaticApproval": { + "title": "Einen API Schlüssel benutzen", + "text": "Diese Anfrage wird automatisch genehmigt" + } + } + }, + "seasonSelector": { + "caption": "Kreuze die Staffeln an, die heruntergeladen werden sollen.", + "table": { + "header": { + "season": "Staffel", + "numberOfEpisodes": "Anzahl von Episoden" + } + } + } + } +} \ No newline at end of file diff --git a/public/locales/de/modules/ping.json b/public/locales/de/modules/ping.json new file mode 100644 index 000000000..645e2dd61 --- /dev/null +++ b/public/locales/de/modules/ping.json @@ -0,0 +1,11 @@ +{ + "descriptor": { + "name": "Ping Services", + "description": "Pings your services and shows their status as an indicator" + }, + "states": { + "online": "Online {{response}}", + "offline": "Offline {{response}}", + "loading": "Laden..." + } +} \ No newline at end of file diff --git a/public/locales/de/modules/search.json b/public/locales/de/modules/search.json new file mode 100644 index 000000000..e794b039b --- /dev/null +++ b/public/locales/de/modules/search.json @@ -0,0 +1,5 @@ +{ + "input": { + "placeholder": "Das Internet durchsuchen..." + } +} \ No newline at end of file diff --git a/public/locales/de/modules/torrents-status.json b/public/locales/de/modules/torrents-status.json new file mode 100644 index 000000000..5beba90a4 --- /dev/null +++ b/public/locales/de/modules/torrents-status.json @@ -0,0 +1,31 @@ +{ + "card": { + "table": { + "header": { + "name": "Name", + "size": "Grösse", + "download": "Eingehend", + "upload": "Ausgehend", + "estimatedTimeOfArrival": "Vorraussichtlicher Abschluss", + "progress": "Fortschritt" + }, + "body": { + "nothingFound": "Keine Torrents gefunden" + } + }, + "lineChart": { + "title": "Derzeitige Download Geschwindigkeit", + "download": "Download: {{download}}", + "upload": "Upload: {{upload}}", + "timeSpan": "{{seconds}} Sekunden zuvor", + "totalDownload": "Download: {{download}}/s", + "totalUpload": "Upload: {{upload}}/s" + }, + "errors": { + "noDownloadClients": { + "title": "Keine unterstützten Download Clients gefunden", + "text": "Füge einen Download Service hinzu, um deine derzeitigen Downloads zu sehen" + } + } + } +} \ No newline at end of file diff --git a/public/locales/de/modules/weather.json b/public/locales/de/modules/weather.json new file mode 100644 index 000000000..4d8fa725a --- /dev/null +++ b/public/locales/de/modules/weather.json @@ -0,0 +1,20 @@ +{ + "card": { + "weatherDescriptions": { + "clear": "Klar", + "mainlyClear": "Überwiegend klar", + "fog": "Nebel", + "drizzle": "Niesel", + "freezingDrizzle": "Eisiger Nieselregen", + "rain": "Regen", + "freezingRain": "Eisiger Regen", + "snowFall": "Schneefall", + "snowGrains": "Schneekörner", + "rainShowers": "Regenschauer", + "snowShowers": "Schneeschauer", + "thunderstorm": "Gewitter", + "thunderstormWithHail": "Gewitter mit Hagel", + "unknown": "Unbekannt" + } + } +} \ No newline at end of file diff --git a/public/locales/de/settings/common.json b/public/locales/de/settings/common.json new file mode 100644 index 000000000..433c0197b --- /dev/null +++ b/public/locales/de/settings/common.json @@ -0,0 +1,14 @@ +{ + "title": "Einstellungen", + "tooltip": "Einstellungen", + "tabs": { + "common": "Gewöhnlich", + "customizations": "Anpassungen" + }, + "tips": { + "configTip": "Lade eine neue Konfiguration hoch, indem du eine neue auf die Seite ziehst!" + }, + "credits": { + "madeWithLove": "Gemacht mit ❤️ von @" + } +} \ No newline at end of file diff --git a/public/locales/de/settings/customization/app-width.json b/public/locales/de/settings/customization/app-width.json new file mode 100644 index 000000000..631bd9d0b --- /dev/null +++ b/public/locales/de/settings/customization/app-width.json @@ -0,0 +1,3 @@ +{ + "label": "Applikations Breite" +} \ No newline at end of file diff --git a/public/locales/de/settings/customization/color-selector.json b/public/locales/de/settings/customization/color-selector.json new file mode 100644 index 000000000..fd1e6ff10 --- /dev/null +++ b/public/locales/de/settings/customization/color-selector.json @@ -0,0 +1,3 @@ +{ + "suffix": "{{color}} Farbe" +} \ No newline at end of file diff --git a/public/locales/de/settings/customization/opacity-selector.json b/public/locales/de/settings/customization/opacity-selector.json new file mode 100644 index 000000000..35d0fcb44 --- /dev/null +++ b/public/locales/de/settings/customization/opacity-selector.json @@ -0,0 +1,3 @@ +{ + "label": "Applikation Deckkraft" +} \ No newline at end of file diff --git a/public/locales/de/settings/customization/page-appearance.json b/public/locales/de/settings/customization/page-appearance.json new file mode 100644 index 000000000..cb9740b31 --- /dev/null +++ b/public/locales/de/settings/customization/page-appearance.json @@ -0,0 +1,21 @@ +{ + "pageTitle": { + "label": "Seiten Titel", + "placeholder": "Homarr 🦞" + }, + "logo": { + "label": "Logo", + "placeholder": "/img/logo.png" + }, + "favicon": { + "label": "Favicon", + "placeholder": "/favicon.png" + }, + "background": { + "label": "Hintergrund", + "placeholder": "/img/background.png" + }, + "buttons": { + "submit": "Absenden" + } +} \ No newline at end of file diff --git a/public/locales/de/settings/customization/shade-selector.json b/public/locales/de/settings/customization/shade-selector.json new file mode 100644 index 000000000..88ad84009 --- /dev/null +++ b/public/locales/de/settings/customization/shade-selector.json @@ -0,0 +1,3 @@ +{ + "label": "Shatten" +} \ No newline at end of file diff --git a/public/locales/de/settings/general/color-schema.json b/public/locales/de/settings/general/color-schema.json new file mode 100644 index 000000000..5c885bd49 --- /dev/null +++ b/public/locales/de/settings/general/color-schema.json @@ -0,0 +1,3 @@ +{ + "label": "Wechseln zu {{scheme}} Modus" +} \ No newline at end of file diff --git a/public/locales/de/settings/general/config-changer.json b/public/locales/de/settings/general/config-changer.json new file mode 100644 index 000000000..975ef4539 --- /dev/null +++ b/public/locales/de/settings/general/config-changer.json @@ -0,0 +1,55 @@ +{ + "configSelect": { + "label": "Konfigurations Lader" + }, + "modal": { + "title": "Wähle einen Namen für deine neue Konfiguration", + "form": { + "configName": { + "label": "Konfigurations Name", + "placeholder": "Dein neuer Konfigurtionsname" + }, + "submitButton": "Bestätigen" + }, + "events": { + "configSaved": { + "title": "Konfiguration gespeichert", + "message": "Konfiguration gespeichert als {{configName}}" + } + } + }, + "buttons": { + "download": "Konfiguration herunterladen", + "delete": { + "text": "Konfiguration löschen", + "notifications": { + "deleted": { + "title": "Konfiguration gelöscht", + "message": "Konfiguration wurde gelöscht" + }, + "deleteFailed": { + "title": "Config delete failed", + "message": "Config delete failed" + } + } + }, + "saveCopy": "Eine Kopie speichern" + }, + "dropzone": { + "notifications": { + "invalidConfig": { + "title": "Konfiguration konnte nicht geladen werden", + "message": "Konfiguration konnte nicht geladen werden. Invalides JSON" + }, + "loadedSuccessfully": { + "title": "Konfiguration {{configName}} wurde erfolgreich geladen" + } + }, + "accept": { + "text": "Ziehe Konfigurationen hier um sie hochzuladen. Es werden nur JSON Dateien unterstützt." + }, + "reject": { + "text": "Dieses Dateiformat wird nicht unterstützt. Bitte lade nur JSON hoch." + } + } +} \ No newline at end of file diff --git a/public/locales/de/settings/general/internationalization.json b/public/locales/de/settings/general/internationalization.json new file mode 100644 index 000000000..786d61394 --- /dev/null +++ b/public/locales/de/settings/general/internationalization.json @@ -0,0 +1,3 @@ +{ + "label": "Sprache" +} \ No newline at end of file diff --git a/public/locales/de/settings/general/module-enabler.json b/public/locales/de/settings/general/module-enabler.json new file mode 100644 index 000000000..a48dcdc97 --- /dev/null +++ b/public/locales/de/settings/general/module-enabler.json @@ -0,0 +1,3 @@ +{ + "title": "Modul-Enabler" +} \ No newline at end of file diff --git a/public/locales/de/settings/general/search-engine.json b/public/locales/de/settings/general/search-engine.json new file mode 100644 index 000000000..def5db657 --- /dev/null +++ b/public/locales/de/settings/general/search-engine.json @@ -0,0 +1,11 @@ +{ + "title": "Suchmaschine", + "tips": { +"generalTip": "Verwenden die Präfixe !yt und !t vor deiner Suchanfrage, um auf YouTube bzw. nach einem Torrent zu suchen.", + "placeholderTip": "%s kann als Platzhalter für deine Suchanfrage verwendet werden." + }, + "customEngine": { + "label": "Suchanfrage URL", + "placeholder": "Benutzerdefinierte Adresse" + } +} \ No newline at end of file diff --git a/public/locales/de/settings/general/theme-selector.json b/public/locales/de/settings/general/theme-selector.json new file mode 100644 index 000000000..23d4efacc --- /dev/null +++ b/public/locales/de/settings/general/theme-selector.json @@ -0,0 +1,3 @@ +{ + "label": "Wechsel zu {{theme}} Modus" +} \ No newline at end of file diff --git a/public/locales/de/settings/general/widget-positions.json b/public/locales/de/settings/general/widget-positions.json new file mode 100644 index 000000000..fe4fc4d6e --- /dev/null +++ b/public/locales/de/settings/general/widget-positions.json @@ -0,0 +1,3 @@ +{ + "label": "Widgets auf der linken Seite" +} \ No newline at end of file diff --git a/public/locales/en/common.json b/public/locales/en/common.json new file mode 100644 index 000000000..9a0b9a9d9 --- /dev/null +++ b/public/locales/en/common.json @@ -0,0 +1,6 @@ +{ + "actions": { + "save": "Save" + }, + "tip": "Tip: " +} \ No newline at end of file diff --git a/public/locales/en/layout/add-service-app-shelf.json b/public/locales/en/layout/add-service-app-shelf.json new file mode 100644 index 000000000..c6417a161 --- /dev/null +++ b/public/locales/en/layout/add-service-app-shelf.json @@ -0,0 +1,118 @@ +{ + "actionIcon": { + "tooltip": "Add a service" + }, + "modal": { + "title": "Add service", + "form": { + "validation": { + "invalidUrl": "Please enter a valid URL", + "noStatusCodeSelected": "Please select a status code" + } + }, + "tabs": { + "options": { + "title": "Options", + "form": { + "serviceName": { + "label": "Service name", + "placeholder": "Plex" + }, + "iconUrl": { + "label": "Icon URL" + }, + "serviceUrl": { + "label": "Service URL" + }, + "onClickUrl": { + "label": "On Click URL" + }, + "serviceType": { + "label": "Service type", + "defaultValue": "Other", + "placeholder": "Pick one" + }, + "category": { + "label": "Category", + "placeholder": "Select a category or create a new one", + "nothingFound": "Nothing found", + "createLabel": "+ Create {{query}}" + }, + "integrations": { + "apiKey": { + "label": "API key", + "placeholder": "Your API key", + "validation": { + "noKey": "Invalid Key" + }, + "tip": { + "text": "Get your API key", + "link": "here." + } + }, + "qBittorrent": { + "username": { + "label": "Username", + "placeholder": "admin", + "validation": { + "invalidUsername": "Invalid username" + } + }, + "password": { + "label": "Password", + "placeholder": "adminadmin", + "validation": { + "invalidPassword": "Invalid password" + } + } + }, + "deluge": { + "password": { + "label": "Password", + "placeholder": "password", + "validation": { + "invalidPassword": "Invalid password" + } + } + }, + "transmission": { + "username": { + "label": "Username", + "placeholder": "admin", + "validation": { + "invalidUsername": "Invalid username" + } + }, + "password": { + "label": "Password", + "placeholder": "adminadmin", + "validation": { + "invalidPassword": "Invalid password" + } + } + } + } + } + }, + "advancedOptions": { + "title": "Advanced options", + "form": { + "httpStatusCodes": { + "label": "HTTP Status Codes", + "placeholder": "Select valid status codes", + "clearButtonLabel": "Clear selection", + "nothingFound": "Nothing found" + }, + "openServiceInNewTab": { + "label": "Open service in new tab" + }, + "buttons": { + "submit": { + "content": "Add service" + } + } + } + } + } + } + } \ No newline at end of file diff --git a/public/locales/en/layout/app-shelf-menu.json b/public/locales/en/layout/app-shelf-menu.json new file mode 100644 index 000000000..006e906c2 --- /dev/null +++ b/public/locales/en/layout/app-shelf-menu.json @@ -0,0 +1,18 @@ +{ + "modal": { + "title": "Modify a service", + "buttons": { + "save": "Save service" + } + }, + "menu": { + "labels": { + "settings": "Settings", + "dangerZone": "Danger zone" + }, + "actions": { + "edit": "Edit", + "delete": "Delete" + } + } +} \ No newline at end of file diff --git a/public/locales/en/layout/app-shelf.json b/public/locales/en/layout/app-shelf.json new file mode 100644 index 000000000..3297ffe7d --- /dev/null +++ b/public/locales/en/layout/app-shelf.json @@ -0,0 +1,10 @@ +{ + "accordions": { + "downloads": { + "text": "Your downloads" + }, + "others": { + "text": "Others" + } + } +} \ No newline at end of file diff --git a/public/locales/en/modules/calendar.json b/public/locales/en/modules/calendar.json new file mode 100644 index 000000000..d470eabe9 --- /dev/null +++ b/public/locales/en/modules/calendar.json @@ -0,0 +1,11 @@ +{ + "descriptor": { + "name": "Calendar", + "description": "A calendar module for displaying upcoming releases. It interacts with the Sonarr and Radarr API.", + "settings": { + "sundayStart": { + "label": "Start the week on Sunday" + } + } + } +} \ No newline at end of file diff --git a/public/locales/en/modules/common-media-cards.json b/public/locales/en/modules/common-media-cards.json new file mode 100644 index 000000000..af13d5d34 --- /dev/null +++ b/public/locales/en/modules/common-media-cards.json @@ -0,0 +1,6 @@ +{ + "buttons": { + "play": "Play", + "request": "Request" + } +} \ No newline at end of file diff --git a/public/locales/en/modules/common.json b/public/locales/en/modules/common.json new file mode 100644 index 000000000..3f4b36b03 --- /dev/null +++ b/public/locales/en/modules/common.json @@ -0,0 +1,5 @@ +{ + "settings": { + "label": "Settings" + } +} \ No newline at end of file diff --git a/public/locales/en/modules/dashdot.json b/public/locales/en/modules/dashdot.json new file mode 100644 index 000000000..77caf974e --- /dev/null +++ b/public/locales/en/modules/dashdot.json @@ -0,0 +1,60 @@ +{ + "descriptor": { + "name": "Dash.", + "description": "A module for displaying the graphs of your running Dash. instance.", + "settings": { + "cpuMultiView": { + "label": "CPU Multi-Core View" + }, + "storageMultiView": { + "label": "Storage Multi-Drive View" + }, + "useCompactView": { + "label": "Use Compact View" + }, + "graphs": { + "label": "Graphs", + "options": { + "cpu": "CPU", + "ram": "RAM", + "storage": "Storage", + "network": "Network", + "gpu": "GPU" + } + }, + "url": { + "label": "Dash. URL" + } + } + }, + "card": { + "title": "Dash.", + "errors": { + "noService": "No dash. service found. Please add one to your Homarr dashboard or set a dashdot URL in the module options", + "noInformation": "Cannot acquire information from dash. - are you running the latest version?" + }, + "graphs": { + "storage": { + "title": "Storage", + "label": "Storage:" + }, + "network": { + "title": "Network", + "label": "Network:", + "metrics": { + "download": "Down", + "upload": "Up" + } + }, + "cpu": { + "title": "CPU" + }, + "memory": { + "title": "RAM" + }, + "gpu": { + "title": "GPU" + } + } + } +} \ No newline at end of file diff --git a/public/locales/en/modules/date.json b/public/locales/en/modules/date.json new file mode 100644 index 000000000..521e220a4 --- /dev/null +++ b/public/locales/en/modules/date.json @@ -0,0 +1,11 @@ +{ + "descriptor": { + "name": "Date", + "description": "Show the current time and date in a card", + "settings": { + "display24HourFormat": { + "label": "Display full time (24-hour)" + } + } + } +} \ No newline at end of file diff --git a/public/locales/en/modules/dlspeed.json b/public/locales/en/modules/dlspeed.json new file mode 100644 index 000000000..ab0c23c98 --- /dev/null +++ b/public/locales/en/modules/dlspeed.json @@ -0,0 +1,6 @@ +{ + "descriptor": { + "name": "Download Speed", + "description": "Show the current download speed of supported services" + } +} \ No newline at end of file diff --git a/public/locales/en/modules/docker.json b/public/locales/en/modules/docker.json new file mode 100644 index 000000000..a4d738b61 --- /dev/null +++ b/public/locales/en/modules/docker.json @@ -0,0 +1,69 @@ +{ + "descriptor": { + "name": "Docker", + "description": "Allows you to easily manage your torrents" + }, + "search": { + "placeholder": "Search by container or image name" + }, + "table": { + "header": { + "name": "Name", + "image": "Image", + "ports": "Ports", + "state": "State" + }, + "body": { + "portCollapse": "{{ports}} more" + }, + "states": { + "running": "Running", + "created": "Created", + "stopped": "Stopped", + "unknown": "Unknown" + } + }, + "actionBar": { + "addService": { + "title": "Add service", + "message": "Add service to Homarr" + }, + "restart": { + "title": "Restart" + }, + "stop": { + "title": "Stop" + }, + "start": { + "title": "Start" + }, + "refreshData": "Refresh data", + "addToHomarr": { + "title": "Add to Homarr" + }, + "remove": { + "title": "Remove" + } + }, + "messages": { + "successfullyExecuted": { + "title": "Container {{containerName}} {{action}}ed", + "message": "Your container was successfully {{action}}ed" + } + }, + "errors": { + "integrationFailed": { + "title": "Docker integration failed", + "message": "Did you forget to mount the docker socket ?" + }, + "unknownError": { + "title": "There was an error" + }, + "oneServiceAtATime": { + "title": "Please only add one service at a time!" + } + }, + "actionIcon": { + "tooltip": "Docker" + } +} \ No newline at end of file diff --git a/public/locales/en/modules/overseerr.json b/public/locales/en/modules/overseerr.json new file mode 100644 index 000000000..ed9df1477 --- /dev/null +++ b/public/locales/en/modules/overseerr.json @@ -0,0 +1,30 @@ +{ + "descriptor": { + "name": "Overseerr", + "description": "Allows you to search and add media from Overseerr/Jellyseerr" + }, + "popup": { + "item": { + "buttons": { + "askFor": "Ask for {{title}}", + "cancel": "Cancel", + "request": "Request" + }, + "alerts": { + "automaticApproval": { + "title": "Using API key", + "text": "This request will be automatically approved" + } + } + }, + "seasonSelector": { + "caption": "Tick the seasons that you want to be downloaded", + "table": { + "header": { + "season": "Season", + "numberOfEpisodes": "Number of episodes" + } + } + } + } +} \ No newline at end of file diff --git a/public/locales/en/modules/ping.json b/public/locales/en/modules/ping.json new file mode 100644 index 000000000..403c8027b --- /dev/null +++ b/public/locales/en/modules/ping.json @@ -0,0 +1,11 @@ +{ + "descriptor": { + "name": "Ping", + "description": "Allows you to check if the service is up or returns a specific HTTP status code." + }, + "states": { + "online": "Online {{response}}", + "offline": "Offline {{response}}", + "loading": "Loading..." + } +} \ No newline at end of file diff --git a/public/locales/en/modules/search.json b/public/locales/en/modules/search.json new file mode 100644 index 000000000..0258afd59 --- /dev/null +++ b/public/locales/en/modules/search.json @@ -0,0 +1,9 @@ +{ + "descriptor": { + "name": "Search Bar", + "description": "Search bar to search the web, youtube, torrents or overseerr" + }, + "input": { + "placeholder": "Search the web..." + } +} \ No newline at end of file diff --git a/public/locales/en/modules/torrents-status.json b/public/locales/en/modules/torrents-status.json new file mode 100644 index 000000000..7e8970a92 --- /dev/null +++ b/public/locales/en/modules/torrents-status.json @@ -0,0 +1,40 @@ +{ + "descriptor": { + "name": "Torrent", + "description": "Show the current download speed of supported services", + "settings": { + "hideComplete": { + "label": "Hide completed torrents" + } + } + }, + "card": { + "table": { + "header": { + "name": "Name", + "size": "Size", + "download": "Down", + "upload": "Up", + "estimatedTimeOfArrival": "ETA", + "progress": "Progress" + }, + "body": { + "nothingFound": "No torrents found" + } + }, + "lineChart": { + "title": "Current download speed", + "download": "Download: {{download}}", + "upload": "Upload: {{upload}}", + "timeSpan": "{{seconds}} seconds ago", + "totalDownload": "Download: {{download}}/s", + "totalUpload": "Upload: {{upload}}/s" + }, + "errors": { + "noDownloadClients": { + "title": "No supported download clients found!", + "text": "Add a download service to view your current downloads" + } + } + } +} \ No newline at end of file diff --git a/public/locales/en/modules/weather.json b/public/locales/en/modules/weather.json new file mode 100644 index 000000000..c0aec151a --- /dev/null +++ b/public/locales/en/modules/weather.json @@ -0,0 +1,32 @@ +{ + "descriptor": { + "name": "Weather", + "description": "Look up the current weather in your location", + "settings": { + "displayInFahrenheit": { + "label": "Display in Fahrenheit" + }, + "location": { + "label": "Weather location" + } + } + }, + "card": { + "weatherDescriptions": { + "clear": "Clear", + "mainlyClear": "Mainly clear", + "fog": "Fog", + "drizzle": "Drizzle", + "freezingDrizzle": "Freezing drizzle", + "rain": "Rain", + "freezingRain": "Freezing rain", + "snowFall": "Snow fall", + "snowGrains": "Snow grains", + "rainShowers": "Rain showers", + "snowShowers": "Snow showers", + "thunderstorm": "Thunderstorm", + "thunderstormWithHail": "Thunderstorm with hail", + "unknown": "Unknown" + } + } +} \ No newline at end of file diff --git a/public/locales/en/settings/common.json b/public/locales/en/settings/common.json new file mode 100644 index 000000000..776816b2a --- /dev/null +++ b/public/locales/en/settings/common.json @@ -0,0 +1,14 @@ +{ + "title": "Settings", + "tooltip": "Settings", + "tabs": { + "common": "Common", + "customizations": "Customizations" + }, + "tips": { + "configTip": "Upload your config file by dragging and dropping it onto the page!" + }, + "credits": { + "madeWithLove": "Made with ❤️ by @" + } +} \ No newline at end of file diff --git a/public/locales/en/settings/customization/app-width.json b/public/locales/en/settings/customization/app-width.json new file mode 100644 index 000000000..e7636eef0 --- /dev/null +++ b/public/locales/en/settings/customization/app-width.json @@ -0,0 +1,3 @@ +{ + "label": "App Width" +} \ No newline at end of file diff --git a/public/locales/en/settings/customization/color-selector.json b/public/locales/en/settings/customization/color-selector.json new file mode 100644 index 000000000..d66bbfe6e --- /dev/null +++ b/public/locales/en/settings/customization/color-selector.json @@ -0,0 +1,3 @@ +{ + "suffix": "{{color}} color" +} \ No newline at end of file diff --git a/public/locales/en/settings/customization/opacity-selector.json b/public/locales/en/settings/customization/opacity-selector.json new file mode 100644 index 000000000..edd46daea --- /dev/null +++ b/public/locales/en/settings/customization/opacity-selector.json @@ -0,0 +1,3 @@ +{ + "label": "App Opacity" +} \ No newline at end of file diff --git a/public/locales/en/settings/customization/page-appearance.json b/public/locales/en/settings/customization/page-appearance.json new file mode 100644 index 000000000..051c11d86 --- /dev/null +++ b/public/locales/en/settings/customization/page-appearance.json @@ -0,0 +1,21 @@ +{ + "pageTitle": { + "label": "Page Title", + "placeholder": "Homarr 🦞" + }, + "logo": { + "label": "Logo", + "placeholder": "/img/logo.png" + }, + "favicon": { + "label": "Favicon", + "placeholder": "/favicon.png" + }, + "background": { + "label": "Background", + "placeholder": "/img/background.png" + }, + "buttons": { + "submit": "Submit" + } +} \ No newline at end of file diff --git a/public/locales/en/settings/customization/shade-selector.json b/public/locales/en/settings/customization/shade-selector.json new file mode 100644 index 000000000..076aee080 --- /dev/null +++ b/public/locales/en/settings/customization/shade-selector.json @@ -0,0 +1,3 @@ +{ + "label": "Shade" +} \ No newline at end of file diff --git a/public/locales/en/settings/general/color-schema.json b/public/locales/en/settings/general/color-schema.json new file mode 100644 index 000000000..16672bf7e --- /dev/null +++ b/public/locales/en/settings/general/color-schema.json @@ -0,0 +1,3 @@ +{ + "label": "Switch to {{scheme}} mode" +} \ No newline at end of file diff --git a/public/locales/en/settings/general/config-changer.json b/public/locales/en/settings/general/config-changer.json new file mode 100644 index 000000000..ad4ac012d --- /dev/null +++ b/public/locales/en/settings/general/config-changer.json @@ -0,0 +1,55 @@ +{ + "configSelect": { + "label": "Config loader" + }, + "modal": { + "title": "Choose the name of your new config", + "form": { + "configName": { + "label": "Config name", + "placeholder": "Your new config name" + }, + "submitButton": "Confirm" + }, + "events": { + "configSaved": { + "title": "Config saved", + "message": "Config saved as {{configName}}" + } + } + }, + "buttons": { + "download": "Download config", + "delete": { + "text": "Delete config", + "notifications": { + "deleted": { + "title": "Config deleted", + "message": "Config deleted" + }, + "deleteFailed": { + "title": "Config delete failed", + "message": "Config delete failed" + } + } + }, + "saveCopy": "Save a copy" + }, + "dropzone": { + "notifications": { + "invalidConfig": { + "title": "Unable to load config", + "message": "Could not load your config. Invalid JSON format." + }, + "loadedSuccessfully": { + "title": "Config {{configName}} loaded successfully" + } + }, + "accept": { + "text": "Drag files here to upload a config. Support for JSON only." + }, + "reject": { + "text": "This file format is not supported. Please only upload JSON." + } + } +} \ No newline at end of file diff --git a/public/locales/en/settings/general/internationalization.json b/public/locales/en/settings/general/internationalization.json new file mode 100644 index 000000000..17f0a13bf --- /dev/null +++ b/public/locales/en/settings/general/internationalization.json @@ -0,0 +1,3 @@ +{ + "label": "Language" +} \ No newline at end of file diff --git a/public/locales/en/settings/general/module-enabler.json b/public/locales/en/settings/general/module-enabler.json new file mode 100644 index 000000000..179753b6f --- /dev/null +++ b/public/locales/en/settings/general/module-enabler.json @@ -0,0 +1,3 @@ +{ + "title": "Module enabler" +} \ No newline at end of file diff --git a/public/locales/en/settings/general/search-engine.json b/public/locales/en/settings/general/search-engine.json new file mode 100644 index 000000000..789b1715b --- /dev/null +++ b/public/locales/en/settings/general/search-engine.json @@ -0,0 +1,11 @@ +{ + "title": "Search engine", + "tips": { +"generalTip": "Use the prefixes !yt and !t in front of your query to search on YouTube or for a Torrent respectively.", + "placeholderTip": "%s can be used as a placeholder for the query." + }, + "customEngine": { + "label": "Query URL", + "placeholder": "Custom query URL" + } +} \ No newline at end of file diff --git a/public/locales/en/settings/general/theme-selector.json b/public/locales/en/settings/general/theme-selector.json new file mode 100644 index 000000000..4e04d5e54 --- /dev/null +++ b/public/locales/en/settings/general/theme-selector.json @@ -0,0 +1,3 @@ +{ + "label": "Switch to {{theme}} mode" +} \ No newline at end of file diff --git a/public/locales/en/settings/general/widget-positions.json b/public/locales/en/settings/general/widget-positions.json new file mode 100644 index 000000000..746578cce --- /dev/null +++ b/public/locales/en/settings/general/widget-positions.json @@ -0,0 +1,3 @@ +{ + "label": "Position widgets on left" +} \ No newline at end of file diff --git a/src/components/AppShelf/AddAppShelfItem.tsx b/src/components/AppShelf/AddAppShelfItem.tsx index 9cfc402ba..efeba042a 100644 --- a/src/components/AppShelf/AddAppShelfItem.tsx +++ b/src/components/AppShelf/AddAppShelfItem.tsx @@ -22,24 +22,26 @@ import { IconApps } from '@tabler/icons'; import { useEffect, useState } from 'react'; import { v4 as uuidv4 } from 'uuid'; import { useDebouncedValue } from '@mantine/hooks'; +import { useTranslation } from 'next-i18next'; import { useConfig } from '../../tools/state'; import { tryMatchPort, ServiceTypeList, StatusCodes } from '../../tools/types'; import Tip from '../layout/Tip'; export function AddItemShelfButton(props: any) { const [opened, setOpened] = useState(false); + const { t } = useTranslation('layout/add-service-app-shelf'); return ( <> Add service} + title={{t('modal.title')}} opened={props.opened || opened} onClose={() => setOpened(false)} > - + void } & const { setOpened } = props; const { config, setConfig } = useConfig(); const [isLoading, setLoading] = useState(false); + const { t } = useTranslation('layout/add-service-app-shelf'); // Extract all the categories from the services in config const InitialCategories = config.services.reduce((acc, cur) => { @@ -120,13 +123,13 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & try { const _isValid = new URL(value); } catch (e) { - return 'Please enter a valid URL'; + return t('modal.form.validation.invalidUrl'); } return null; }, status: (value: string[]) => { if (!value.length) { - return 'Please select a status code'; + return t('modal.form.validation.noStatusCodeSelected'); } return null; }, @@ -203,48 +206,48 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & > - Options - Advanced options + {t('modal.tabs.options.title')} + {t('modal.tabs.advancedOptions.title')} void } & setCategories([...InitialCategories, query]); return item; }} - getCreateLabel={(query) => `+ Create "${query}"`} + getCreateLabel={(query) => + t('modal.tabs.options.form.category.createLabel', { + query, + }) + } {...form.getInputProps('category')} /> @@ -266,23 +273,26 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & <> { form.setFieldValue('apiKey', event.currentTarget.value); }} - error={form.errors.apiKey && 'Invalid API key'} + error={ + form.errors.apiKey && + t('modal.tabs.options.form.integrations.apiKey.validation.noKey') + } /> - Get your API key{' '} + {t('modal.tabs.options.form.integrations.apiKey.tip.text')}{' '} - here. + {t('modal.tabs.options.form.integrations.apiKey.tip.link')} @@ -291,79 +301,116 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & <> { form.setFieldValue('username', event.currentTarget.value); }} - error={form.errors.username && 'Invalid username'} + error={ + form.errors.username && + t( + 'modal.tabs.options.form.integrations.qBittorrent.username.validation.invalidUsername' + ) + } /> { form.setFieldValue('password', event.currentTarget.value); }} - error={form.errors.password && 'Invalid password'} + error={ + form.errors.password && + t( + 'modal.tabs.options.form.integrations.qBittorrent.password.validation.invalidPassword' + ) + } /> )} {form.values.type === 'Deluge' && ( <> { form.setFieldValue('password', event.currentTarget.value); }} - error={form.errors.password && 'Invalid password'} + error={ + form.errors.password && + t( + 'modal.tabs.options.form.integrations.deluge.password.validation.invalidPassword' + ) + } /> )} {form.values.type === 'Transmission' && ( <> { form.setFieldValue('username', event.currentTarget.value); }} - error={form.errors.username && 'Invalid username'} + error={ + form.errors.username && + t( + 'modal.tabs.options.form.integrations.transmission.username.validation.invalidUsername' + ) + } /> { form.setFieldValue('password', event.currentTarget.value); }} - error={form.errors.password && 'Invalid password'} + error={ + form.errors.password && + t( + 'modal.tabs.options.form.integrations.transmission.password.validation.invalidPassword' + ) + } /> )} - + @@ -371,7 +418,9 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & - + diff --git a/src/components/AppShelf/AppShelf.tsx b/src/components/AppShelf/AppShelf.tsx index 2da1274e6..51aed451e 100644 --- a/src/components/AppShelf/AppShelf.tsx +++ b/src/components/AppShelf/AppShelf.tsx @@ -11,6 +11,7 @@ import { } from '@dnd-kit/core'; import { arrayMove, SortableContext } from '@dnd-kit/sortable'; import { useLocalStorage } from '@mantine/hooks'; +import { useTranslation } from 'next-i18next'; import { useConfig } from '../../tools/state'; import { SortableAppShelfItem, AppShelfItem } from './AppShelfItem'; @@ -36,6 +37,8 @@ const AppShelf = (props: any) => { const [activeId, setActiveId] = useState(null); const { colorScheme } = useMantineColorScheme(); + const { t } = useTranslation('layout/app-shelf'); + const sensors = useSensors( useSensor(TouchSensor, { activationConstraint: { @@ -123,7 +126,7 @@ const AppShelf = (props: any) => { const noCategory = config.services.filter( (e) => e.category === undefined || e.category === null ); - const downloadEnabled = config.modules?.[DownloadsModule.title]?.enabled ?? false; + const downloadEnabled = config.modules?.[DownloadsModule.id]?.enabled ?? false; // Create an item with 0: true, 1: true, 2: true... For each category return ( // TODO: Style accordion so that the bar is transparent to the user settings @@ -147,13 +150,13 @@ const AppShelf = (props: any) => { {/* Return the item for all services without category */} {noCategory && noCategory.length > 0 ? ( - Other + {t('accordions.others.text')} {getItems()} ) : null} {downloadEnabled ? ( - Your downloads + {t('accordions.downloads.text')} @@ -20,9 +21,9 @@ export default function AppShelfMenu(props: any) { radius="md" opened={props.opened || opened} onClose={() => setOpened(false)} - title="Modify a service" + title={t('modal.title')} > - + - Settings + {t('menu.labels.settings')} } onClick={() => setOpened(true)}> - Edit + {t('menu.actions.edit')} - Danger zone + {t('menu.labels.dangerZone')} { @@ -70,7 +71,7 @@ export default function AppShelfMenu(props: any) { }} icon={} > - Delete + {t('menu.actions.delete')} diff --git a/src/components/ColorSchemeToggle/ColorSchemeSwitch.tsx b/src/components/ColorSchemeToggle/ColorSchemeSwitch.tsx index 410dcbaf2..076db44ee 100644 --- a/src/components/ColorSchemeToggle/ColorSchemeSwitch.tsx +++ b/src/components/ColorSchemeToggle/ColorSchemeSwitch.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { createStyles, Switch, Group, useMantineColorScheme, Kbd } from '@mantine/core'; import { IconSun as Sun, IconMoonStars as MoonStars } from '@tabler/icons'; +import { useTranslation } from 'next-i18next'; import { useConfig } from '../../tools/state'; const useStyles = createStyles((theme) => ({ @@ -33,6 +34,7 @@ export function ColorSchemeSwitch() { const { config } = useConfig(); const { colorScheme, toggleColorScheme } = useMantineColorScheme(); const { classes, cx } = useStyles(); + const { t } = useTranslation('settings/general/theme-selector'); return ( @@ -41,7 +43,9 @@ export function ColorSchemeSwitch() { toggleColorScheme()} size="md" /> - Switch to {colorScheme === 'dark' ? 'light' : 'dark'} mode + {t('label', { + theme: colorScheme === 'dark' ? 'light' : 'dark', + })} Ctrl+J diff --git a/src/components/Config/ConfigChanger.tsx b/src/components/Config/ConfigChanger.tsx index b2c5a3898..0e129bd2c 100644 --- a/src/components/Config/ConfigChanger.tsx +++ b/src/components/Config/ConfigChanger.tsx @@ -1,5 +1,6 @@ import { Center, Loader, Select, Tooltip } from '@mantine/core'; import { setCookie } from 'cookies-next'; +import { useTranslation } from 'next-i18next'; import { useEffect, useState } from 'react'; import { useConfig } from '../../tools/state'; @@ -7,6 +8,8 @@ export default function ConfigChanger() { const { config, loadConfig, setConfig, getConfigs } = useConfig(); const [configList, setConfigList] = useState([]); const [value, setValue] = useState(config.name); + const { t } = useTranslation('settings/general/config-changer'); + useEffect(() => { getConfigs().then((configs) => setConfigList(configs)); }, [config]); @@ -23,7 +26,7 @@ export default function ConfigChanger() { // return { diff --git a/src/components/Config/LoadConfig.tsx b/src/components/Config/LoadConfig.tsx index 75d6972fa..a41f0e027 100644 --- a/src/components/Config/LoadConfig.tsx +++ b/src/components/Config/LoadConfig.tsx @@ -3,6 +3,7 @@ import { IconX as X, IconCheck as Check, IconX, IconPhoto, IconUpload } from '@t import { showNotification } from '@mantine/notifications'; import { setCookie } from 'cookies-next'; import { Dropzone } from '@mantine/dropzone'; +import { useTranslation } from 'next-i18next'; import { useConfig } from '../../tools/state'; import { Config } from '../../tools/types'; import { migrateToIdConfig } from '../../tools/migrate'; @@ -10,6 +11,7 @@ import { migrateToIdConfig } from '../../tools/migrate'; export default function LoadConfigComponent(props: any) { const { setConfig } = useConfig(); const theme = useMantineTheme(); + const { t } = useTranslation('settings/general/config-changer'); return ( Error, + title: {t('dropzone.notifications.invalidConfig.title')}, color: 'red', icon: , - message: 'could not load your config. Invalid JSON format.', + message: t('dropzone.notifications.invalidConfig.message'), }); return; } @@ -33,7 +35,9 @@ export default function LoadConfigComponent(props: any) { radius: 'md', title: ( - Config {newConfig.name} loaded successfully + {t('dropzone.notifications.loadedSuccessfully.title', { + configName: newConfig.name, + })} ), color: 'green', @@ -58,7 +62,7 @@ export default function LoadConfigComponent(props: any) { stroke={1.5} color={theme.colors[theme.primaryColor][theme.colorScheme === 'dark' ? 4 : 6]} /> - Drag files here to upload a config. Support for JSON only. + {t('dropzone.accept.text')} @@ -68,7 +72,7 @@ export default function LoadConfigComponent(props: any) { stroke={1.5} color={theme.colors.red[theme.colorScheme === 'dark' ? 4 : 6]} /> - This file format is not supported. Please only upload JSON. + {t('dropzone.reject.text')} diff --git a/src/components/Config/SaveConfig.tsx b/src/components/Config/SaveConfig.tsx index d18b38ae2..327f50523 100644 --- a/src/components/Config/SaveConfig.tsx +++ b/src/components/Config/SaveConfig.tsx @@ -4,6 +4,7 @@ import { showNotification } from '@mantine/notifications'; import axios from 'axios'; import fileDownload from 'js-file-download'; import { useState } from 'react'; +import { useTranslation } from 'next-i18next'; import { IconCheck as Check, IconDownload as Download, @@ -16,6 +17,7 @@ import { useConfig } from '../../tools/state'; export default function SaveConfigComponent(props: any) { const [opened, setOpened] = useState(false); const { config, setConfig } = useConfig(); + const { t } = useTranslation('settings/general/config-changer'); const form = useForm({ initialValues: { configName: config.name, @@ -28,39 +30,34 @@ export default function SaveConfigComponent(props: any) { } return ( - setOpened(false)} - title="Choose the name of your new config" - > + setOpened(false)} title={t('modal.title')}>
{ setConfig({ ...config, name: values.configName }); setOpened(false); showNotification({ - title: 'Config saved', + title: t('modal.events.configSaved.title'), icon: , color: 'green', autoClose: 1500, radius: 'md', - message: `Config saved as ${values.configName}`, + message: t('modal.events.configSaved.message', { configName: values.configName }), }); })} > - +
); diff --git a/src/components/Settings/AdvancedSettings.tsx b/src/components/Settings/AdvancedSettings.tsx index 877fc79c7..552e5b483 100644 --- a/src/components/Settings/AdvancedSettings.tsx +++ b/src/components/Settings/AdvancedSettings.tsx @@ -1,5 +1,6 @@ import { TextInput, Button, Stack } from '@mantine/core'; import { useForm } from '@mantine/form'; +import { useTranslation } from 'next-i18next'; import { useConfig } from '../../tools/state'; import { ColorSelector } from './ColorSelector'; import { OpacitySelector } from './OpacitySelector'; @@ -8,6 +9,7 @@ import { ShadeSelector } from './ShadeSelector'; export default function TitleChanger() { const { config, setConfig } = useConfig(); + const { t } = useTranslation('settings/customization/page-appearance'); const form = useForm({ initialValues: { @@ -40,19 +42,27 @@ export default function TitleChanger() {
saveChanges(values))}> - - + + - +
diff --git a/src/components/Settings/AppCardWidthSelector.tsx b/src/components/Settings/AppCardWidthSelector.tsx index 26b453cd3..a29ecc18a 100644 --- a/src/components/Settings/AppCardWidthSelector.tsx +++ b/src/components/Settings/AppCardWidthSelector.tsx @@ -1,9 +1,11 @@ import React from 'react'; import { Text, Slider, Stack } from '@mantine/core'; +import { useTranslation } from 'next-i18next'; import { useConfig } from '../../tools/state'; export function AppCardWidthSelector() { const { config, setConfig } = useConfig(); + const { t } = useTranslation('settings/customization/app-width'); const setappCardWidth = (appCardWidth: number) => { setConfig({ @@ -17,7 +19,7 @@ export function AppCardWidthSelector() { return ( - App Width + {t('label')} ({ @@ -82,7 +83,11 @@ export function ColorSelector({ type }: ColorControlProps) { - {type[0].toUpperCase() + type.slice(1)} color + + {t('suffix', { + color: type[0].toUpperCase() + type.slice(1), + })} +
); } diff --git a/src/components/Settings/CommonSettings.tsx b/src/components/Settings/CommonSettings.tsx index 739f65869..45adbb735 100644 --- a/src/components/Settings/CommonSettings.tsx +++ b/src/components/Settings/CommonSettings.tsx @@ -1,5 +1,6 @@ import { Text, SegmentedControl, TextInput, Stack } from '@mantine/core'; import { useState } from 'react'; +import { useTranslation } from 'next-i18next'; import { useConfig } from '../../tools/state'; import { ColorSchemeSwitch } from '../ColorSchemeToggle/ColorSchemeSwitch'; import { WidgetsPositionSwitch } from '../WidgetsPositionSwitch/WidgetsPositionSwitch'; @@ -7,9 +8,11 @@ import ConfigChanger from '../Config/ConfigChanger'; import SaveConfigComponent from '../Config/SaveConfig'; import ModuleEnabler from './ModuleEnabler'; import Tip from '../layout/Tip'; +import LanguageSwitch from './LanguageSwitch'; export default function CommonSettings(args: any) { const { config, setConfig } = useConfig(); + const { t } = useTranslation(['settings/general/search-engine', 'settings/common']); const matches = [ { label: 'Google', value: 'https://google.com/search?q=' }, @@ -26,15 +29,12 @@ export default function CommonSettings(args: any) { return ( - Search engine - - Use the prefixes !yt and !t in front of your query to search on YouTube or - for a Torrent respectively. - + {t('title')} + {t('tips.generalTip')} {searchUrl === 'Custom' && ( <> - %s can be used as a placeholder for the query. + {t('tips.placeholderTip')} { setCustomSearchUrl(event.currentTarget.value); @@ -78,9 +78,10 @@ export default function CommonSettings(args: any) { + - Upload your config file by dragging and dropping it onto the page! + {t('settings/common:tips.configTip')} ); } diff --git a/src/components/Settings/Credits.tsx b/src/components/Settings/Credits.tsx index 73778b0d5..641889879 100644 --- a/src/components/Settings/Credits.tsx +++ b/src/components/Settings/Credits.tsx @@ -1,8 +1,12 @@ import { Group, ActionIcon, Anchor, Text } from '@mantine/core'; import { IconBrandDiscord, IconBrandGithub } from '@tabler/icons'; +import { useTranslation } from 'next-i18next'; + import { CURRENT_VERSION } from '../../../data/constants'; export default function Credits(props: any) { + const { t } = useTranslation('settings/common'); + return ( @@ -27,7 +31,7 @@ export default function Credits(props: any) { color: 'gray', }} > - Made with ❤️ by @ + {t('credits.madeWithLove')} ( + (configLocale as string) ?? locale + ); + + const data = locales + ? locales.map((localeItem) => ({ + value: localeItem, + label: getLanguageByCode(localeItem).originalName, + image: `imgs/flags/${localeItem}.png`, + language: getLanguageByCode(localeItem), + })) + : []; + + const onChangeSelect = (value: string) => { + setSelectedLanguage(value); + + const newLanguage = getLanguageByCode(value); + changeLanguage(value) + .then(() => { + setCookie('config-locale', value, { + maxAge: 60 * 60 * 24 * 30, + sameSite: 'strict', + }); + + showNotification({ + title: 'Language changed', + message: `You changed the language to '${newLanguage.originalName}'`, + color: 'green', + autoClose: 5000, + }); + }) + .catch((err) => { + showNotification({ + title: 'Failed to change language', + message: `Failed to change to '${newLanguage.originalName}', Error:'${err}`, + color: 'red', + autoClose: 5000, + }); + }); + }; + + return ( + +