From 57cfb58c0b3b875dda1002cfb2f0e824a6dd9034 Mon Sep 17 00:00:00 2001 From: Manuel Ruwe Date: Thu, 18 Aug 2022 20:20:18 +0200 Subject: [PATCH 01/11] =?UTF-8?q?=F0=9F=93=A6=EF=B8=8F=20add=20i18n=20tran?= =?UTF-8?q?slation=20packages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 4 ++ yarn.lock | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 117 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 75482c3b5..c1f504a8d 100644 --- a/package.json +++ b/package.json @@ -53,11 +53,15 @@ "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", "prism-react-renderer": "^1.3.5", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-i18next": "^11.18.4", "sharp": "^0.30.7", "systeminformation": "^5.12.1", "uuid": "^8.3.2", diff --git a/yarn.lock b/yarn.lock index 94f2ddb3f..0491e5fdc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -364,7 +364,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.9, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7": +"@babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.9, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7": version: 7.18.9 resolution: "@babel/runtime@npm:7.18.9" dependencies: @@ -3139,6 +3139,15 @@ __metadata: languageName: node linkType: hard +"cross-fetch@npm:3.1.5": + version: 3.1.5 + resolution: "cross-fetch@npm:3.1.5" + dependencies: + node-fetch: 2.6.7 + checksum: f6b8c6ee3ef993ace6277fd789c71b6acf1b504fd5f5c7128df4ef2f125a429e29cd62dc8c127523f04a5f2fa4771ed80e3f3d9695617f441425045f505cf3bb + languageName: node + linkType: hard + "cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": version: 7.0.3 resolution: "cross-spawn@npm:7.0.3" @@ -4732,6 +4741,9 @@ __metadata: eslint-plugin-testing-library: ^5.5.1 eslint-plugin-unused-imports: ^2.0.0 framer-motion: ^6.5.1 + i18next: ^21.9.1 + i18next-browser-languagedetector: ^6.1.5 + i18next-http-backend: ^1.4.1 jest: ^28.1.3 js-file-download: ^0.4.12 next: 12.1.6 @@ -4739,6 +4751,7 @@ __metadata: prism-react-renderer: ^1.3.5 react: ^18.2.0 react-dom: ^18.2.0 + react-i18next: ^11.18.4 sharp: ^0.30.7 systeminformation: ^5.12.1 typescript: ^4.7.4 @@ -4764,6 +4777,15 @@ __metadata: languageName: node linkType: hard +"html-parse-stringify@npm:^3.0.1": + version: 3.0.1 + resolution: "html-parse-stringify@npm:3.0.1" + dependencies: + void-elements: 3.1.0 + checksum: 334fdebd4b5c355dba8e95284cead6f62bf865a2359da2759b039db58c805646350016d2017875718bc3c4b9bf81a0d11be5ee0cf4774a3a5a7b97cde21cfd67 + languageName: node + linkType: hard + "html-react-parser@npm:1.4.12": version: 1.4.12 resolution: "html-react-parser@npm:1.4.12" @@ -4859,6 +4881,33 @@ __metadata: languageName: node linkType: hard +"i18next-browser-languagedetector@npm:^6.1.5": + version: 6.1.5 + resolution: "i18next-browser-languagedetector@npm:6.1.5" + dependencies: + "@babel/runtime": ^7.18.9 + checksum: af3a785454dfb7e3595215c3738062b82173601a788b0a7c6fe78b6c50f0f46cd89508300f694c324d4a07b19743d2410be50aa6e6494967a75aedb17ad5033f + languageName: node + linkType: hard + +"i18next-http-backend@npm:^1.4.1": + version: 1.4.1 + resolution: "i18next-http-backend@npm:1.4.1" + dependencies: + cross-fetch: 3.1.5 + checksum: 1ed4c68c458cc5e7c60af3b641223b9f1b49b6e7ded0fb908cf034ddf62de401db9bb8bb0f6be0634c53ceeee0fec7e03e7171b0dea2cbebca5bbcee6da46e2f + languageName: node + linkType: hard + +"i18next@npm:^21.9.1": + version: 21.9.1 + resolution: "i18next@npm:21.9.1" + dependencies: + "@babel/runtime": ^7.17.2 + checksum: 1bc59c61fbb27385841f76436c7dd60e9f42a3fb326797db44a65dd165c489420e549b5370e3de75b85f8d61239f4869fc9fbcf63deae5f40ee606bc04916e6d + languageName: node + linkType: hard + "iconv-lite@npm:^0.6.2": version: 0.6.3 resolution: "iconv-lite@npm:0.6.3" @@ -6286,6 +6335,20 @@ __metadata: languageName: node linkType: hard +"node-fetch@npm:2.6.7": + version: 2.6.7 + resolution: "node-fetch@npm:2.6.7" + dependencies: + whatwg-url: ^5.0.0 + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: 8d816ffd1ee22cab8301c7756ef04f3437f18dace86a1dae22cf81db8ef29c0bf6655f3215cb0cdb22b420b6fe141e64b26905e7f33f9377a7fa59135ea3e10b + languageName: node + linkType: hard + "node-gyp@npm:latest": version: 9.1.0 resolution: "node-gyp@npm:9.1.0" @@ -6862,6 +6925,24 @@ __metadata: languageName: node linkType: hard +"react-i18next@npm:^11.18.4": + version: 11.18.4 + resolution: "react-i18next@npm:11.18.4" + dependencies: + "@babel/runtime": ^7.14.5 + html-parse-stringify: ^3.0.1 + peerDependencies: + i18next: ">= 19.0.0" + react: ">= 16.8.0" + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + checksum: c64546e22447410cb09020156d86a35cc672f5e34899155456068a7caedfb70dacd70e3619e671ebc831b11d0ca95c1de1c56897842fd1de401a87b86d040beb + languageName: node + linkType: hard + "react-is@npm:^16.13.1, react-is@npm:^16.7.0": version: 16.13.1 resolution: "react-is@npm:16.13.1" @@ -7765,6 +7846,13 @@ __metadata: languageName: node linkType: hard +"tr46@npm:~0.0.3": + version: 0.0.3 + resolution: "tr46@npm:0.0.3" + checksum: 726321c5eaf41b5002e17ffbd1fb7245999a073e8979085dacd47c4b4e8068ff5777142fc6726d6ca1fd2ff16921b48788b87225cbc57c72636f6efa8efbffe3 + languageName: node + linkType: hard + "tsconfig-paths@npm:^3.14.1": version: 3.14.1 resolution: "tsconfig-paths@npm:3.14.1" @@ -7997,6 +8085,13 @@ __metadata: languageName: node linkType: hard +"void-elements@npm:3.1.0": + version: 3.1.0 + resolution: "void-elements@npm:3.1.0" + checksum: 0390f818107fa8fce55bb0a5c3f661056001c1d5a2a48c28d582d4d847347c2ab5b7f8272314cac58acf62345126b6b09bea623a185935f6b1c3bbce0dfd7f7f + languageName: node + linkType: hard + "walker@npm:^1.0.8": version: 1.0.8 resolution: "walker@npm:1.0.8" @@ -8013,6 +8108,13 @@ __metadata: languageName: node linkType: hard +"webidl-conversions@npm:^3.0.0": + version: 3.0.1 + resolution: "webidl-conversions@npm:3.0.1" + checksum: c92a0a6ab95314bde9c32e1d0a6dfac83b578f8fa5f21e675bc2706ed6981bc26b7eb7e6a1fab158e5ce4adf9caa4a0aee49a52505d4d13c7be545f15021b17c + languageName: node + linkType: hard + "webpack-bundle-analyzer@npm:4.3.0": version: 4.3.0 resolution: "webpack-bundle-analyzer@npm:4.3.0" @@ -8032,6 +8134,16 @@ __metadata: languageName: node linkType: hard +"whatwg-url@npm:^5.0.0": + version: 5.0.0 + resolution: "whatwg-url@npm:5.0.0" + dependencies: + tr46: ~0.0.3 + webidl-conversions: ^3.0.0 + checksum: b8daed4ad3356cc4899048a15b2c143a9aed0dfae1f611ebd55073310c7b910f522ad75d727346ad64203d7e6c79ef25eafd465f4d12775ca44b90fa82ed9e2c + languageName: node + linkType: hard + "which-boxed-primitive@npm:^1.0.2": version: 1.0.2 resolution: "which-boxed-primitive@npm:1.0.2" From ac4dc23e0808e6fea4cf75c4e096f4d33a94849c Mon Sep 17 00:00:00 2001 From: Manuel Ruwe Date: Thu, 18 Aug 2022 21:46:46 +0200 Subject: [PATCH 02/11] =?UTF-8?q?=F0=9F=9A=A7=20wip=20extract=20to=20trans?= =?UTF-8?q?lations=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/de-de.json | 385 ++++++++++++++++++ public/locales/en-us.json | 385 ++++++++++++++++++ src/components/AppShelf/AddAppShelfItem.tsx | 180 ++++++-- .../ColorSchemeToggle/ColorSchemeSwitch.tsx | 5 +- src/components/Config/ConfigChanger.tsx | 3 +- src/components/Config/SaveConfig.tsx | 35 +- src/components/Settings/AdvancedSettings.tsx | 23 +- src/components/Settings/ColorSelector.tsx | 7 +- src/components/Settings/CommonSettings.tsx | 20 +- src/components/Settings/Credits.tsx | 4 +- src/components/Settings/LanguageSwitch.tsx | 78 ++++ src/components/Settings/ModuleEnabler.tsx | 3 +- src/components/Settings/OpacitySelector.tsx | 3 +- src/components/Settings/SettingsMenu.tsx | 10 +- src/components/Settings/ShadeSelector.tsx | 3 +- .../WidgetsPositionSwitch.tsx | 3 +- src/modules/calendar/CalendarModule.tsx | 8 +- src/modules/common/MediaDisplay.tsx | 5 +- src/modules/dashdot/DashdotModule.tsx | 37 +- src/modules/docker/DockerModule.tsx | 8 +- src/modules/docker/DockerTable.tsx | 16 +- src/modules/downloads/DownloadsModule.tsx | 15 +- src/modules/overseerr/RequestModal.tsx | 31 +- src/modules/ping/PingModule.tsx | 7 +- src/modules/search/SearchModule.tsx | 4 +- src/modules/weather/WeatherModule.tsx | 35 +- src/pages/_app.tsx | 7 +- src/providers/translation.provider.tsx | 16 + src/translations/i18n.ts | 36 ++ 29 files changed, 1216 insertions(+), 156 deletions(-) create mode 100644 public/locales/de-de.json create mode 100644 public/locales/en-us.json create mode 100644 src/components/Settings/LanguageSwitch.tsx create mode 100644 src/providers/translation.provider.tsx create mode 100644 src/translations/i18n.ts diff --git a/public/locales/de-de.json b/public/locales/de-de.json new file mode 100644 index 000000000..331c956e6 --- /dev/null +++ b/public/locales/de-de.json @@ -0,0 +1,385 @@ +{ + "settings": { + "title": "Einstellungen", + "tooltip": "Einstellungen", + "tabs": { + "common": { + "title": "Allgemein", + "settings": { + "searchEngine": { + "title": "Suchmaschine", + "tips": { + "generalTip": "Benutze die Prefixe !yt und !t vor deiner Suchanfrage um auf Youtube oder nach einem Torrent zu suchen.", + "placeholderTip": "%s can be used as a placeholder for the query." + }, + "customEngine": { + "label": "Suchadresse", + "placeholder": "Benutzerdefinierte Adresse" + } + }, + "colorScheme": { + "label": "Wechseln zu {{scheme}} Modus" + }, + "widgetsPositionSwitch": { + "label": "Positionieren von Widgets auf linker Seite" + }, + "moduleEnabler": { + "title": "Modul Enabler" + }, + "language": { + "title": "Sprache" + }, + "configChanger": { + "configSelect": { + "label": "Konfigurations Lader" + }, + "modal": { + "title": "Choose the name of your new config", + "form": { + "configName": { + "label": "Config name", + "placeholder": "Your new config name" + }, + "buttons:": { + "submit": "Confirm" + } + } + }, + "buttons": { + "download": "Konfiguration herunterladen", + "delete": { + "text": "Konfiguration löschen", + "notifications": { + "deleted": { + "title": "Config deleted", + "message": "Config deleted" + }, + "deleteFailed": { + "title": "Config delete failed", + "message": "Config delete failed" + } + } + }, + "saveCopy": "Kopie speichern" + } + }, + "configTip": "Lade deine Konfiguration hoch, indem du sie per drag-and-drop auf die Seite ziehst." + } + }, + "customizations": { + "title": "Anpassungen", + "settings": { + "opacitySelector": { + "label": "App Opacity" + }, + "colorSelector": { + "suffix": "{{color}} color" + }, + "shadeSelector": { + "label": "Shade" + }, + "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": "Save" + } + } + } + }, + "credits": { + "madeWithLove": "Gemacht mit ❤️ von @" + } + }, + "layout": { + "header": { + "search": { + "input": { + "placeholder": "Search the web..." + } + }, + "docker": { + "errors": { + "integrationFailed": { + "title": "Docker integration failed", + "message": "Did you forget to mount the docker socket ?" + } + }, + "actionIcon": { + "tooltip": "Docker" + } + }, + "addService": { + "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" + } + } + } + } + } + } + } + } + }, + "modules": { + "common": { + "mediaCard": { + "buttons": { + "play": "Play", + "request": "Request" + } + } + }, + "calendar": { + "title": "Calendar", + "description": "A calendar module for displaying upcoming releases. It interacts with the Sonarr and Radarr API.", + "options": { + "sundayStart": "Start the week on Sunday" + } + }, + "dashDot": { + "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" + } + } + } + }, + "torrent": { + "card": { + "title": "Your Downloads" + } + }, + "downloads": { + "card": { + "table": { + "header": { + "name": "Name", + "size": "Size", + "download": "Down", + "upload": "Up", + "estimatedTimeOfArrival": "ETA", + "progress": "Progress" + }, + "body": { + "nothingFound": "No torrents found" + } + } + } + }, + "weather": { + "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" + } + } + }, + "overseerr": { + "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" + } + } + } + } + }, + "ping": { + "states": { + "online": "Online {{response}}", + "offline": "Offline {{response}}", + "loading": "Loading..." + } + }, + "docker": { + "search": { + "placeholder": "Search by container or image name" + }, + "table": { + "header": { + "name": "Name", + "image": "Image", + "ports": "Ports", + "state": "State" + }, + "body": { + "portCollapse": "{{ports}} more" + } + } + } + } +} \ No newline at end of file diff --git a/public/locales/en-us.json b/public/locales/en-us.json new file mode 100644 index 000000000..142283853 --- /dev/null +++ b/public/locales/en-us.json @@ -0,0 +1,385 @@ +{ + "settings": { + "title": "Settings", + "tooltip": "Settings", + "tabs": { + "common": { + "title": "Common", + "settings": { + "searchEngine": { + "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" + } + }, + "colorScheme": { + "label": "Switch to {{scheme}} mode" + }, + "widgetsPositionSwitch": { + "label": "Position widgets on left" + }, + "moduleEnabler": { + "title": "Module enabler" + }, + "language": { + "title": "Language" + }, + "configChanger": { + "configSelect": { + "label": "Config loader" + }, + "modal": { + "title": "Choose the name of your new config", + "form": { + "configName": { + "label": "Config name", + "placeholder": "Your new config name" + }, + "buttons:": { + "submit": "Confirm" + } + } + }, + "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" + } + }, + "configTip": "Upload your config file by dragging and dropping it onto the page!" + } + }, + "customizations": { + "title": "Customizations", + "settings": { + "opacitySelector": { + "label": "App Opacity" + }, + "colorSelector": { + "suffix": "{{color}} color" + }, + "shadeSelector": { + "label": "Shade" + }, + "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": "Save" + } + } + } + }, + "credits": { + "madeWithLove": "Made with ❤️ by @" + } + }, + "layout": { + "header": { + "search": { + "input": { + "placeholder": "Search the web..." + } + }, + "docker": { + "errors": { + "integrationFailed": { + "title": "Docker integration failed", + "message": "Did you forget to mount the docker socket ?" + } + }, + "actionIcon": { + "tooltip": "Docker" + } + }, + "addService": { + "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" + } + } + } + } + } + } + } + } + }, + "modules": { + "common": { + "mediaCard": { + "buttons": { + "play": "Play", + "request": "Request" + } + } + }, + "calendar": { + "title": "Calendar", + "description": "A calendar module for displaying upcoming releases. It interacts with the Sonarr and Radarr API.", + "options": { + "sundayStart": "Start the week on Sunday" + } + }, + "dashDot": { + "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" + } + } + } + }, + "torrent": { + "card": { + "title": "Your Downloads" + } + }, + "downloads": { + "card": { + "table": { + "header": { + "name": "Name", + "size": "Size", + "download": "Down", + "upload": "Up", + "estimatedTimeOfArrival": "ETA", + "progress": "Progress" + }, + "body": { + "nothingFound": "No torrents found" + } + } + } + }, + "weather": { + "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" + } + } + }, + "overseerr": { + "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" + } + } + } + } + }, + "ping": { + "states": { + "online": "Online {{response}}", + "offline": "Offline {{response}}", + "loading": "Loading..." + } + }, + "docker": { + "search": { + "placeholder": "Search by container or image name" + }, + "table": { + "header": { + "name": "Name", + "image": "Image", + "ports": "Ports", + "state": "State" + }, + "body": { + "portCollapse": "{{ports}} more" + } + } + } + } +} \ No newline at end of file diff --git a/src/components/AppShelf/AddAppShelfItem.tsx b/src/components/AppShelf/AddAppShelfItem.tsx index 9cfc402ba..db163f4bd 100644 --- a/src/components/AppShelf/AddAppShelfItem.tsx +++ b/src/components/AppShelf/AddAppShelfItem.tsx @@ -22,6 +22,7 @@ import { IconApps } from '@tabler/icons'; import { useEffect, useState } from 'react'; import { v4 as uuidv4 } from 'uuid'; import { useDebouncedValue } from '@mantine/hooks'; +import { t } from 'i18next'; import { useConfig } from '../../tools/state'; import { tryMatchPort, ServiceTypeList, StatusCodes } from '../../tools/types'; import Tip from '../layout/Tip'; @@ -33,13 +34,13 @@ export function AddItemShelfButton(props: any) { Add service} + title={{t('layout.header.addService.modal.title')}} opened={props.opened || opened} onClose={() => setOpened(false)} > - + void } & try { const _isValid = new URL(value); } catch (e) { - return 'Please enter a valid URL'; + return t('layout.header.addService.modal.form.validation.invalidUrl'); } return null; }, status: (value: string[]) => { if (!value.length) { - return 'Please select a status code'; + return t('layout.header.addService.modal.form.validation.noStatusCodeSelected'); } return null; }, @@ -203,48 +204,62 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & > - Options - Advanced options + + {t('layout.header.addService.modal.tabs.options.title')} + + + {t('layout.header.addService.modal.tabs.advancedOptions.title')} + void } & setCategories([...InitialCategories, query]); return item; }} - getCreateLabel={(query) => `+ Create "${query}"`} + getCreateLabel={(query) => + t('layout.header.addService.modal.tabs.options.form.category.createLabel', { + query, + }) + } {...form.getInputProps('category')} /> @@ -266,23 +285,36 @@ 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( + 'layout.header.addService.modal.tabs.options.form.integrations.apiKey.validation.noKey' + ) + } /> - Get your API key{' '} + {t( + 'layout.header.addService.modal.tabs.options.form.integrations.apiKey.tip.text' + )}{' '} - here. + {t( + 'layout.header.addService.modal.tabs.options.form.integrations.apiKey.tip.link' + )} @@ -291,79 +323,134 @@ 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( + 'layout.header.addService.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( + 'layout.header.addService.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( + 'layout.header.addService.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( + 'layout.header.addService.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( + 'layout.header.addService.modal.tabs.options.form.integrations.transmission.password.validation.invalidPassword' + ) + } /> )} - + @@ -371,7 +458,10 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & - + diff --git a/src/components/ColorSchemeToggle/ColorSchemeSwitch.tsx b/src/components/ColorSchemeToggle/ColorSchemeSwitch.tsx index 410dcbaf2..b6a68d2ff 100644 --- a/src/components/ColorSchemeToggle/ColorSchemeSwitch.tsx +++ b/src/components/ColorSchemeToggle/ColorSchemeSwitch.tsx @@ -2,6 +2,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 { useConfig } from '../../tools/state'; +import { t } from 'i18next'; const useStyles = createStyles((theme) => ({ root: { @@ -41,7 +42,9 @@ export function ColorSchemeSwitch() { toggleColorScheme()} size="md" /> - Switch to {colorScheme === 'dark' ? 'light' : 'dark'} mode + {t('settings.tabs.common.settings.colorScheme.label', { + scheme: colorScheme === 'dark' ? 'light' : 'dark', + })} Ctrl+J diff --git a/src/components/Config/ConfigChanger.tsx b/src/components/Config/ConfigChanger.tsx index b2c5a3898..7025c79e8 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 { t } from 'i18next'; import { useEffect, useState } from 'react'; import { useConfig } from '../../tools/state'; @@ -23,7 +24,7 @@ export default function ConfigChanger() { // return { diff --git a/src/components/Config/SaveConfig.tsx b/src/components/Config/SaveConfig.tsx index d18b38ae2..fc7f6a266 100644 --- a/src/components/Config/SaveConfig.tsx +++ b/src/components/Config/SaveConfig.tsx @@ -12,6 +12,7 @@ import { IconX as X, } from '@tabler/icons'; import { useConfig } from '../../tools/state'; +import { t } from 'i18next'; export default function SaveConfigComponent(props: any) { const [opened, setOpened] = useState(false); @@ -32,7 +33,7 @@ export default function SaveConfigComponent(props: any) { radius="md" opened={opened} onClose={() => setOpened(false)} - title="Choose the name of your new config" + title={t('settings.tabs.common.settings.configChanger.modal.title')} >
{ @@ -50,17 +51,21 @@ export default function SaveConfigComponent(props: any) { > - + ); diff --git a/src/components/Settings/AdvancedSettings.tsx b/src/components/Settings/AdvancedSettings.tsx index 877fc79c7..ef27a2f45 100644 --- a/src/components/Settings/AdvancedSettings.tsx +++ b/src/components/Settings/AdvancedSettings.tsx @@ -5,6 +5,7 @@ import { ColorSelector } from './ColorSelector'; import { OpacitySelector } from './OpacitySelector'; import { AppCardWidthSelector } from './AppCardWidthSelector'; import { ShadeSelector } from './ShadeSelector'; +import { t } from 'i18next'; export default function TitleChanger() { const { config, setConfig } = useConfig(); @@ -40,19 +41,27 @@ export default function TitleChanger() {
saveChanges(values))}> - - + + - +
diff --git a/src/components/Settings/ColorSelector.tsx b/src/components/Settings/ColorSelector.tsx index 68a762989..084d6de1b 100644 --- a/src/components/Settings/ColorSelector.tsx +++ b/src/components/Settings/ColorSelector.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import { ColorSwatch, Grid, Group, Popover, Text, useMantineTheme } from '@mantine/core'; import { useConfig } from '../../tools/state'; import { useColorTheme } from '../../tools/color'; +import { t } from 'i18next'; interface ColorControlProps { type: string; @@ -82,7 +83,11 @@ export function ColorSelector({ type }: ColorControlProps) { - {type[0].toUpperCase() + type.slice(1)} color + + {t('settings.tabs.customizations.settings.colorSelector.suffix', { + color: type[0].toUpperCase() + type.slice(1), + })} + ); } diff --git a/src/components/Settings/CommonSettings.tsx b/src/components/Settings/CommonSettings.tsx index 739f65869..0258ae95e 100644 --- a/src/components/Settings/CommonSettings.tsx +++ b/src/components/Settings/CommonSettings.tsx @@ -7,6 +7,8 @@ import ConfigChanger from '../Config/ConfigChanger'; import SaveConfigComponent from '../Config/SaveConfig'; import ModuleEnabler from './ModuleEnabler'; import Tip from '../layout/Tip'; +import { t } from 'i18next'; +import LanguageSwitch from './LanguageSwitch'; export default function CommonSettings(args: any) { const { config, setConfig } = useConfig(); @@ -26,15 +28,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('settings.tabs.common.settings.searchEngine.title')} + {t('settings.tabs.common.settings.searchEngine.tips.generalTip')} {searchUrl === 'Custom' && ( <> - %s can be used as a placeholder for the query. + {t('settings.tabs.common.settings.searchEngine.tips.placeholderTip')} { setCustomSearchUrl(event.currentTarget.value); @@ -78,9 +77,10 @@ export default function CommonSettings(args: any) { + - Upload your config file by dragging and dropping it onto the page! + {t('settings.tabs.common.settings.configTip')} ); } diff --git a/src/components/Settings/Credits.tsx b/src/components/Settings/Credits.tsx index 73778b0d5..32cb9f001 100644 --- a/src/components/Settings/Credits.tsx +++ b/src/components/Settings/Credits.tsx @@ -1,5 +1,7 @@ import { Group, ActionIcon, Anchor, Text } from '@mantine/core'; import { IconBrandDiscord, IconBrandGithub } from '@tabler/icons'; +import { t } from 'i18next'; + import { CURRENT_VERSION } from '../../../data/constants'; export default function Credits(props: any) { @@ -27,7 +29,7 @@ export default function Credits(props: any) { color: 'gray', }} > - Made with ❤️ by @ + {t('settings.credits.madeWithLove')} (language); + + const data = languages.map((language) => ({ + image: `https://countryflagsapi.com/png/${language.split('-').pop()}`, + label: convertCodeToName(language), + value: language, + })); + + const onChangeSelect = (value: string) => { + setSelectedLanguage(value); + + const languageName = convertCodeToName(value); + + changeLanguage(value) + .then(() => { + showNotification({ + title: 'Language changed', + message: `You changed the language to '${languageName}'`, + color: 'green', + autoClose: 5000, + }); + }) + .catch((err) => { + showNotification({ + title: 'Failed to change language', + message: `Failed to change to '${languageName}', Error:'${err}`, + color: 'red', + autoClose: 5000, + }); + }); + }; + + return ( + + console.log(e)} value="1" />; return ( } - label={t('settings.tabs.common.settings.language.title')} - data={data} + label={t('label')} + data={[ + { + value: 'uwu', + label: 'asdf', + }, + ]} itemComponent={SelectItem} nothingFound="Nothing found" onChange={onChangeSelect} + /* value={selectedLanguage} defaultValue={language} + */ /> ); diff --git a/src/components/Settings/ModuleEnabler.tsx b/src/components/Settings/ModuleEnabler.tsx index e67c636a3..1600636bc 100644 --- a/src/components/Settings/ModuleEnabler.tsx +++ b/src/components/Settings/ModuleEnabler.tsx @@ -1,14 +1,15 @@ import { Checkbox, SimpleGrid, Stack, Title } from '@mantine/core'; -import { t } from 'i18next'; +import { useTranslation } from 'next-i18next'; import * as Modules from '../../modules'; import { useConfig } from '../../tools/state'; export default function ModuleEnabler(props: any) { const { config, setConfig } = useConfig(); + const { t } = useTranslation('settings/general/module-enabler'); const modules = Object.values(Modules).map((module) => module); return ( - {t('settings.tabs.common.settings.moduleEnabler.title')} + {t('title')} {modules.map((module) => ( - {t('settings.tabs.customizations.settings.opacitySelector.label')} + {t('label')} - {t('settings.tabs.common.title')} - {t('settings.tabs.customizations.title')} + {t('tabs.common')} + {t('tabs.customizations')} @@ -31,22 +33,24 @@ function SettingsMenu(props: any) { export function SettingsMenuButton(props: any) { useHotkeys([['ctrl+L', () => setOpened(!opened)]]); + const { t } = useTranslation('settings/common'); const [opened, setOpened] = useState(false); + return ( <> {t('settings.title')}} + title={{t('title')}} opened={props.opened || opened} onClose={() => setOpened(false)} > - + - {t('settings.tabs.customizations.settings.shadeSelector.label')} + {t('label')} ); } diff --git a/src/components/WidgetsPositionSwitch/WidgetsPositionSwitch.tsx b/src/components/WidgetsPositionSwitch/WidgetsPositionSwitch.tsx index 215ca8a13..e7ccdc2a2 100644 --- a/src/components/WidgetsPositionSwitch/WidgetsPositionSwitch.tsx +++ b/src/components/WidgetsPositionSwitch/WidgetsPositionSwitch.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { createStyles, Switch, Group } from '@mantine/core'; +import { useTranslation } from 'next-i18next'; import { useConfig } from '../../tools/state'; -import { t } from 'i18next'; const useStyles = createStyles((theme) => ({ root: { @@ -34,6 +34,7 @@ export function WidgetsPositionSwitch() { const { classes, cx } = useStyles(); const defaultPosition = config?.settings?.widgetPosition || 'right'; const [widgetPosition, setWidgetPosition] = useState(defaultPosition); + const { t } = useTranslation('settings/general/widget-positions'); const toggleWidgetPosition = () => { const position = widgetPosition === 'right' ? 'left' : 'right'; setWidgetPosition(position); @@ -55,7 +56,7 @@ export function WidgetsPositionSwitch() { size="md" /> - {t('settings.tabs.common.settings.widgetsPositionSwitch.label')} + {t('label')} ); } diff --git a/src/modules/calendar/CalendarModule.tsx b/src/modules/calendar/CalendarModule.tsx index 439a856fa..896faa940 100644 --- a/src/modules/calendar/CalendarModule.tsx +++ b/src/modules/calendar/CalendarModule.tsx @@ -23,16 +23,16 @@ import { } from '../common'; import { serviceItem } from '../../tools/types'; import { useColorTheme } from '../../tools/color'; -import { t } from 'i18next'; export const CalendarModule: IModule = { - title: t('modules.calendar.title'), - description: t('modules.calendar.description'), + title: 'Calendar', + description: + 'A calendar module for displaying upcoming releases. It interacts with the Sonarr and Radarr API.', icon: CalendarIcon, component: CalendarComponent, options: { sundaystart: { - name: t('modules.calendar.options.sundayStart'), + name: 'Start the week on Sunday', value: false, }, }, diff --git a/src/modules/common/MediaDisplay.tsx b/src/modules/common/MediaDisplay.tsx index dcb5fd038..c37d2cd10 100644 --- a/src/modules/common/MediaDisplay.tsx +++ b/src/modules/common/MediaDisplay.tsx @@ -1,6 +1,6 @@ import { Badge, Button, Group, Image, Stack, Text, Title } from '@mantine/core'; import { IconDownload, IconExternalLink, IconPlayerPlay } from '@tabler/icons'; -import { t } from 'i18next'; +import { useTranslation } from 'next-i18next'; import { useState } from 'react'; import { useColorTheme } from '../../tools/color'; import { useConfig } from '../../tools/state'; @@ -160,6 +160,7 @@ export function SonarrMediaDisplay(props: any) { export function MediaDisplay({ media }: { media: IMedia }) { const [opened, setOpened] = useState(false); const { secondaryColor } = useColorTheme(); + const { t } = useTranslation('modules/common-media-cards-module'); return ( @@ -210,7 +211,7 @@ export function MediaDisplay({ media }: { media: IMedia }) { size="sm" rightIcon={} > - {t('modules.common.mediaCard.buttons.play')} + {t('buttons.play')} )} {media.imdbId && ( @@ -250,7 +251,7 @@ export function MediaDisplay({ media }: { media: IMedia }) { size="sm" rightIcon={} > - {t('modules.common.mediaCard.buttons.request')} + {t('buttons.request')} )} diff --git a/src/modules/dashdot/DashdotModule.tsx b/src/modules/dashdot/DashdotModule.tsx index 29aaa7e18..483c5b46f 100644 --- a/src/modules/dashdot/DashdotModule.tsx +++ b/src/modules/dashdot/DashdotModule.tsx @@ -1,7 +1,7 @@ import { createStyles, Stack, Title, useMantineColorScheme, useMantineTheme } from '@mantine/core'; import { IconCalendar as CalendarIcon } from '@tabler/icons'; import axios from 'axios'; -import { t } from 'i18next'; +import { useTranslation } from 'next-i18next'; import { useEffect, useState } from 'react'; import { useConfig } from '../../tools/state'; import { serviceItem } from '../../tools/types'; @@ -142,32 +142,34 @@ export function DashdotComponent() { const totalSize = (info?.storage?.layout as any[])?.reduce((acc, curr) => (curr.size ?? 0) + acc, 0) ?? 0; + const { t } = useTranslation('modules/dashdot-module'); + const graphs = [ { - name: t('modules.dashDot.card.graphs.cpu.title'), + name: t('card.graphs.cpu.title'), enabled: cpuEnabled, params: { multiView: dashConfig?.cpuMultiView?.value ?? false, }, }, { - name: t('modules.dashDot.card.graphs.cpu.title'), + name: t('card.graphs.cpu.title'), enabled: storageEnabled && !isCompact, params: { multiView: dashConfig?.storageMultiView?.value ?? false, }, }, { - name: t('modules.dashDot.card.graphs.memory.title'), + name: t('card.graphs.memory.title'), enabled: ramEnabled, }, { - name: t('modules.dashDot.card.graphs.network.title'), + name: t('card.graphs.network.title'), enabled: networkEnabled, spanTwo: true, }, { - name: t('modules.dashDot.card.graphs.gpu.title'), + name: t('card.graphs.gpu.title'), enabled: gpuEnabled, spanTwo: true, }, @@ -176,26 +178,24 @@ export function DashdotComponent() { if (dashdotUrl === '') { return (
-

{t('modules.dashDot.card.title')}

-

{t('modules.dashDot.card.errors.noService')}

+

{t('card.title')}

+

{t('card.errors.noService')}

); } return (
-

{t('modules.dashDot.card.title')}

+

{t('card.title')}

{!info ? ( -

{t('modules.dashDot.card.errors.noInformation')}

+

{t('card.errors.noInformation')}

) : (
{storageEnabled && isCompact && (
-

- {t('modules.dashDot.card.graphs.storage.label')} -

+

{t('card.graphs.storage.label')}

{((100 * totalUsed) / (totalSize || 1)).toFixed(1)}%{'\n'} {bytePrettyPrint(totalUsed)} / {bytePrettyPrint(totalSize)} @@ -204,15 +204,12 @@ export function DashdotComponent() { )} {networkEnabled && (

-

- {t('modules.dashDot.card.graphs.network.label')} -

+

{t('card.graphs.network.label')}

- {bpsPrettyPrint(info?.network?.speedUp)}{' '} - {t('modules.dashDot.card.graphs.network.metrics.upload')} + {bpsPrettyPrint(info?.network?.speedUp)} {t('card.graphs.network.metrics.upload')} {'\n'} {bpsPrettyPrint(info?.network?.speedDown)} - {t('modules.dashDot.card.graphs.network.metrics.download')} + {t('card.graphs.network.metrics.download')}

)} diff --git a/src/modules/docker/ContainerActionBar.tsx b/src/modules/docker/ContainerActionBar.tsx index 0ae7e2ed1..a12731d9d 100644 --- a/src/modules/docker/ContainerActionBar.tsx +++ b/src/modules/docker/ContainerActionBar.tsx @@ -11,9 +11,10 @@ import { } from '@tabler/icons'; import axios from 'axios'; import Dockerode from 'dockerode'; +import { useState } from 'react'; +import { useTranslation } from 'next-i18next'; import { tryMatchService } from '../../tools/addToHomarr'; import { AddAppShelfItemForm } from '../../components/AppShelf/AddAppShelfItem'; -import { useState } from 'react'; function sendDockerCommand( action: string, @@ -21,6 +22,8 @@ function sendDockerCommand( containerName: string, reload: () => void ) { + const { t } = useTranslation('modules/docker-module'); + showNotification({ id: containerId, loading: true, @@ -34,8 +37,8 @@ function sendDockerCommand( .then((res) => { updateNotification({ id: containerId, - title: `Container ${containerName} ${action}ed`, - message: `Your container was successfully ${action}ed`, + title: t('messages.successfullyExecuted.message', { containerName, action }), + message: t('messages.successfullyExecuted.message', { action }), icon: , autoClose: 2000, }); @@ -44,7 +47,7 @@ function sendDockerCommand( updateNotification({ id: containerId, color: 'red', - title: 'There was an error', + title: t('errors.unknownError.title'), message: err.response.data.reason, autoClose: 2000, }); @@ -61,6 +64,8 @@ export interface ContainerActionBarProps { export default function ContainerActionBar({ selected, reload }: ContainerActionBarProps) { const [opened, setOpened] = useState(false); + const { t } = useTranslation('modules/docker-module'); + return ( setOpened(false)} - title="Add service" + title={t('actionBar.addService.title')} > ); diff --git a/src/modules/docker/ContainerState.tsx b/src/modules/docker/ContainerState.tsx index d5c6b5077..459dcb70f 100644 --- a/src/modules/docker/ContainerState.tsx +++ b/src/modules/docker/ContainerState.tsx @@ -1,4 +1,5 @@ import { Badge, BadgeVariant, MantineSize } from '@mantine/core'; +import { useTranslation } from 'next-i18next'; import Dockerode from 'dockerode'; export interface ContainerStateProps { @@ -7,6 +8,9 @@ export interface ContainerStateProps { export default function ContainerState(props: ContainerStateProps) { const { state } = props; + + const { t } = useTranslation('modules/docker-module'); + const options: { size: MantineSize; radius: MantineSize; @@ -20,28 +24,28 @@ export default function ContainerState(props: ContainerStateProps) { case 'running': { return ( - Running + {t('table.states.running')} ); } case 'created': { return ( - Created + {t('table.states.created')} ); } case 'exited': { return ( - Stopped + {t('table.states.stopped')} ); } default: { return ( - Unknown + {t('table.states.unknown')} ); } diff --git a/src/modules/docker/DockerModule.tsx b/src/modules/docker/DockerModule.tsx index 91920c93a..7ef03daaa 100644 --- a/src/modules/docker/DockerModule.tsx +++ b/src/modules/docker/DockerModule.tsx @@ -1,10 +1,10 @@ -import { ActionIcon, Drawer, Group, LoadingOverlay, Text, Tooltip } from '@mantine/core'; +import { ActionIcon, Drawer, Text, Tooltip } from '@mantine/core'; import axios from 'axios'; import { useEffect, useState } from 'react'; import Docker from 'dockerode'; import { IconBrandDocker, IconX } from '@tabler/icons'; import { showNotification } from '@mantine/notifications'; -import { t } from 'i18next'; +import { useTranslation } from 'next-i18next'; import ContainerActionBar from './ContainerActionBar'; import DockerTable from './DockerTable'; @@ -25,6 +25,8 @@ export default function DockerMenuButton(props: any) { const { config } = useConfig(); const moduleEnabled = config.modules?.[DockerModule.title]?.enabled ?? false; + const { t } = useTranslation('modules/docker-module'); + useEffect(() => { reload(); }, [config.modules]); @@ -44,10 +46,10 @@ export default function DockerMenuButton(props: any) { // Send an Error notification showNotification({ autoClose: 1500, - title: {t('layout.header.docker.errors.integrationFailed.title')}, + title: {t('errors.integrationFailed.title')}, color: 'red', icon: , - message: t('layout.header.docker.errors.integrationFailed.message'), + message: t('errors.integrationFailed.message'), }) ); }, 300); @@ -69,7 +71,7 @@ export default function DockerMenuButton(props: any) { > - + { setContainers(containers); }, [containers]); @@ -83,7 +85,7 @@ export default function DockerTable({ ))} {element.Ports.length > 3 && ( - {t('modules.docker.table.body.portCollapse', { ports: element.Ports.length - 3 })} + {t('table.body.portCollapse', { ports: element.Ports.length - 3 })} )} @@ -98,7 +100,7 @@ export default function DockerTable({ return ( } value={search} @@ -115,10 +117,10 @@ export default function DockerTable({ transitionDuration={0} /> - {t('modules.docker.table.header.name')} - {t('modules.docker.table.header.image')} - {t('modules.docker.table.header.ports')} - {t('modules.docker.table.header.state')} + {t('table.header.name')} + {t('table.header.image')} + {t('table.header.ports')} + {t('table.header.state')} {rows} diff --git a/src/modules/downloads/DownloadsModule.tsx b/src/modules/downloads/DownloadsModule.tsx index a8bdad466..f4f9a8575 100644 --- a/src/modules/downloads/DownloadsModule.tsx +++ b/src/modules/downloads/DownloadsModule.tsx @@ -20,7 +20,7 @@ import { useConfig } from '../../tools/state'; import { AddItemShelfButton } from '../../components/AppShelf/AddAppShelfItem'; import { useSetSafeInterval } from '../../tools/hooks/useSetSafeInterval'; import { humanFileSize } from '../../tools/humanFileSize'; -import { t } from 'i18next'; +import { useTranslation } from 'next-i18next'; export const DownloadsModule: IModule = { title: 'Torrent', @@ -50,6 +50,9 @@ export default function DownloadComponent() { const [torrents, setTorrents] = useState([]); const setSafeInterval = useSetSafeInterval(); const [isLoading, setIsLoading] = useState(true); + + const { t } = useTranslation('modules/downloads-module'); + useEffect(() => { setIsLoading(true); if (downloadServices.length === 0) return; @@ -106,12 +109,12 @@ export default function DownloadComponent() { const DEVICE_WIDTH = 576; const ths = ( - {t('modules.downloads.card.table.header.name')} - {t('modules.downloads.card.table.header.size')} - {width > 576 ? {t('modules.downloads.card.table.header.download')} : ''} - {width > 576 ? {t('modules.downloads.card.table.header.upload')} : ''} - {t('modules.downloads.card.table.header.estimatedTimeOfArrival')} - {t('modules.downloads.card.table.header.progress')} + {t('card.table.header.name')} + {t('card.table.header.size')} + {width > 576 ? {t('card.table.header.download')} : ''} + {width > 576 ? {t('card.table.header.upload')} : ''} + {t('card.table.header.estimatedTimeOfArrival')} + {t('card.table.header.progress')} ); // Convert Seconds to readable format. @@ -196,7 +199,7 @@ export default function DownloadComponent() { ) : (
- {t('modules.downloads.card.table.body.nothingFound')} + {t('card.table.body.nothingFound')}
)}
diff --git a/src/modules/overseerr/RequestModal.tsx b/src/modules/overseerr/RequestModal.tsx index ffa06c5a6..e632290f8 100644 --- a/src/modules/overseerr/RequestModal.tsx +++ b/src/modules/overseerr/RequestModal.tsx @@ -3,7 +3,7 @@ import { showNotification, updateNotification } from '@mantine/notifications'; import { IconAlertCircle, IconCheck, IconDownload } from '@tabler/icons'; import axios from 'axios'; import Consola from 'consola'; -import { t } from 'i18next'; +import { useTranslation } from 'next-i18next'; import { useState } from 'react'; import { useColorTheme } from '../../tools/color'; @@ -28,6 +28,7 @@ const useStyles = createStyles((theme) => ({ export function RequestModal({ base, opened, setOpened }: RequestModalProps) { const [result, setResult] = useState(); const { secondaryColor } = useColorTheme(); + function getResults(base: Result) { axios.get(`/api/modules/overseerr/${base.id}?type=${base.mediaType}`).then((res) => { setResult(res.data); @@ -56,6 +57,8 @@ export function MovieRequestModal({ setOpened: (opened: boolean) => void; }) { const { secondaryColor } = useColorTheme(); + const { t } = useTranslation('modules/overseerr-module'); + return ( setOpened(false)} @@ -68,23 +71,23 @@ export function MovieRequestModal({ title={ - {t('modules.overseerr.popup.item.buttons.askFor', { title: result.title })} + {t('popup.item.buttons.askFor', { title: result.title })} } > } - title={t('modules.overseerr.popup.item.alerts.automaticApproval.title')} + title={t('popup.item.alerts.automaticApproval.title')} color={secondaryColor} radius="md" variant="filled" > - {t('modules.overseerr.popup.item.alerts.automaticApproval.text')} + {t('popup.item.alerts.automaticApproval.text')} @@ -111,6 +114,7 @@ export function TvRequestModal({ }) { const [selection, setSelection] = useState(result.seasons); const { classes, cx } = useStyles(); + const { t } = useTranslation('modules/overseerr-module'); const toggleRow = (container: TvShowResultSeason) => setSelection((current: TvShowResultSeason[]) => @@ -149,7 +153,7 @@ export function TvRequestModal({ title={ - {t('modules.overseerr.popup.item.buttons.askFor', { + {t('popup.item.buttons.askFor', { title: result.name ?? result.originalName ?? 'a TV show', })} @@ -158,15 +162,15 @@ export function TvRequestModal({ } - title={t('modules.overseerr.popup.item.alerts.automaticApproval.title')} + title={t('popup.item.alerts.automaticApproval.title')} color={secondaryColor} radius="md" variant="filled" > - {t('modules.overseerr.popup.item.alerts.automaticApproval.text')} + {t('popup.item.alerts.automaticApproval.text')} - + - - + + {rows}
{t('modules.overseerr.popup.seasonSelector.caption')}{t('popup.seasonSelector.caption')}
@@ -177,15 +181,15 @@ export function TvRequestModal({ transitionDuration={0} /> {t('modules.overseerr.popup.seasonSelector.table.header.season')}{t('modules.overseerr.popup.seasonSelector.table.header.numberOfEpisodes')}{t('popup.seasonSelector.table.header.season')}{t('popup.seasonSelector.table.header.numberOfEpisodes')}
diff --git a/src/modules/ping/PingModule.tsx b/src/modules/ping/PingModule.tsx index f8ab442fe..fd7468124 100644 --- a/src/modules/ping/PingModule.tsx +++ b/src/modules/ping/PingModule.tsx @@ -5,7 +5,7 @@ import { useEffect, useState } from 'react'; import { IconPlug as Plug } from '@tabler/icons'; import { useConfig } from '../../tools/state'; import { IModule } from '../ModuleTypes'; -import { t } from 'i18next'; +import { useTranslation } from 'next-i18next'; export const PingModule: IModule = { title: 'Ping Services', @@ -23,6 +23,8 @@ export default function PingComponent(props: any) { const [response, setResponse] = useState(500); const exists = config.modules?.[PingModule.title]?.enabled ?? false; + const { t } = useTranslation('modules/ping-module'); + function statusCheck(response: AxiosResponse) { const { status }: { status: string[] } = props; //Default Status @@ -69,10 +71,10 @@ export default function PingComponent(props: any) { radius="lg" label={ isOnline === 'loading' - ? t('modules.ping.states.loading') + ? t('states.loading') : isOnline === 'online' - ? t('modules.ping.states.online', { response }) - : t('modules.ping.states.offline', { response }) + ? t('states.online', { response }) + : t('states.offline', { response }) } > { if (OverseerrService === undefined && isOverseerrEnabled) { @@ -177,7 +177,7 @@ export default function SearchBar(props: any) { radius="md" size="md" styles={{ rightSection: { pointerEvents: 'none' } }} - placeholder={t('layout.header.search.input.placeholder')} + placeholder={t('input.placeholder')} {...props} {...form.getInputProps('query')} /> diff --git a/src/modules/weather/WeatherModule.tsx b/src/modules/weather/WeatherModule.tsx index 4d3ce8ff7..930b17405 100644 --- a/src/modules/weather/WeatherModule.tsx +++ b/src/modules/weather/WeatherModule.tsx @@ -13,10 +13,10 @@ import { IconSnowflake as Snowflake, IconSun as Sun, } from '@tabler/icons'; +import { useTranslation } from 'next-i18next'; import { useConfig } from '../../tools/state'; import { IModule } from '../ModuleTypes'; import { WeatherResponse } from './WeatherInterface'; -import { t } from 'i18next'; export const WeatherModule: IModule = { title: 'Weather', @@ -49,85 +49,87 @@ export const WeatherModule: IModule = { // 95 *Thunderstorm: Slight or moderate // 96, 99 *Thunderstorm with slight and heavy hail export function WeatherIcon(props: any) { + const { t } = useTranslation('modules/weather-module'); + const { code } = props; let data: { icon: any; name: string }; switch (code) { case 0: { - data = { icon: Sun, name: t('modules.weather.card.weatherDescriptions.clear') }; + data = { icon: Sun, name: t('card.weatherDescriptions.clear') }; break; } case 1: case 2: case 3: { - data = { icon: Cloud, name: t('modules.weather.card.weatherDescriptions.mainlyClear') }; + data = { icon: Cloud, name: t('card.weatherDescriptions.mainlyClear') }; break; } case 45: case 48: { - data = { icon: CloudFog, name: t('modules.weather.card.weatherDescriptions.fog') }; + data = { icon: CloudFog, name: t('card.weatherDescriptions.fog') }; break; } case 51: case 53: case 55: { - data = { icon: Cloud, name: t('modules.weather.card.weatherDescriptions.drizzle') }; + data = { icon: Cloud, name: t('card.weatherDescriptions.drizzle') }; break; } case 56: case 57: { data = { icon: Snowflake, - name: t('modules.weather.card.weatherDescriptions.freezingDrizzle'), + name: t('card.weatherDescriptions.freezingDrizzle'), }; break; } case 61: case 63: case 65: { - data = { icon: CloudRain, name: t('modules.weather.card.weatherDescriptions.rain') }; + data = { icon: CloudRain, name: t('card.weatherDescriptions.rain') }; break; } case 66: case 67: { - data = { icon: CloudRain, name: t('modules.weather.card.weatherDescriptions.freezingRain') }; + data = { icon: CloudRain, name: t('card.weatherDescriptions.freezingRain') }; break; } case 71: case 73: case 75: { - data = { icon: CloudSnow, name: t('modules.weather.card.weatherDescriptions.snowFall') }; + data = { icon: CloudSnow, name: t('card.weatherDescriptions.snowFall') }; break; } case 77: { - data = { icon: CloudSnow, name: t('modules.weather.card.weatherDescriptions.snowGrains') }; + data = { icon: CloudSnow, name: t('card.weatherDescriptions.snowGrains') }; break; } case 80: case 81: case 82: { - data = { icon: CloudRain, name: t('modules.weather.card.weatherDescriptions.rainShowers') }; + data = { icon: CloudRain, name: t('card.weatherDescriptions.rainShowers') }; break; } case 85: case 86: { - data = { icon: CloudSnow, name: t('modules.weather.card.weatherDescriptions.snowShowers') }; + data = { icon: CloudSnow, name: t('card.weatherDescriptions.snowShowers') }; break; } case 95: { - data = { icon: CloudStorm, name: t('modules.weather.card.weatherDescriptions.thunderstorm') }; + data = { icon: CloudStorm, name: t('card.weatherDescriptions.thunderstorm') }; break; } case 96: case 99: { data = { icon: CloudStorm, - name: t('modules.weather.card.weatherDescriptions.thunderstormWithHail'), + name: t('card.weatherDescriptions.thunderstormWithHail'), }; break; } default: { - data = { icon: QuestionMark, name: t('modules.weather.card.weatherDescriptions.unknown') }; + data = { icon: QuestionMark, name: t('card.weatherDescriptions.unknown') }; } } return ( diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 2a0188c8f..f5912a598 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -4,17 +4,16 @@ import { AppProps } from 'next/app'; import { getCookie, setCookie } from 'cookies-next'; import Head from 'next/head'; import { MantineProvider, ColorScheme, ColorSchemeProvider, MantineTheme } from '@mantine/core'; +import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; import { NotificationsProvider } from '@mantine/notifications'; import { useHotkeys } from '@mantine/hooks'; import { ModalsProvider } from '@mantine/modals'; +import { appWithTranslation } from 'next-i18next'; import { ConfigProvider } from '../tools/state'; import { theme } from '../tools/theme'; import { ColorTheme } from '../tools/color'; -import { I18nextProvider } from 'react-i18next'; -import { loadI18n } from '../translations/i18n'; -import { TranslationProvider } from '../providers/translation.provider'; -export default function App(this: any, props: AppProps & { colorScheme: ColorScheme }) { +function App(this: any, props: AppProps & { colorScheme: ColorScheme }) { const { Component, pageProps } = props; const [colorScheme, setColorScheme] = useState(props.colorScheme); @@ -72,9 +71,7 @@ export default function App(this: any, props: AppProps & { colorScheme: ColorSch - - - + @@ -88,3 +85,5 @@ export default function App(this: any, props: AppProps & { colorScheme: ColorSch App.getInitialProps = ({ ctx }: { ctx: GetServerSidePropsContext }) => ({ colorScheme: getCookie('color-scheme', ctx) || 'light', }); + +export default appWithTranslation(App); diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 3191f6baf..8bd43e2f8 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,6 +1,8 @@ import { getCookie, setCookie } from 'cookies-next'; import { GetServerSidePropsContext } from 'next'; import { useEffect } from 'react'; +import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; + import AppShelf from '../components/AppShelf/AppShelf'; import LoadConfigComponent from '../components/Config/LoadConfig'; import { Config } from '../tools/types'; @@ -13,6 +15,7 @@ import Layout from '../components/layout/Layout'; export async function getServerSideProps({ req, res, + locale, }: GetServerSidePropsContext): Promise<{ props: { config: Config } }> { let cookie = getCookie('config-name', { req, res }); if (!cookie) { @@ -24,7 +27,31 @@ export async function getServerSideProps({ }); cookie = 'default'; } - return getConfig(cookie as string); + + const translations = await serverSideTranslations(locale as string, [ + 'common', + 'layout/app-shelf', + 'layout/add-service-app-shelf', + 'settings/common', + 'settings/general/theme-selector', + 'settings/general/config-changer', + 'settings/general/internationalization', + 'settings/general/module-enabler', + 'settings/general/search-engine', + 'settings/general/widget-positions', + 'settings/customization/color-selector', + 'settings/customization/page-appearance', + 'settings/customization/shade-selector', + 'modules/search-module', + 'modules/downloads-module', + 'modules/weather-module', + 'modules/ping-module', + 'modules/docker-module', + 'modules/dashdot-module', + 'modules/overseerr-module', + 'modules/common-media-cards-module', + ]); + return getConfig(cookie as string, translations); } export default function HomePage(props: any) { diff --git a/src/providers/translation.provider.tsx b/src/providers/translation.provider.tsx deleted file mode 100644 index 922c91115..000000000 --- a/src/providers/translation.provider.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { ReactNode, Suspense } from 'react'; - -import { I18nextProvider } from 'react-i18next'; -import { loadI18n } from '../translations/i18n'; - -interface TranslationProviderProps { - children: ReactNode; -} - -export const TranslationProvider = ({ children }: TranslationProviderProps) => { - return ( - - {children} - - ); -}; diff --git a/src/tools/getConfig.ts b/src/tools/getConfig.ts index b2a5559fa..d2a4a9c08 100644 --- a/src/tools/getConfig.ts +++ b/src/tools/getConfig.ts @@ -1,7 +1,7 @@ import path from 'path'; import fs from 'fs'; -export function getConfig(name: string) { +export function getConfig(name: string, props: any = undefined) { // Check if the config file exists const configPath = path.join(process.cwd(), 'data/configs', `${name}.json`); if (!fs.existsSync(configPath)) { @@ -30,6 +30,7 @@ export function getConfig(name: string) { props: { configName: name, config: JSON.parse(config), + ...props, }, }; } diff --git a/src/translations/i18n.ts b/src/translations/i18n.ts deleted file mode 100644 index ce3ad3996..000000000 --- a/src/translations/i18n.ts +++ /dev/null @@ -1,36 +0,0 @@ -import i18n from 'i18next'; - -import Backend from 'i18next-http-backend'; -import LanguageDetector from 'i18next-browser-languagedetector'; - -export const loadI18n = () => { - i18n - .use(LanguageDetector) - .use(Backend) - .init({ - fallbackLng: ['de-de', 'en-us'], - supportedLngs: ['en-us', 'de-de'], - lowerCaseLng: true, - keySeparator: '.', - backend: { - loadPath: constructLoadPath, - }, - debug: true, - }); - return i18n; -}; - -export const convertCodeToName = (code: string) => { - switch (code) { - case 'en-us': - return 'English (US)'; - case 'de-de': - return 'German'; - default: - return `Unknown (${code})`; - } -}; - -const constructLoadPath = () => { - return '/locales/{{lng}}.json'; -}; diff --git a/yarn.lock b/yarn.lock index 0491e5fdc..e841d41c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -364,7 +364,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.9, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7": +"@babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.18.9, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7": version: 7.18.9 resolution: "@babel/runtime@npm:7.18.9" dependencies: @@ -2005,6 +2005,16 @@ __metadata: languageName: node linkType: hard +"@types/hoist-non-react-statics@npm:^3.3.1": + version: 3.3.1 + resolution: "@types/hoist-non-react-statics@npm:3.3.1" + dependencies: + "@types/react": "*" + hoist-non-react-statics: ^3.3.0 + checksum: 2c0778570d9a01d05afabc781b32163f28409bb98f7245c38d5eaf082416fdb73034003f5825eb5e21313044e8d2d9e1f3fe2831e345d3d1b1d20bcd12270719 + languageName: node + linkType: hard + "@types/http-cache-semantics@npm:*": version: 4.0.1 resolution: "@types/http-cache-semantics@npm:4.0.1" @@ -2109,6 +2119,17 @@ __metadata: languageName: node linkType: hard +"@types/react@npm:*": + version: 18.0.17 + resolution: "@types/react@npm:18.0.17" + dependencies: + "@types/prop-types": "*" + "@types/scheduler": "*" + csstype: ^3.0.2 + checksum: 18cae64f5bfd6bb58fbd8ee2ba52ec82de844f114254e26de7b513e4b86621f643f9b71d7066958cd571b0d78cb86cbceda449c5289f9349ca573df29ab69252 + languageName: node + linkType: hard + "@types/react@npm:17.0.1": version: 17.0.1 resolution: "@types/react@npm:17.0.1" @@ -2128,6 +2149,13 @@ __metadata: languageName: node linkType: hard +"@types/scheduler@npm:*": + version: 0.16.2 + resolution: "@types/scheduler@npm:0.16.2" + checksum: b6b4dcfeae6deba2e06a70941860fb1435730576d3689225a421280b7742318d1548b3d22c1f66ab68e414f346a9542f29240bc955b6332c5b11e561077583bc + languageName: node + linkType: hard + "@types/ssh2@npm:*": version: 1.11.5 resolution: "@types/ssh2@npm:1.11.5" @@ -3108,6 +3136,13 @@ __metadata: languageName: node linkType: hard +"core-js@npm:^3": + version: 3.24.1 + resolution: "core-js@npm:3.24.1" + checksum: 6fb5bf0fd9e9f3e69d95616dd03332fea6758a715d2628c108b5faf17b48b0f580e90c4febb0a523c4665b0991a810de16289f86187fe79d70cc722dbd3edf0e + languageName: node + linkType: hard + "core-util-is@npm:~1.0.0": version: 1.0.3 resolution: "core-util-is@npm:1.0.3" @@ -4679,7 +4714,7 @@ __metadata: languageName: node linkType: hard -"hoist-non-react-statics@npm:^3.3.1": +"hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.1, hoist-non-react-statics@npm:^3.3.2": version: 3.3.2 resolution: "hoist-non-react-statics@npm:3.3.2" dependencies: @@ -4747,11 +4782,11 @@ __metadata: jest: ^28.1.3 js-file-download: ^0.4.12 next: 12.1.6 + next-i18next: ^11.3.0 prettier: ^2.7.1 prism-react-renderer: ^1.3.5 react: ^18.2.0 react-dom: ^18.2.0 - react-i18next: ^11.18.4 sharp: ^0.30.7 systeminformation: ^5.12.1 typescript: ^4.7.4 @@ -4890,6 +4925,13 @@ __metadata: languageName: node linkType: hard +"i18next-fs-backend@npm:^1.1.4": + version: 1.1.5 + resolution: "i18next-fs-backend@npm:1.1.5" + checksum: 71f6c4b0ff071676d69f1668675a68f2d72e1836dafcc8014123523bb584a78b0e4fccd16f83d7f37755b58d1dfcb4d6ad36c60b261833b509ccf20313419d9e + languageName: node + linkType: hard + "i18next-http-backend@npm:^1.4.1": version: 1.4.1 resolution: "i18next-http-backend@npm:1.4.1" @@ -4899,7 +4941,7 @@ __metadata: languageName: node linkType: hard -"i18next@npm:^21.9.1": +"i18next@npm:^21.8.13, i18next@npm:^21.9.1": version: 21.9.1 resolution: "i18next@npm:21.9.1" dependencies: @@ -6246,6 +6288,24 @@ __metadata: languageName: node linkType: hard +"next-i18next@npm:^11.3.0": + version: 11.3.0 + resolution: "next-i18next@npm:11.3.0" + dependencies: + "@babel/runtime": ^7.18.6 + "@types/hoist-non-react-statics": ^3.3.1 + core-js: ^3 + hoist-non-react-statics: ^3.3.2 + i18next: ^21.8.13 + i18next-fs-backend: ^1.1.4 + react-i18next: ^11.18.0 + peerDependencies: + next: ">= 10.0.0" + react: ">= 16.8.0" + checksum: fbce97a4fbf9ad846c08652471a833c7f173c3e7ddc7cafa1423625b4a684715bb85f76ae06fe9cbed3e70f12b8e78e2459e5bc1a3c3f5c517743f17648f8939 + languageName: node + linkType: hard + "next@npm:12.1.6": version: 12.1.6 resolution: "next@npm:12.1.6" @@ -6925,7 +6985,7 @@ __metadata: languageName: node linkType: hard -"react-i18next@npm:^11.18.4": +"react-i18next@npm:^11.18.0": version: 11.18.4 resolution: "react-i18next@npm:11.18.4" dependencies: From 4d757ccf667510e6645b9d62a5ea77bb479cde16 Mon Sep 17 00:00:00 2001 From: Manuel Ruwe Date: Wed, 24 Aug 2022 17:58:14 +0200 Subject: [PATCH 04/11] =?UTF-8?q?=E2=9C=A8=20add=20language=20switch,=20ad?= =?UTF-8?q?d=20german?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- next-i18next.config.js | 1 + public/imgs/flags/de.png | Bin 0 -> 151 bytes public/imgs/flags/en.png | Bin 0 -> 1669 bytes public/locales/de/common.json | 385 +----------------- .../de/layout/add-service-app-shelf.json | 118 ++++++ public/locales/de/layout/app-shelf-menu.json | 18 + public/locales/de/layout/app-shelf.json | 10 + .../de/modules/common-media-cards-module.json | 6 + public/locales/de/modules/dashdot-module.json | 32 ++ public/locales/de/modules/docker-module.json | 65 +++ .../locales/de/modules/downloads-module.json | 23 ++ .../locales/de/modules/overseerr-module.json | 26 ++ public/locales/de/modules/ping-module.json | 7 + public/locales/de/modules/search-module.json | 5 + public/locales/de/modules/weather-module.json | 20 + public/locales/de/settings/common.json | 14 + .../de/settings/customization/app-width.json | 3 + .../customization/color-selector.json | 3 + .../customization/opacity-selector.json | 3 + .../customization/page-appearance.json | 21 + .../customization/shade-selector.json | 3 + .../de/settings/general/color-schema.json | 3 + .../de/settings/general/config-changer.json | 34 ++ .../general/internationalization.json | 3 + .../de/settings/general/module-enabler.json | 3 + .../de/settings/general/search-engine.json | 11 + .../de/settings/general/theme-selector.json | 3 + .../de/settings/general/widget-positions.json | 3 + public/locales/en/common.json | 3 +- public/locales/en/layout/app-shelf-menu.json | 18 + .../locales/en/modules/downloads-module.json | 6 + public/locales/en/settings/common.json | 3 + .../en/settings/customization/app-width.json | 3 + .../customization/page-appearance.json | 3 + .../en/settings/general/config-changer.json | 3 +- src/components/AppShelf/AppShelfMenu.tsx | 15 +- .../Settings/AppCardWidthSelector.tsx | 4 +- src/components/Settings/CommonSettings.tsx | 7 +- src/components/Settings/LanguageSwitch.tsx | 74 ++-- src/components/layout/Tip.tsx | 6 +- src/languages/language.ts | 27 ++ src/modules/downloads/DownloadsModule.tsx | 4 +- .../downloads/TotalDownloadsModule.tsx | 6 +- src/pages/index.tsx | 2 + 44 files changed, 578 insertions(+), 429 deletions(-) create mode 100644 public/imgs/flags/de.png create mode 100644 public/imgs/flags/en.png create mode 100644 public/locales/de/layout/add-service-app-shelf.json create mode 100644 public/locales/de/layout/app-shelf-menu.json create mode 100644 public/locales/de/layout/app-shelf.json create mode 100644 public/locales/de/modules/common-media-cards-module.json create mode 100644 public/locales/de/modules/dashdot-module.json create mode 100644 public/locales/de/modules/docker-module.json create mode 100644 public/locales/de/modules/downloads-module.json create mode 100644 public/locales/de/modules/overseerr-module.json create mode 100644 public/locales/de/modules/ping-module.json create mode 100644 public/locales/de/modules/search-module.json create mode 100644 public/locales/de/modules/weather-module.json create mode 100644 public/locales/de/settings/common.json create mode 100644 public/locales/de/settings/customization/app-width.json create mode 100644 public/locales/de/settings/customization/color-selector.json create mode 100644 public/locales/de/settings/customization/opacity-selector.json create mode 100644 public/locales/de/settings/customization/page-appearance.json create mode 100644 public/locales/de/settings/customization/shade-selector.json create mode 100644 public/locales/de/settings/general/color-schema.json create mode 100644 public/locales/de/settings/general/config-changer.json create mode 100644 public/locales/de/settings/general/internationalization.json create mode 100644 public/locales/de/settings/general/module-enabler.json create mode 100644 public/locales/de/settings/general/search-engine.json create mode 100644 public/locales/de/settings/general/theme-selector.json create mode 100644 public/locales/de/settings/general/widget-positions.json create mode 100644 public/locales/en/layout/app-shelf-menu.json create mode 100644 public/locales/en/settings/customization/app-width.json create mode 100644 src/languages/language.ts diff --git a/next-i18next.config.js b/next-i18next.config.js index 875c03fc8..ae76b4355 100644 --- a/next-i18next.config.js +++ b/next-i18next.config.js @@ -4,6 +4,7 @@ module.exports = { i18n: { defaultLocale: 'en', locales: ['en', 'de'], + localeDetection: true, }, reloadOnPrerender: process.env.NODE_ENV === 'development', }; diff --git a/public/imgs/flags/de.png b/public/imgs/flags/de.png new file mode 100644 index 0000000000000000000000000000000000000000..c62776bf39ed351d09c30dc78cff64640fa4623c GIT binary patch literal 151 zcmeAS@N?(olHy`uVBq!ia0y~yU~~Yo4=^zUNe;`PP$0z_;1l9{7sy~>_F3N*lRBg&=BEjb%Ik8!b~9DG0$eANoe$N YiU;Ywl}mMuvID8`boFyt=akR{0FK!w1poj5 literal 0 HcmV?d00001 diff --git a/public/imgs/flags/en.png b/public/imgs/flags/en.png new file mode 100644 index 0000000000000000000000000000000000000000..e6a6dc8be17ee629fe2d9db0af7e46142072b06b GIT binary patch literal 1669 zcmZXVeN@u-9>;&EnOa`WOx(>{BxU0L+O5+z9}H1?cV#CWgAR4&C=yR|sLjC-H5L7(!6irAv3uoMz4S558bs ze;Spj)oOixQrh=mPLlJgtGXs8WTim*m( z3ETSjYMJ<0&($)h=7Cefx)J-!RhecPx}#8;129W~17Jopcbx38Q(IGKcfqQDrd200 zL{6kn29&UhuMbIy-(fp?lij&SHKw;0t&i4pK%I_aG0kLU4pCsa5AC>n{>6KtRUW&t zl77F?d9S;dSK;F$J|12)?;v@@rP;8i&78*bN`&E%JFU4fn@Wvy zC=pL?^xF?M!E9@%uG6qh9!*INXAT@_4uqnB50zEX?`TtHwDV1kE|TGxHbGUW6cYF` z!b(xk6G#vJ7uKExZJ<_JU@nrovxbFx$pm-r4Zj`NxeQynhVzE3j~=|R;10; z7w(c-r;?aqQU5NBL`h+)M{Y&rY$Jc4lG+t%b#_2}rVsk+R=&f2Q3P}dmLthb#?#yR zyAlpu9L%PC9uQwu+hQXx35k1aDgP)$=M8Lv!ElRACT~w3$to84`wcigQe2pJtaBL< zB19zXvu=kE4H76@13n-HjXUHBM*2T~t(v-D<&U_V=m+@vMQAh4^r&JwX8toCR)IB5;>ZHF*Zc@I;3&o*0CgIZ)RmN zqB4{siXEhD)yR4t9E|Z!rBJr9)ibF33A)@M=fa!Vj zSqN4I=5`mI@?oe0pkxpsf+kz{Tn@C7PYeBJx=cOU2KKm0T=8qMJzRXGMDc{bcN?f; z@}eswyFa|f@oQOQ-9i=FE{Oy9cd)|SJ-;QepZB2Z+k|6*Iq+ELIZI!3;Z?kMhT{H8 zeo}JHTx*>lY5tk>*`H}$sB)AD4#`hrMt<{X9^}tradj6(JR)d6Hp!8i4vA&C50Lp_ zR*DV&oZT~}!HZ$Sxwp}q*%WxM0yB~W_%*V^4ThrZ4vxputD5hZpw^Y(Tq+~8T56ES z!D%{=wT^$;%~ZeNV*i?sRT>^Qly82o47JGoYh{c5s!R$A?_x(HtrOna$XOc;3vP#h z(H6ytZGLhRH0G&sNRneT+)ijaTh2 zj@s})3df??oCJdgM4RcTJ;nMt`LKN>jP&rj)bvWWnTk)nxnP;_gLBea{52dYqzrY=BUGvu092wbo704x3VtwBBSr5#uXWvtW~AHT zD+T!Va7s`$9(*%xnQ-Wy!d85mzGD?FDCG3Kjb)d&K3?ojP4nea2QIL2S=4*4a@v{c zQ`@&Ih6(-^5r*!N&3C_ua`?^JwPBJ6)8%+l~bzWl8-+Dc7<+oJ!_u @@ -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/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')} + {t('settings/common:tips.configTip')} ); } diff --git a/src/components/Settings/LanguageSwitch.tsx b/src/components/Settings/LanguageSwitch.tsx index a21e8e547..6acd18c10 100644 --- a/src/components/Settings/LanguageSwitch.tsx +++ b/src/components/Settings/LanguageSwitch.tsx @@ -4,29 +4,35 @@ import { IconLanguage } from '@tabler/icons'; import { forwardRef, useState } from 'react'; import { useTranslation } from 'next-i18next'; +import { useRouter } from 'next/router'; +import { getLanguageByCode, Language } from '../../languages/language'; export default function LanguageSwitch() { const { t, i18n } = useTranslation('settings/general/internationalization'); - /*const { language, languages, changeLanguage } = i18n; + const { changeLanguage } = i18n; - const [selectedLanguage, setSelectedLanguage] = useState(language); + const { locale, locales } = useRouter(); + const [selectedLanguage, setSelectedLanguage] = useState(locale); - const data = languages.map((language) => ({ - image: `https://countryflagsapi.com/png/${language.split('-').pop()}`, - label: 'JA', - value: language, - }));*/ + 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); + setSelectedLanguage(value); - const languageName = 'JA IS HALZ SCHEISSE NE'; + const newLanguage = getLanguageByCode(value); - /*changeLanguage(value) + changeLanguage(value) .then(() => { showNotification({ title: 'Language changed', - message: `You changed the language to '${languageName}'`, + message: `You changed the language to '${newLanguage.originalName}'`, color: 'green', autoClose: 5000, }); @@ -34,31 +40,45 @@ export default function LanguageSwitch() { .catch((err) => { showNotification({ title: 'Failed to change language', - message: `Failed to change to '${languageName}', Error:'${err}`, + message: `Failed to change to '${newLanguage.originalName}', Error:'${err}`, color: 'red', autoClose: 5000, }); - });*/ + }); }; return (