mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-10 15:35:55 +01:00
🚧 wip migrate to next-i18n
This commit is contained in:
9
next-i18next.config.js
Normal file
9
next-i18next.config.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
module.exports = {
|
||||||
|
// https://www.i18next.com/overview/configuration-options#logging
|
||||||
|
debug: process.env.NODE_ENV === 'development',
|
||||||
|
i18n: {
|
||||||
|
defaultLocale: 'en',
|
||||||
|
locales: ['en', 'de'],
|
||||||
|
},
|
||||||
|
reloadOnPrerender: process.env.NODE_ENV === 'development',
|
||||||
|
};
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
const { env } = require('process');
|
const { env } = require('process');
|
||||||
|
|
||||||
|
const { i18n } = require('./next-i18next.config');
|
||||||
|
|
||||||
const withBundleAnalyzer = require('@next/bundle-analyzer')({
|
const withBundleAnalyzer = require('@next/bundle-analyzer')({
|
||||||
enabled: process.env.ANALYZE === 'true',
|
enabled: process.env.ANALYZE === 'true',
|
||||||
});
|
});
|
||||||
@@ -13,4 +15,5 @@ module.exports = withBundleAnalyzer({
|
|||||||
outputStandalone: true,
|
outputStandalone: true,
|
||||||
},
|
},
|
||||||
output: 'standalone',
|
output: 'standalone',
|
||||||
|
i18n,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -58,10 +58,10 @@
|
|||||||
"i18next-http-backend": "^1.4.1",
|
"i18next-http-backend": "^1.4.1",
|
||||||
"js-file-download": "^0.4.12",
|
"js-file-download": "^0.4.12",
|
||||||
"next": "12.1.6",
|
"next": "12.1.6",
|
||||||
|
"next-i18next": "^11.3.0",
|
||||||
"prism-react-renderer": "^1.3.5",
|
"prism-react-renderer": "^1.3.5",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-i18next": "^11.18.4",
|
|
||||||
"sharp": "^0.30.7",
|
"sharp": "^0.30.7",
|
||||||
"systeminformation": "^5.12.1",
|
"systeminformation": "^5.12.1",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
|
|||||||
@@ -108,14 +108,14 @@
|
|||||||
"header": {
|
"header": {
|
||||||
"search": {
|
"search": {
|
||||||
"input": {
|
"input": {
|
||||||
"placeholder": "Search the web..."
|
"placeholder": "Das Internet durchsuchen..."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"docker": {
|
"docker": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"integrationFailed": {
|
"integrationFailed": {
|
||||||
"title": "Docker integration failed",
|
"title": "Docker Integration ist gescheitert",
|
||||||
"message": "Did you forget to mount the docker socket ?"
|
"message": "Hast du vergessen, den Docker Socket zu verbinden?"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"actionIcon": {
|
"actionIcon": {
|
||||||
@@ -124,22 +124,22 @@
|
|||||||
},
|
},
|
||||||
"addService": {
|
"addService": {
|
||||||
"actionIcon": {
|
"actionIcon": {
|
||||||
"tooltip": "Add a service"
|
"tooltip": "Einen Service hinzufügen"
|
||||||
},
|
},
|
||||||
"modal": {
|
"modal": {
|
||||||
"title": "Add service",
|
"title": "Service hinzufügen",
|
||||||
"form": {
|
"form": {
|
||||||
"validation": {
|
"validation": {
|
||||||
"invalidUrl": "Please enter a valid URL",
|
"invalidUrl": "Bitte gebe eine gültige Addresse ein",
|
||||||
"noStatusCodeSelected": "Please select a status code"
|
"noStatusCodeSelected": "Bitte wähle einen gültigen Status Code aus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"options": {
|
"options": {
|
||||||
"title": "Options",
|
"title": "Optionen",
|
||||||
"form": {
|
"form": {
|
||||||
"serviceName": {
|
"serviceName": {
|
||||||
"label": "Service name",
|
"label": "Service Namen",
|
||||||
"placeholder": "Plex"
|
"placeholder": "Plex"
|
||||||
},
|
},
|
||||||
"iconUrl": {
|
"iconUrl": {
|
||||||
@@ -149,59 +149,59 @@
|
|||||||
"label": "Service URL"
|
"label": "Service URL"
|
||||||
},
|
},
|
||||||
"onClickUrl": {
|
"onClickUrl": {
|
||||||
"label": "On Click URL"
|
"label": "URL beim Klicken"
|
||||||
},
|
},
|
||||||
"serviceType": {
|
"serviceType": {
|
||||||
"label": "Service type",
|
"label": "Service Typ",
|
||||||
"defaultValue": "Other",
|
"defaultValue": "Andere",
|
||||||
"placeholder": "Pick one"
|
"placeholder": "Wähle einen aus..."
|
||||||
},
|
},
|
||||||
"category": {
|
"category": {
|
||||||
"label": "Category",
|
"label": "Kategorie",
|
||||||
"placeholder": "Select a category or create a new one",
|
"placeholder": "Wähle eine Kategorie oder erstelle eine neue...",
|
||||||
"nothingFound": "Nothing found",
|
"nothingFound": "Keine Übereinstimmungen gefunden",
|
||||||
"createLabel": "+ Create {{query}}"
|
"createLabel": "+ Erstellen {{query}}"
|
||||||
},
|
},
|
||||||
"integrations": {
|
"integrations": {
|
||||||
"apiKey": {
|
"apiKey": {
|
||||||
"label": "API key",
|
"label": "API Schlüssel",
|
||||||
"placeholder": "Your API key",
|
"placeholder": "Dein API Schlüssel",
|
||||||
"validation": {
|
"validation": {
|
||||||
"noKey": "Invalid Key"
|
"noKey": "Invalider Schlüssel"
|
||||||
},
|
},
|
||||||
"tip": {
|
"tip": {
|
||||||
"text": "Get your API key",
|
"text": "Hole deinen API Schlüssel",
|
||||||
"link": "here."
|
"link": "hier."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"qBittorrent": {
|
"qBittorrent": {
|
||||||
"username": {
|
"username": {
|
||||||
"label": "Username",
|
"label": "Benutzername",
|
||||||
"placeholder": "admin",
|
"placeholder": "admin",
|
||||||
"validation": {
|
"validation": {
|
||||||
"invalidUsername": "Invalid username"
|
"invalidUsername": "Invalider Benutzername"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Password",
|
"label": "Passwort",
|
||||||
"placeholder": "adminadmin",
|
"placeholder": "adminadmin",
|
||||||
"validation": {
|
"validation": {
|
||||||
"invalidPassword": "Invalid password"
|
"invalidPassword": "Invalides Passwort"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deluge": {
|
"deluge": {
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Password",
|
"label": "Passwort",
|
||||||
"placeholder": "password",
|
"placeholder": "password",
|
||||||
"validation": {
|
"validation": {
|
||||||
"invalidPassword": "Invalid password"
|
"invalidPassword": "Invalides PassworT"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"transmission": {
|
"transmission": {
|
||||||
"username": {
|
"username": {
|
||||||
"label": "Username",
|
"label": "Benutzername",
|
||||||
"placeholder": "admin",
|
"placeholder": "admin",
|
||||||
"validation": {
|
"validation": {
|
||||||
"invalidUsername": "Invalid username"
|
"invalidUsername": "Invalid username"
|
||||||
@@ -1,385 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
5
public/locales/en/common.json
Normal file
5
public/locales/en/common.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"actions": {
|
||||||
|
"save": "Save"
|
||||||
|
}
|
||||||
|
}
|
||||||
118
public/locales/en/layout/add-service-app-shelf.json
Normal file
118
public/locales/en/layout/add-service-app-shelf.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
public/locales/en/layout/app-shelf.json
Normal file
10
public/locales/en/layout/app-shelf.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"accordions": {
|
||||||
|
"downloads": {
|
||||||
|
"text": "Your downloads"
|
||||||
|
},
|
||||||
|
"others": {
|
||||||
|
"text": "Others"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
public/locales/en/modules/common-media-cards-module.json
Normal file
6
public/locales/en/modules/common-media-cards-module.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"buttons": {
|
||||||
|
"play": "Play",
|
||||||
|
"request": "Request"
|
||||||
|
}
|
||||||
|
}
|
||||||
32
public/locales/en/modules/dashdot-module.json
Normal file
32
public/locales/en/modules/dashdot-module.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
65
public/locales/en/modules/docker-module.json
Normal file
65
public/locales/en/modules/docker-module.json
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
17
public/locales/en/modules/downloads-module.json
Normal file
17
public/locales/en/modules/downloads-module.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"card": {
|
||||||
|
"table": {
|
||||||
|
"header": {
|
||||||
|
"name": "Name",
|
||||||
|
"size": "Size",
|
||||||
|
"download": "Down",
|
||||||
|
"upload": "Up",
|
||||||
|
"estimatedTimeOfArrival": "ETA",
|
||||||
|
"progress": "Progress"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"nothingFound": "No torrents found"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
public/locales/en/modules/overseerr-module.json
Normal file
26
public/locales/en/modules/overseerr-module.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
public/locales/en/modules/ping-module.json
Normal file
7
public/locales/en/modules/ping-module.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"states": {
|
||||||
|
"online": "Online {{response}}",
|
||||||
|
"offline": "Offline {{response}}",
|
||||||
|
"loading": "Loading..."
|
||||||
|
}
|
||||||
|
}
|
||||||
5
public/locales/en/modules/search-module.json
Normal file
5
public/locales/en/modules/search-module.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"input": {
|
||||||
|
"placeholder": "Search the web..."
|
||||||
|
}
|
||||||
|
}
|
||||||
20
public/locales/en/modules/weather-module.json
Normal file
20
public/locales/en/modules/weather-module.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
public/locales/en/settings/common.json
Normal file
11
public/locales/en/settings/common.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"title": "Settings",
|
||||||
|
"tooltip": "Settings",
|
||||||
|
"tabs": {
|
||||||
|
"common": "Common",
|
||||||
|
"customizations": "Customizations"
|
||||||
|
},
|
||||||
|
"credits": {
|
||||||
|
"madeWithLove": "Made with ❤️ by @"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"suffix": "{{color}} color"
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"label": "App Opacity"
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"label": "Shade"
|
||||||
|
}
|
||||||
3
public/locales/en/settings/general/color-schema.json
Normal file
3
public/locales/en/settings/general/color-schema.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"label": "Switch to {{scheme}} mode"
|
||||||
|
}
|
||||||
35
public/locales/en/settings/general/config-changer.json
Normal file
35
public/locales/en/settings/general/config-changer.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"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!"
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"label": "Language"
|
||||||
|
}
|
||||||
3
public/locales/en/settings/general/module-enabler.json
Normal file
3
public/locales/en/settings/general/module-enabler.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"title": "Module enabler"
|
||||||
|
}
|
||||||
11
public/locales/en/settings/general/search-engine.json
Normal file
11
public/locales/en/settings/general/search-engine.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
3
public/locales/en/settings/general/theme-selector.json
Normal file
3
public/locales/en/settings/general/theme-selector.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"label": "Switch to {{theme}} mode"
|
||||||
|
}
|
||||||
3
public/locales/en/settings/general/widget-positions.json
Normal file
3
public/locales/en/settings/general/widget-positions.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"label": "Position widgets on left"
|
||||||
|
}
|
||||||
@@ -22,25 +22,26 @@ import { IconApps } from '@tabler/icons';
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { useDebouncedValue } from '@mantine/hooks';
|
import { useDebouncedValue } from '@mantine/hooks';
|
||||||
import { t } from 'i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useConfig } from '../../tools/state';
|
import { useConfig } from '../../tools/state';
|
||||||
import { tryMatchPort, ServiceTypeList, StatusCodes } from '../../tools/types';
|
import { tryMatchPort, ServiceTypeList, StatusCodes } from '../../tools/types';
|
||||||
import Tip from '../layout/Tip';
|
import Tip from '../layout/Tip';
|
||||||
|
|
||||||
export function AddItemShelfButton(props: any) {
|
export function AddItemShelfButton(props: any) {
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
|
const { t } = useTranslation('layout/add-service-app-shelf');
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Modal
|
||||||
size="xl"
|
size="xl"
|
||||||
radius="md"
|
radius="md"
|
||||||
title={<Title order={3}>{t('layout.header.addService.modal.title')}</Title>}
|
title={<Title order={3}>{t('modal.title')}</Title>}
|
||||||
opened={props.opened || opened}
|
opened={props.opened || opened}
|
||||||
onClose={() => setOpened(false)}
|
onClose={() => setOpened(false)}
|
||||||
>
|
>
|
||||||
<AddAppShelfItemForm setOpened={setOpened} />
|
<AddAppShelfItemForm setOpened={setOpened} />
|
||||||
</Modal>
|
</Modal>
|
||||||
<Tooltip withinPortal label={t('layout.header.addService.actionIcon.tooltip')}>
|
<Tooltip withinPortal label={t('actionIcon.tooltip')}>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant="default"
|
variant="default"
|
||||||
radius="md"
|
radius="md"
|
||||||
@@ -85,6 +86,7 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
|
|||||||
const { setOpened } = props;
|
const { setOpened } = props;
|
||||||
const { config, setConfig } = useConfig();
|
const { config, setConfig } = useConfig();
|
||||||
const [isLoading, setLoading] = useState(false);
|
const [isLoading, setLoading] = useState(false);
|
||||||
|
const { t } = useTranslation('layout/add-service-app-shelf');
|
||||||
|
|
||||||
// Extract all the categories from the services in config
|
// Extract all the categories from the services in config
|
||||||
const InitialCategories = config.services.reduce((acc, cur) => {
|
const InitialCategories = config.services.reduce((acc, cur) => {
|
||||||
@@ -121,13 +123,13 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
|
|||||||
try {
|
try {
|
||||||
const _isValid = new URL(value);
|
const _isValid = new URL(value);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return t('layout.header.addService.modal.form.validation.invalidUrl');
|
return t('modal.form.validation.invalidUrl');
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
status: (value: string[]) => {
|
status: (value: string[]) => {
|
||||||
if (!value.length) {
|
if (!value.length) {
|
||||||
return t('layout.header.addService.modal.form.validation.noStatusCodeSelected');
|
return t('modal.form.validation.noStatusCodeSelected');
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
@@ -204,62 +206,48 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
|
|||||||
>
|
>
|
||||||
<Tabs defaultValue="Options">
|
<Tabs defaultValue="Options">
|
||||||
<Tabs.List grow>
|
<Tabs.List grow>
|
||||||
<Tabs.Tab value="Options">
|
<Tabs.Tab value="Options">{t('modal.tabs.options.title')}</Tabs.Tab>
|
||||||
{t('layout.header.addService.modal.tabs.options.title')}
|
<Tabs.Tab value="Advanced Options">{t('modal.tabs.advancedOptions.title')}</Tabs.Tab>
|
||||||
</Tabs.Tab>
|
|
||||||
<Tabs.Tab value="Advanced Options">
|
|
||||||
{t('layout.header.addService.modal.tabs.advancedOptions.title')}
|
|
||||||
</Tabs.Tab>
|
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
<Tabs.Panel value="Options">
|
<Tabs.Panel value="Options">
|
||||||
<Stack>
|
<Stack>
|
||||||
<TextInput
|
<TextInput
|
||||||
required
|
required
|
||||||
label={t('layout.header.addService.modal.tabs.options.form.serviceName.label')}
|
label={t('modal.tabs.options.form.serviceName.label')}
|
||||||
placeholder={t(
|
placeholder={t('modal.tabs.options.form.serviceName.placeholder')}
|
||||||
'layout.header.addService.modal.tabs.options.form.serviceName.placeholder'
|
|
||||||
)}
|
|
||||||
{...form.getInputProps('name')}
|
{...form.getInputProps('name')}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
required
|
required
|
||||||
label={t('layout.header.addService.modal.tabs.options.form.iconUrl.label')}
|
label={t('modal.tabs.options.form.iconUrl.label')}
|
||||||
placeholder={DEFAULT_ICON}
|
placeholder={DEFAULT_ICON}
|
||||||
{...form.getInputProps('icon')}
|
{...form.getInputProps('icon')}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
required
|
required
|
||||||
label={t('layout.header.addService.modal.tabs.options.form.serviceUrl.label')}
|
label={t('modal.tabs.options.form.serviceUrl.label')}
|
||||||
placeholder="http://localhost:7575"
|
placeholder="http://localhost:7575"
|
||||||
{...form.getInputProps('url')}
|
{...form.getInputProps('url')}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={t('layout.header.addService.modal.tabs.options.form.onClickUrl.label')}
|
label={t('modal.tabs.options.form.onClickUrl.label')}
|
||||||
placeholder="http://sonarr.example.com"
|
placeholder="http://sonarr.example.com"
|
||||||
{...form.getInputProps('openedUrl')}
|
{...form.getInputProps('openedUrl')}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
label={t('layout.header.addService.modal.tabs.options.form.serviceType.label')}
|
label={t('modal.tabs.options.form.serviceType.label')}
|
||||||
defaultValue={t(
|
defaultValue={t('modal.tabs.options.form.serviceType.defaultValue')}
|
||||||
'layout.header.addService.modal.tabs.options.form.serviceType.defaultValue'
|
placeholder={t('modal.tabs.options.form.serviceType.placeholder')}
|
||||||
)}
|
|
||||||
placeholder={t(
|
|
||||||
'layout.header.addService.modal.tabs.options.form.serviceType.placeholder'
|
|
||||||
)}
|
|
||||||
required
|
required
|
||||||
searchable
|
searchable
|
||||||
data={ServiceTypeList}
|
data={ServiceTypeList}
|
||||||
{...form.getInputProps('type')}
|
{...form.getInputProps('type')}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
label={t('layout.header.addService.modal.tabs.options.form.category.label')}
|
label={t('modal.tabs.options.form.category.label')}
|
||||||
data={categories}
|
data={categories}
|
||||||
placeholder={t(
|
placeholder={t('modal.tabs.options.form.category.placeholder')}
|
||||||
'layout.header.addService.modal.tabs.options.form.category.placeholder'
|
nothingFound={t('modal.tabs.options.form.category.nothingFound')}
|
||||||
)}
|
|
||||||
nothingFound={t(
|
|
||||||
'layout.header.addService.modal.tabs.options.form.category.nothingFound'
|
|
||||||
)}
|
|
||||||
searchable
|
searchable
|
||||||
clearable
|
clearable
|
||||||
creatable
|
creatable
|
||||||
@@ -269,7 +257,7 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
|
|||||||
return item;
|
return item;
|
||||||
}}
|
}}
|
||||||
getCreateLabel={(query) =>
|
getCreateLabel={(query) =>
|
||||||
t('layout.header.addService.modal.tabs.options.form.category.createLabel', {
|
t('modal.tabs.options.form.category.createLabel', {
|
||||||
query,
|
query,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -285,36 +273,26 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
|
|||||||
<>
|
<>
|
||||||
<TextInput
|
<TextInput
|
||||||
required
|
required
|
||||||
label={t(
|
label={t('modal.tabs.options.form.integrations.apiKey.label')}
|
||||||
'layout.header.addService.modal.tabs.options.form.integrations.apiKey.label'
|
placeholder={t('modal.tabs.options.form.integrations.apiKey.placeholder')}
|
||||||
)}
|
|
||||||
placeholder={t(
|
|
||||||
'layout.header.addService.modal.tabs.options.form.integrations.apiKey.placeholder'
|
|
||||||
)}
|
|
||||||
value={form.values.apiKey}
|
value={form.values.apiKey}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
form.setFieldValue('apiKey', event.currentTarget.value);
|
form.setFieldValue('apiKey', event.currentTarget.value);
|
||||||
}}
|
}}
|
||||||
error={
|
error={
|
||||||
form.errors.apiKey &&
|
form.errors.apiKey &&
|
||||||
t(
|
t('modal.tabs.options.form.integrations.apiKey.validation.noKey')
|
||||||
'layout.header.addService.modal.tabs.options.form.integrations.apiKey.validation.noKey'
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Tip>
|
<Tip>
|
||||||
{t(
|
{t('modal.tabs.options.form.integrations.apiKey.tip.text')}{' '}
|
||||||
'layout.header.addService.modal.tabs.options.form.integrations.apiKey.tip.text'
|
|
||||||
)}{' '}
|
|
||||||
<Anchor
|
<Anchor
|
||||||
target="_blank"
|
target="_blank"
|
||||||
weight="bold"
|
weight="bold"
|
||||||
style={{ fontStyle: 'inherit', fontSize: 'inherit' }}
|
style={{ fontStyle: 'inherit', fontSize: 'inherit' }}
|
||||||
href={`${hostname}/settings/general`}
|
href={`${hostname}/settings/general`}
|
||||||
>
|
>
|
||||||
{t(
|
{t('modal.tabs.options.form.integrations.apiKey.tip.link')}
|
||||||
'layout.header.addService.modal.tabs.options.form.integrations.apiKey.tip.link'
|
|
||||||
)}
|
|
||||||
</Anchor>
|
</Anchor>
|
||||||
</Tip>
|
</Tip>
|
||||||
</>
|
</>
|
||||||
@@ -323,11 +301,9 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
|
|||||||
<>
|
<>
|
||||||
<TextInput
|
<TextInput
|
||||||
required
|
required
|
||||||
label={t(
|
label={t('modal.tabs.options.form.integrations.qBittorrent.username.label')}
|
||||||
'layout.header.addService.modal.tabs.options.form.integrations.qBittorrent.username.label'
|
|
||||||
)}
|
|
||||||
placeholder={t(
|
placeholder={t(
|
||||||
'layout.header.addService.modal.tabs.options.form.integrations.qBittorrent.username.placeholder'
|
'modal.tabs.options.form.integrations.qBittorrent.username.placeholder'
|
||||||
)}
|
)}
|
||||||
value={form.values.username}
|
value={form.values.username}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
@@ -336,17 +312,15 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
|
|||||||
error={
|
error={
|
||||||
form.errors.username &&
|
form.errors.username &&
|
||||||
t(
|
t(
|
||||||
'layout.header.addService.modal.tabs.options.form.integrations.qBittorrent.username.validation.invalidUsername'
|
'modal.tabs.options.form.integrations.qBittorrent.username.validation.invalidUsername'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
required
|
required
|
||||||
label={t(
|
label={t('modal.tabs.options.form.integrations.qBittorrent.password.label')}
|
||||||
'layout.header.addService.modal.tabs.options.form.integrations.qBittorrent.password.label'
|
|
||||||
)}
|
|
||||||
placeholder={t(
|
placeholder={t(
|
||||||
'layout.header.addService.modal.tabs.options.form.integrations.qBittorrent.password.placeholder'
|
'modal.tabs.options.form.integrations.qBittorrent.password.placeholder'
|
||||||
)}
|
)}
|
||||||
value={form.values.password}
|
value={form.values.password}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
@@ -355,7 +329,7 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
|
|||||||
error={
|
error={
|
||||||
form.errors.password &&
|
form.errors.password &&
|
||||||
t(
|
t(
|
||||||
'layout.header.addService.modal.tabs.options.form.integrations.qBittorrent.password.validation.invalidPassword'
|
'modal.tabs.options.form.integrations.qBittorrent.password.validation.invalidPassword'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -364,11 +338,9 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
|
|||||||
{form.values.type === 'Deluge' && (
|
{form.values.type === 'Deluge' && (
|
||||||
<>
|
<>
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
label={t(
|
label={t('modal.tabs.options.form.integrations.deluge.password.label')}
|
||||||
'layout.header.addService.modal.tabs.options.form.integrations.deluge.password.label'
|
|
||||||
)}
|
|
||||||
placeholder={t(
|
placeholder={t(
|
||||||
'layout.header.addService.modal.tabs.options.form.integrations.deluge.password.placeholder'
|
'modal.tabs.options.form.integrations.deluge.password.placeholder'
|
||||||
)}
|
)}
|
||||||
value={form.values.password}
|
value={form.values.password}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
@@ -377,7 +349,7 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
|
|||||||
error={
|
error={
|
||||||
form.errors.password &&
|
form.errors.password &&
|
||||||
t(
|
t(
|
||||||
'layout.header.addService.modal.tabs.options.form.integrations.deluge.password.validation.invalidPassword'
|
'modal.tabs.options.form.integrations.deluge.password.validation.invalidPassword'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -386,11 +358,9 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
|
|||||||
{form.values.type === 'Transmission' && (
|
{form.values.type === 'Transmission' && (
|
||||||
<>
|
<>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={t(
|
label={t('modal.tabs.options.form.integrations.transmission.username.label')}
|
||||||
'layout.header.addService.modal.tabs.options.form.integrations.transmission.username.label'
|
|
||||||
)}
|
|
||||||
placeholder={t(
|
placeholder={t(
|
||||||
'layout.header.addService.modal.tabs.options.form.integrations.transmission.username.placeholder'
|
'modal.tabs.options.form.integrations.transmission.username.placeholder'
|
||||||
)}
|
)}
|
||||||
value={form.values.username}
|
value={form.values.username}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
@@ -399,16 +369,14 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
|
|||||||
error={
|
error={
|
||||||
form.errors.username &&
|
form.errors.username &&
|
||||||
t(
|
t(
|
||||||
'layout.header.addService.modal.tabs.options.form.integrations.transmission.username.validation.invalidUsername'
|
'modal.tabs.options.form.integrations.transmission.username.validation.invalidUsername'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
label={t(
|
label={t('modal.tabs.options.form.integrations.transmission.password.label')}
|
||||||
'layout.header.addService.modal.tabs.options.form.integrations.transmission.password.label'
|
|
||||||
)}
|
|
||||||
placeholder={t(
|
placeholder={t(
|
||||||
'layout.header.addService.modal.tabs.options.form.integrations.transmission.password.placeholder'
|
'modal.tabs.options.form.integrations.transmission.password.placeholder'
|
||||||
)}
|
)}
|
||||||
value={form.values.password}
|
value={form.values.password}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
@@ -417,7 +385,7 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
|
|||||||
error={
|
error={
|
||||||
form.errors.password &&
|
form.errors.password &&
|
||||||
t(
|
t(
|
||||||
'layout.header.addService.modal.tabs.options.form.integrations.transmission.password.validation.invalidPassword'
|
'modal.tabs.options.form.integrations.transmission.password.validation.invalidPassword'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -425,32 +393,24 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
|
|||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
<Tabs.Panel value={t('layout.header.addService.modal.tabs.advancedOptions.title')}>
|
<Tabs.Panel value={t('modal.tabs.advancedOptions.title')}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
required
|
required
|
||||||
label={t(
|
label={t('modal.tabs.advancedOptions.form.httpStatusCodes.label')}
|
||||||
'layout.header.addService.modal.tabs.advancedOptions.form.httpStatusCodes.label'
|
|
||||||
)}
|
|
||||||
data={StatusCodes}
|
data={StatusCodes}
|
||||||
placeholder={t(
|
placeholder={t('modal.tabs.advancedOptions.form.httpStatusCodes.placeholder')}
|
||||||
'layout.header.addService.modal.tabs.advancedOptions.form.httpStatusCodes.placeholder'
|
|
||||||
)}
|
|
||||||
clearButtonLabel={t(
|
clearButtonLabel={t(
|
||||||
'layout.header.addService.modal.tabs.advancedOptions.form.httpStatusCodes.clearButtonLabel'
|
'modal.tabs.advancedOptions.form.httpStatusCodes.clearButtonLabel'
|
||||||
)}
|
|
||||||
nothingFound={t(
|
|
||||||
'layout.header.addService.modal.tabs.advancedOptions.form.httpStatusCodes.nothingFound'
|
|
||||||
)}
|
)}
|
||||||
|
nothingFound={t('modal.tabs.advancedOptions.form.httpStatusCodes.nothingFound')}
|
||||||
defaultValue={['200']}
|
defaultValue={['200']}
|
||||||
clearable
|
clearable
|
||||||
searchable
|
searchable
|
||||||
{...form.getInputProps('status')}
|
{...form.getInputProps('status')}
|
||||||
/>
|
/>
|
||||||
<Switch
|
<Switch
|
||||||
label={t(
|
label={t('modal.tabs.advancedOptions.form.openServiceInNewTab.label')}
|
||||||
'layout.header.addService.modal.tabs.advancedOptions.form.openServiceInNewTab.label'
|
|
||||||
)}
|
|
||||||
defaultChecked={form.values.newTab}
|
defaultChecked={form.values.newTab}
|
||||||
{...form.getInputProps('newTab')}
|
{...form.getInputProps('newTab')}
|
||||||
/>
|
/>
|
||||||
@@ -459,8 +419,7 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
<Group grow position="center" mt="xl">
|
<Group grow position="center" mt="xl">
|
||||||
<Button type="submit">
|
<Button type="submit">
|
||||||
{props.message ??
|
{props.message ?? t('modal.tabs.advancedOptions.form.buttons.submit.content')}
|
||||||
t('layout.header.addService.modal.tabs.advancedOptions.form.buttons.submit.content')}
|
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
} from '@dnd-kit/core';
|
} from '@dnd-kit/core';
|
||||||
import { arrayMove, SortableContext } from '@dnd-kit/sortable';
|
import { arrayMove, SortableContext } from '@dnd-kit/sortable';
|
||||||
import { useLocalStorage } from '@mantine/hooks';
|
import { useLocalStorage } from '@mantine/hooks';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useConfig } from '../../tools/state';
|
import { useConfig } from '../../tools/state';
|
||||||
|
|
||||||
import { SortableAppShelfItem, AppShelfItem } from './AppShelfItem';
|
import { SortableAppShelfItem, AppShelfItem } from './AppShelfItem';
|
||||||
@@ -36,6 +37,8 @@ const AppShelf = (props: any) => {
|
|||||||
const [activeId, setActiveId] = useState(null);
|
const [activeId, setActiveId] = useState(null);
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
|
|
||||||
|
const { t } = useTranslation('layout/app-shelf');
|
||||||
|
|
||||||
const sensors = useSensors(
|
const sensors = useSensors(
|
||||||
useSensor(TouchSensor, {
|
useSensor(TouchSensor, {
|
||||||
activationConstraint: {
|
activationConstraint: {
|
||||||
@@ -147,13 +150,13 @@ const AppShelf = (props: any) => {
|
|||||||
{/* Return the item for all services without category */}
|
{/* Return the item for all services without category */}
|
||||||
{noCategory && noCategory.length > 0 ? (
|
{noCategory && noCategory.length > 0 ? (
|
||||||
<Accordion.Item key="Other" value="Other">
|
<Accordion.Item key="Other" value="Other">
|
||||||
<Accordion.Control>Other</Accordion.Control>
|
<Accordion.Control>{t('accordions.others.text')}</Accordion.Control>
|
||||||
<Accordion.Panel>{getItems()}</Accordion.Panel>
|
<Accordion.Panel>{getItems()}</Accordion.Panel>
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
) : null}
|
) : null}
|
||||||
{downloadEnabled ? (
|
{downloadEnabled ? (
|
||||||
<Accordion.Item key="Downloads" value="Your downloads">
|
<Accordion.Item key="Downloads" value="Your downloads">
|
||||||
<Accordion.Control>Your downloads</Accordion.Control>
|
<Accordion.Control>{t('accordions.downloads.text')}</Accordion.Control>
|
||||||
<Accordion.Panel>
|
<Accordion.Panel>
|
||||||
<Paper
|
<Paper
|
||||||
p="lg"
|
p="lg"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import { createStyles, Switch, Group, useMantineColorScheme, Kbd } from '@mantine/core';
|
import { createStyles, Switch, Group, useMantineColorScheme, Kbd } from '@mantine/core';
|
||||||
import { IconSun as Sun, IconMoonStars as MoonStars } from '@tabler/icons';
|
import { IconSun as Sun, IconMoonStars as MoonStars } from '@tabler/icons';
|
||||||
import { useConfig } from '../../tools/state';
|
import { useConfig } from '../../tools/state';
|
||||||
import { t } from 'i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
||||||
const useStyles = createStyles((theme) => ({
|
const useStyles = createStyles((theme) => ({
|
||||||
root: {
|
root: {
|
||||||
@@ -34,6 +34,7 @@ export function ColorSchemeSwitch() {
|
|||||||
const { config } = useConfig();
|
const { config } = useConfig();
|
||||||
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
|
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
|
||||||
const { classes, cx } = useStyles();
|
const { classes, cx } = useStyles();
|
||||||
|
const { t } = useTranslation('settings/general/theme-selector');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group>
|
<Group>
|
||||||
@@ -42,8 +43,8 @@ export function ColorSchemeSwitch() {
|
|||||||
<MoonStars className={cx(classes.icon, classes.iconDark)} size={18} />
|
<MoonStars className={cx(classes.icon, classes.iconDark)} size={18} />
|
||||||
<Switch checked={colorScheme === 'dark'} onChange={() => toggleColorScheme()} size="md" />
|
<Switch checked={colorScheme === 'dark'} onChange={() => toggleColorScheme()} size="md" />
|
||||||
</div>
|
</div>
|
||||||
{t('settings.tabs.common.settings.colorScheme.label', {
|
{t('label', {
|
||||||
scheme: colorScheme === 'dark' ? 'light' : 'dark',
|
theme: colorScheme === 'dark' ? 'light' : 'dark',
|
||||||
})}
|
})}
|
||||||
<Group spacing={2}>
|
<Group spacing={2}>
|
||||||
<Kbd>Ctrl</Kbd>+<Kbd>J</Kbd>
|
<Kbd>Ctrl</Kbd>+<Kbd>J</Kbd>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Center, Loader, Select, Tooltip } from '@mantine/core';
|
import { Center, Loader, Select, Tooltip } from '@mantine/core';
|
||||||
import { setCookie } from 'cookies-next';
|
import { setCookie } from 'cookies-next';
|
||||||
import { t } from 'i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useConfig } from '../../tools/state';
|
import { useConfig } from '../../tools/state';
|
||||||
|
|
||||||
@@ -8,6 +8,8 @@ export default function ConfigChanger() {
|
|||||||
const { config, loadConfig, setConfig, getConfigs } = useConfig();
|
const { config, loadConfig, setConfig, getConfigs } = useConfig();
|
||||||
const [configList, setConfigList] = useState<string[]>([]);
|
const [configList, setConfigList] = useState<string[]>([]);
|
||||||
const [value, setValue] = useState(config.name);
|
const [value, setValue] = useState(config.name);
|
||||||
|
const { t } = useTranslation('settings/general/config-changer');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getConfigs().then((configs) => setConfigList(configs));
|
getConfigs().then((configs) => setConfigList(configs));
|
||||||
}, [config]);
|
}, [config]);
|
||||||
@@ -24,7 +26,7 @@ export default function ConfigChanger() {
|
|||||||
// return <Select data={[{ value: '1', label: '1' },]} onChange={(e) => console.log(e)} value="1" />;
|
// return <Select data={[{ value: '1', label: '1' },]} onChange={(e) => console.log(e)} value="1" />;
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
label={t('settings.tabs.common.settings.configChanger.configSelect.label')}
|
label={t('configSelect.label')}
|
||||||
value={value}
|
value={value}
|
||||||
defaultValue={config.name}
|
defaultValue={config.name}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { showNotification } from '@mantine/notifications';
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import fileDownload from 'js-file-download';
|
import fileDownload from 'js-file-download';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
import {
|
import {
|
||||||
IconCheck as Check,
|
IconCheck as Check,
|
||||||
IconDownload as Download,
|
IconDownload as Download,
|
||||||
@@ -12,11 +13,11 @@ import {
|
|||||||
IconX as X,
|
IconX as X,
|
||||||
} from '@tabler/icons';
|
} from '@tabler/icons';
|
||||||
import { useConfig } from '../../tools/state';
|
import { useConfig } from '../../tools/state';
|
||||||
import { t } from 'i18next';
|
|
||||||
|
|
||||||
export default function SaveConfigComponent(props: any) {
|
export default function SaveConfigComponent(props: any) {
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
const { config, setConfig } = useConfig();
|
const { config, setConfig } = useConfig();
|
||||||
|
const { t } = useTranslation('settings/general/config-changer');
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
configName: config.name,
|
configName: config.name,
|
||||||
@@ -29,12 +30,7 @@ export default function SaveConfigComponent(props: any) {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Group spacing="xs">
|
<Group spacing="xs">
|
||||||
<Modal
|
<Modal radius="md" opened={opened} onClose={() => setOpened(false)} title={t('modal.title')}>
|
||||||
radius="md"
|
|
||||||
opened={opened}
|
|
||||||
onClose={() => setOpened(false)}
|
|
||||||
title={t('settings.tabs.common.settings.configChanger.modal.title')}
|
|
||||||
>
|
|
||||||
<form
|
<form
|
||||||
onSubmit={form.onSubmit((values) => {
|
onSubmit={form.onSubmit((values) => {
|
||||||
setConfig({ ...config, name: values.configName });
|
setConfig({ ...config, name: values.configName });
|
||||||
@@ -51,21 +47,17 @@ export default function SaveConfigComponent(props: any) {
|
|||||||
>
|
>
|
||||||
<TextInput
|
<TextInput
|
||||||
required
|
required
|
||||||
label={t('settings.tabs.common.settings.configChanger.modal.form.configName.label')}
|
label={t('modal.form.configName.label')}
|
||||||
placeholder={t(
|
placeholder={t('modal.form.configName.placeholder')}
|
||||||
'settings.tabs.common.settings.configChanger.modal.form.configName.placeholder'
|
|
||||||
)}
|
|
||||||
{...form.getInputProps('configName')}
|
{...form.getInputProps('configName')}
|
||||||
/>
|
/>
|
||||||
<Group position="right" mt="md">
|
<Group position="right" mt="md">
|
||||||
<Button type="submit">
|
<Button type="submit">{t('modal.form.buttons.submit')}</Button>
|
||||||
{t('settings.tabs.common.settings.configChanger.modal.form.buttons.submit')}
|
|
||||||
</Button>
|
|
||||||
</Group>
|
</Group>
|
||||||
</form>
|
</form>
|
||||||
</Modal>
|
</Modal>
|
||||||
<Button size="xs" leftIcon={<Download />} variant="outline" onClick={onClick}>
|
<Button size="xs" leftIcon={<Download />} variant="outline" onClick={onClick}>
|
||||||
{t('settings.tabs.common.settings.configChanger.buttons.download')}
|
{t('buttons.download')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
@@ -76,39 +68,31 @@ export default function SaveConfigComponent(props: any) {
|
|||||||
.delete(`/api/configs/${config.name}`)
|
.delete(`/api/configs/${config.name}`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
showNotification({
|
showNotification({
|
||||||
title: t(
|
title: t('buttons.delete.deleted.title'),
|
||||||
'settings.tabs.common.settings.configChanger.buttons.delete.deleted.title'
|
|
||||||
),
|
|
||||||
icon: <Check />,
|
icon: <Check />,
|
||||||
color: 'green',
|
color: 'green',
|
||||||
autoClose: 1500,
|
autoClose: 1500,
|
||||||
radius: 'md',
|
radius: 'md',
|
||||||
message: t(
|
message: t('buttons.delete.deleted.message'),
|
||||||
'settings.tabs.common.settings.configChanger.buttons.delete.deleted.message'
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
showNotification({
|
showNotification({
|
||||||
title: t(
|
title: t('buttons.delete.deleteFailed.title'),
|
||||||
'settings.tabs.common.settings.configChanger.buttons.delete.deleteFailed.title'
|
|
||||||
),
|
|
||||||
icon: <X />,
|
icon: <X />,
|
||||||
color: 'red',
|
color: 'red',
|
||||||
autoClose: 1500,
|
autoClose: 1500,
|
||||||
radius: 'md',
|
radius: 'md',
|
||||||
message: t(
|
message: t('buttons.delete.deleteFailed.message'),
|
||||||
'settings.tabs.common.settings.configChanger.buttons.delete.deleteFailed.message'
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
setConfig({ ...config, name: 'default' });
|
setConfig({ ...config, name: 'default' });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('settings.tabs.common.settings.configChanger.buttons.delete.text')}
|
{t('buttons.delete.text')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="xs" leftIcon={<Plus />} variant="outline" onClick={() => setOpened(true)}>
|
<Button size="xs" leftIcon={<Plus />} variant="outline" onClick={() => setOpened(true)}>
|
||||||
{t('settings.tabs.common.settings.configChanger.buttons.saveCopy')}
|
{t('buttons.saveCopy')}
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import { TextInput, Button, Stack } from '@mantine/core';
|
import { TextInput, Button, Stack } from '@mantine/core';
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useConfig } from '../../tools/state';
|
import { useConfig } from '../../tools/state';
|
||||||
import { ColorSelector } from './ColorSelector';
|
import { ColorSelector } from './ColorSelector';
|
||||||
import { OpacitySelector } from './OpacitySelector';
|
import { OpacitySelector } from './OpacitySelector';
|
||||||
import { AppCardWidthSelector } from './AppCardWidthSelector';
|
import { AppCardWidthSelector } from './AppCardWidthSelector';
|
||||||
import { ShadeSelector } from './ShadeSelector';
|
import { ShadeSelector } from './ShadeSelector';
|
||||||
import { t } from 'i18next';
|
|
||||||
|
|
||||||
export default function TitleChanger() {
|
export default function TitleChanger() {
|
||||||
const { config, setConfig } = useConfig();
|
const { config, setConfig } = useConfig();
|
||||||
|
const { t } = useTranslation('settings/customization/page-appearance');
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
@@ -42,26 +43,26 @@ export default function TitleChanger() {
|
|||||||
<form onSubmit={form.onSubmit((values) => saveChanges(values))}>
|
<form onSubmit={form.onSubmit((values) => saveChanges(values))}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={t('settings.tabs.customizations.settings.pageTitle.label')}
|
label={t('pageTitle.label')}
|
||||||
placeholder={t('settings.tabs.customizations.settings.pageTitle.placeholder')}
|
placeholder={t('pageTitle.placeholder')}
|
||||||
{...form.getInputProps('title')}
|
{...form.getInputProps('title')}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={t('settings.tabs.customizations.settings.logo.label')}
|
label={t('logo.label')}
|
||||||
placeholder={t('settings.tabs.customizations.settings.logo.placeholder')}
|
placeholder={t('logo.placeholder')}
|
||||||
{...form.getInputProps('logo')}
|
{...form.getInputProps('logo')}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={t('settings.tabs.customizations.settings.favicon.label')}
|
label={t('favicon.label')}
|
||||||
placeholder={t('settings.tabs.customizations.settings.favicon.placeholder')}
|
placeholder={t('favicon.placeholder')}
|
||||||
{...form.getInputProps('favicon')}
|
{...form.getInputProps('favicon')}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={t('settings.tabs.customizations.settings.background.label')}
|
label={t('background.label')}
|
||||||
placeholder={t('settings.tabs.customizations.settings.background.placeholder')}
|
placeholder={t('background.placeholder')}
|
||||||
{...form.getInputProps('background')}
|
{...form.getInputProps('background')}
|
||||||
/>
|
/>
|
||||||
<Button type="submit">{t('settings.tabs.customizations.settings.buttons.submit')}</Button>
|
<Button type="submit">{t('buttons.submit')}</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</form>
|
</form>
|
||||||
<ColorSelector type="primary" />
|
<ColorSelector type="primary" />
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { ColorSwatch, Grid, Group, Popover, Text, useMantineTheme } from '@mantine/core';
|
import { ColorSwatch, Grid, Group, Popover, Text, useMantineTheme } from '@mantine/core';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useConfig } from '../../tools/state';
|
import { useConfig } from '../../tools/state';
|
||||||
import { useColorTheme } from '../../tools/color';
|
import { useColorTheme } from '../../tools/color';
|
||||||
import { t } from 'i18next';
|
|
||||||
|
|
||||||
interface ColorControlProps {
|
interface ColorControlProps {
|
||||||
type: string;
|
type: string;
|
||||||
@@ -11,8 +11,8 @@ interface ColorControlProps {
|
|||||||
export function ColorSelector({ type }: ColorControlProps) {
|
export function ColorSelector({ type }: ColorControlProps) {
|
||||||
const { config, setConfig } = useConfig();
|
const { config, setConfig } = useConfig();
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
|
|
||||||
const { primaryColor, secondaryColor, setPrimaryColor, setSecondaryColor } = useColorTheme();
|
const { primaryColor, secondaryColor, setPrimaryColor, setSecondaryColor } = useColorTheme();
|
||||||
|
const { t } = useTranslation('settings/customization/color-selector');
|
||||||
|
|
||||||
const theme = useMantineTheme();
|
const theme = useMantineTheme();
|
||||||
const colors = Object.keys(theme.colors).map((color) => ({
|
const colors = Object.keys(theme.colors).map((color) => ({
|
||||||
@@ -84,7 +84,7 @@ export function ColorSelector({ type }: ColorControlProps) {
|
|||||||
</Popover.Dropdown>
|
</Popover.Dropdown>
|
||||||
</Popover>
|
</Popover>
|
||||||
<Text>
|
<Text>
|
||||||
{t('settings.tabs.customizations.settings.colorSelector.suffix', {
|
{t('suffix', {
|
||||||
color: type[0].toUpperCase() + type.slice(1),
|
color: type[0].toUpperCase() + type.slice(1),
|
||||||
})}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Text, SegmentedControl, TextInput, Stack } from '@mantine/core';
|
import { Text, SegmentedControl, TextInput, Stack } from '@mantine/core';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useConfig } from '../../tools/state';
|
import { useConfig } from '../../tools/state';
|
||||||
import { ColorSchemeSwitch } from '../ColorSchemeToggle/ColorSchemeSwitch';
|
import { ColorSchemeSwitch } from '../ColorSchemeToggle/ColorSchemeSwitch';
|
||||||
import { WidgetsPositionSwitch } from '../WidgetsPositionSwitch/WidgetsPositionSwitch';
|
import { WidgetsPositionSwitch } from '../WidgetsPositionSwitch/WidgetsPositionSwitch';
|
||||||
@@ -7,11 +8,14 @@ import ConfigChanger from '../Config/ConfigChanger';
|
|||||||
import SaveConfigComponent from '../Config/SaveConfig';
|
import SaveConfigComponent from '../Config/SaveConfig';
|
||||||
import ModuleEnabler from './ModuleEnabler';
|
import ModuleEnabler from './ModuleEnabler';
|
||||||
import Tip from '../layout/Tip';
|
import Tip from '../layout/Tip';
|
||||||
import { t } from 'i18next';
|
|
||||||
import LanguageSwitch from './LanguageSwitch';
|
import LanguageSwitch from './LanguageSwitch';
|
||||||
|
|
||||||
export default function CommonSettings(args: any) {
|
export default function CommonSettings(args: any) {
|
||||||
const { config, setConfig } = useConfig();
|
const { config, setConfig } = useConfig();
|
||||||
|
const { t } = useTranslation([
|
||||||
|
'settings/general/search-engine',
|
||||||
|
'settings/general/config-changer',
|
||||||
|
]);
|
||||||
|
|
||||||
const matches = [
|
const matches = [
|
||||||
{ label: 'Google', value: 'https://google.com/search?q=' },
|
{ label: 'Google', value: 'https://google.com/search?q=' },
|
||||||
@@ -28,12 +32,12 @@ export default function CommonSettings(args: any) {
|
|||||||
return (
|
return (
|
||||||
<Stack mb="md" mr="sm">
|
<Stack mb="md" mr="sm">
|
||||||
<Stack spacing={0} mt="xs">
|
<Stack spacing={0} mt="xs">
|
||||||
<Text>{t('settings.tabs.common.settings.searchEngine.title')}</Text>
|
<Text>{t('title')}</Text>
|
||||||
<Tip>{t('settings.tabs.common.settings.searchEngine.tips.generalTip')}</Tip>
|
<Tip>{t('tips.generalTip')}</Tip>
|
||||||
<SegmentedControl
|
<SegmentedControl
|
||||||
fullWidth
|
fullWidth
|
||||||
mb="sm"
|
mb="sm"
|
||||||
title={t('settings.tabs.common.settings.searchEngine.title')}
|
title={t('title')}
|
||||||
value={
|
value={
|
||||||
// Match config.settings.searchUrl with a key in the matches array
|
// Match config.settings.searchUrl with a key in the matches array
|
||||||
searchUrl
|
searchUrl
|
||||||
@@ -55,10 +59,10 @@ export default function CommonSettings(args: any) {
|
|||||||
/>
|
/>
|
||||||
{searchUrl === 'Custom' && (
|
{searchUrl === 'Custom' && (
|
||||||
<>
|
<>
|
||||||
<Tip>{t('settings.tabs.common.settings.searchEngine.tips.placeholderTip')}</Tip>
|
<Tip>{t('tips.placeholderTip')}</Tip>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={t('settings.tabs.common.settings.searchEngine.customEngine.label')}
|
label={t('customEngine.label')}
|
||||||
placeholder={t('settings.tabs.common.settings.searchEngine.customEngine.placeholder')}
|
placeholder={t('customEngine.placeholder')}
|
||||||
value={customSearchUrl}
|
value={customSearchUrl}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
setCustomSearchUrl(event.currentTarget.value);
|
setCustomSearchUrl(event.currentTarget.value);
|
||||||
@@ -80,7 +84,7 @@ export default function CommonSettings(args: any) {
|
|||||||
<LanguageSwitch />
|
<LanguageSwitch />
|
||||||
<ConfigChanger />
|
<ConfigChanger />
|
||||||
<SaveConfigComponent />
|
<SaveConfigComponent />
|
||||||
<Tip>{t('settings.tabs.common.settings.configTip')}</Tip>
|
<Tip>{t('configTip')}</Tip>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { Group, ActionIcon, Anchor, Text } from '@mantine/core';
|
import { Group, ActionIcon, Anchor, Text } from '@mantine/core';
|
||||||
import { IconBrandDiscord, IconBrandGithub } from '@tabler/icons';
|
import { IconBrandDiscord, IconBrandGithub } from '@tabler/icons';
|
||||||
import { t } from 'i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
||||||
import { CURRENT_VERSION } from '../../../data/constants';
|
import { CURRENT_VERSION } from '../../../data/constants';
|
||||||
|
|
||||||
export default function Credits(props: any) {
|
export default function Credits(props: any) {
|
||||||
|
const { t } = useTranslation('settings/common');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group position="center" mt="xs">
|
<Group position="center" mt="xs">
|
||||||
<Group spacing={0}>
|
<Group spacing={0}>
|
||||||
@@ -29,7 +31,7 @@ export default function Credits(props: any) {
|
|||||||
color: 'gray',
|
color: 'gray',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('settings.credits.madeWithLove')}
|
{t('credits.madeWithLove')}
|
||||||
<Anchor
|
<Anchor
|
||||||
href="https://github.com/ajnart"
|
href="https://github.com/ajnart"
|
||||||
style={{ color: 'inherit', fontStyle: 'inherit', fontSize: 'inherit' }}
|
style={{ color: 'inherit', fontStyle: 'inherit', fontSize: 'inherit' }}
|
||||||
|
|||||||
@@ -3,27 +3,26 @@ import { showNotification } from '@mantine/notifications';
|
|||||||
import { IconLanguage } from '@tabler/icons';
|
import { IconLanguage } from '@tabler/icons';
|
||||||
|
|
||||||
import { forwardRef, useState } from 'react';
|
import { forwardRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { convertCodeToName } from '../../translations/i18n';
|
|
||||||
|
|
||||||
export default function LanguageSwitch() {
|
export default function LanguageSwitch() {
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation('settings/general/internationalization');
|
||||||
const { language, languages, changeLanguage } = i18n;
|
/*const { language, languages, changeLanguage } = i18n;
|
||||||
|
|
||||||
const [selectedLanguage, setSelectedLanguage] = useState<string | null>(language);
|
const [selectedLanguage, setSelectedLanguage] = useState<string | null>(language);
|
||||||
|
|
||||||
const data = languages.map((language) => ({
|
const data = languages.map((language) => ({
|
||||||
image: `https://countryflagsapi.com/png/${language.split('-').pop()}`,
|
image: `https://countryflagsapi.com/png/${language.split('-').pop()}`,
|
||||||
label: convertCodeToName(language),
|
label: 'JA',
|
||||||
value: language,
|
value: language,
|
||||||
}));
|
}));*/
|
||||||
|
|
||||||
const onChangeSelect = (value: string) => {
|
const onChangeSelect = (value: string) => {
|
||||||
setSelectedLanguage(value);
|
//setSelectedLanguage(value);
|
||||||
|
|
||||||
const languageName = convertCodeToName(value);
|
const languageName = 'JA IS HALZ SCHEISSE NE';
|
||||||
|
|
||||||
changeLanguage(value)
|
/*changeLanguage(value)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
showNotification({
|
showNotification({
|
||||||
title: 'Language changed',
|
title: 'Language changed',
|
||||||
@@ -39,20 +38,27 @@ export default function LanguageSwitch() {
|
|||||||
color: 'red',
|
color: 'red',
|
||||||
autoClose: 5000,
|
autoClose: 5000,
|
||||||
});
|
});
|
||||||
});
|
});*/
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
<Select
|
<Select
|
||||||
icon={<IconLanguage size={18} />}
|
icon={<IconLanguage size={18} />}
|
||||||
label={t('settings.tabs.common.settings.language.title')}
|
label={t('label')}
|
||||||
data={data}
|
data={[
|
||||||
|
{
|
||||||
|
value: 'uwu',
|
||||||
|
label: 'asdf',
|
||||||
|
},
|
||||||
|
]}
|
||||||
itemComponent={SelectItem}
|
itemComponent={SelectItem}
|
||||||
nothingFound="Nothing found"
|
nothingFound="Nothing found"
|
||||||
onChange={onChangeSelect}
|
onChange={onChangeSelect}
|
||||||
|
/*
|
||||||
value={selectedLanguage}
|
value={selectedLanguage}
|
||||||
defaultValue={language}
|
defaultValue={language}
|
||||||
|
*/
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import { Checkbox, SimpleGrid, Stack, Title } from '@mantine/core';
|
import { Checkbox, SimpleGrid, Stack, Title } from '@mantine/core';
|
||||||
import { t } from 'i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import * as Modules from '../../modules';
|
import * as Modules from '../../modules';
|
||||||
import { useConfig } from '../../tools/state';
|
import { useConfig } from '../../tools/state';
|
||||||
|
|
||||||
export default function ModuleEnabler(props: any) {
|
export default function ModuleEnabler(props: any) {
|
||||||
const { config, setConfig } = useConfig();
|
const { config, setConfig } = useConfig();
|
||||||
|
const { t } = useTranslation('settings/general/module-enabler');
|
||||||
const modules = Object.values(Modules).map((module) => module);
|
const modules = Object.values(Modules).map((module) => module);
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
<Title order={4}>{t('settings.tabs.common.settings.moduleEnabler.title')}</Title>
|
<Title order={4}>{t('title')}</Title>
|
||||||
<SimpleGrid cols={3} spacing="xs">
|
<SimpleGrid cols={3} spacing="xs">
|
||||||
{modules.map((module) => (
|
{modules.map((module) => (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text, Slider, Stack } from '@mantine/core';
|
import { Text, Slider, Stack } from '@mantine/core';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useConfig } from '../../tools/state';
|
import { useConfig } from '../../tools/state';
|
||||||
import { t } from 'i18next';
|
|
||||||
|
|
||||||
export function OpacitySelector() {
|
export function OpacitySelector() {
|
||||||
const { config, setConfig } = useConfig();
|
const { config, setConfig } = useConfig();
|
||||||
|
const { t } = useTranslation('settings/customization/shade-selector');
|
||||||
|
|
||||||
const MARKS = [
|
const MARKS = [
|
||||||
{ value: 10, label: '10' },
|
{ value: 10, label: '10' },
|
||||||
@@ -31,7 +32,7 @@ export function OpacitySelector() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing="xs">
|
<Stack spacing="xs">
|
||||||
<Text>{t('settings.tabs.customizations.settings.opacitySelector.label')}</Text>
|
<Text>{t('label')}</Text>
|
||||||
<Slider
|
<Slider
|
||||||
defaultValue={config.settings.appOpacity || 100}
|
defaultValue={config.settings.appOpacity || 100}
|
||||||
step={10}
|
step={10}
|
||||||
|
|||||||
@@ -2,18 +2,20 @@ import { ActionIcon, Title, Tooltip, Drawer, Tabs, ScrollArea } from '@mantine/c
|
|||||||
import { useHotkeys } from '@mantine/hooks';
|
import { useHotkeys } from '@mantine/hooks';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { IconSettings } from '@tabler/icons';
|
import { IconSettings } from '@tabler/icons';
|
||||||
import { t } from 'i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
||||||
import AdvancedSettings from './AdvancedSettings';
|
import AdvancedSettings from './AdvancedSettings';
|
||||||
import CommonSettings from './CommonSettings';
|
import CommonSettings from './CommonSettings';
|
||||||
import Credits from './Credits';
|
import Credits from './Credits';
|
||||||
|
|
||||||
function SettingsMenu(props: any) {
|
function SettingsMenu(props: any) {
|
||||||
|
const { t } = useTranslation('settings/common');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs defaultValue="Common">
|
<Tabs defaultValue="Common">
|
||||||
<Tabs.List grow>
|
<Tabs.List grow>
|
||||||
<Tabs.Tab value="Common">{t('settings.tabs.common.title')}</Tabs.Tab>
|
<Tabs.Tab value="Common">{t('tabs.common')}</Tabs.Tab>
|
||||||
<Tabs.Tab value="Customizations">{t('settings.tabs.customizations.title')}</Tabs.Tab>
|
<Tabs.Tab value="Customizations">{t('tabs.customizations')}</Tabs.Tab>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
<Tabs.Panel data-autofocus value="Common">
|
<Tabs.Panel data-autofocus value="Common">
|
||||||
<ScrollArea style={{ height: '78vh' }} offsetScrollbars>
|
<ScrollArea style={{ height: '78vh' }} offsetScrollbars>
|
||||||
@@ -31,22 +33,24 @@ function SettingsMenu(props: any) {
|
|||||||
|
|
||||||
export function SettingsMenuButton(props: any) {
|
export function SettingsMenuButton(props: any) {
|
||||||
useHotkeys([['ctrl+L', () => setOpened(!opened)]]);
|
useHotkeys([['ctrl+L', () => setOpened(!opened)]]);
|
||||||
|
const { t } = useTranslation('settings/common');
|
||||||
|
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Drawer
|
<Drawer
|
||||||
size="xl"
|
size="xl"
|
||||||
padding="lg"
|
padding="lg"
|
||||||
position="right"
|
position="right"
|
||||||
title={<Title order={5}>{t('settings.title')}</Title>}
|
title={<Title order={5}>{t('title')}</Title>}
|
||||||
opened={props.opened || opened}
|
opened={props.opened || opened}
|
||||||
onClose={() => setOpened(false)}
|
onClose={() => setOpened(false)}
|
||||||
>
|
>
|
||||||
<SettingsMenu />
|
<SettingsMenu />
|
||||||
<Credits />
|
<Credits />
|
||||||
</Drawer>
|
</Drawer>
|
||||||
<Tooltip label={t('settings.tooltip')}>
|
<Tooltip label={t('tooltip')}>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant="default"
|
variant="default"
|
||||||
radius="md"
|
radius="md"
|
||||||
|
|||||||
@@ -9,13 +9,14 @@ import {
|
|||||||
Stack,
|
Stack,
|
||||||
Grid,
|
Grid,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useConfig } from '../../tools/state';
|
import { useConfig } from '../../tools/state';
|
||||||
import { useColorTheme } from '../../tools/color';
|
import { useColorTheme } from '../../tools/color';
|
||||||
import { t } from 'i18next';
|
|
||||||
|
|
||||||
export function ShadeSelector() {
|
export function ShadeSelector() {
|
||||||
const { config, setConfig } = useConfig();
|
const { config, setConfig } = useConfig();
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
|
const { t } = useTranslation('settings/general/shade-selector');
|
||||||
|
|
||||||
const { primaryColor, secondaryColor, primaryShade, setPrimaryShade } = useColorTheme();
|
const { primaryColor, secondaryColor, primaryShade, setPrimaryShade } = useColorTheme();
|
||||||
|
|
||||||
@@ -95,7 +96,7 @@ export function ShadeSelector() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
</Popover.Dropdown>
|
</Popover.Dropdown>
|
||||||
</Popover>
|
</Popover>
|
||||||
<Text>{t('settings.tabs.customizations.settings.shadeSelector.label')}</Text>
|
<Text>{t('label')}</Text>
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { createStyles, Switch, Group } from '@mantine/core';
|
import { createStyles, Switch, Group } from '@mantine/core';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useConfig } from '../../tools/state';
|
import { useConfig } from '../../tools/state';
|
||||||
import { t } from 'i18next';
|
|
||||||
|
|
||||||
const useStyles = createStyles((theme) => ({
|
const useStyles = createStyles((theme) => ({
|
||||||
root: {
|
root: {
|
||||||
@@ -34,6 +34,7 @@ export function WidgetsPositionSwitch() {
|
|||||||
const { classes, cx } = useStyles();
|
const { classes, cx } = useStyles();
|
||||||
const defaultPosition = config?.settings?.widgetPosition || 'right';
|
const defaultPosition = config?.settings?.widgetPosition || 'right';
|
||||||
const [widgetPosition, setWidgetPosition] = useState(defaultPosition);
|
const [widgetPosition, setWidgetPosition] = useState(defaultPosition);
|
||||||
|
const { t } = useTranslation('settings/general/widget-positions');
|
||||||
const toggleWidgetPosition = () => {
|
const toggleWidgetPosition = () => {
|
||||||
const position = widgetPosition === 'right' ? 'left' : 'right';
|
const position = widgetPosition === 'right' ? 'left' : 'right';
|
||||||
setWidgetPosition(position);
|
setWidgetPosition(position);
|
||||||
@@ -55,7 +56,7 @@ export function WidgetsPositionSwitch() {
|
|||||||
size="md"
|
size="md"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{t('settings.tabs.common.settings.widgetsPositionSwitch.label')}
|
{t('label')}
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,16 +23,16 @@ import {
|
|||||||
} from '../common';
|
} from '../common';
|
||||||
import { serviceItem } from '../../tools/types';
|
import { serviceItem } from '../../tools/types';
|
||||||
import { useColorTheme } from '../../tools/color';
|
import { useColorTheme } from '../../tools/color';
|
||||||
import { t } from 'i18next';
|
|
||||||
|
|
||||||
export const CalendarModule: IModule = {
|
export const CalendarModule: IModule = {
|
||||||
title: t('modules.calendar.title'),
|
title: 'Calendar',
|
||||||
description: t('modules.calendar.description'),
|
description:
|
||||||
|
'A calendar module for displaying upcoming releases. It interacts with the Sonarr and Radarr API.',
|
||||||
icon: CalendarIcon,
|
icon: CalendarIcon,
|
||||||
component: CalendarComponent,
|
component: CalendarComponent,
|
||||||
options: {
|
options: {
|
||||||
sundaystart: {
|
sundaystart: {
|
||||||
name: t('modules.calendar.options.sundayStart'),
|
name: 'Start the week on Sunday',
|
||||||
value: false,
|
value: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Badge, Button, Group, Image, Stack, Text, Title } from '@mantine/core';
|
import { Badge, Button, Group, Image, Stack, Text, Title } from '@mantine/core';
|
||||||
import { IconDownload, IconExternalLink, IconPlayerPlay } from '@tabler/icons';
|
import { IconDownload, IconExternalLink, IconPlayerPlay } from '@tabler/icons';
|
||||||
import { t } from 'i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useColorTheme } from '../../tools/color';
|
import { useColorTheme } from '../../tools/color';
|
||||||
import { useConfig } from '../../tools/state';
|
import { useConfig } from '../../tools/state';
|
||||||
@@ -160,6 +160,7 @@ export function SonarrMediaDisplay(props: any) {
|
|||||||
export function MediaDisplay({ media }: { media: IMedia }) {
|
export function MediaDisplay({ media }: { media: IMedia }) {
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
const { secondaryColor } = useColorTheme();
|
const { secondaryColor } = useColorTheme();
|
||||||
|
const { t } = useTranslation('modules/common-media-cards-module');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group mr="xs" align="stretch" noWrap style={{ maxHeight: 200 }}>
|
<Group mr="xs" align="stretch" noWrap style={{ maxHeight: 200 }}>
|
||||||
@@ -210,7 +211,7 @@ export function MediaDisplay({ media }: { media: IMedia }) {
|
|||||||
size="sm"
|
size="sm"
|
||||||
rightIcon={<IconPlayerPlay size={15} />}
|
rightIcon={<IconPlayerPlay size={15} />}
|
||||||
>
|
>
|
||||||
{t('modules.common.mediaCard.buttons.play')}
|
{t('buttons.play')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{media.imdbId && (
|
{media.imdbId && (
|
||||||
@@ -250,7 +251,7 @@ export function MediaDisplay({ media }: { media: IMedia }) {
|
|||||||
size="sm"
|
size="sm"
|
||||||
rightIcon={<IconDownload size={15} />}
|
rightIcon={<IconDownload size={15} />}
|
||||||
>
|
>
|
||||||
{t('modules.common.mediaCard.buttons.request')}
|
{t('buttons.request')}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createStyles, Stack, Title, useMantineColorScheme, useMantineTheme } from '@mantine/core';
|
import { createStyles, Stack, Title, useMantineColorScheme, useMantineTheme } from '@mantine/core';
|
||||||
import { IconCalendar as CalendarIcon } from '@tabler/icons';
|
import { IconCalendar as CalendarIcon } from '@tabler/icons';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { t } from 'i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useConfig } from '../../tools/state';
|
import { useConfig } from '../../tools/state';
|
||||||
import { serviceItem } from '../../tools/types';
|
import { serviceItem } from '../../tools/types';
|
||||||
@@ -142,32 +142,34 @@ export function DashdotComponent() {
|
|||||||
const totalSize =
|
const totalSize =
|
||||||
(info?.storage?.layout as any[])?.reduce((acc, curr) => (curr.size ?? 0) + acc, 0) ?? 0;
|
(info?.storage?.layout as any[])?.reduce((acc, curr) => (curr.size ?? 0) + acc, 0) ?? 0;
|
||||||
|
|
||||||
|
const { t } = useTranslation('modules/dashdot-module');
|
||||||
|
|
||||||
const graphs = [
|
const graphs = [
|
||||||
{
|
{
|
||||||
name: t('modules.dashDot.card.graphs.cpu.title'),
|
name: t('card.graphs.cpu.title'),
|
||||||
enabled: cpuEnabled,
|
enabled: cpuEnabled,
|
||||||
params: {
|
params: {
|
||||||
multiView: dashConfig?.cpuMultiView?.value ?? false,
|
multiView: dashConfig?.cpuMultiView?.value ?? false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: t('modules.dashDot.card.graphs.cpu.title'),
|
name: t('card.graphs.cpu.title'),
|
||||||
enabled: storageEnabled && !isCompact,
|
enabled: storageEnabled && !isCompact,
|
||||||
params: {
|
params: {
|
||||||
multiView: dashConfig?.storageMultiView?.value ?? false,
|
multiView: dashConfig?.storageMultiView?.value ?? false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: t('modules.dashDot.card.graphs.memory.title'),
|
name: t('card.graphs.memory.title'),
|
||||||
enabled: ramEnabled,
|
enabled: ramEnabled,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: t('modules.dashDot.card.graphs.network.title'),
|
name: t('card.graphs.network.title'),
|
||||||
enabled: networkEnabled,
|
enabled: networkEnabled,
|
||||||
spanTwo: true,
|
spanTwo: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: t('modules.dashDot.card.graphs.gpu.title'),
|
name: t('card.graphs.gpu.title'),
|
||||||
enabled: gpuEnabled,
|
enabled: gpuEnabled,
|
||||||
spanTwo: true,
|
spanTwo: true,
|
||||||
},
|
},
|
||||||
@@ -176,26 +178,24 @@ export function DashdotComponent() {
|
|||||||
if (dashdotUrl === '') {
|
if (dashdotUrl === '') {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2 className={classes.heading}>{t('modules.dashDot.card.title')}</h2>
|
<h2 className={classes.heading}>{t('card.title')}</h2>
|
||||||
<p>{t('modules.dashDot.card.errors.noService')}</p>
|
<p>{t('card.errors.noService')}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2 className={classes.heading}>{t('modules.dashDot.card.title')}</h2>
|
<h2 className={classes.heading}>{t('card.title')}</h2>
|
||||||
|
|
||||||
{!info ? (
|
{!info ? (
|
||||||
<p>{t('modules.dashDot.card.errors.noInformation')}</p>
|
<p>{t('card.errors.noInformation')}</p>
|
||||||
) : (
|
) : (
|
||||||
<div className={classes.graphsContainer}>
|
<div className={classes.graphsContainer}>
|
||||||
<div className={classes.table}>
|
<div className={classes.table}>
|
||||||
{storageEnabled && isCompact && (
|
{storageEnabled && isCompact && (
|
||||||
<div className={classes.tableRow}>
|
<div className={classes.tableRow}>
|
||||||
<p className={classes.tableLabel}>
|
<p className={classes.tableLabel}>{t('card.graphs.storage.label')}</p>
|
||||||
{t('modules.dashDot.card.graphs.storage.label')}
|
|
||||||
</p>
|
|
||||||
<p className={classes.tableValue}>
|
<p className={classes.tableValue}>
|
||||||
{((100 * totalUsed) / (totalSize || 1)).toFixed(1)}%{'\n'}
|
{((100 * totalUsed) / (totalSize || 1)).toFixed(1)}%{'\n'}
|
||||||
{bytePrettyPrint(totalUsed)} / {bytePrettyPrint(totalSize)}
|
{bytePrettyPrint(totalUsed)} / {bytePrettyPrint(totalSize)}
|
||||||
@@ -204,15 +204,12 @@ export function DashdotComponent() {
|
|||||||
)}
|
)}
|
||||||
{networkEnabled && (
|
{networkEnabled && (
|
||||||
<div className={classes.tableRow}>
|
<div className={classes.tableRow}>
|
||||||
<p className={classes.tableLabel}>
|
<p className={classes.tableLabel}>{t('card.graphs.network.label')}</p>
|
||||||
{t('modules.dashDot.card.graphs.network.label')}
|
|
||||||
</p>
|
|
||||||
<p className={classes.tableValue}>
|
<p className={classes.tableValue}>
|
||||||
{bpsPrettyPrint(info?.network?.speedUp)}{' '}
|
{bpsPrettyPrint(info?.network?.speedUp)} {t('card.graphs.network.metrics.upload')}
|
||||||
{t('modules.dashDot.card.graphs.network.metrics.upload')}
|
|
||||||
{'\n'}
|
{'\n'}
|
||||||
{bpsPrettyPrint(info?.network?.speedDown)}
|
{bpsPrettyPrint(info?.network?.speedDown)}
|
||||||
{t('modules.dashDot.card.graphs.network.metrics.download')}
|
{t('card.graphs.network.metrics.download')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ import {
|
|||||||
} from '@tabler/icons';
|
} from '@tabler/icons';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import Dockerode from 'dockerode';
|
import Dockerode from 'dockerode';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
import { tryMatchService } from '../../tools/addToHomarr';
|
import { tryMatchService } from '../../tools/addToHomarr';
|
||||||
import { AddAppShelfItemForm } from '../../components/AppShelf/AddAppShelfItem';
|
import { AddAppShelfItemForm } from '../../components/AppShelf/AddAppShelfItem';
|
||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
function sendDockerCommand(
|
function sendDockerCommand(
|
||||||
action: string,
|
action: string,
|
||||||
@@ -21,6 +22,8 @@ function sendDockerCommand(
|
|||||||
containerName: string,
|
containerName: string,
|
||||||
reload: () => void
|
reload: () => void
|
||||||
) {
|
) {
|
||||||
|
const { t } = useTranslation('modules/docker-module');
|
||||||
|
|
||||||
showNotification({
|
showNotification({
|
||||||
id: containerId,
|
id: containerId,
|
||||||
loading: true,
|
loading: true,
|
||||||
@@ -34,8 +37,8 @@ function sendDockerCommand(
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
updateNotification({
|
updateNotification({
|
||||||
id: containerId,
|
id: containerId,
|
||||||
title: `Container ${containerName} ${action}ed`,
|
title: t('messages.successfullyExecuted.message', { containerName, action }),
|
||||||
message: `Your container was successfully ${action}ed`,
|
message: t('messages.successfullyExecuted.message', { action }),
|
||||||
icon: <IconCheck />,
|
icon: <IconCheck />,
|
||||||
autoClose: 2000,
|
autoClose: 2000,
|
||||||
});
|
});
|
||||||
@@ -44,7 +47,7 @@ function sendDockerCommand(
|
|||||||
updateNotification({
|
updateNotification({
|
||||||
id: containerId,
|
id: containerId,
|
||||||
color: 'red',
|
color: 'red',
|
||||||
title: 'There was an error',
|
title: t('errors.unknownError.title'),
|
||||||
message: err.response.data.reason,
|
message: err.response.data.reason,
|
||||||
autoClose: 2000,
|
autoClose: 2000,
|
||||||
});
|
});
|
||||||
@@ -61,6 +64,8 @@ export interface ContainerActionBarProps {
|
|||||||
|
|
||||||
export default function ContainerActionBar({ selected, reload }: ContainerActionBarProps) {
|
export default function ContainerActionBar({ selected, reload }: ContainerActionBarProps) {
|
||||||
const [opened, setOpened] = useState<boolean>(false);
|
const [opened, setOpened] = useState<boolean>(false);
|
||||||
|
const { t } = useTranslation('modules/docker-module');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group>
|
<Group>
|
||||||
<Modal
|
<Modal
|
||||||
@@ -68,12 +73,12 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
|
|||||||
radius="md"
|
radius="md"
|
||||||
opened={opened}
|
opened={opened}
|
||||||
onClose={() => setOpened(false)}
|
onClose={() => setOpened(false)}
|
||||||
title="Add service"
|
title={t('actionBar.addService.title')}
|
||||||
>
|
>
|
||||||
<AddAppShelfItemForm
|
<AddAppShelfItemForm
|
||||||
setOpened={setOpened}
|
setOpened={setOpened}
|
||||||
{...tryMatchService(selected.at(0))}
|
{...tryMatchService(selected.at(0))}
|
||||||
message="Add service to homarr"
|
message={t('actionBar.addService.message')}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
<Button
|
<Button
|
||||||
@@ -89,7 +94,7 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
|
|||||||
color="orange"
|
color="orange"
|
||||||
radius="md"
|
radius="md"
|
||||||
>
|
>
|
||||||
Restart
|
{t('actionBar.restart.title')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<IconPlayerStop />}
|
leftIcon={<IconPlayerStop />}
|
||||||
@@ -104,7 +109,7 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
|
|||||||
color="red"
|
color="red"
|
||||||
radius="md"
|
radius="md"
|
||||||
>
|
>
|
||||||
Stop
|
{t('actionBar.stop.title')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<IconPlayerPlay />}
|
leftIcon={<IconPlayerPlay />}
|
||||||
@@ -119,10 +124,10 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
|
|||||||
color="green"
|
color="green"
|
||||||
radius="md"
|
radius="md"
|
||||||
>
|
>
|
||||||
Start
|
{t('actionBar.start.title')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button leftIcon={<IconRefresh />} onClick={() => reload()} variant="light" radius="md">
|
<Button leftIcon={<IconRefresh />} onClick={() => reload()} variant="light" radius="md">
|
||||||
Refresh data
|
{t('actionBar.refreshData.title')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<IconPlus />}
|
leftIcon={<IconPlus />}
|
||||||
@@ -133,7 +138,7 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
|
|||||||
if (selected.length !== 1) {
|
if (selected.length !== 1) {
|
||||||
showNotification({
|
showNotification({
|
||||||
autoClose: 5000,
|
autoClose: 5000,
|
||||||
title: <Title order={5}>Please only add one service at a time!</Title>,
|
title: <Title order={5}>{t('errors.oneServiceAtATime')}</Title>,
|
||||||
color: 'red',
|
color: 'red',
|
||||||
message: undefined,
|
message: undefined,
|
||||||
});
|
});
|
||||||
@@ -142,7 +147,7 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Add to Homarr
|
{t('actionBar.addToHomarr.title')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<IconTrash />}
|
leftIcon={<IconTrash />}
|
||||||
@@ -157,7 +162,7 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Remove
|
{t('actionBar.remove.title')}
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Badge, BadgeVariant, MantineSize } from '@mantine/core';
|
import { Badge, BadgeVariant, MantineSize } from '@mantine/core';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
import Dockerode from 'dockerode';
|
import Dockerode from 'dockerode';
|
||||||
|
|
||||||
export interface ContainerStateProps {
|
export interface ContainerStateProps {
|
||||||
@@ -7,6 +8,9 @@ export interface ContainerStateProps {
|
|||||||
|
|
||||||
export default function ContainerState(props: ContainerStateProps) {
|
export default function ContainerState(props: ContainerStateProps) {
|
||||||
const { state } = props;
|
const { state } = props;
|
||||||
|
|
||||||
|
const { t } = useTranslation('modules/docker-module');
|
||||||
|
|
||||||
const options: {
|
const options: {
|
||||||
size: MantineSize;
|
size: MantineSize;
|
||||||
radius: MantineSize;
|
radius: MantineSize;
|
||||||
@@ -20,28 +24,28 @@ export default function ContainerState(props: ContainerStateProps) {
|
|||||||
case 'running': {
|
case 'running': {
|
||||||
return (
|
return (
|
||||||
<Badge color="green" {...options}>
|
<Badge color="green" {...options}>
|
||||||
Running
|
{t('table.states.running')}
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case 'created': {
|
case 'created': {
|
||||||
return (
|
return (
|
||||||
<Badge color="cyan" {...options}>
|
<Badge color="cyan" {...options}>
|
||||||
Created
|
{t('table.states.created')}
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case 'exited': {
|
case 'exited': {
|
||||||
return (
|
return (
|
||||||
<Badge color="red" {...options}>
|
<Badge color="red" {...options}>
|
||||||
Stopped
|
{t('table.states.stopped')}
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
return (
|
return (
|
||||||
<Badge color="purple" {...options}>
|
<Badge color="purple" {...options}>
|
||||||
Unknown
|
{t('table.states.unknown')}
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 axios from 'axios';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import Docker from 'dockerode';
|
import Docker from 'dockerode';
|
||||||
import { IconBrandDocker, IconX } from '@tabler/icons';
|
import { IconBrandDocker, IconX } from '@tabler/icons';
|
||||||
import { showNotification } from '@mantine/notifications';
|
import { showNotification } from '@mantine/notifications';
|
||||||
import { t } from 'i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
||||||
import ContainerActionBar from './ContainerActionBar';
|
import ContainerActionBar from './ContainerActionBar';
|
||||||
import DockerTable from './DockerTable';
|
import DockerTable from './DockerTable';
|
||||||
@@ -25,6 +25,8 @@ export default function DockerMenuButton(props: any) {
|
|||||||
const { config } = useConfig();
|
const { config } = useConfig();
|
||||||
const moduleEnabled = config.modules?.[DockerModule.title]?.enabled ?? false;
|
const moduleEnabled = config.modules?.[DockerModule.title]?.enabled ?? false;
|
||||||
|
|
||||||
|
const { t } = useTranslation('modules/docker-module');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reload();
|
reload();
|
||||||
}, [config.modules]);
|
}, [config.modules]);
|
||||||
@@ -44,10 +46,10 @@ export default function DockerMenuButton(props: any) {
|
|||||||
// Send an Error notification
|
// Send an Error notification
|
||||||
showNotification({
|
showNotification({
|
||||||
autoClose: 1500,
|
autoClose: 1500,
|
||||||
title: <Text>{t('layout.header.docker.errors.integrationFailed.title')}</Text>,
|
title: <Text>{t('errors.integrationFailed.title')}</Text>,
|
||||||
color: 'red',
|
color: 'red',
|
||||||
icon: <IconX />,
|
icon: <IconX />,
|
||||||
message: t('layout.header.docker.errors.integrationFailed.message'),
|
message: t('errors.integrationFailed.message'),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}, 300);
|
}, 300);
|
||||||
@@ -69,7 +71,7 @@ export default function DockerMenuButton(props: any) {
|
|||||||
>
|
>
|
||||||
<DockerTable containers={containers} selection={selection} setSelection={setSelection} />
|
<DockerTable containers={containers} selection={selection} setSelection={setSelection} />
|
||||||
</Drawer>
|
</Drawer>
|
||||||
<Tooltip label={t('layout.header.docker.actionIcon.tooltip')}>
|
<Tooltip label={t('actionIcon.tooltip')}>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant="default"
|
variant="default"
|
||||||
radius="md"
|
radius="md"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Table, Checkbox, Group, Badge, createStyles, ScrollArea, TextInput } from '@mantine/core';
|
import { Table, Checkbox, Group, Badge, createStyles, ScrollArea, TextInput } from '@mantine/core';
|
||||||
import { IconSearch } from '@tabler/icons';
|
import { IconSearch } from '@tabler/icons';
|
||||||
import { t } from 'i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
||||||
import Dockerode from 'dockerode';
|
import Dockerode from 'dockerode';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -28,6 +28,8 @@ export default function DockerTable({
|
|||||||
const { classes, cx } = useStyles();
|
const { classes, cx } = useStyles();
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
|
|
||||||
|
const { t } = useTranslation('modules/docker-module');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setContainers(containers);
|
setContainers(containers);
|
||||||
}, [containers]);
|
}, [containers]);
|
||||||
@@ -83,7 +85,7 @@ export default function DockerTable({
|
|||||||
))}
|
))}
|
||||||
{element.Ports.length > 3 && (
|
{element.Ports.length > 3 && (
|
||||||
<Badge variant="filled">
|
<Badge variant="filled">
|
||||||
{t('modules.docker.table.body.portCollapse', { ports: element.Ports.length - 3 })}
|
{t('table.body.portCollapse', { ports: element.Ports.length - 3 })}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
@@ -98,7 +100,7 @@ export default function DockerTable({
|
|||||||
return (
|
return (
|
||||||
<ScrollArea style={{ height: '80vh' }}>
|
<ScrollArea style={{ height: '80vh' }}>
|
||||||
<TextInput
|
<TextInput
|
||||||
placeholder={t('modules.docker.search.placeholder')}
|
placeholder={t('search.placeholder')}
|
||||||
mt="md"
|
mt="md"
|
||||||
icon={<IconSearch size={14} />}
|
icon={<IconSearch size={14} />}
|
||||||
value={search}
|
value={search}
|
||||||
@@ -115,10 +117,10 @@ export default function DockerTable({
|
|||||||
transitionDuration={0}
|
transitionDuration={0}
|
||||||
/>
|
/>
|
||||||
</th>
|
</th>
|
||||||
<th>{t('modules.docker.table.header.name')}</th>
|
<th>{t('table.header.name')}</th>
|
||||||
<th>{t('modules.docker.table.header.image')}</th>
|
<th>{t('table.header.image')}</th>
|
||||||
<th>{t('modules.docker.table.header.ports')}</th>
|
<th>{t('table.header.ports')}</th>
|
||||||
<th>{t('modules.docker.table.header.state')}</th>
|
<th>{t('table.header.state')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>{rows}</tbody>
|
<tbody>{rows}</tbody>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { useConfig } from '../../tools/state';
|
|||||||
import { AddItemShelfButton } from '../../components/AppShelf/AddAppShelfItem';
|
import { AddItemShelfButton } from '../../components/AppShelf/AddAppShelfItem';
|
||||||
import { useSetSafeInterval } from '../../tools/hooks/useSetSafeInterval';
|
import { useSetSafeInterval } from '../../tools/hooks/useSetSafeInterval';
|
||||||
import { humanFileSize } from '../../tools/humanFileSize';
|
import { humanFileSize } from '../../tools/humanFileSize';
|
||||||
import { t } from 'i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
||||||
export const DownloadsModule: IModule = {
|
export const DownloadsModule: IModule = {
|
||||||
title: 'Torrent',
|
title: 'Torrent',
|
||||||
@@ -50,6 +50,9 @@ export default function DownloadComponent() {
|
|||||||
const [torrents, setTorrents] = useState<NormalizedTorrent[]>([]);
|
const [torrents, setTorrents] = useState<NormalizedTorrent[]>([]);
|
||||||
const setSafeInterval = useSetSafeInterval();
|
const setSafeInterval = useSetSafeInterval();
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
|
const { t } = useTranslation('modules/downloads-module');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
if (downloadServices.length === 0) return;
|
if (downloadServices.length === 0) return;
|
||||||
@@ -106,12 +109,12 @@ export default function DownloadComponent() {
|
|||||||
const DEVICE_WIDTH = 576;
|
const DEVICE_WIDTH = 576;
|
||||||
const ths = (
|
const ths = (
|
||||||
<tr>
|
<tr>
|
||||||
<th>{t('modules.downloads.card.table.header.name')}</th>
|
<th>{t('card.table.header.name')}</th>
|
||||||
<th>{t('modules.downloads.card.table.header.size')}</th>
|
<th>{t('card.table.header.size')}</th>
|
||||||
{width > 576 ? <th>{t('modules.downloads.card.table.header.download')}</th> : ''}
|
{width > 576 ? <th>{t('card.table.header.download')}</th> : ''}
|
||||||
{width > 576 ? <th>{t('modules.downloads.card.table.header.upload')}</th> : ''}
|
{width > 576 ? <th>{t('card.table.header.upload')}</th> : ''}
|
||||||
<th>{t('modules.downloads.card.table.header.estimatedTimeOfArrival')}</th>
|
<th>{t('card.table.header.estimatedTimeOfArrival')}</th>
|
||||||
<th>{t('modules.downloads.card.table.header.progress')}</th>
|
<th>{t('card.table.header.progress')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
// Convert Seconds to readable format.
|
// Convert Seconds to readable format.
|
||||||
@@ -196,7 +199,7 @@ export default function DownloadComponent() {
|
|||||||
</Table>
|
</Table>
|
||||||
) : (
|
) : (
|
||||||
<Center style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
<Center style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||||
<Title order={3}>{t('modules.downloads.card.table.body.nothingFound')}</Title>
|
<Title order={3}>{t('card.table.body.nothingFound')}</Title>
|
||||||
</Center>
|
</Center>
|
||||||
)}
|
)}
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { showNotification, updateNotification } from '@mantine/notifications';
|
|||||||
import { IconAlertCircle, IconCheck, IconDownload } from '@tabler/icons';
|
import { IconAlertCircle, IconCheck, IconDownload } from '@tabler/icons';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import Consola from 'consola';
|
import Consola from 'consola';
|
||||||
import { t } from 'i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useColorTheme } from '../../tools/color';
|
import { useColorTheme } from '../../tools/color';
|
||||||
@@ -28,6 +28,7 @@ const useStyles = createStyles((theme) => ({
|
|||||||
export function RequestModal({ base, opened, setOpened }: RequestModalProps) {
|
export function RequestModal({ base, opened, setOpened }: RequestModalProps) {
|
||||||
const [result, setResult] = useState<MovieResult | TvShowResult>();
|
const [result, setResult] = useState<MovieResult | TvShowResult>();
|
||||||
const { secondaryColor } = useColorTheme();
|
const { secondaryColor } = useColorTheme();
|
||||||
|
|
||||||
function getResults(base: Result) {
|
function getResults(base: Result) {
|
||||||
axios.get(`/api/modules/overseerr/${base.id}?type=${base.mediaType}`).then((res) => {
|
axios.get(`/api/modules/overseerr/${base.id}?type=${base.mediaType}`).then((res) => {
|
||||||
setResult(res.data);
|
setResult(res.data);
|
||||||
@@ -56,6 +57,8 @@ export function MovieRequestModal({
|
|||||||
setOpened: (opened: boolean) => void;
|
setOpened: (opened: boolean) => void;
|
||||||
}) {
|
}) {
|
||||||
const { secondaryColor } = useColorTheme();
|
const { secondaryColor } = useColorTheme();
|
||||||
|
const { t } = useTranslation('modules/overseerr-module');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
onClose={() => setOpened(false)}
|
onClose={() => setOpened(false)}
|
||||||
@@ -68,23 +71,23 @@ export function MovieRequestModal({
|
|||||||
title={
|
title={
|
||||||
<Group>
|
<Group>
|
||||||
<IconDownload />
|
<IconDownload />
|
||||||
{t('modules.overseerr.popup.item.buttons.askFor', { title: result.title })}
|
{t('popup.item.buttons.askFor', { title: result.title })}
|
||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Alert
|
<Alert
|
||||||
icon={<IconAlertCircle size={16} />}
|
icon={<IconAlertCircle size={16} />}
|
||||||
title={t('modules.overseerr.popup.item.alerts.automaticApproval.title')}
|
title={t('popup.item.alerts.automaticApproval.title')}
|
||||||
color={secondaryColor}
|
color={secondaryColor}
|
||||||
radius="md"
|
radius="md"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
>
|
>
|
||||||
{t('modules.overseerr.popup.item.alerts.automaticApproval.text')}
|
{t('popup.item.alerts.automaticApproval.text')}
|
||||||
</Alert>
|
</Alert>
|
||||||
<Group>
|
<Group>
|
||||||
<Button variant="outline" color="gray" onClick={() => setOpened(false)}>
|
<Button variant="outline" color="gray" onClick={() => setOpened(false)}>
|
||||||
{t('modules.overseerr.popup.item.buttons.cancel')}
|
{t('popup.item.buttons.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -92,7 +95,7 @@ export function MovieRequestModal({
|
|||||||
askForMedia(MediaType.Movie, result.id, result.title, []);
|
askForMedia(MediaType.Movie, result.id, result.title, []);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('modules.overseerr.popup.item.buttons.request')}
|
{t('popup.item.buttons.request')}
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -111,6 +114,7 @@ export function TvRequestModal({
|
|||||||
}) {
|
}) {
|
||||||
const [selection, setSelection] = useState<TvShowResultSeason[]>(result.seasons);
|
const [selection, setSelection] = useState<TvShowResultSeason[]>(result.seasons);
|
||||||
const { classes, cx } = useStyles();
|
const { classes, cx } = useStyles();
|
||||||
|
const { t } = useTranslation('modules/overseerr-module');
|
||||||
|
|
||||||
const toggleRow = (container: TvShowResultSeason) =>
|
const toggleRow = (container: TvShowResultSeason) =>
|
||||||
setSelection((current: TvShowResultSeason[]) =>
|
setSelection((current: TvShowResultSeason[]) =>
|
||||||
@@ -149,7 +153,7 @@ export function TvRequestModal({
|
|||||||
title={
|
title={
|
||||||
<Group>
|
<Group>
|
||||||
<IconDownload />
|
<IconDownload />
|
||||||
{t('modules.overseerr.popup.item.buttons.askFor', {
|
{t('popup.item.buttons.askFor', {
|
||||||
title: result.name ?? result.originalName ?? 'a TV show',
|
title: result.name ?? result.originalName ?? 'a TV show',
|
||||||
})}
|
})}
|
||||||
</Group>
|
</Group>
|
||||||
@@ -158,15 +162,15 @@ export function TvRequestModal({
|
|||||||
<Stack>
|
<Stack>
|
||||||
<Alert
|
<Alert
|
||||||
icon={<IconAlertCircle size={16} />}
|
icon={<IconAlertCircle size={16} />}
|
||||||
title={t('modules.overseerr.popup.item.alerts.automaticApproval.title')}
|
title={t('popup.item.alerts.automaticApproval.title')}
|
||||||
color={secondaryColor}
|
color={secondaryColor}
|
||||||
radius="md"
|
radius="md"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
>
|
>
|
||||||
{t('modules.overseerr.popup.item.alerts.automaticApproval.text')}
|
{t('popup.item.alerts.automaticApproval.text')}
|
||||||
</Alert>
|
</Alert>
|
||||||
<Table captionSide="bottom" highlightOnHover>
|
<Table captionSide="bottom" highlightOnHover>
|
||||||
<caption>{t('modules.overseerr.popup.seasonSelector.caption')}</caption>
|
<caption>{t('popup.seasonSelector.caption')}</caption>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
@@ -177,15 +181,15 @@ export function TvRequestModal({
|
|||||||
transitionDuration={0}
|
transitionDuration={0}
|
||||||
/>
|
/>
|
||||||
</th>
|
</th>
|
||||||
<th>{t('modules.overseerr.popup.seasonSelector.table.header.season')}</th>
|
<th>{t('popup.seasonSelector.table.header.season')}</th>
|
||||||
<th>{t('modules.overseerr.popup.seasonSelector.table.header.numberOfEpisodes')}</th>
|
<th>{t('popup.seasonSelector.table.header.numberOfEpisodes')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>{rows}</tbody>
|
<tbody>{rows}</tbody>
|
||||||
</Table>
|
</Table>
|
||||||
<Group>
|
<Group>
|
||||||
<Button variant="outline" color="gray" onClick={() => setOpened(false)}>
|
<Button variant="outline" color="gray" onClick={() => setOpened(false)}>
|
||||||
{t('modules.overseerr.popup.item.buttons.cancel')}
|
{t('popup.item.buttons.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -199,7 +203,7 @@ export function TvRequestModal({
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('modules.overseerr.popup.item.buttons.request')}
|
{t('popup.item.buttons.request')}
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useEffect, useState } from 'react';
|
|||||||
import { IconPlug as Plug } from '@tabler/icons';
|
import { IconPlug as Plug } from '@tabler/icons';
|
||||||
import { useConfig } from '../../tools/state';
|
import { useConfig } from '../../tools/state';
|
||||||
import { IModule } from '../ModuleTypes';
|
import { IModule } from '../ModuleTypes';
|
||||||
import { t } from 'i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
||||||
export const PingModule: IModule = {
|
export const PingModule: IModule = {
|
||||||
title: 'Ping Services',
|
title: 'Ping Services',
|
||||||
@@ -23,6 +23,8 @@ export default function PingComponent(props: any) {
|
|||||||
const [response, setResponse] = useState(500);
|
const [response, setResponse] = useState(500);
|
||||||
const exists = config.modules?.[PingModule.title]?.enabled ?? false;
|
const exists = config.modules?.[PingModule.title]?.enabled ?? false;
|
||||||
|
|
||||||
|
const { t } = useTranslation('modules/ping-module');
|
||||||
|
|
||||||
function statusCheck(response: AxiosResponse) {
|
function statusCheck(response: AxiosResponse) {
|
||||||
const { status }: { status: string[] } = props;
|
const { status }: { status: string[] } = props;
|
||||||
//Default Status
|
//Default Status
|
||||||
@@ -69,10 +71,10 @@ export default function PingComponent(props: any) {
|
|||||||
radius="lg"
|
radius="lg"
|
||||||
label={
|
label={
|
||||||
isOnline === 'loading'
|
isOnline === 'loading'
|
||||||
? t('modules.ping.states.loading')
|
? t('states.loading')
|
||||||
: isOnline === 'online'
|
: isOnline === 'online'
|
||||||
? t('modules.ping.states.online', { response })
|
? t('states.online', { response })
|
||||||
: t('modules.ping.states.offline', { response })
|
: t('states.offline', { response })
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Indicator
|
<Indicator
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ import {
|
|||||||
IconDownload as Download,
|
IconDownload as Download,
|
||||||
IconMovie,
|
IconMovie,
|
||||||
} from '@tabler/icons';
|
} from '@tabler/icons';
|
||||||
import { t } from 'i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { showNotification } from '@mantine/notifications';
|
import { showNotification } from '@mantine/notifications';
|
||||||
import { useConfig } from '../../tools/state';
|
import { useConfig } from '../../tools/state';
|
||||||
@@ -61,6 +60,7 @@ export default function SearchBar(props: any) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
const [debounced, cancel] = useDebouncedValue(form.values.query, 250);
|
const [debounced, cancel] = useDebouncedValue(form.values.query, 250);
|
||||||
|
const { t } = useTranslation('modules/search-module');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (OverseerrService === undefined && isOverseerrEnabled) {
|
if (OverseerrService === undefined && isOverseerrEnabled) {
|
||||||
@@ -177,7 +177,7 @@ export default function SearchBar(props: any) {
|
|||||||
radius="md"
|
radius="md"
|
||||||
size="md"
|
size="md"
|
||||||
styles={{ rightSection: { pointerEvents: 'none' } }}
|
styles={{ rightSection: { pointerEvents: 'none' } }}
|
||||||
placeholder={t('layout.header.search.input.placeholder')}
|
placeholder={t('input.placeholder')}
|
||||||
{...props}
|
{...props}
|
||||||
{...form.getInputProps('query')}
|
{...form.getInputProps('query')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ import {
|
|||||||
IconSnowflake as Snowflake,
|
IconSnowflake as Snowflake,
|
||||||
IconSun as Sun,
|
IconSun as Sun,
|
||||||
} from '@tabler/icons';
|
} from '@tabler/icons';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useConfig } from '../../tools/state';
|
import { useConfig } from '../../tools/state';
|
||||||
import { IModule } from '../ModuleTypes';
|
import { IModule } from '../ModuleTypes';
|
||||||
import { WeatherResponse } from './WeatherInterface';
|
import { WeatherResponse } from './WeatherInterface';
|
||||||
import { t } from 'i18next';
|
|
||||||
|
|
||||||
export const WeatherModule: IModule = {
|
export const WeatherModule: IModule = {
|
||||||
title: 'Weather',
|
title: 'Weather',
|
||||||
@@ -49,85 +49,87 @@ export const WeatherModule: IModule = {
|
|||||||
// 95 *Thunderstorm: Slight or moderate
|
// 95 *Thunderstorm: Slight or moderate
|
||||||
// 96, 99 *Thunderstorm with slight and heavy hail
|
// 96, 99 *Thunderstorm with slight and heavy hail
|
||||||
export function WeatherIcon(props: any) {
|
export function WeatherIcon(props: any) {
|
||||||
|
const { t } = useTranslation('modules/weather-module');
|
||||||
|
|
||||||
const { code } = props;
|
const { code } = props;
|
||||||
let data: { icon: any; name: string };
|
let data: { icon: any; name: string };
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case 0: {
|
case 0: {
|
||||||
data = { icon: Sun, name: t('modules.weather.card.weatherDescriptions.clear') };
|
data = { icon: Sun, name: t('card.weatherDescriptions.clear') };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 1:
|
case 1:
|
||||||
case 2:
|
case 2:
|
||||||
case 3: {
|
case 3: {
|
||||||
data = { icon: Cloud, name: t('modules.weather.card.weatherDescriptions.mainlyClear') };
|
data = { icon: Cloud, name: t('card.weatherDescriptions.mainlyClear') };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 45:
|
case 45:
|
||||||
case 48: {
|
case 48: {
|
||||||
data = { icon: CloudFog, name: t('modules.weather.card.weatherDescriptions.fog') };
|
data = { icon: CloudFog, name: t('card.weatherDescriptions.fog') };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 51:
|
case 51:
|
||||||
case 53:
|
case 53:
|
||||||
case 55: {
|
case 55: {
|
||||||
data = { icon: Cloud, name: t('modules.weather.card.weatherDescriptions.drizzle') };
|
data = { icon: Cloud, name: t('card.weatherDescriptions.drizzle') };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 56:
|
case 56:
|
||||||
case 57: {
|
case 57: {
|
||||||
data = {
|
data = {
|
||||||
icon: Snowflake,
|
icon: Snowflake,
|
||||||
name: t('modules.weather.card.weatherDescriptions.freezingDrizzle'),
|
name: t('card.weatherDescriptions.freezingDrizzle'),
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 61:
|
case 61:
|
||||||
case 63:
|
case 63:
|
||||||
case 65: {
|
case 65: {
|
||||||
data = { icon: CloudRain, name: t('modules.weather.card.weatherDescriptions.rain') };
|
data = { icon: CloudRain, name: t('card.weatherDescriptions.rain') };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 66:
|
case 66:
|
||||||
case 67: {
|
case 67: {
|
||||||
data = { icon: CloudRain, name: t('modules.weather.card.weatherDescriptions.freezingRain') };
|
data = { icon: CloudRain, name: t('card.weatherDescriptions.freezingRain') };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 71:
|
case 71:
|
||||||
case 73:
|
case 73:
|
||||||
case 75: {
|
case 75: {
|
||||||
data = { icon: CloudSnow, name: t('modules.weather.card.weatherDescriptions.snowFall') };
|
data = { icon: CloudSnow, name: t('card.weatherDescriptions.snowFall') };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 77: {
|
case 77: {
|
||||||
data = { icon: CloudSnow, name: t('modules.weather.card.weatherDescriptions.snowGrains') };
|
data = { icon: CloudSnow, name: t('card.weatherDescriptions.snowGrains') };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 80:
|
case 80:
|
||||||
case 81:
|
case 81:
|
||||||
case 82: {
|
case 82: {
|
||||||
data = { icon: CloudRain, name: t('modules.weather.card.weatherDescriptions.rainShowers') };
|
data = { icon: CloudRain, name: t('card.weatherDescriptions.rainShowers') };
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 85:
|
case 85:
|
||||||
case 86: {
|
case 86: {
|
||||||
data = { icon: CloudSnow, name: t('modules.weather.card.weatherDescriptions.snowShowers') };
|
data = { icon: CloudSnow, name: t('card.weatherDescriptions.snowShowers') };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 95: {
|
case 95: {
|
||||||
data = { icon: CloudStorm, name: t('modules.weather.card.weatherDescriptions.thunderstorm') };
|
data = { icon: CloudStorm, name: t('card.weatherDescriptions.thunderstorm') };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 96:
|
case 96:
|
||||||
case 99: {
|
case 99: {
|
||||||
data = {
|
data = {
|
||||||
icon: CloudStorm,
|
icon: CloudStorm,
|
||||||
name: t('modules.weather.card.weatherDescriptions.thunderstormWithHail'),
|
name: t('card.weatherDescriptions.thunderstormWithHail'),
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
data = { icon: QuestionMark, name: t('modules.weather.card.weatherDescriptions.unknown') };
|
data = { icon: QuestionMark, name: t('card.weatherDescriptions.unknown') };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -4,17 +4,16 @@ import { AppProps } from 'next/app';
|
|||||||
import { getCookie, setCookie } from 'cookies-next';
|
import { getCookie, setCookie } from 'cookies-next';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import { MantineProvider, ColorScheme, ColorSchemeProvider, MantineTheme } from '@mantine/core';
|
import { MantineProvider, ColorScheme, ColorSchemeProvider, MantineTheme } from '@mantine/core';
|
||||||
|
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||||
import { NotificationsProvider } from '@mantine/notifications';
|
import { NotificationsProvider } from '@mantine/notifications';
|
||||||
import { useHotkeys } from '@mantine/hooks';
|
import { useHotkeys } from '@mantine/hooks';
|
||||||
import { ModalsProvider } from '@mantine/modals';
|
import { ModalsProvider } from '@mantine/modals';
|
||||||
|
import { appWithTranslation } from 'next-i18next';
|
||||||
import { ConfigProvider } from '../tools/state';
|
import { ConfigProvider } from '../tools/state';
|
||||||
import { theme } from '../tools/theme';
|
import { theme } from '../tools/theme';
|
||||||
import { ColorTheme } from '../tools/color';
|
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 { Component, pageProps } = props;
|
||||||
const [colorScheme, setColorScheme] = useState<ColorScheme>(props.colorScheme);
|
const [colorScheme, setColorScheme] = useState<ColorScheme>(props.colorScheme);
|
||||||
|
|
||||||
@@ -72,9 +71,7 @@ export default function App(this: any, props: AppProps & { colorScheme: ColorSch
|
|||||||
<NotificationsProvider limit={4} position="bottom-left">
|
<NotificationsProvider limit={4} position="bottom-left">
|
||||||
<ModalsProvider>
|
<ModalsProvider>
|
||||||
<ConfigProvider>
|
<ConfigProvider>
|
||||||
<TranslationProvider>
|
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
</TranslationProvider>
|
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</ModalsProvider>
|
</ModalsProvider>
|
||||||
</NotificationsProvider>
|
</NotificationsProvider>
|
||||||
@@ -88,3 +85,5 @@ export default function App(this: any, props: AppProps & { colorScheme: ColorSch
|
|||||||
App.getInitialProps = ({ ctx }: { ctx: GetServerSidePropsContext }) => ({
|
App.getInitialProps = ({ ctx }: { ctx: GetServerSidePropsContext }) => ({
|
||||||
colorScheme: getCookie('color-scheme', ctx) || 'light',
|
colorScheme: getCookie('color-scheme', ctx) || 'light',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default appWithTranslation(App);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { getCookie, setCookie } from 'cookies-next';
|
import { getCookie, setCookie } from 'cookies-next';
|
||||||
import { GetServerSidePropsContext } from 'next';
|
import { GetServerSidePropsContext } from 'next';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||||
|
|
||||||
import AppShelf from '../components/AppShelf/AppShelf';
|
import AppShelf from '../components/AppShelf/AppShelf';
|
||||||
import LoadConfigComponent from '../components/Config/LoadConfig';
|
import LoadConfigComponent from '../components/Config/LoadConfig';
|
||||||
import { Config } from '../tools/types';
|
import { Config } from '../tools/types';
|
||||||
@@ -13,6 +15,7 @@ import Layout from '../components/layout/Layout';
|
|||||||
export async function getServerSideProps({
|
export async function getServerSideProps({
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
|
locale,
|
||||||
}: GetServerSidePropsContext): Promise<{ props: { config: Config } }> {
|
}: GetServerSidePropsContext): Promise<{ props: { config: Config } }> {
|
||||||
let cookie = getCookie('config-name', { req, res });
|
let cookie = getCookie('config-name', { req, res });
|
||||||
if (!cookie) {
|
if (!cookie) {
|
||||||
@@ -24,7 +27,31 @@ export async function getServerSideProps({
|
|||||||
});
|
});
|
||||||
cookie = 'default';
|
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) {
|
export default function HomePage(props: any) {
|
||||||
|
|||||||
@@ -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 (
|
|
||||||
<Suspense>
|
|
||||||
<I18nextProvider i18n={loadI18n()}>{children}</I18nextProvider>
|
|
||||||
</Suspense>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
|
||||||
export function getConfig(name: string) {
|
export function getConfig(name: string, props: any = undefined) {
|
||||||
// Check if the config file exists
|
// Check if the config file exists
|
||||||
const configPath = path.join(process.cwd(), 'data/configs', `${name}.json`);
|
const configPath = path.join(process.cwd(), 'data/configs', `${name}.json`);
|
||||||
if (!fs.existsSync(configPath)) {
|
if (!fs.existsSync(configPath)) {
|
||||||
@@ -30,6 +30,7 @@ export function getConfig(name: string) {
|
|||||||
props: {
|
props: {
|
||||||
configName: name,
|
configName: name,
|
||||||
config: JSON.parse(config),
|
config: JSON.parse(config),
|
||||||
|
...props,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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';
|
|
||||||
};
|
|
||||||
70
yarn.lock
70
yarn.lock
@@ -364,7 +364,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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
|
version: 7.18.9
|
||||||
resolution: "@babel/runtime@npm:7.18.9"
|
resolution: "@babel/runtime@npm:7.18.9"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -2005,6 +2005,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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:*":
|
"@types/http-cache-semantics@npm:*":
|
||||||
version: 4.0.1
|
version: 4.0.1
|
||||||
resolution: "@types/http-cache-semantics@npm:4.0.1"
|
resolution: "@types/http-cache-semantics@npm:4.0.1"
|
||||||
@@ -2109,6 +2119,17 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@types/react@npm:17.0.1":
|
||||||
version: 17.0.1
|
version: 17.0.1
|
||||||
resolution: "@types/react@npm:17.0.1"
|
resolution: "@types/react@npm:17.0.1"
|
||||||
@@ -2128,6 +2149,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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:*":
|
"@types/ssh2@npm:*":
|
||||||
version: 1.11.5
|
version: 1.11.5
|
||||||
resolution: "@types/ssh2@npm:1.11.5"
|
resolution: "@types/ssh2@npm:1.11.5"
|
||||||
@@ -3108,6 +3136,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"core-util-is@npm:~1.0.0":
|
||||||
version: 1.0.3
|
version: 1.0.3
|
||||||
resolution: "core-util-is@npm:1.0.3"
|
resolution: "core-util-is@npm:1.0.3"
|
||||||
@@ -4679,7 +4714,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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
|
version: 3.3.2
|
||||||
resolution: "hoist-non-react-statics@npm:3.3.2"
|
resolution: "hoist-non-react-statics@npm:3.3.2"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4747,11 +4782,11 @@ __metadata:
|
|||||||
jest: ^28.1.3
|
jest: ^28.1.3
|
||||||
js-file-download: ^0.4.12
|
js-file-download: ^0.4.12
|
||||||
next: 12.1.6
|
next: 12.1.6
|
||||||
|
next-i18next: ^11.3.0
|
||||||
prettier: ^2.7.1
|
prettier: ^2.7.1
|
||||||
prism-react-renderer: ^1.3.5
|
prism-react-renderer: ^1.3.5
|
||||||
react: ^18.2.0
|
react: ^18.2.0
|
||||||
react-dom: ^18.2.0
|
react-dom: ^18.2.0
|
||||||
react-i18next: ^11.18.4
|
|
||||||
sharp: ^0.30.7
|
sharp: ^0.30.7
|
||||||
systeminformation: ^5.12.1
|
systeminformation: ^5.12.1
|
||||||
typescript: ^4.7.4
|
typescript: ^4.7.4
|
||||||
@@ -4890,6 +4925,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"i18next-http-backend@npm:^1.4.1":
|
||||||
version: 1.4.1
|
version: 1.4.1
|
||||||
resolution: "i18next-http-backend@npm:1.4.1"
|
resolution: "i18next-http-backend@npm:1.4.1"
|
||||||
@@ -4899,7 +4941,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"i18next@npm:^21.9.1":
|
"i18next@npm:^21.8.13, i18next@npm:^21.9.1":
|
||||||
version: 21.9.1
|
version: 21.9.1
|
||||||
resolution: "i18next@npm:21.9.1"
|
resolution: "i18next@npm:21.9.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6246,6 +6288,24 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"next@npm:12.1.6":
|
||||||
version: 12.1.6
|
version: 12.1.6
|
||||||
resolution: "next@npm:12.1.6"
|
resolution: "next@npm:12.1.6"
|
||||||
@@ -6925,7 +6985,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"react-i18next@npm:^11.18.4":
|
"react-i18next@npm:^11.18.0":
|
||||||
version: 11.18.4
|
version: 11.18.4
|
||||||
resolution: "react-i18next@npm:11.18.4"
|
resolution: "react-i18next@npm:11.18.4"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user