diff --git a/public/locales/de-DE/admin-global-settings.json b/public/locales/de-DE/admin-global-settings.json new file mode 100644 index 0000000..155606c --- /dev/null +++ b/public/locales/de-DE/admin-global-settings.json @@ -0,0 +1,71 @@ +{ + "title": "Global Settings", + "description": "Configure global settings for your instance.", + "updatedSuccessfullyMessage": "Settings have been saved successfully!", + "randomAliasesPreview": { + "title": "Random aliases will look like this", + "helperText": "This is just a preview. Those are not real aliases." + }, + "randomAliasesIncreaseExplanation": "Random aliases' length will be increased from {{originalLength}} to {{increasedLength}} characters after {{amount}} aliases have been created.", + "resetLabel": "Reset to defaults", + "disabled": { + "title": "Global settings are disabled", + "description": "Global settings have been disabled. You can enable them in the configuration file." + }, + "fields": { + "randomEmailIdMinLength": { + "label": "Minimum random alias ID length", + "description": "The minimum length for randomly generated emails. The server will automatically increase the length if required so." + }, + "randomEmailIdChars": { + "label": "Random alias character pool", + "description": "Characters that are used to generate random emails." + }, + "randomEmailLengthIncreaseOnPercentage": { + "label": "Percentage of used aliases", + "description": "If the percentage of used random email IDs is higher than this value, the length of the random email ID will be increased. This is used to prevent spammers from guessing the email ID." + }, + "customEmailSuffixLength": { + "label": "Custom email suffix length", + "description": "The length of the custom email suffix." + }, + "customEmailSuffixChars": { + "label": "Custom email suffix character pool", + "description": "Characters that are used to generate custom email suffixes." + }, + "imageProxyStorageLifeTimeInHours": { + "label": "Image proxy storage lifetime", + "description": "The lifetime of images that are stored on the server in hours. After this time, the image will be deleted.", + "unit_one": "hour", + "unit_other": "hours" + }, + "enableImageProxy": { + "label": "Enable image proxy", + "description": "If enabled, images will be proxied through the server. This enhances your user's privacy as their ip address will not be leaked." + }, + "enableImageProxyStorage": { + "label": "Enable image proxy storage", + "description": "If enabled, images will be stored on the server and forwarded to the user. This makes email tracking nearly impossible as every message will be marked as read instantly, which is obviously not true as a user is typically not able to view an email in just a few seconds after it has been sent. This will only affect new images." + }, + "userEmailEnableDisposableEmails": { + "label": "Enable disposable emails for new accounts", + "description": "If enabled, users will be able to use disposable emails when creating a new account. This will only affect new accounts." + }, + "userEmailEnableOtherRelays": { + "label": "Enable other relays for new accounts", + "description": "If enabled, users will be able to use other relays (such as SimpleLogin or DuckDuckGo's Email Tracking Protection) when creating a new account. This will only affect new accounts." + }, + "allowStatistics": { + "label": "Allow statistics", + "description": "If enabled, your instance will collect anonymous statistics and share them. They will only be stored locally on this instance but made public." + }, + "allowAliasDeletion": { + "label": "Allow alias deletion", + "description": "If enabled, users will be able to delete their aliases." + }, + "maxAliasesPerUser": { + "label": "Maximum aliases per user", + "description": "The maximum number of aliases a user can create. 0 means unlimited. Existing aliases will not be affected." + } + } +} diff --git a/public/locales/de-DE/admin-reserved-aliases.json b/public/locales/de-DE/admin-reserved-aliases.json new file mode 100644 index 0000000..21e9f96 --- /dev/null +++ b/public/locales/de-DE/admin-reserved-aliases.json @@ -0,0 +1,44 @@ +{ + "title": "Reserved Aliases", + "detailsTitle": "Reserved Alias Details", + "pageActions": { + "search": { + "placeholder": "Search for aliases" + } + }, + "actions": { + "create": { + "label": "Create new Reserved Alias" + }, + "delete": { + "label": "Delete Reserved Alias", + "description": "Are you sure you want to delete this reserved alias?", + "continueActionLabel": "Delete Reserved Alias" + } + }, + "userAmount_one": "Forwards to one user", + "userAmount_other": "Forwards to {{count}} users", + "emptyState": { + "title": "Create your first reserved alias", + "description": "Reserved aliases are aliases that will be forwarded to selected admin users. This is useful if you want to create aliases that are meant to be public, like contact@example.com or hello@example.com." + }, + "fields": { + "active": { + "label": "Active" + }, + "users": { + "label": "Users", + "me": "{{email}} (Me)" + } + }, + "createNew": { + "title": "Reserved Aliases", + "description": "Define what alias should forward to whom.", + "continueActionLabel": "Create Reserved Alias", + "explanation": { + "step1": "User from outside", + "step2": "Sends mail to", + "step4": "KleckRelay forwards to" + } + } +} diff --git a/public/locales/de-DE/admin.json b/public/locales/de-DE/admin.json new file mode 100644 index 0000000..fc3bebf --- /dev/null +++ b/public/locales/de-DE/admin.json @@ -0,0 +1,12 @@ +{ + "title": "Seite konfigurieren", + "routes": { + "reservedAliases": "Reservierte Aliase", + "settings": "Einstellungen" + }, + "serverStatus": { + "noRecentReports": "Es scheint, als gäbe es Probleme mit deinem Server. Cleanup Jobs wurden in den letzten Tagen nicht mehr ausgeführt. Der letzte Bereicht war am {{date}}.", + "error": "Es gab einen Fehler beim Ausführen des Cleanup Jobs {{relativeDescription}}. Bitte überprüfe die Logs für weitere Informationen.", + "success": "Alles okay mit deinem Server! Der letzte Cleanup Job {{relativeDescription}} wurde erfolgreich ausgeführt." + } +} diff --git a/public/locales/de-DE/alias-notes.json b/public/locales/de-DE/alias-notes.json new file mode 100644 index 0000000..8dca49e --- /dev/null +++ b/public/locales/de-DE/alias-notes.json @@ -0,0 +1,33 @@ +{ + "title": "Notes", + "form": { + "createdAt": { + "label": "Created at", + "empty": "Unavailable" + }, + "creationContext": { + "label": "Creation Context", + "values": { + "web": "Created on this instance", + "extension": "Created in the extension", + "extension-inline": "Created using the extension" + } + }, + "createdOn": { + "label": "Created on" + }, + "personalNotes": { + "label": "Personal Notes", + "helperText": "You can enter personal notes for this alias here. Notes are encrypted." + }, + "websites": { + "label": "Websites", + "emptyText": "You haven't used this alias on any site yet.", + "placeholder": "https://example.com", + "helperText": "Add a website to this alias. Used to autofill.", + "errors": { + "invalid": "This URL is invalid." + } + } + } +} diff --git a/public/locales/de-DE/aliases.json b/public/locales/de-DE/aliases.json new file mode 100644 index 0000000..832a2f7 --- /dev/null +++ b/public/locales/de-DE/aliases.json @@ -0,0 +1,84 @@ +{ + "title": "Aliases", + "detailsTitle": "Alias Details", + "isInCopyMode": "You are in copy mode. Click on an alias to copy it to your clipboard.", + "emptyState": { + "title": "Welcome to your Aliases!", + "description": "Create your first Alias to get started." + }, + "pageActions": { + "search": { + "placeholder": "Search for names" + }, + "searchFilter": { + "active": "Active", + "inactive": "Inactive" + }, + "typeFilter": { + "custom": "Custom made", + "random": "Randomly generated" + } + }, + "actions": { + "createRandomAlias": { + "title": "Create Random Alias" + }, + "createCustomAlias": { + "title": "Create Custom Alias", + "description": "You can define your own custom alias. Note that a random suffix will be added at the end to avoid duplicates.", + "continueActionLabel": "Create Alias" + }, + "delete": { + "label": "Delete Alias", + "description": "Are you sure you want to delete this alias?", + "continueActionLabel": "Delete Alias" + } + }, + "aliasTypeExplanation": { + "random": "This is a randomly generated alias", + "custom": "This is a custom-made alias" + }, + "settings": { + "title": "Settings", + "description": "These settings apply to this alias only. You can either set a value manually or refer to your default settings. Note that this does change in behavior. When you set a value to refer to your default setting, the alias will always use the latest value. So when you change your default setting, the alias will automatically use the new value.", + "continueActionLabel": "Save Settings", + "fields": { + "removeTrackers": { + "label": "Remove Trackers", + "helperText": "Remove single-pixel image trackers as well as url trackers." + }, + "createMailReport": { + "label": "Create Mail Reports", + "helperText": "Create reports of emails sent to aliases. Reports are end-to-end encrypted. Only you can access them." + }, + "proxyImages": { + "label": "Proxy Images", + "helperText": "Proxies images in your emails through this KleckRelay instance. This adds an extra layer of privacy. Images are loaded immediately after we receive the email. They then will be stored for some time (cache time). During that time, the image will be served from us. This means the sender has no idea you have opened the mail. After the cache time, the image is loaded from the sender, but it will be forwarded by us. This means the sender will not be able to access your IP address nor your browser data." + }, + "imageProxyFormat": { + "label": "Image File Type", + "values": { + "jpeg": "JPEG", + "png": "PNG", + "webp": "WEBP" + } + }, + "proxyUserAgent": { + "label": "Proxy User Agent", + "helperText": "An User Agent is a identifier each browser and email client sends when retrieving files, such as images. You can specify here what user agent you would like to be used when we forward it. User Agents are kept up-to-date.", + "values": { + "apple-mail": "Apple Mail", + "google-mail": "Google Mail", + "outlook-windows": "Outlook / Windows", + "outlook-macos": "Outlook / MacOS", + "firefox": "Firefox Browser", + "chrome": "Chrome Browser" + } + }, + "expandUrlShorteners": { + "label": "Expand URL Shorteners", + "helperText": "Expand shortened URLs (for example bit.ly) to their original URL. This way those services can't track you." + } + } + } +} diff --git a/public/locales/de-DE/common.json b/public/locales/de-DE/common.json new file mode 100644 index 0000000..4c0504a --- /dev/null +++ b/public/locales/de-DE/common.json @@ -0,0 +1,89 @@ +{ + "fields": { + "email": { + "label": "Email", + "placeholder": "johndoe@example.com", + "errors": { + "disposable": "Disposable email addresses are not allowed." + } + }, + "2faCode": { + "label": "Code", + "placeholder": "123456", + "errors": { + "shouldOnlyBeDigits": "The code should only contain digits." + } + }, + "recoveryCode": { + "label": "Recovery Code" + }, + "password": { + "label": "Password", + "placeholder": "********", + "errors": { + "invalid": "Password is invalid." + } + }, + "passwordConfirmation": { + "label": "Confirm Password", + "placeholder": "********", + "errors": { + "mismatch": "Passwords do not match." + } + }, + "customAliasLocal": { + "label": "Address", + "placeholder": "awesome-fish" + }, + "local": { + "label": "Address" + }, + "search": { + "label": "Search" + } + }, + "messages": { + "errors": { + "unknown": "An unknown error occurred.", + "copyFailed": "Copying to clipboard did not work. Please copy the text manually." + }, + "alias": { + "addressCopied": "Address has been copied to your clipboard!", + "created": "Alias has been created successfully!", + "deleted": "Alias has been deleted!", + "updated": "Alias has been updated successfully!", + "changedToEnabled": "Alias has been enabled", + "changedToDisabled": "Alias has been disabled" + }, + "report": { + "deleted": "Report has been deleted!" + } + }, + "general": { + "cancelLabel": "Cancel", + "yesLabel": "Yes", + "noLabel": "No", + "continueLabel": "Continue", + "unavailableValue": "Unavailable", + "experimentalFeatureExplanation": "This is an experimental feature.", + "saveLabel": "Save", + "resetLabel": "Reset", + "loading": "Loading..." + }, + "noSearchResults": { + "title": "Nothing found", + "description": "We couldn't find anything for your search query. Try again with a different query." + }, + "navigation": { + "overview": "Overview", + "aliases": "Aliases", + "reports": "Reports", + "settings": "Settings", + "admin": "Admin" + }, + "routes": { + "signup": "Sign up", + "login": "Log in", + "logout": "Log out" + } +} diff --git a/public/locales/de-DE/complete-account.json b/public/locales/de-DE/complete-account.json new file mode 100644 index 0000000..8bc7792 --- /dev/null +++ b/public/locales/de-DE/complete-account.json @@ -0,0 +1,16 @@ +{ + "forms": { + "askForGeneration": { + "title": "Generate Email Reports?", + "description": "Would you like to create fully encrypted email reports for your mails? Only you will be able to access them. Not even we can decrypt them." + }, + "enterPassword": { + "title": "Set up your password", + "description": "Please enter a safe password so that we can encrypt your data." + } + }, + "alreadyCompleted": { + "title": "Encryption already enabled", + "description": "You already have encryption enabled. Changing passwords is currently not supported." + } +} diff --git a/public/locales/de-DE/components.json b/public/locales/de-DE/components.json new file mode 100644 index 0000000..c735d6d --- /dev/null +++ b/public/locales/de-DE/components.json @@ -0,0 +1,33 @@ +{ + "ResendMailButton": { + "label": "Resend Mail" + }, + "OpenMailButton": { + "label": "Open Mail" + }, + "TimedButton": { + "remainingTime_one": "({{count}})", + "remainingTime_other": "({{count}})" + }, + "ErrorLoadingDataMessage": { + "tryAgain": "Try Again" + }, + "LockNavigationContextProvider": { + "title": "Are you sure you want to leave?", + "description": "You have unsaved changes. If you leave, your changes will be lost.", + "continueLabel": "Leave" + }, + "StringPoolField": { + "addCustom": { + "label": "Add custom" + }, + "forms": { + "addNew": { + "title": "Add new value", + "description": "Enter your characters you would like to include", + "label": "Characters", + "submit": "Add" + } + } + } +} diff --git a/public/locales/de-DE/decryption.json b/public/locales/de-DE/decryption.json new file mode 100644 index 0000000..5a2147e --- /dev/null +++ b/public/locales/de-DE/decryption.json @@ -0,0 +1,21 @@ +{ + "actions": { + "enterDecryptionPassword": { + "title": "Decrypt Reports", + "description": "Please enter your password so that your reports can de decrypted.", + "cancelActionLabel": "Decrypt later" + }, + "passwordMissing": { + "unavailable": { + "title": "Encryption required", + "description": "You need to set up encryption to use this feature.", + "continueActionLabel": "Set up encryption" + }, + "passwordRequired": { + "title": "Password required", + "description": "Your decryption password is required to view this section.", + "continueActionLabel": "Enter password" + } + } + } +} diff --git a/public/locales/de-DE/extension.json b/public/locales/de-DE/extension.json new file mode 100644 index 0000000..c8f1efe --- /dev/null +++ b/public/locales/de-DE/extension.json @@ -0,0 +1,12 @@ +{ + "sharePassword": { + "title": "Share Password?", + "description": "An extension is asking for your password. Do you want to share it? Only continue if you initiated this action.", + "warning": "THIS WILL SHARE YOUR PASSWORD WITH THE EXTENSION. ALL YOUR DATA CAN BE DECRYPTED USING IT. ONLY CONTINUE IF YOU TRUST THE EXTENSION AND IF YOU INITIATED THIS REQUEST.", + + "sharePassword": "Share Password", + "doNotShare": "Do not share", + "decideLater": "Decide later", + "doNotAskAgain": "Do not ask again" + } +} diff --git a/public/locales/de-DE/login.json b/public/locales/de-DE/login.json new file mode 100644 index 0000000..899bd47 --- /dev/null +++ b/public/locales/de-DE/login.json @@ -0,0 +1,35 @@ +{ + "title": "Log in", + "forms": { + "email": { + "description": "We will send you a code to log in", + "continueActionLabel": "Send code" + }, + "confirmCode": { + "title": "You got mail!", + "description": "We sent you a code to your email. Enter it below to login", + "continueActionLabel": "Log in", + "allowLoginFromDifferentDevices": "Allow login from different devices", + "expiringSoonWarning": "Your code will expire in less than a minute.", + "fields": { + "code": { + "label": "Verification Code", + "errors": { + "invalidChars": "Invalid verification code" + } + } + } + }, + "confirmFromDifferentDevice": { + "title": "Login failed", + "description": "You could not be logged in. This could either be because you are not allowed to login from different devices or the verification code is invalid or expired." + }, + "otp": { + "title": "Two-Factor Authentication", + "description": "Enter the code from your authenticator app", + "isUnavailable": "Your OTP verification time expired or you exceeded the maximum number of attempts. Please log in again.", + "codesLostActionLabel": "I lost my codes", + "continueActionLabel": "Log in" + } + } +} diff --git a/public/locales/de-DE/logout.json b/public/locales/de-DE/logout.json new file mode 100644 index 0000000..ce11cd5 --- /dev/null +++ b/public/locales/de-DE/logout.json @@ -0,0 +1,4 @@ +{ + "title": "Log out", + "description": "We are logging you out..." +} diff --git a/public/locales/de-DE/recover-2fa.json b/public/locales/de-DE/recover-2fa.json new file mode 100644 index 0000000..66897eb --- /dev/null +++ b/public/locales/de-DE/recover-2fa.json @@ -0,0 +1,10 @@ +{ + "title": "Recover Two-Factor Authentication", + "description": "We are very sorry if you lost your codes. Please enter a recovery code to continue. Note that this will disable two-factor authentication for your account. You can enable it again in the settings.", + "continueActionLabel": "Disable 2FA", + "events": { + "unauthorized": "Please make sure to log in first and then reset your two-factor authentication on its screen.", + "canLogInNow": "Two-factor authentication has been disabled. You can log in now.", + "loggedIn": "Two-factor authentication has been disabled. You are logged in now." + } +} diff --git a/public/locales/de-DE/relay-service-detected.json b/public/locales/de-DE/relay-service-detected.json new file mode 100644 index 0000000..02ec783 --- /dev/null +++ b/public/locales/de-DE/relay-service-detected.json @@ -0,0 +1,6 @@ +{ + "title": "Email relay service detected", + "description": "We detected that you are using an email relay service to sign up. This KleckRelay instance does not support relaying to another email relay service. You can either choose a different instance or sign up with a different email address.", + "detectedExplanation": "Detected email relay:", + "closeActionLabel": "Got it" +} diff --git a/public/locales/de-DE/reports.json b/public/locales/de-DE/reports.json new file mode 100644 index 0000000..954a704 --- /dev/null +++ b/public/locales/de-DE/reports.json @@ -0,0 +1,68 @@ +{ + "title": "Reports", + "detailsTitle": "Report Details", + "emptyState": { + "title": "Welcome to your Reports!", + "description": "Here you will find your email reports. Currently, you don't have any reports. Wait until you receive an email." + }, + "pageActions": { + "sort": { + "label": "Sorting", + "values": { + "List": "List reports by their date", + "GroupByAlias": "Group reports by their alias" + } + } + }, + "emailMeta": { + "flow": "{{from}} -> {{to}}", + "emptySubject": "" + }, + "actions": { + "delete": { + "label": "Delete Report", + "description": "Are you sure you want to delete this report?", + "continueActionLabel": "Delete Report" + } + }, + "sections": { + "information": { + "title": "Email Information", + "form": { + "from": { + "label": "From" + }, + "to": { + "label": "To" + }, + "subject": { + "label": "Subject" + } + } + }, + "trackers": { + "title": "Trackers", + "results": { + "imageTrackers": { + "text_zero": "No image trackers found", + "text_one": "Removed 1 image tracker", + "text_other": "Removed {{count}} image trackers" + }, + "proxiedImages": { + "text_zero": "No images found", + "text_one": "Forwarding 1 image", + "text_other": "Forwarding {{count}} images", + "status": { + "isStored": "Stored on Server", + "isProxying": "Being forwarded" + } + }, + "expandedUrls": { + "text_zero": "No shortened URLs found", + "text_one": "Expanded 1 URL", + "text_other": "Expanded {{count}} URLs" + } + } + } + } +} diff --git a/public/locales/de-DE/settings-2fa.json b/public/locales/de-DE/settings-2fa.json new file mode 100644 index 0000000..fb4e637 --- /dev/null +++ b/public/locales/de-DE/settings-2fa.json @@ -0,0 +1,33 @@ +{ + "title": "Two-Factor-Authentication", + "alreadyEnabled": "You have successfully enabled 2FA!", + "setup": { + "description": "Enable 2FA to add an extra layer of security to your account. Each time you log in, you will need to enter a code generated from your authenticator app. This makes it harder for an attacker to hack into your account as they would need to have access to your phone.", + "setupLabel": "Enable 2FA", + "continueActionLabel": "Enable 2FA", + "codeExpired": "The verification time for your current Two-Factor-Authentication code has expired. A new code has been generated.", + "recoveryCodes": { + "title": "Note down your recovery codes", + "description": "These codes are used to recover your account if you lose access to your authenticator app. Note them down and store them in a safe place. You will not be able to view them again. Do not store them in your password manager. IF YOU LOSE YOUR RECOVERY CODES, YOU WILL LOSE ACCESS TO YOUR ACCOUNT. WE WILL NOT BE ABLE TO HELP YOU.", + "continueActionLabel": "I have noted down my recovery codes" + }, + "success": "You have successfully enabled 2FA!" + }, + "delete": { + "title": "Disable 2FA", + "steps": { + "askType": { + "code": "I have my 2FA code", + "recoveryCode": "I have a recovery code" + }, + "askCode": { + "label": "Code" + }, + "askRecoveryCode": { + "label": "Recovery Code" + } + }, + "continueActionLabel": "Disable 2FA", + "success": "You have successfully disabled 2FA!" + } +} diff --git a/public/locales/de-DE/settings-preferences.json b/public/locales/de-DE/settings-preferences.json new file mode 100644 index 0000000..fe30a4e --- /dev/null +++ b/public/locales/de-DE/settings-preferences.json @@ -0,0 +1,5 @@ +{ + "title": "Alias Preferences", + "description": "Select default values for your aliases. This only affects aliases you haven't set a custom value for.", + "continueActionLabel": "Save preferences" +} diff --git a/public/locales/de-DE/settings.json b/public/locales/de-DE/settings.json new file mode 100644 index 0000000..7ab1855 --- /dev/null +++ b/public/locales/de-DE/settings.json @@ -0,0 +1,7 @@ +{ + "title": "Settings", + "actions": { + "enable2fa": "Two-Factor-Authentication", + "aliasPreferences": "Alias Preferences" + } +} diff --git a/public/locales/de-DE/signup.json b/public/locales/de-DE/signup.json new file mode 100644 index 0000000..40e654c --- /dev/null +++ b/public/locales/de-DE/signup.json @@ -0,0 +1,18 @@ +{ + "forms": { + "email": { + "title": "Sign up", + "description": "We only need your email and you are ready to go!", + "continueActionLabel": "Continue" + }, + "mailVerification": { + "title": "You got mail!", + "description": "We sent you an email with a link to confirm your email address. Please check your inbox and click on the link to continue.", + "editEmail": { + "title": "Edit email address?", + "description": "Would you like to return to the previous step and edit your email address?", + "continueActionLabel": "Yes, edit email" + } + } + } +} diff --git a/public/locales/de-DE/translation.json b/public/locales/de-DE/translation.json index 583cbb8..a05b8e5 100644 --- a/public/locales/de-DE/translation.json +++ b/public/locales/de-DE/translation.json @@ -1,410 +1,35 @@ { "general": { - "cancelLabel": "Abbrechen", + "cancelLabel": "Cancel", "emptyValue": "-", - "emptyUnavailableValue": "Nicht verfügbar", + "emptyUnavailableValue": "Unavailable", + "saveLabel": "Save", - "defaultValueSelection": "Standard <{{value}}>", + "defaultValueSelection": "Default <{{value}}>", "defaultValueSelectionRaw": "<{{value}}>", "booleanSelection": { - "true": "Ja", - "false": "Nein" + "true": "Yes", + "false": "No" }, - "defaultError": "Ein Fehler ist aufgetreten.", - "defaultSuccess": "Erfolgreich übernommen!", - "loading": "Lädt...", - "actionNotUndoable": "Diese Aktion kann nicht rückgängig gemacht werden!", - "copyError": "Konnte nicht in Zwischenable kopieren. Bitte kopiere den Text manuell.", - "experimentalFeature": "Diese Funktion ist experimentell und kann Fehler verursachen." - }, - - "routes": { - "OverviewRoute": { - "title": "Überblick", - "description": "Nicht viel zu sehen hier, bisher." - }, - "LoginRoute": { - "forms": { - "email": { - "title": "Anmelden", - "description": "Wir senden dir einen Verifizierungscode an deine E-Mail Adresse.", - "continueAction": "Code senden", - "form": { - "email": { - "label": "E-Mail", - "placeholder": "maxmustermann@example.com" - } - } - }, - "confirmCode": { - "title": "Du hast Mail!", - "description": "Wir haben einen Code an deine E-Mail gesendet. Gib ihn hier ein, um dich anzumelden.", - "continueAction": "Anmelden", - "allowLoginFromDifferentDevices": "Anmelden von anderen Geräten erlauben", - "expiringSoon": "Dein Code läuft in weniger als einer Minute ab.", - "form": { - "code": { - "label": "Verifizierungscode", - "errors": { - "invalidChars": "Ungültiger Verifizierungscode" - } - } - } - }, - "confirmFromDifferentDevice": { - "title": "Anmelden fehlgeschlagen", - "description": "Du konntest nicht angemeldet werden. Dies könnte daran legen, dass du Anmeldeungen aus anderen Geräten nicht aktiviert hast oder der Verifizierungscode inkorrekt oder abgelaufen ist." - } - } - }, - "SignupRoute": { - "forms": { - "email": { - "title": "Registrieren", - "description": "Wir brauchen nur deine E-Mail und du kannst direkt loslegen!", - "continueAction": "Weiter", - "form": { - "email": { - "label": "E-Mail", - "placeholder": "maxmustermann@example.com" - } - } - }, - "mailVerification": { - "title": "Du hast Mail!", - "description": "Wir haben dir eine E-Mail mit einem Link geschickt, um deinen Account zu verifizieren. Bitte überprüfe deine E-Mails und klicke auf den Link, um fortzufahren.", - "editEmail": { - "title": "E-Mail ändern?", - "description": "Möchtest du einen Schritt zurückgehen um deine E-Mail zu ändern?", - "continueAction": "Ja, E-Mail ändern" - } - } - } - }, - "VerifyEmailRoute": { - "title": "Bestätige deine E-Mail", - "isLoading": "Deine E-Mail wird bestätigt...", - "isCodeInvalid": "Der Verifizierungscode ist ungültig oder abgelaufen.", - "errors": { - "code": { - "invalid": "Dieser Verifizierungscode ist ungültig" - } - } - }, - "CompleteAccountRoute": { - "forms": { - "generateReports": { - "title": "E-Mail-Berichte aktivieren?", - "description": "Möchtest du vollständig verschlüsselte Berichte für deine E-Mails erstellen lassen? Nur du wirst sie entschlüsseln können. Selbst wir können sie nicht entschlüsseln.", - "continueAction": "Ja", - "cancelAction": "Nein" - }, - "password": { - "title": "Passwort festlegen", - "description": "Bitte gib ein sicheres Passwort ein, damit wir deine Berichte verschlüsseln können.", - "continueAction": "Weiter", - "form": { - "password": { - "label": "Passwort", - "placeholder": "********" - }, - "passwordConfirm": { - "label": "Passwort bestätigen", - "placeholder": "Gib dein Passwort erneut ein", - "mustMatchHelperText": "Passwörter stimmen nicht überein." - } - } - }, - "available": { - "title": "Verschlüsselung bereits eingestellt", - "description": "Du hast die Verschlüsselung bereits eingestellt. Passwörter können momentan noch nicht geändert werden." - } - } - }, - "AliasesRoute": { - "title": "Aliase", - "isInCopyMode": "Du bist im Kopier-Modus. Klicke auf einen Alias um ihn in deine Zwischenablage zu kopieren.", - "emptyState": { - "title": "Willkommen zu deinen Aliases!", - "description": "Erstelle dein erstes Alias, um loszulegen." - }, - "pageActions": { - "search": { - "label": "Suche", - "placeholder": "Suche nach Namen" - } - }, - "actions": { - "createRandomAlias": { - "label": "Zufälliges Alias erstellen" - }, - "createCustomAlias": { - "label": "Eigenes Alias erstellen", - "description": "Du kannst dein eigenes Alias erstellen. Beachte das ein zufälliges Suffix angehangen wird, um Duplikate zu vermeiden.", - "continueAction": "Alias erstellen", - "form": { - "address": { - "label": "Adresse", - "placeholder": "awesome-fish" - } - } - } - } - }, - "AliasDetailRoute": { - "title": "Alias-Details", - "sections": { - "settings": { - "title": "Einstellungen", - "description": "Diese Einstellungen gelten nur für dieses Alias. Du kannst entweder einen manuellen Wert einstellen oder auf deine Standard-Werte verweisen. Beachte das dieser Wert das Verhalten ändert. Wenn du auf einen Standard-Wert verweist, verwendet dein Alias immer den aktuellsten Wert. Wenn du also deine Standard-Werte änderst, übernimmt dein Alias diese Änderungen." - }, - "notes": { - "title": "Notizen", - "form": { - "createdAt": { - "label": "Erstellungsdatum", - "empty": "Nicht verfügbar" - }, - "creationContext": { - "label": "Kontext", - "web": { - "label": "Auf dieser Instanz erstellt" - }, - "extension": { - "label": "In der Erweiterung erstellt" - }, - "extension-inline": { - "label": "Mit der Erweiterung erstellt" - } - }, - "createdOn": { - "label": "Erstellt auf" - }, - "personalNotes": { - "label": "Persönliche Notizen", - "empty": "-", - "helperText": "Hier kannst du persönliche Notizen für dieses Alias eingeben. Notizen sind verschlüsselt." - }, - "websites": { - "label": "Webseiten", - "emptyText": "Du hast dieses Alias auf keiner Webseite bisher genutzt.", - "placeholder": "https://example.com", - "helperText": "Füge eine Webseite zu diesem Alias hinzu. Wird verwendet um automatisch E-Mail-Felder auszufüllen." - } - } - } - } - }, - "ReportsRoute": { - "title": "Berichte", - "emptyState": { - "title": "Willkommen zu deinen Berichten!", - "description": "Hier kannst du deine E-Mail-Berichte finden. Momentan sind noch keine Berichte verfügbar. Warte, bis du eine E-Mail erhalten hast." - }, - "pageActions": { - "sort": { - "List": "Berichte anhand ihrer Daten auflisten", - "GroupByAlias": "Berichte nach Alias gruppieren" - } - } - }, - "ReportDetailRoute": { - "title": "Bericht-Details", - "actions": { - "delete": { - "label": "Bericht löschen", - "description": "Bist du dir sicher, dass du diesen Bericht löschen möchtest?", - "continueAction": "Bericht löschen" - } - }, - "sections": { - "information": { - "title": "Email-Informationen", - "form": { - "from": { - "label": "Von" - }, - "to": { - "label": "Zu" - }, - "subject": { - "label": "Betreff" - } - } - }, - "trackers": { - "title": "Tracker", - "results": { - "imageTrackers": { - "text_zero": "Keine Bild-Tracker gefunden", - "text_one": "Ein Bild-Tracker entfernt", - "text_other": "{{count}} Bild-Tracker entfernt" - }, - "proxiedImages": { - "text_zero": "Keine Bilder gefunden", - "text_one": "Ein Bild wird weitergeleitet", - "text_other": "{{count}} Bilder werden weitergeleitet", - "status": { - "isStored": "Auf Server gespeichert", - "isProxying": "Wird weitergeleitet" - } - }, - "expandedUrls": { - "text_zero": "Keine gekürzten URLs gefunden", - "text_one": "Eine URL entkürzt", - "text_other": "{{count}} URLs entkürzt" - } - } - } - } - }, - "SettingsRoute": { - "title": "Einstellungen", - "forms": { - "aliasPreferences": { - "title": "Alias-Präferenzen", - "description": "Wähle die Standard-Werte für deine Aliase aus. Dies betrifft nur Aliase, bei denen du keinen manuellen Wert gesetzt hast.", - "saveAction": "Präferenzen speichern" - } - } - }, - "LogoutRoute": { - "title": "Abmelden", - "description": "Wir sind dich am abmelden..." - } + "defaultError": "An error occurred.", + "defaultSuccess": "Success!", + "loading": "Loading...", + "actionNotUndoable": "This action cannot be undone!", + "copyError": "Copying to clipboard did not work. Please copy the text manually.", + "experimentalFeature": "This is an experimental feature.", + "deletedSuccessfully": "Deleted successfully!", + "appError": "We are sorry but there was an error. Please try again later." }, "components": { - "NavigationButton": { - "overview": "Überblick", - "aliases": "Aliase", - "reports": "Berichte", - "settings": "Einstellungen" - }, - "AuthenticateRoute": { - "signup": "Registrieren", - "login": "Anmelden" - }, - "AuthenticatedRoute": { - "logout": "Abmelden" - }, - "EnterDecryptionPassword": { - "title": "Berichte entschlüsseln", - "description": "Bitte gib dein Passwort ein, damit deine Berichte entschlüsselt werden können.", - "cancelAction": "Später entschlüsseln", - "continueAction": "Weiter", - "form": { - "password": { - "label": "Passwort", - "placeholder": "********", - "errors": { - "invalidPassword": "Das Passwort ist ungültig" - } - } - } - }, - "ResendMailButton": { - "label": "E-Mail erneut senden" - }, - "OpenMailButton": { - "label": "E-Mail öffnen" - }, - "DecryptionPasswordMissingAlert": { - "unavailable": { - "title": "Verschlüsselung benötigt", - "description": "Du musst die Verschlüsselung aktivieren, um dieses Feature nutzen zu können.", - "continueAction": "Verschlüsselung aktivieren" - }, - "passwordRequired": { - "title": "Passwort benötigt", - "description": "Dein Passwort wird benötigt, um dieses Feature nutzen zu können.", - "continueAction": "Passwort eingeben" - } - }, - "TimedButton": { - "remainingTime_one": "({{count}})", - "remainingTime_other": "({{count}})" - }, - "ErrorLoadingDataMessage": { - "tryAgain": "Neu laden" - }, - "AliasTypeIndicator": { - "random": "Dies ist ein zufällig-generiertes Alias", - "custom": "Dies ist ein benutzerdefiniertes Alias" - }, - "NoSearchResults": { - "title": "Keine Ergebnisse gefunden", - "description": "Wir konnten keine Ergebnisse für diese Suche finden. Versuche es mit einem anderen Suchbegriff." - }, - "LockNavigationContextProvider": { - "title": "Möchtest du wirklich die Seite verlassen?", - "description": "Du hast Änderungen, welche noch nicht gespeichert wurden. Wenn du jetzt diese Seite verlässt, gehen deine Änderungen verloren.", - "continueLabel": "Verlassen" - } - }, - - "relations": { - "alias": { - "mutations": { - "success": { - "aliasCreation": "Alias wurde erfolgreich erstellt!", - "aliasUpdated": "Alias wurde erfolgreich upgedatet!", - "notesUpdated": "Notizen wurden erfolgreich upgedated & verschlüsselt!", - "aliasChangedToEnabled": "Alias wurde aktiviert", - "aliasChangedToDisabled": "Alias wurde deaktiviert", - "addressCopiedToClipboard": "E-Mail-Adresse wurde in deine Zwischenablage kopiert!" - } - }, - "settings": { - "removeTrackers": { - "label": "Tracker entfernen", - "helperText": "Entferne Einzelpixel-Tracker und URL-Tracker" - }, - "createMailReports": { - "label": "E-Mail-Berichte erstellen", - "helperText": "Erstelle Berichte von E-Mails, die an Aliase gesendet werden. Berichte sind Ende-zu-Ende verschlüsselt. Nur du kannst sie entschlüsseln." - }, - "proxyImages": { - "label": "Bilder weiterleiten", - "helperText": "Leitet Bilder durch diese KleckRelay-Instanz weiter. Dies stellt einen weiteren Schutz deiner Privatspähre dar. Bilder werden direkt runtergeladen nachdem wir eine E-Mail erhalten haben. Diese werden dann für eine gewisse Zeit auf dem Server gespeichert. Während dieser Zeit wird das Bild von uns an dich gesendet. Dies bedeutet, dass der Absender keine Chance haben wird, herauszufinden, dass du diese E-Mail geöffnet hast. Nach der Zeit wird das Bild vom Absender geladen, aber durch uns weitergeleitet. Dies bedeutet, dass der Absender weder auf deine IP-Adresse, noch auf deine Browserdaten zugreifen kann." - }, - "imageProxyFormat": { - "label": "Bild-Format", - "enumTexts": { - "jpeg": "JPEG", - "png": "PNG", - "webp": "WEBP" - } - }, - "proxyUserAgent": { - "label": "Bild-Weiterleitungs-User-Agent", - "helperText": "Ein User-Agent ist eine Kennzeichnung, die jeden Browser und E-Mail-Client identifiziert, wenn Dateien runtergeladen werden, so wie beispielsweise Bilder. Du kannst hier einstellen, welchen User-Agent du beim Weiterleiten verwenden möchtest. User-Agents werden aktuell gehalten.", - "enumTexts": { - "apple-mail": "Apple Mail", - "google-mail": "Google Mail", - "outlook-windows": "Outlook / Windows", - "outlook-macos": "Outlook / MacOS", - "firefox": "Firefox Browser", - "chrome": "Chrome Browser" - } - }, - "expandUrlShorteners": { - "label": "URL-Kürzer entkürzen", - "helperText": "Entkürzt URl-Kürzerer (wie zum Beispiel bit.ly) zu der Original-URL. Dadurch können dich diese Services nicht mehr tracken." - }, - "saveAction": "Einstellungen speichern" - } - }, - "report": { - "mutations": { - "success": { - "reportDeleted": "Bericht wurde gelöscht!" - } - }, - "emailMeta": { - "flow": "{{from}} -> {{to}}", - "emptySubject": "" - } + "passwordShareConfirmationDialog": { + "title": "Share Password?", + "description": "An extension is asking for your password. Do you want to share it? Only continue if you initiated this action.", + "warning": "THIS WILL SHARE YOUR PASSWORD WITH THE EXTENSION. ALL YOUR DATA CAN BE DECRYPTED USING IT. ONLY CONTINUE IF YOU TRUST THE EXTENSION AND IF YOU INITIATED THIS REQUEST.", + "continueAction": "Share Password", + "doNotShare": "Do not share", + "decideLater": "Decide later", + "doNotAskAgain": "Do not ask again" } } } diff --git a/public/locales/de-DE/verify-email.json b/public/locales/de-DE/verify-email.json new file mode 100644 index 0000000..98a10ef --- /dev/null +++ b/public/locales/de-DE/verify-email.json @@ -0,0 +1,7 @@ +{ + "title": "Verify your email", + "isLoading": "We are verifying your email address...", + "errors": { + "invalid": "The verification link is invalid or has expired." + } +} diff --git a/public/locales/en-US/admin-global-settings.json b/public/locales/en-US/admin-global-settings.json new file mode 100644 index 0000000..155606c --- /dev/null +++ b/public/locales/en-US/admin-global-settings.json @@ -0,0 +1,71 @@ +{ + "title": "Global Settings", + "description": "Configure global settings for your instance.", + "updatedSuccessfullyMessage": "Settings have been saved successfully!", + "randomAliasesPreview": { + "title": "Random aliases will look like this", + "helperText": "This is just a preview. Those are not real aliases." + }, + "randomAliasesIncreaseExplanation": "Random aliases' length will be increased from {{originalLength}} to {{increasedLength}} characters after {{amount}} aliases have been created.", + "resetLabel": "Reset to defaults", + "disabled": { + "title": "Global settings are disabled", + "description": "Global settings have been disabled. You can enable them in the configuration file." + }, + "fields": { + "randomEmailIdMinLength": { + "label": "Minimum random alias ID length", + "description": "The minimum length for randomly generated emails. The server will automatically increase the length if required so." + }, + "randomEmailIdChars": { + "label": "Random alias character pool", + "description": "Characters that are used to generate random emails." + }, + "randomEmailLengthIncreaseOnPercentage": { + "label": "Percentage of used aliases", + "description": "If the percentage of used random email IDs is higher than this value, the length of the random email ID will be increased. This is used to prevent spammers from guessing the email ID." + }, + "customEmailSuffixLength": { + "label": "Custom email suffix length", + "description": "The length of the custom email suffix." + }, + "customEmailSuffixChars": { + "label": "Custom email suffix character pool", + "description": "Characters that are used to generate custom email suffixes." + }, + "imageProxyStorageLifeTimeInHours": { + "label": "Image proxy storage lifetime", + "description": "The lifetime of images that are stored on the server in hours. After this time, the image will be deleted.", + "unit_one": "hour", + "unit_other": "hours" + }, + "enableImageProxy": { + "label": "Enable image proxy", + "description": "If enabled, images will be proxied through the server. This enhances your user's privacy as their ip address will not be leaked." + }, + "enableImageProxyStorage": { + "label": "Enable image proxy storage", + "description": "If enabled, images will be stored on the server and forwarded to the user. This makes email tracking nearly impossible as every message will be marked as read instantly, which is obviously not true as a user is typically not able to view an email in just a few seconds after it has been sent. This will only affect new images." + }, + "userEmailEnableDisposableEmails": { + "label": "Enable disposable emails for new accounts", + "description": "If enabled, users will be able to use disposable emails when creating a new account. This will only affect new accounts." + }, + "userEmailEnableOtherRelays": { + "label": "Enable other relays for new accounts", + "description": "If enabled, users will be able to use other relays (such as SimpleLogin or DuckDuckGo's Email Tracking Protection) when creating a new account. This will only affect new accounts." + }, + "allowStatistics": { + "label": "Allow statistics", + "description": "If enabled, your instance will collect anonymous statistics and share them. They will only be stored locally on this instance but made public." + }, + "allowAliasDeletion": { + "label": "Allow alias deletion", + "description": "If enabled, users will be able to delete their aliases." + }, + "maxAliasesPerUser": { + "label": "Maximum aliases per user", + "description": "The maximum number of aliases a user can create. 0 means unlimited. Existing aliases will not be affected." + } + } +} diff --git a/public/locales/en-US/admin-reserved-aliases.json b/public/locales/en-US/admin-reserved-aliases.json new file mode 100644 index 0000000..21e9f96 --- /dev/null +++ b/public/locales/en-US/admin-reserved-aliases.json @@ -0,0 +1,44 @@ +{ + "title": "Reserved Aliases", + "detailsTitle": "Reserved Alias Details", + "pageActions": { + "search": { + "placeholder": "Search for aliases" + } + }, + "actions": { + "create": { + "label": "Create new Reserved Alias" + }, + "delete": { + "label": "Delete Reserved Alias", + "description": "Are you sure you want to delete this reserved alias?", + "continueActionLabel": "Delete Reserved Alias" + } + }, + "userAmount_one": "Forwards to one user", + "userAmount_other": "Forwards to {{count}} users", + "emptyState": { + "title": "Create your first reserved alias", + "description": "Reserved aliases are aliases that will be forwarded to selected admin users. This is useful if you want to create aliases that are meant to be public, like contact@example.com or hello@example.com." + }, + "fields": { + "active": { + "label": "Active" + }, + "users": { + "label": "Users", + "me": "{{email}} (Me)" + } + }, + "createNew": { + "title": "Reserved Aliases", + "description": "Define what alias should forward to whom.", + "continueActionLabel": "Create Reserved Alias", + "explanation": { + "step1": "User from outside", + "step2": "Sends mail to", + "step4": "KleckRelay forwards to" + } + } +} diff --git a/public/locales/en-US/admin.json b/public/locales/en-US/admin.json new file mode 100644 index 0000000..5484e20 --- /dev/null +++ b/public/locales/en-US/admin.json @@ -0,0 +1,12 @@ +{ + "title": "Site configuration", + "routes": { + "reservedAliases": "Reserved Aliases", + "settings": "Global Settings" + }, + "serverStatus": { + "noRecentReports": "There seems to be some issues with your server. The server hasn't done its cleanup in the last few days. The last report was on {{date}}.", + "error": "There was an error during the last server cleanup job from {{relativeDescription}}. Please check the logs for more information.", + "success": "Everything okay with your server! The last cleanup job was {{relativeDescription}}." + } +} diff --git a/public/locales/en-US/alias-notes.json b/public/locales/en-US/alias-notes.json new file mode 100644 index 0000000..8dca49e --- /dev/null +++ b/public/locales/en-US/alias-notes.json @@ -0,0 +1,33 @@ +{ + "title": "Notes", + "form": { + "createdAt": { + "label": "Created at", + "empty": "Unavailable" + }, + "creationContext": { + "label": "Creation Context", + "values": { + "web": "Created on this instance", + "extension": "Created in the extension", + "extension-inline": "Created using the extension" + } + }, + "createdOn": { + "label": "Created on" + }, + "personalNotes": { + "label": "Personal Notes", + "helperText": "You can enter personal notes for this alias here. Notes are encrypted." + }, + "websites": { + "label": "Websites", + "emptyText": "You haven't used this alias on any site yet.", + "placeholder": "https://example.com", + "helperText": "Add a website to this alias. Used to autofill.", + "errors": { + "invalid": "This URL is invalid." + } + } + } +} diff --git a/public/locales/en-US/aliases.json b/public/locales/en-US/aliases.json new file mode 100644 index 0000000..832a2f7 --- /dev/null +++ b/public/locales/en-US/aliases.json @@ -0,0 +1,84 @@ +{ + "title": "Aliases", + "detailsTitle": "Alias Details", + "isInCopyMode": "You are in copy mode. Click on an alias to copy it to your clipboard.", + "emptyState": { + "title": "Welcome to your Aliases!", + "description": "Create your first Alias to get started." + }, + "pageActions": { + "search": { + "placeholder": "Search for names" + }, + "searchFilter": { + "active": "Active", + "inactive": "Inactive" + }, + "typeFilter": { + "custom": "Custom made", + "random": "Randomly generated" + } + }, + "actions": { + "createRandomAlias": { + "title": "Create Random Alias" + }, + "createCustomAlias": { + "title": "Create Custom Alias", + "description": "You can define your own custom alias. Note that a random suffix will be added at the end to avoid duplicates.", + "continueActionLabel": "Create Alias" + }, + "delete": { + "label": "Delete Alias", + "description": "Are you sure you want to delete this alias?", + "continueActionLabel": "Delete Alias" + } + }, + "aliasTypeExplanation": { + "random": "This is a randomly generated alias", + "custom": "This is a custom-made alias" + }, + "settings": { + "title": "Settings", + "description": "These settings apply to this alias only. You can either set a value manually or refer to your default settings. Note that this does change in behavior. When you set a value to refer to your default setting, the alias will always use the latest value. So when you change your default setting, the alias will automatically use the new value.", + "continueActionLabel": "Save Settings", + "fields": { + "removeTrackers": { + "label": "Remove Trackers", + "helperText": "Remove single-pixel image trackers as well as url trackers." + }, + "createMailReport": { + "label": "Create Mail Reports", + "helperText": "Create reports of emails sent to aliases. Reports are end-to-end encrypted. Only you can access them." + }, + "proxyImages": { + "label": "Proxy Images", + "helperText": "Proxies images in your emails through this KleckRelay instance. This adds an extra layer of privacy. Images are loaded immediately after we receive the email. They then will be stored for some time (cache time). During that time, the image will be served from us. This means the sender has no idea you have opened the mail. After the cache time, the image is loaded from the sender, but it will be forwarded by us. This means the sender will not be able to access your IP address nor your browser data." + }, + "imageProxyFormat": { + "label": "Image File Type", + "values": { + "jpeg": "JPEG", + "png": "PNG", + "webp": "WEBP" + } + }, + "proxyUserAgent": { + "label": "Proxy User Agent", + "helperText": "An User Agent is a identifier each browser and email client sends when retrieving files, such as images. You can specify here what user agent you would like to be used when we forward it. User Agents are kept up-to-date.", + "values": { + "apple-mail": "Apple Mail", + "google-mail": "Google Mail", + "outlook-windows": "Outlook / Windows", + "outlook-macos": "Outlook / MacOS", + "firefox": "Firefox Browser", + "chrome": "Chrome Browser" + } + }, + "expandUrlShorteners": { + "label": "Expand URL Shorteners", + "helperText": "Expand shortened URLs (for example bit.ly) to their original URL. This way those services can't track you." + } + } + } +} diff --git a/public/locales/en-US/common.json b/public/locales/en-US/common.json new file mode 100644 index 0000000..4c0504a --- /dev/null +++ b/public/locales/en-US/common.json @@ -0,0 +1,89 @@ +{ + "fields": { + "email": { + "label": "Email", + "placeholder": "johndoe@example.com", + "errors": { + "disposable": "Disposable email addresses are not allowed." + } + }, + "2faCode": { + "label": "Code", + "placeholder": "123456", + "errors": { + "shouldOnlyBeDigits": "The code should only contain digits." + } + }, + "recoveryCode": { + "label": "Recovery Code" + }, + "password": { + "label": "Password", + "placeholder": "********", + "errors": { + "invalid": "Password is invalid." + } + }, + "passwordConfirmation": { + "label": "Confirm Password", + "placeholder": "********", + "errors": { + "mismatch": "Passwords do not match." + } + }, + "customAliasLocal": { + "label": "Address", + "placeholder": "awesome-fish" + }, + "local": { + "label": "Address" + }, + "search": { + "label": "Search" + } + }, + "messages": { + "errors": { + "unknown": "An unknown error occurred.", + "copyFailed": "Copying to clipboard did not work. Please copy the text manually." + }, + "alias": { + "addressCopied": "Address has been copied to your clipboard!", + "created": "Alias has been created successfully!", + "deleted": "Alias has been deleted!", + "updated": "Alias has been updated successfully!", + "changedToEnabled": "Alias has been enabled", + "changedToDisabled": "Alias has been disabled" + }, + "report": { + "deleted": "Report has been deleted!" + } + }, + "general": { + "cancelLabel": "Cancel", + "yesLabel": "Yes", + "noLabel": "No", + "continueLabel": "Continue", + "unavailableValue": "Unavailable", + "experimentalFeatureExplanation": "This is an experimental feature.", + "saveLabel": "Save", + "resetLabel": "Reset", + "loading": "Loading..." + }, + "noSearchResults": { + "title": "Nothing found", + "description": "We couldn't find anything for your search query. Try again with a different query." + }, + "navigation": { + "overview": "Overview", + "aliases": "Aliases", + "reports": "Reports", + "settings": "Settings", + "admin": "Admin" + }, + "routes": { + "signup": "Sign up", + "login": "Log in", + "logout": "Log out" + } +} diff --git a/public/locales/en-US/complete-account.json b/public/locales/en-US/complete-account.json new file mode 100644 index 0000000..8bc7792 --- /dev/null +++ b/public/locales/en-US/complete-account.json @@ -0,0 +1,16 @@ +{ + "forms": { + "askForGeneration": { + "title": "Generate Email Reports?", + "description": "Would you like to create fully encrypted email reports for your mails? Only you will be able to access them. Not even we can decrypt them." + }, + "enterPassword": { + "title": "Set up your password", + "description": "Please enter a safe password so that we can encrypt your data." + } + }, + "alreadyCompleted": { + "title": "Encryption already enabled", + "description": "You already have encryption enabled. Changing passwords is currently not supported." + } +} diff --git a/public/locales/en-US/components.json b/public/locales/en-US/components.json new file mode 100644 index 0000000..c735d6d --- /dev/null +++ b/public/locales/en-US/components.json @@ -0,0 +1,33 @@ +{ + "ResendMailButton": { + "label": "Resend Mail" + }, + "OpenMailButton": { + "label": "Open Mail" + }, + "TimedButton": { + "remainingTime_one": "({{count}})", + "remainingTime_other": "({{count}})" + }, + "ErrorLoadingDataMessage": { + "tryAgain": "Try Again" + }, + "LockNavigationContextProvider": { + "title": "Are you sure you want to leave?", + "description": "You have unsaved changes. If you leave, your changes will be lost.", + "continueLabel": "Leave" + }, + "StringPoolField": { + "addCustom": { + "label": "Add custom" + }, + "forms": { + "addNew": { + "title": "Add new value", + "description": "Enter your characters you would like to include", + "label": "Characters", + "submit": "Add" + } + } + } +} diff --git a/public/locales/en-US/decryption.json b/public/locales/en-US/decryption.json new file mode 100644 index 0000000..5a2147e --- /dev/null +++ b/public/locales/en-US/decryption.json @@ -0,0 +1,21 @@ +{ + "actions": { + "enterDecryptionPassword": { + "title": "Decrypt Reports", + "description": "Please enter your password so that your reports can de decrypted.", + "cancelActionLabel": "Decrypt later" + }, + "passwordMissing": { + "unavailable": { + "title": "Encryption required", + "description": "You need to set up encryption to use this feature.", + "continueActionLabel": "Set up encryption" + }, + "passwordRequired": { + "title": "Password required", + "description": "Your decryption password is required to view this section.", + "continueActionLabel": "Enter password" + } + } + } +} diff --git a/public/locales/en-US/extension.json b/public/locales/en-US/extension.json new file mode 100644 index 0000000..c8f1efe --- /dev/null +++ b/public/locales/en-US/extension.json @@ -0,0 +1,12 @@ +{ + "sharePassword": { + "title": "Share Password?", + "description": "An extension is asking for your password. Do you want to share it? Only continue if you initiated this action.", + "warning": "THIS WILL SHARE YOUR PASSWORD WITH THE EXTENSION. ALL YOUR DATA CAN BE DECRYPTED USING IT. ONLY CONTINUE IF YOU TRUST THE EXTENSION AND IF YOU INITIATED THIS REQUEST.", + + "sharePassword": "Share Password", + "doNotShare": "Do not share", + "decideLater": "Decide later", + "doNotAskAgain": "Do not ask again" + } +} diff --git a/public/locales/en-US/login.json b/public/locales/en-US/login.json new file mode 100644 index 0000000..899bd47 --- /dev/null +++ b/public/locales/en-US/login.json @@ -0,0 +1,35 @@ +{ + "title": "Log in", + "forms": { + "email": { + "description": "We will send you a code to log in", + "continueActionLabel": "Send code" + }, + "confirmCode": { + "title": "You got mail!", + "description": "We sent you a code to your email. Enter it below to login", + "continueActionLabel": "Log in", + "allowLoginFromDifferentDevices": "Allow login from different devices", + "expiringSoonWarning": "Your code will expire in less than a minute.", + "fields": { + "code": { + "label": "Verification Code", + "errors": { + "invalidChars": "Invalid verification code" + } + } + } + }, + "confirmFromDifferentDevice": { + "title": "Login failed", + "description": "You could not be logged in. This could either be because you are not allowed to login from different devices or the verification code is invalid or expired." + }, + "otp": { + "title": "Two-Factor Authentication", + "description": "Enter the code from your authenticator app", + "isUnavailable": "Your OTP verification time expired or you exceeded the maximum number of attempts. Please log in again.", + "codesLostActionLabel": "I lost my codes", + "continueActionLabel": "Log in" + } + } +} diff --git a/public/locales/en-US/logout.json b/public/locales/en-US/logout.json new file mode 100644 index 0000000..ce11cd5 --- /dev/null +++ b/public/locales/en-US/logout.json @@ -0,0 +1,4 @@ +{ + "title": "Log out", + "description": "We are logging you out..." +} diff --git a/public/locales/en-US/recover-2fa.json b/public/locales/en-US/recover-2fa.json new file mode 100644 index 0000000..66897eb --- /dev/null +++ b/public/locales/en-US/recover-2fa.json @@ -0,0 +1,10 @@ +{ + "title": "Recover Two-Factor Authentication", + "description": "We are very sorry if you lost your codes. Please enter a recovery code to continue. Note that this will disable two-factor authentication for your account. You can enable it again in the settings.", + "continueActionLabel": "Disable 2FA", + "events": { + "unauthorized": "Please make sure to log in first and then reset your two-factor authentication on its screen.", + "canLogInNow": "Two-factor authentication has been disabled. You can log in now.", + "loggedIn": "Two-factor authentication has been disabled. You are logged in now." + } +} diff --git a/public/locales/en-US/relay-service-detected.json b/public/locales/en-US/relay-service-detected.json new file mode 100644 index 0000000..02ec783 --- /dev/null +++ b/public/locales/en-US/relay-service-detected.json @@ -0,0 +1,6 @@ +{ + "title": "Email relay service detected", + "description": "We detected that you are using an email relay service to sign up. This KleckRelay instance does not support relaying to another email relay service. You can either choose a different instance or sign up with a different email address.", + "detectedExplanation": "Detected email relay:", + "closeActionLabel": "Got it" +} diff --git a/public/locales/en-US/reports.json b/public/locales/en-US/reports.json new file mode 100644 index 0000000..954a704 --- /dev/null +++ b/public/locales/en-US/reports.json @@ -0,0 +1,68 @@ +{ + "title": "Reports", + "detailsTitle": "Report Details", + "emptyState": { + "title": "Welcome to your Reports!", + "description": "Here you will find your email reports. Currently, you don't have any reports. Wait until you receive an email." + }, + "pageActions": { + "sort": { + "label": "Sorting", + "values": { + "List": "List reports by their date", + "GroupByAlias": "Group reports by their alias" + } + } + }, + "emailMeta": { + "flow": "{{from}} -> {{to}}", + "emptySubject": "" + }, + "actions": { + "delete": { + "label": "Delete Report", + "description": "Are you sure you want to delete this report?", + "continueActionLabel": "Delete Report" + } + }, + "sections": { + "information": { + "title": "Email Information", + "form": { + "from": { + "label": "From" + }, + "to": { + "label": "To" + }, + "subject": { + "label": "Subject" + } + } + }, + "trackers": { + "title": "Trackers", + "results": { + "imageTrackers": { + "text_zero": "No image trackers found", + "text_one": "Removed 1 image tracker", + "text_other": "Removed {{count}} image trackers" + }, + "proxiedImages": { + "text_zero": "No images found", + "text_one": "Forwarding 1 image", + "text_other": "Forwarding {{count}} images", + "status": { + "isStored": "Stored on Server", + "isProxying": "Being forwarded" + } + }, + "expandedUrls": { + "text_zero": "No shortened URLs found", + "text_one": "Expanded 1 URL", + "text_other": "Expanded {{count}} URLs" + } + } + } + } +} diff --git a/public/locales/en-US/settings-2fa.json b/public/locales/en-US/settings-2fa.json new file mode 100644 index 0000000..fb4e637 --- /dev/null +++ b/public/locales/en-US/settings-2fa.json @@ -0,0 +1,33 @@ +{ + "title": "Two-Factor-Authentication", + "alreadyEnabled": "You have successfully enabled 2FA!", + "setup": { + "description": "Enable 2FA to add an extra layer of security to your account. Each time you log in, you will need to enter a code generated from your authenticator app. This makes it harder for an attacker to hack into your account as they would need to have access to your phone.", + "setupLabel": "Enable 2FA", + "continueActionLabel": "Enable 2FA", + "codeExpired": "The verification time for your current Two-Factor-Authentication code has expired. A new code has been generated.", + "recoveryCodes": { + "title": "Note down your recovery codes", + "description": "These codes are used to recover your account if you lose access to your authenticator app. Note them down and store them in a safe place. You will not be able to view them again. Do not store them in your password manager. IF YOU LOSE YOUR RECOVERY CODES, YOU WILL LOSE ACCESS TO YOUR ACCOUNT. WE WILL NOT BE ABLE TO HELP YOU.", + "continueActionLabel": "I have noted down my recovery codes" + }, + "success": "You have successfully enabled 2FA!" + }, + "delete": { + "title": "Disable 2FA", + "steps": { + "askType": { + "code": "I have my 2FA code", + "recoveryCode": "I have a recovery code" + }, + "askCode": { + "label": "Code" + }, + "askRecoveryCode": { + "label": "Recovery Code" + } + }, + "continueActionLabel": "Disable 2FA", + "success": "You have successfully disabled 2FA!" + } +} diff --git a/public/locales/en-US/settings-preferences.json b/public/locales/en-US/settings-preferences.json new file mode 100644 index 0000000..fe30a4e --- /dev/null +++ b/public/locales/en-US/settings-preferences.json @@ -0,0 +1,5 @@ +{ + "title": "Alias Preferences", + "description": "Select default values for your aliases. This only affects aliases you haven't set a custom value for.", + "continueActionLabel": "Save preferences" +} diff --git a/public/locales/en-US/settings.json b/public/locales/en-US/settings.json new file mode 100644 index 0000000..7ab1855 --- /dev/null +++ b/public/locales/en-US/settings.json @@ -0,0 +1,7 @@ +{ + "title": "Settings", + "actions": { + "enable2fa": "Two-Factor-Authentication", + "aliasPreferences": "Alias Preferences" + } +} diff --git a/public/locales/en-US/signup.json b/public/locales/en-US/signup.json new file mode 100644 index 0000000..40e654c --- /dev/null +++ b/public/locales/en-US/signup.json @@ -0,0 +1,18 @@ +{ + "forms": { + "email": { + "title": "Sign up", + "description": "We only need your email and you are ready to go!", + "continueActionLabel": "Continue" + }, + "mailVerification": { + "title": "You got mail!", + "description": "We sent you an email with a link to confirm your email address. Please check your inbox and click on the link to continue.", + "editEmail": { + "title": "Edit email address?", + "description": "Would you like to return to the previous step and edit your email address?", + "continueActionLabel": "Yes, edit email" + } + } + } +} diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index 7c6ebbf..a05b8e5 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -21,555 +21,7 @@ "appError": "We are sorry but there was an error. Please try again later." }, - "routes": { - "OverviewRoute": { - "title": "Overview", - "description": "Not much to see here, yet." - }, - "LoginRoute": { - "forms": { - "email": { - "title": "Sign in", - "description": "We'll send you a verification code to your email.", - "continueAction": "Send Code", - "form": { - "email": { - "label": "Email", - "placeholder": "johndoe@example.com" - } - } - }, - "confirmCode": { - "title": "You got mail!", - "description": "We sent you a code to your email. Enter it below to login", - "continueAction": "Log in", - "allowLoginFromDifferentDevices": "Allow login from different devices", - "expiringSoon": "Your code will expire in less than a minute.", - "form": { - "code": { - "label": "Verification Code", - "errors": { - "invalidChars": "Invalid verification code" - } - } - } - }, - "confirmFromDifferentDevice": { - "title": "Login failed", - "description": "You could not be logged in. This could either be because you are not allowed to login from different devices or the verification code is invalid or expired." - }, - "otp": { - "title": "Two-factor authentication", - "description": "Please enter the code from your authenticator app.", - "submit": "Log in", - "lost": "I lost my codes", - "code": { - "label": "Code" - }, - "unavailable": "Your OTP verification time expired or you exceeded the maximum number of attempts. Please log in again." - } - } - }, - "SignupRoute": { - "forms": { - "email": { - "title": "Sign up", - "description": "We only need your email and you are ready to go!", - "continueAction": "Continue", - "form": { - "email": { - "label": "Email", - "placeholder": "johndoe@example.com" - } - } - }, - "mailVerification": { - "title": "You got mail!", - "description": "We sent you an email with a link to confirm your email address. Please check your inbox and click on the link to continue.", - "editEmail": { - "title": "Edit email address?", - "description": "Would you like to return to the previous step and edit your email address?", - "continueAction": "Yes, edit email" - } - } - } - }, - "VerifyEmailRoute": { - "title": "Verify your email", - "isLoading": "Verifying your email...", - "isCodeInvalid": "Sorry, but this verification code is invalid.", - "errors": { - "code": { - "invalid": "The verification code is invalid." - } - } - }, - "Recover2FARoute": { - "title": "Recover Two-Factor-Authentication", - "description": "We are very sorry if you lost your codes. Please enter a recovery code to continue. Note that this will disable two-factor authentication for your account. You can enable it again in the settings.", - "forms": { - "recoveryCode": { - "label": "Recovery Code" - }, - "submit": "Disable 2FA" - }, - "unauthorized": "Please make sure to log in first and then reset your two-factor authentication on its screen.", - "canLoginNow": "Two-factor authentication has been disabled. You can now log in.", - "loggedIn": "Two-factor authentication has been disabled. You are now logged in." - }, - "CompleteAccountRoute": { - "forms": { - "generateReports": { - "title": "Generate Email Reports?", - "description": "Would you like to create fully encrypted email reports for your mails? Only you will be able to access them. Not even we can decrypt them.", - "continueAction": "Yes", - "cancelAction": "No" - }, - "password": { - "title": "Set up your password", - "description": "Please enter a safe password so that we can encrypt your data.", - "continueAction": "Continue", - "form": { - "password": { - "label": "Password", - "placeholder": "********" - }, - "passwordConfirm": { - "label": "Confirm Password", - "placeholder": "Re-enter your password", - "mustMatchHelperText": "Passwords do not match." - } - } - }, - "available": { - "title": "Encryption already enabled", - "description": "You already have encryption enabled. Changing passwords is currently not supported." - } - } - }, - "AliasesRoute": { - "title": "Aliases", - "isInCopyMode": "You are in copy mode. Click on an alias to copy it to your clipboard.", - "emptyState": { - "title": "Welcome to your Aliases!", - "description": "Create your first Alias to get started." - }, - "pageActions": { - "search": { - "label": "Search", - "placeholder": "Search for names" - }, - "searchFilter": { - "active": "Active", - "inactive": "Inactive" - }, - "typeFilter": { - "custom": "Custom made", - "random": "Randomly generated" - } - }, - "actions": { - "createRandomAlias": { - "label": "Create Random Alias" - }, - "createCustomAlias": { - "label": "Create Custom Alias", - "description": "You can define your own custom alias. Note that a random suffix will be added at the end to avoid duplicates.", - "continueAction": "Create Alias", - "form": { - "address": { - "label": "Address", - "placeholder": "awesome-fish" - } - } - } - } - }, - "AliasDetailRoute": { - "title": "Alias Details", - "sections": { - "settings": { - "title": "Settings", - "description": "These settings apply to this alias only. You can either set a value manually or refer to your default settings. Note that this does change in behavior. When you set a value to refer to your default setting, the alias will always use the latest value. So when you change your default setting, the alias will automatically use the new value." - }, - "notes": { - "title": "Notes", - "form": { - "createdAt": { - "label": "Created at", - "empty": "Unavailable" - }, - "creationContext": { - "label": "Creation Context", - "web": { - "label": "Created on this instance" - }, - "extension": { - "label": "Created in the extension" - }, - "extension-inline": { - "label": "Created using the extension" - } - }, - "createdOn": { - "label": "Created on" - }, - "personalNotes": { - "label": "Personal Notes", - "empty": "-", - "helperText": "You can enter personal notes for this alias here. Notes are encrypted." - }, - "websites": { - "label": "Websites", - "emptyText": "You haven't used this alias on any site yet.", - "placeholder": "https://example.com", - "helperText": "Add a website to this alias. Used to autofill." - } - } - } - }, - "actions": { - "delete": { - "label": "Delete Alias", - "description": "Are you sure you want to delete this alias?", - "continueAction": "Delete Alias" - } - } - }, - "ReportsRoute": { - "title": "Reports", - "emptyState": { - "title": "Welcome to your Reports!", - "description": "Here you will find your email reports. Currently, you don't have any reports. Wait until you receive an email." - }, - "pageActions": { - "sort": { - "List": "List reports by their date", - "GroupByAlias": "Group reports by their alias" - } - } - }, - "ReportDetailRoute": { - "title": "Report Details", - "actions": { - "delete": { - "label": "Delete Report", - "description": "Are you sure you want to delete this report?", - "continueAction": "Delete Report" - } - }, - "sections": { - "information": { - "title": "Email Information", - "form": { - "from": { - "label": "From" - }, - "to": { - "label": "To" - }, - "subject": { - "label": "Subject" - } - } - }, - "trackers": { - "title": "Trackers", - "results": { - "imageTrackers": { - "text_zero": "No image trackers found", - "text_one": "Removed 1 image tracker", - "text_other": "Removed {{count}} image trackers" - }, - "proxiedImages": { - "text_zero": "No images found", - "text_one": "Forwarding 1 image", - "text_other": "Forwarding {{count}} images", - "status": { - "isStored": "Stored on Server", - "isProxying": "Being forwarded" - } - }, - "expandedUrls": { - "text_zero": "No shortened URLs found", - "text_one": "Expanded 1 URL", - "text_other": "Expanded {{count}} URLs" - } - } - } - } - }, - "SettingsRoute": { - "title": "Settings", - "forms": { - "aliasPreferences": { - "title": "Alias Preferences", - "description": "Select default values for your aliases. This only affects aliases you haven't set a custom value for.", - "saveAction": "Save preferences" - } - }, - "actions": { - "enable2fa": "Two-Factor-Authentication", - "aliasPreferences": "Alias Preferences" - }, - "2fa": { - "title": "Two-Factor-Authentication", - "alreadyEnabled": "You have successfully enabled 2FA!", - "setup": { - "description": "Enable 2FA to add an extra layer of security to your account. Each time you log in, you will need to enter a code generated from your authenticator app. This makes it harder for an attacker to hack into your account as they would need to have access to your phone.", - "setupLabel": "Enable 2FA", - "code": { - "label": "Code", - "description": "Enter the code generated by your authenticator app.", - "onlyDigits": "The code can only contain digits." - }, - "submit": "Enable 2FA", - "expired": "The verification time for your current Two-Factor-Authentication code has expired. A new code has been generated.", - "recoveryCodes": { - "title": "Note down your recovery codes", - "description": "These codes are used to recover your account if you lose access to your authenticator app. Note them down and store them in a safe place. You will not be able to view them again. Do not store them in your password manager. IF YOU LOSE YOUR RECOVERY CODES, YOU WILL LOSE ACCESS TO YOUR ACCOUNT. WE WILL NOT BE ABLE TO HELP YOU.", - "submit": "I have noted down my recovery codes" - }, - "success": "You have successfully enabled 2FA!" - }, - "delete": { - "showAction": "Disable 2FA", - "askType": { - "code": "I have my 2FA code", - "recoveryCode": "I have a recovery code" - }, - "askCode": { - "label": "Code" - }, - "askRecoveryCode": { - "label": "Recovery Code" - }, - "submit": "Disable 2FA", - "success": "You have successfully disabled 2FA!" - } - } - }, - "LogoutRoute": { - "title": "Log out", - "description": "We are logging you out..." - }, - "ReservedAliasesRoute": { - "title": "Reserved Aliases", - "pageActions": { - "search": { - "label": "Search", - "placeholder": "Search for aliases" - } - }, - "actions": { - "create": { - "label": "Create new Reserved Alias" - } - }, - "userAmount_one": "Forwards to one user", - "userAmount_other": "Forwards to {{count}} users", - "emptyState": { - "title": "Create your first reserved alias", - "description": "Reserved aliases are aliases that will be forwarded to selected admin users. This is useful if you want to create aliases that are meant to be public, like contact@example.com or hello@example.com." - } - }, - "ReservedAliasDetailRoute": { - "title": "Reserved Alias Details", - "sections": { - "users": { - "title": "Users", - "fields": { - "users": { - "label": "Users", - "me": "{{email}} (Me)" - } - } - } - } - }, - "AdminRoute": { - "title": "Site configuration", - "routes": { - "reservedAliases": "Reserved Aliases", - "settings": "Global Settings" - }, - "forms": { - "reservedAliases": { - "title": "Reserved Aliases", - "description": "Define what alias should forward to whom.", - "saveAction": "Create Alias", - "fields": { - "local": { - "label": "Local" - }, - "users": { - "label": "Users", - "me": "{{email}} (Me)" - } - }, - "explanation": { - "step1": "User from outside", - "step2": "Sends mail to", - "step4": "KleckRelay forwards to" - } - }, - "settings": { - "randomEmailIdMinLength": { - "label": "Minimum random alias ID length", - "description": "The minimum length for randomly generated emails. The server will automatically increase the length if required so." - }, - "randomEmailIdChars": { - "label": "Random alias character pool", - "description": "Characters that are used to generate random emails." - }, - "randomEmailLengthIncreaseOnPercentage": { - "label": "Percentage of used aliases", - "description": "If the percentage of used random email IDs is higher than this value, the length of the random email ID will be increased. This is used to prevent spammers from guessing the email ID." - }, - "customEmailSuffixLength": { - "label": "Custom email suffix length", - "description": "The length of the custom email suffix." - }, - "customEmailSuffixChars": { - "label": "Custom email suffix character pool", - "description": "Characters that are used to generate custom email suffixes." - }, - "imageProxyStorageLifeTimeInHours": { - "label": "Image proxy storage lifetime", - "description": "The lifetime of images that are stored on the server in hours. After this time, the image will be deleted.", - "unit_one": "hour", - "unit_other": "hours" - }, - "enableImageProxy": { - "label": "Enable image proxy", - "description": "If enabled, images will be proxied through the server. This enhances your user's privacy as their ip address will not be leaked." - }, - "enableImageProxyStorage": { - "label": "Enable image proxy storage", - "description": "If enabled, images will be stored on the server and forwarded to the user. This makes email tracking nearly impossible as every message will be marked as read instantly, which is obviously not true as a user is typically not able to view an email in just a few seconds after it has been sent. This will only affect new images." - }, - "userEmailEnableDisposableEmails": { - "label": "Enable disposable emails for new accounts", - "description": "If enabled, users will be able to use disposable emails when creating a new account. This will only affect new accounts." - }, - "userEmailEnableOtherRelays": { - "label": "Enable other relays for new accounts", - "description": "If enabled, users will be able to use other relays (such as SimpleLogin or DuckDuckGo's Email Tracking Protection) when creating a new account. This will only affect new accounts." - }, - "allowStatistics": { - "label": "Allow statistics", - "description": "If enabled, your instance will collect anonymous statistics and share them. They will only be stored locally on this instance but made public." - }, - "allowAliasDeletion": { - "label": "Allow alias deletion", - "description": "If enabled, users will be able to delete their aliases." - }, - "maxAliasesPerUser": { - "label": "Maximum aliases per user", - "description": "The maximum number of aliases a user can create. 0 means unlimited. Existing aliases will not be affected." - } - } - }, - "settings": { - "title": "Global Settings", - "description": "Configure global settings for your instance.", - "successMessage": "Settings have been saved successfully!", - "randomAliasesPreview": { - "title": "Random aliases will look like this", - "helperText": "This is just a preview. Those are not real aliases." - }, - "randomAliasesIncreaseExplanation": "Random aliases' length will be increased from {{originalLength}} to {{increasedLength}} characters after {{amount}} aliases have been created.", - "resetLabel": "Reset to defaults", - "disabled": { - "title": "Global settings are disabled", - "description": "Global settings have been disabled. You can enable them in the configuration file." - } - }, - "reservedAlias": { - "actions": { - "delete": { - "label": "Delete Reserved Alias", - "description": "Are you sure you want to delete this reserved alias?", - "continueAction": "Delete Reserved Alias" - } - } - }, - "serverStatus": { - "noRecentReports": "There seems to be some issues with your server. The server hasn't done its cleanup in the last few days. The last report was on {{date}}.", - "error": "There was an error during the last server cleanup job from {{relativeDescription}}. Please check the logs for more information.", - "success": "Everything okay with your server! The last cleanup job was {{relativeDescription}}." - } - } - }, - "components": { - "NavigationButton": { - "overview": "Overview", - "aliases": "Aliases", - "reports": "Reports", - "settings": "Settings", - "admin": "Admin" - }, - "AuthenticateRoute": { - "signup": "Sign up", - "login": "Log in" - }, - "AuthenticatedRoute": { - "logout": "Logout" - }, - "EnterDecryptionPassword": { - "title": "Decrypt Reports", - "description": "Please enter your password so that your reports can de decrypted.", - "cancelAction": "Decrypt later", - "continueAction": "Continue", - "form": { - "password": { - "label": "Password", - "placeholder": "********", - "errors": { - "invalidPassword": "Password is invalid" - } - } - } - }, - "ResendMailButton": { - "label": "Resend Mail" - }, - "OpenMailButton": { - "label": "Open Mail" - }, - "DecryptionPasswordMissingAlert": { - "unavailable": { - "title": "Encryption required", - "description": "You need to set up encryption to use this feature.", - "continueAction": "Set up encryption" - }, - "passwordRequired": { - "title": "Password required", - "description": "Your decryption password is required to view this section.", - "continueAction": "Enter password" - } - }, - "TimedButton": { - "remainingTime_one": "({{count}})", - "remainingTime_other": "({{count}})" - }, - "ErrorLoadingDataMessage": { - "tryAgain": "Try Again" - }, - "AliasTypeIndicator": { - "random": "This is a randomly generated alias", - "custom": "This is a custom-made alias" - }, - "NoSearchResults": { - "title": "Nothing found", - "description": "We couldn't find anything for your search query. Try again with a different query." - }, - "LockNavigationContextProvider": { - "title": "Are you sure you want to leave?", - "description": "You have unsaved changes. If you leave, your changes will be lost.", - "continueLabel": "Leave" - }, "passwordShareConfirmationDialog": { "title": "Share Password?", "description": "An extension is asking for your password. Do you want to share it? Only continue if you initiated this action.", @@ -578,85 +30,6 @@ "doNotShare": "Do not share", "decideLater": "Decide later", "doNotAskAgain": "Do not ask again" - }, - "StringPoolField": { - "addCustom": { - "label": "Add custom" - }, - "forms": { - "addNew": { - "title": "Add new value", - "description": "Enter your characters you would like to include", - "label": "Characters", - "submit": "Add" - } - } - } - }, - - "relations": { - "alias": { - "mutations": { - "success": { - "aliasCreation": "Created Alias successfully!", - "aliasUpdated": "Updated Alias successfully!", - "notesUpdated": "Updated & encrypted notes successfully!", - "aliasChangedToEnabled": "Alias has been enabled", - "aliasChangedToDisabled": "Alias has been disabled", - "addressCopiedToClipboard": "Address has been copied to your clipboard!", - "aliasDeleted": "Alias has been deleted!" - } - }, - "settings": { - "removeTrackers": { - "label": "Remove Trackers", - "helperText": "Remove single-pixel image trackers as well as url trackers." - }, - "createMailReports": { - "label": "Create Mail Reports", - "helperText": "Create reports of emails sent to aliases. Reports are end-to-end encrypted. Only you can access them." - }, - "proxyImages": { - "label": "Proxy Images", - "helperText": "Proxies images in your emails through this KleckRelay instance. This adds an extra layer of privacy. Images are loaded immediately after we receive the email. They then will be stored for some time (cache time). During that time, the image will be served from us. This means the sender has no idea you have opened the mail. After the cache time, the image is loaded from the sender, but it will be forwarded by us. This means the sender will not be able to access your IP address nor your browser data." - }, - "imageProxyFormat": { - "label": "Image File Type", - "enumTexts": { - "jpeg": "JPEG", - "png": "PNG", - "webp": "WEBP" - } - }, - "proxyUserAgent": { - "label": "Proxy User Agent", - "helperText": "An User Agent is a identifier each browser and email client sends when retrieving files, such as images. You can specify here what user agent you would like to be used when we forward it. User Agents are kept up-to-date.", - "enumTexts": { - "apple-mail": "Apple Mail", - "google-mail": "Google Mail", - "outlook-windows": "Outlook / Windows", - "outlook-macos": "Outlook / MacOS", - "firefox": "Firefox Browser", - "chrome": "Chrome Browser" - } - }, - "expandUrlShorteners": { - "label": "Expand URL Shorteners", - "helperText": "Expand shortened URLs (for example bit.ly) to their original URL. This way those services can't track you." - }, - "saveAction": "Save Settings" - } - }, - "report": { - "mutations": { - "success": { - "reportDeleted": "Report has been deleted!" - } - }, - "emailMeta": { - "flow": "{{from}} -> {{to}}", - "emptySubject": "" - } } } } diff --git a/public/locales/en-US/verify-email.json b/public/locales/en-US/verify-email.json new file mode 100644 index 0000000..98a10ef --- /dev/null +++ b/public/locales/en-US/verify-email.json @@ -0,0 +1,7 @@ +{ + "title": "Verify your email", + "isLoading": "We are verifying your email address...", + "errors": { + "invalid": "The verification link is invalid or has expired." + } +} diff --git a/src/components/AuthContext/PasswordShareConfirmationDialog.tsx b/src/components/AuthContext/PasswordShareConfirmationDialog.tsx index b1a92f2..bd43330 100644 --- a/src/components/AuthContext/PasswordShareConfirmationDialog.tsx +++ b/src/components/AuthContext/PasswordShareConfirmationDialog.tsx @@ -25,32 +25,28 @@ export default function PasswordShareConfirmationDialog({ onShare, onClose, }: PasswordShareConfirmationDialogProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("extension") return ( onClose(false)} maxWidth="sm" fullWidth={false}> - {t("components.passwordShareConfirmationDialog.title")} + {t("sharePassword.title")} - - {t("components.passwordShareConfirmationDialog.description")} - + {t("sharePassword.description")} - - {t("components.passwordShareConfirmationDialog.warning")} - + {t("sharePassword.warning")} diff --git a/src/components/LockNavigation/LockNavigationContextProvider.tsx b/src/components/LockNavigation/LockNavigationContextProvider.tsx index 099d858..d4fe433 100644 --- a/src/components/LockNavigation/LockNavigationContextProvider.tsx +++ b/src/components/LockNavigation/LockNavigationContextProvider.tsx @@ -22,7 +22,7 @@ export interface LockNavigationContextProviderProps { export default function LockNavigationContextProvider({ children, }: LockNavigationContextProviderProps): JSX.Element { - const {t} = useTranslation() + const {t} = useTranslation(["components", "common"]) const navigate = useNavigate() const [isLocked, setIsLocked] = useState(false) @@ -97,18 +97,18 @@ export default function LockNavigationContextProvider({ {children} - {t("components.LockNavigationContextProvider.title")} + {t("LockNavigationContextProvider.title")} - {t("components.LockNavigationContextProvider.description")} + {t("LockNavigationContextProvider.description")} diff --git a/src/components/widgets/AliasTypeIndicator.tsx b/src/components/widgets/AliasTypeIndicator.tsx index 4e7028e..4a4b709 100644 --- a/src/components/widgets/AliasTypeIndicator.tsx +++ b/src/components/widgets/AliasTypeIndicator.tsx @@ -16,13 +16,10 @@ export const ALIAS_TYPE_ICON_MAP: Record = { [AliasType.CUSTOM]: , } -const ALIAS_TYPE_TOOLTIP_MAP = createEnumMapFromTranslation( - "components.AliasTypeIndicator", - AliasType, -) +const ALIAS_TYPE_TOOLTIP_MAP = createEnumMapFromTranslation("aliasTypeExplanation", AliasType) export default function AliasTypeIndicator({type}: AliasTypeIndicatorProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("aliases") return ( // @ts-ignore diff --git a/src/components/widgets/DecryptionPasswordMissingAlert.tsx b/src/components/widgets/DecryptionPasswordMissingAlert.tsx index be8b59b..13ac737 100644 --- a/src/components/widgets/DecryptionPasswordMissingAlert.tsx +++ b/src/components/widgets/DecryptionPasswordMissingAlert.tsx @@ -15,7 +15,7 @@ export interface WithEncryptionRequiredProps { export default function DecryptionPasswordMissingAlert({ children = <>, }: WithEncryptionRequiredProps): JSX.Element { - const {t} = useTranslation() + const {t} = useTranslation("decryption") const {handleAnchorClick} = useContext(LockNavigationContext) const {encryptionStatus} = useContext(AuthContext) const theme = useTheme() @@ -33,12 +33,12 @@ export default function DecryptionPasswordMissingAlert({ > - {t("components.DecryptionPasswordMissingAlert.unavailable.title")} + {t("actions.passwordMissing.unavailable.title")} - {t("components.DecryptionPasswordMissingAlert.unavailable.description")} + {t("actions.passwordMissing.unavailable.description")} @@ -49,9 +49,7 @@ export default function DecryptionPasswordMissingAlert({ startIcon={} onClick={handleAnchorClick} > - {t( - "components.DecryptionPasswordMissingAlert.unavailable.continueAction", - )} + {t("actions.passwordMissing.unavailable.continueActionLabel")} @@ -70,14 +68,12 @@ export default function DecryptionPasswordMissingAlert({ > - {t("components.DecryptionPasswordMissingAlert.passwordRequired.title")} + {t("actions.passwordMissing.passwordRequired.title")} - {t( - "components.DecryptionPasswordMissingAlert.passwordRequired.description", - )} + {t("actions.passwordMissing.passwordRequired.description")} @@ -87,9 +83,7 @@ export default function DecryptionPasswordMissingAlert({ startIcon={} onClick={handleAnchorClick} > - {t( - "components.DecryptionPasswordMissingAlert.passwordRequired.continueAction", - )} + {t("actions.passwordMissing.passwordRequired.continueActionLabel")} diff --git a/src/components/widgets/ErrorLoadingDataMessage.tsx b/src/components/widgets/ErrorLoadingDataMessage.tsx index 1072813..0029164 100644 --- a/src/components/widgets/ErrorLoadingDataMessage.tsx +++ b/src/components/widgets/ErrorLoadingDataMessage.tsx @@ -12,7 +12,7 @@ export default function ErrorLoadingDataMessage({ message, onRetry, }: ErrorLoadingDataMessageProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("components") return ( @@ -20,9 +20,7 @@ export default function ErrorLoadingDataMessage({ {message} - + ) diff --git a/src/components/widgets/NoSearchResults.tsx b/src/components/widgets/NoSearchResults.tsx index 12a0ca9..1c09fc4 100644 --- a/src/components/widgets/NoSearchResults.tsx +++ b/src/components/widgets/NoSearchResults.tsx @@ -5,20 +5,18 @@ import {FaQuestion} from "react-icons/fa" import {Grid, Typography} from "@mui/material" export default function NoSearchResults(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("common") return ( - {t("components.NoSearchResults.title")} + {t("noSearchResults.title")} - - {t("components.NoSearchResults.description")} - + {t("noSearchResults.description")} ) diff --git a/src/components/widgets/OpenMailButton.tsx b/src/components/widgets/OpenMailButton.tsx index dbc1715..83476b9 100644 --- a/src/components/widgets/OpenMailButton.tsx +++ b/src/components/widgets/OpenMailButton.tsx @@ -12,14 +12,14 @@ export interface OpenMailButtonProps { } export default function OpenMailButton({domain}: OpenMailButtonProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("components") const userAgent = new UAParser() if (userAgent.getOS().name === "Android" && APP_LINK_MAP[domain]) { return ( ) } diff --git a/src/components/widgets/SimpleForm.tsx b/src/components/widgets/SimpleForm.tsx index 36fc510..bd295c6 100644 --- a/src/components/widgets/SimpleForm.tsx +++ b/src/components/widgets/SimpleForm.tsx @@ -5,12 +5,13 @@ import React, {ReactElement, useEffect, useState} from "react" import {Alert, Button, Grid, Snackbar, Typography, TypographyProps} from "@mui/material" import {LoadingButton} from "@mui/lab" import {OverrideProps} from "@mui/types" +import {useTranslation} from "react-i18next" export interface SimpleFormProps { title: string description: string - continueActionLabel: string + continueActionLabel?: string children?: ReactElement[] cancelActionLabel?: string isSubmitting?: boolean @@ -32,6 +33,8 @@ export default function SimpleForm({ titleComponent = "h1", isSubmitting = false, }: SimpleFormProps): ReactElement { + const {t} = useTranslation("common") + const [showSnackbar, setShowSnackbar] = useState(false) useEffect(() => { @@ -101,7 +104,7 @@ export default function SimpleForm({ type="submit" startIcon={} > - {continueActionLabel} + {continueActionLabel || t("general.continueLabel")} diff --git a/src/components/widgets/StringPoolField/AddNewDialog.tsx b/src/components/widgets/StringPoolField/AddNewDialog.tsx index 9ef899f..1c830e6 100644 --- a/src/components/widgets/StringPoolField/AddNewDialog.tsx +++ b/src/components/widgets/StringPoolField/AddNewDialog.tsx @@ -28,22 +28,22 @@ export default function AddNewDialog({ open = false, onClose, }: StringPoolFieldProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["components", "common"]) const [value, setValue] = useState("") return ( - {t("components.StringPoolField.forms.addNew.title")} + {t("StringPoolField.forms.addNew.title")} - {t("components.StringPoolField.forms.addNew.description")} + {t("StringPoolField.forms.addNew.description")} setValue(e.target.value)} - label={t("components.StringPoolField.forms.addNew.label")} + label={t("StringPoolField.forms.addNew.label")} name="addNew" fullWidth autoFocus @@ -53,14 +53,14 @@ export default function AddNewDialog({ diff --git a/src/components/widgets/StringPoolField/StringPoolField.tsx b/src/components/widgets/StringPoolField/StringPoolField.tsx index 1263769..7f47860 100644 --- a/src/components/widgets/StringPoolField/StringPoolField.tsx +++ b/src/components/widgets/StringPoolField/StringPoolField.tsx @@ -51,7 +51,7 @@ export default function StringPoolField({ fullWidth, ...props }: StringPoolFieldProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("components") const reversedPoolsMap = useMemo( () => Object.fromEntries(Object.entries(pools).map(([key, value]) => [value, key])), @@ -155,9 +155,7 @@ export default function StringPoolField({ - + )} diff --git a/src/components/widgets/TimedButton.tsx b/src/components/widgets/TimedButton.tsx index bdcd711..0579de8 100644 --- a/src/components/widgets/TimedButton.tsx +++ b/src/components/widgets/TimedButton.tsx @@ -18,7 +18,7 @@ export default function TimedButton({ disabled: parentDisabled = false, ...props }: TimedButtonProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("components") const [startDate, resetInterval] = useIntervalUpdate(1000) @@ -35,9 +35,7 @@ export default function TimedButton({ }} > {children} - {secondsLeft > 0 && ( - {t("components.TimedButton.remainingTime", {count: secondsLeft})} - )} + {secondsLeft > 0 && {t("TimedButton.remainingTime", {count: secondsLeft})}} ) } diff --git a/src/constants/enum-mappings.ts b/src/constants/enum-mappings.ts index ecf2070..1f8a448 100644 --- a/src/constants/enum-mappings.ts +++ b/src/constants/enum-mappings.ts @@ -2,10 +2,10 @@ import {ImageProxyFormatType, ProxyUserAgentType} from "~/server-types" import {createEnumMapFromTranslation} from "~/utils" export const IMAGE_PROXY_FORMAT_TYPE_NAME_MAP = createEnumMapFromTranslation( - "relations.alias.settings.imageProxyFormat.enumTexts", + "settings.fields.imageProxyFormat.values", ImageProxyFormatType, ) export const PROXY_USER_AGENT_TYPE_NAME_MAP = createEnumMapFromTranslation( - "relations.alias.settings.proxyUserAgent.enumTexts", + "settings.fields.proxyUserAgent.values", ProxyUserAgentType, ) diff --git a/src/init-i18n.ts b/src/init-i18n.ts index f2275df..bdb19b0 100644 --- a/src/init-i18n.ts +++ b/src/init-i18n.ts @@ -15,6 +15,7 @@ i18n.use(HttpApi) .init({ debug: isDev, fallbackLng: "en-US", + load: "all", backend: { loadPath: "/locales/{{lng}}/{{ns}}.json", }, diff --git a/src/route-widgets/AdminRoute/ServerStatus.tsx b/src/route-widgets/AdminRoute/ServerStatus.tsx index 8140b5e..19ace79 100644 --- a/src/route-widgets/AdminRoute/ServerStatus.tsx +++ b/src/route-widgets/AdminRoute/ServerStatus.tsx @@ -18,8 +18,8 @@ import decryptCronReportData from "~/apis/helpers/decrypt-cron-report-data" const MAX_REPORT_DAY_THRESHOLD = 5 function ServerStatus(): ReactElement | null { + const {t} = useTranslation("admin") const serverSettings = useLoaderData() as ServerSettings - const {t} = useTranslation() const {_decryptUsingPrivateKey} = useContext(AuthContext) const query = useQuery(["get_latest_cron_report"], async () => { @@ -50,7 +50,7 @@ function ServerStatus(): ReactElement | null { if (report.createdAt < thresholdDate) { return ( - {t("routes.AdminRoute.serverStatus.noRecentReports", { + {t("serverStatus.noRecentReports", { date: format(new Date(report.createdAt), "Pp"), })} @@ -60,7 +60,7 @@ function ServerStatus(): ReactElement | null { if (report.reportData.report.status === "error") { return ( - {t("routes.AdminRoute.serverStatus.error", { + {t("serverStatus.error", { relativeDescription: formatRelative( new Date(report.createdAt), new Date(), @@ -72,7 +72,7 @@ function ServerStatus(): ReactElement | null { return ( - {t("routes.AdminRoute.serverStatus.success", { + {t("serverStatus.success", { relativeDescription: formatRelative( new Date(report.createdAt), new Date(), diff --git a/src/route-widgets/AliasDetailRoute/AddWebsiteField.tsx b/src/route-widgets/AliasDetailRoute/AddWebsiteField.tsx index 52d08e4..42d2fc4 100644 --- a/src/route-widgets/AliasDetailRoute/AddWebsiteField.tsx +++ b/src/route-widgets/AliasDetailRoute/AddWebsiteField.tsx @@ -18,14 +18,14 @@ interface WebsiteForm { url: string } -const WEBSITE_SCHEMA = yup.object().shape({ - url: yup.string().matches(URL_REGEX, "This URL is invalid."), -}) - export default function AddWebsiteField({onAdd, isLoading}: AddWebsiteFieldProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("alias-notes") + + const schema = yup.object().shape({ + url: yup.string().matches(URL_REGEX, t("form.websites.error.invalid") as string), + }) const websiteFormik = useFormik({ - validationSchema: WEBSITE_SCHEMA, + validationSchema: schema, initialValues: { url: "", }, @@ -58,10 +58,8 @@ export default function AddWebsiteField({onAdd, isLoading}: AddWebsiteFieldProps {(websiteFormik.touched.url && websiteFormik.errors.url) || - t("routes.AliasDetailRoute.sections.notes.form.websites.helperText")} + t("form.websites.helperText")} diff --git a/src/route-widgets/AliasDetailRoute/AliasAddress.tsx b/src/route-widgets/AliasDetailRoute/AliasAddress.tsx index 4c33840..163a1ee 100644 --- a/src/route-widgets/AliasDetailRoute/AliasAddress.tsx +++ b/src/route-widgets/AliasDetailRoute/AliasAddress.tsx @@ -12,7 +12,7 @@ export interface AliasAddressProps { } export default function AliasAddress({address}: AliasAddressProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("common") const [{value, error}, copyToClipboard] = useCopyToClipboard() return ( @@ -26,11 +26,8 @@ export default function AliasAddress({address}: AliasAddressProps): ReactElement > {address} - - + + ) } diff --git a/src/route-widgets/AliasDetailRoute/AliasNotesForm.tsx b/src/route-widgets/AliasDetailRoute/AliasNotesForm.tsx index 94680e3..599bfb5 100644 --- a/src/route-widgets/AliasDetailRoute/AliasNotesForm.tsx +++ b/src/route-widgets/AliasDetailRoute/AliasNotesForm.tsx @@ -64,21 +64,19 @@ const CREATION_CONTEXT_ICON_MAP: Record - {t("routes.AliasDetailRoute.sections.notes.title")} + {t("title")} @@ -211,11 +209,9 @@ export default function AliasNotesForm({id, notes, queryKey}: AliasNotesFormProp {notes.data.createdAt && ( } - label={t( - "routes.AliasDetailRoute.sections.notes.form.createdAt.label", - )} + label={t("form.createdAt.label")} > {notes.data.createdAt && ( @@ -231,13 +227,11 @@ export default function AliasNotesForm({id, notes, queryKey}: AliasNotesFormProp {t( - `routes.AliasDetailRoute.sections.notes.form.creationContext.${notes.data.creationContext}.label`, + `form.creationContext.values.${notes.data.creationContext}`, )} @@ -247,9 +241,7 @@ export default function AliasNotesForm({id, notes, queryKey}: AliasNotesFormProp } - label={t( - "routes.AliasDetailRoute.sections.notes.form.createdOn.label", - )} + label={t("form.createdOn.label")} > )} - + {isInEditMode ? ( {isInEditMode ? ( diff --git a/src/route-widgets/AliasDetailRoute/AliasPreferencesForm.tsx b/src/route-widgets/AliasDetailRoute/AliasPreferencesForm.tsx index e08ca66..9af8ce2 100644 --- a/src/route-widgets/AliasDetailRoute/AliasPreferencesForm.tsx +++ b/src/route-widgets/AliasDetailRoute/AliasPreferencesForm.tsx @@ -44,7 +44,7 @@ export default function AliasPreferencesForm({ alias, queryKey, }: AliasPreferencesFormProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["aliases", "common"]) const {showSuccess, showError} = useErrorSuccessSnacks() const {_decryptUsingMasterPassword} = useContext(AuthContext) @@ -52,31 +52,34 @@ export default function AliasPreferencesForm({ removeTrackers: yup .mixed() .oneOf([true, false, null]) - .label(t("relations.alias.settings.removeTrackers.label")), + .label(t("settings.fields.removeTrackers.label")), createMailReport: yup .mixed() .oneOf([true, false, null]) - .label(t("relations.alias.settings.createMailReports.label")), - proxyImages: yup.mixed().oneOf([true, false, null]), + .label(t("settings.fields.createMailReport.label")), + proxyImages: yup + .mixed() + .oneOf([true, false, null]) + .label(t("settings.fields.proxyImages.label")), imageProxyFormat: yup .mixed() .oneOf([null, ...Object.values(ImageProxyFormatType)]) - .label(t("relations.alias.settings.imageProxyFormat.label")), + .label(t("settings.fields.imageProxyFormat.label")), proxyUserAgent: yup .mixed() .oneOf([null, ...Object.values(ProxyUserAgentType)]) - .label(t("relations.alias.settings.proxyUserAgent.label")), + .label(t("settings.fields.proxyUserAgent.label")), expandUrlShorteners: yup .mixed() .oneOf([true, false, null]) - .label(t("relations.alias.settings.expandUrlShorteners.label")), + .label(t("settings.fields.expandUrlShorteners.label")), }) const {mutateAsync} = useMutation( data => updateAlias(alias.id, data), { onSuccess: async newAlias => { - showSuccess(t("relations.alias.mutations.success.aliasUpdated")) + showSuccess(t("messages.alias.updated", {ns: "common"})) ;(newAlias as any as DecryptedAlias).notes = decryptAliasNotes( newAlias.encryptedNotes, _decryptUsingMasterPassword, @@ -126,7 +129,7 @@ export default function AliasPreferencesForm({ } name="removeTrackers" @@ -134,9 +137,7 @@ export default function AliasPreferencesForm({ } name="createMailReport" @@ -146,9 +147,7 @@ export default function AliasPreferencesForm({ } name="proxyImages" @@ -160,7 +159,7 @@ export default function AliasPreferencesForm({ } @@ -173,7 +172,7 @@ export default function AliasPreferencesForm({ } > - {t("relations.alias.settings.saveAction")} + {t("settings.continueActionLabel")} - - {t("routes.AliasDetailRoute.sections.settings.description")} - + {t("settings.description")} diff --git a/src/route-widgets/AliasDetailRoute/ChangeAliasActivationStatusSwitch.tsx b/src/route-widgets/AliasDetailRoute/ChangeAliasActivationStatusSwitch.tsx index 5c7b749..f538d6c 100644 --- a/src/route-widgets/AliasDetailRoute/ChangeAliasActivationStatusSwitch.tsx +++ b/src/route-widgets/AliasDetailRoute/ChangeAliasActivationStatusSwitch.tsx @@ -24,7 +24,7 @@ export default function ChangeAliasActivationStatusSwitch({ isActive, queryKey, }: ChangeAliasActivationStatusSwitchProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("common") const {showError, showSuccess} = useErrorSuccessSnacks() const {_decryptUsingMasterPassword, encryptionStatus} = useContext(AuthContext) @@ -83,8 +83,8 @@ export default function ChangeAliasActivationStatusSwitch({ showSuccess( isActive - ? t("relations.alias.mutations.success.aliasChangedToDisabled") - : t("relations.alias.mutations.success.aliasChangedToEnabled"), + ? t("messages.alias.changedToDisabled") + : t("messages.alias.changedToEnabled"), ) } catch {} }} diff --git a/src/route-widgets/AliasesRoute/CreateAliasButton.tsx b/src/route-widgets/AliasesRoute/CreateAliasButton.tsx index 4423ae3..ba14833 100644 --- a/src/route-widgets/AliasesRoute/CreateAliasButton.tsx +++ b/src/route-widgets/AliasesRoute/CreateAliasButton.tsx @@ -26,7 +26,7 @@ import {AuthContext, EncryptionStatus} from "~/components" import CustomAliasDialog from "~/route-widgets/AliasesRoute/CustomAliasDialog" export function CreateAliasButton(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["aliases", "common"]) const {showSuccess, showError} = useErrorSuccessSnacks() const {_encryptUsingMasterPassword, encryptionStatus} = useContext(AuthContext) @@ -54,7 +54,7 @@ export function CreateAliasButton(): ReactElement { { onError: showError, onSuccess: async alias => { - showSuccess(t("relations.alias.mutations.success.aliasCreation")) + showSuccess(t("messages.alias.created", {ns: "common"})) await queryClient.invalidateQueries({ queryKey: ["get_aliases"], @@ -82,7 +82,7 @@ export function CreateAliasButton(): ReactElement { }) } > - {t("routes.AliasesRoute.actions.createRandomAlias.label")} + {t("actions.createRandomAlias.title")} diff --git a/src/route-widgets/AliasesRoute/EmptyStateScreen.tsx b/src/route-widgets/AliasesRoute/EmptyStateScreen.tsx index 5c2ed45..84907c4 100644 --- a/src/route-widgets/AliasesRoute/EmptyStateScreen.tsx +++ b/src/route-widgets/AliasesRoute/EmptyStateScreen.tsx @@ -5,7 +5,7 @@ import {FaMask} from "react-icons/fa" import {Container, Grid, Typography} from "@mui/material" export default function EmptyStateScreen(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("aliases") return ( @@ -20,16 +20,14 @@ export default function EmptyStateScreen(): ReactElement { > - {t("routes.AliasesRoute.emptyState.title")} + {t("emptyState.title")} - - {t("routes.AliasesRoute.emptyState.description")} - + {t("emptyState.description")} diff --git a/src/route-widgets/AuthenticateRoute/NavigationButton.tsx b/src/route-widgets/AuthenticateRoute/NavigationButton.tsx index f7666ab..406cc13 100644 --- a/src/route-widgets/AuthenticateRoute/NavigationButton.tsx +++ b/src/route-widgets/AuthenticateRoute/NavigationButton.tsx @@ -30,11 +30,11 @@ const SECTION_ICON_MAP: Record = { } const SECTION_TEXT_MAP: Record = { - [NavigationSection.Overview]: "components.NavigationButton.overview", - [NavigationSection.Aliases]: "components.NavigationButton.aliases", - [NavigationSection.Reports]: "components.NavigationButton.reports", - [NavigationSection.Settings]: "components.NavigationButton.settings", - [NavigationSection.Admin]: "components.NavigationButton.admin", + [NavigationSection.Overview]: "navigation.overview", + [NavigationSection.Aliases]: "navigation.aliases", + [NavigationSection.Reports]: "navigation.reports", + [NavigationSection.Settings]: "navigation.settings", + [NavigationSection.Admin]: "navigation.admin", } const PATH_SECTION_MAP: Record = { @@ -46,7 +46,7 @@ const PATH_SECTION_MAP: Record = { } export default function NavigationButton({section}: NavigationButtonProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("common") const {handleAnchorClick} = useContext(LockNavigationContext) const location = useLocation() diff --git a/src/route-widgets/CompleteAccountRoute/GenerateEmailReportsForm.tsx b/src/route-widgets/CompleteAccountRoute/GenerateEmailReportsForm.tsx index 291ba68..814bb0e 100644 --- a/src/route-widgets/CompleteAccountRoute/GenerateEmailReportsForm.tsx +++ b/src/route-widgets/CompleteAccountRoute/GenerateEmailReportsForm.tsx @@ -17,7 +17,7 @@ export default function GenerateEmailReportsForm({ onNo, onYes, }: GenerateEmailReportsFormProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["complete-account", "common"]) return ( @@ -37,9 +37,7 @@ export default function GenerateEmailReportsForm({ - {t( - "routes.CompleteAccountRoute.forms.generateReports.title", - )} + {t("forms.askForGeneration.title")} @@ -49,9 +47,7 @@ export default function GenerateEmailReportsForm({ - {t( - "routes.CompleteAccountRoute.forms.generateReports.description", - )} + {t("forms.askForGeneration.description")} @@ -62,9 +58,7 @@ export default function GenerateEmailReportsForm({ @@ -73,9 +67,7 @@ export default function GenerateEmailReportsForm({ color="primary" onClick={onYes} > - {t( - "routes.CompleteAccountRoute.forms.generateReports.continueAction", - )} + {t("general.yesLabel", {ns: "common"})} diff --git a/src/route-widgets/CompleteAccountRoute/PasswordForm.tsx b/src/route-widgets/CompleteAccountRoute/PasswordForm.tsx index 6c90a8a..6c0a929 100644 --- a/src/route-widgets/CompleteAccountRoute/PasswordForm.tsx +++ b/src/route-widgets/CompleteAccountRoute/PasswordForm.tsx @@ -11,7 +11,7 @@ import {Box, InputAdornment} from "@mui/material" import {useMutation} from "@tanstack/react-query" import {AuthContext, PasswordField, SimpleForm} from "~/components" -import {setupEncryptionForUser} from "~/utils" +import {parseFastAPIError, setupEncryptionForUser} from "~/utils" import {useExtensionHandler, useNavigateToNext, useSystemPreferredTheme, useUser} from "~/hooks" import {ServerSettings, ServerUser} from "~/server-types" import {UpdateAccountData, updateAccount} from "~/apis" @@ -27,7 +27,7 @@ interface Form { } export default function PasswordForm({onDone}: PasswordFormProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["complete-account", "common"]) const user = useUser() const theme = useSystemPreferredTheme() const serverSettings = useLoaderData() as ServerSettings @@ -36,17 +36,18 @@ export default function PasswordForm({onDone}: PasswordFormProps): ReactElement const $password = useRef(null) const $passwordConfirmation = useRef(null) const schema = yup.object().shape({ - password: yup.string().required(), + password: yup + .string() + .required() + .label(t("fields.password.label", {ns: "common"})), passwordConfirmation: yup .string() .required() .oneOf( [yup.ref("password"), null], - t( - "routes.CompleteAccountRoute.forms.password.form.passwordConfirm.mustMatchHelperText", - ) as string, + t("fields.passwordConfirmation.errors.mismatch", {ns: "common"}) as string, ) - .label(t("routes.CompleteAccountRoute.forms.password.form.passwordConfirm.label")), + .label(t("fields.passwordConfirmation.label", {ns: "common"})), }) const {_setEncryptionPassword, login} = useContext(AuthContext) @@ -89,7 +90,7 @@ export default function PasswordForm({onDone}: PasswordFormProps): ReactElement }, ) } catch (error) { - setErrors({detail: t("general.defaultError")}) + setErrors(parseFastAPIError(error as AxiosError)) } }, }) @@ -109,11 +110,8 @@ export default function PasswordForm({onDone}: PasswordFormProps): ReactElement
{[ @@ -123,12 +121,8 @@ export default function PasswordForm({onDone}: PasswordFormProps): ReactElement autoFocus id="password" name="password" - label={t( - "routes.CompleteAccountRoute.forms.password.form.password.label", - )} - placeholder={t( - "routes.CompleteAccountRoute.forms.password.form.password.placeholder", - )} + label={t("fields.password.label", {ns: "common"})} + placeholder={t("fields.password.placeholder", {ns: "common"})} autoComplete="new-password" value={formik.values.password} onChange={formik.handleChange} @@ -148,12 +142,10 @@ export default function PasswordForm({onDone}: PasswordFormProps): ReactElement fullWidth id="passwordConfirmation" name="passwordConfirmation" - label={t( - "routes.CompleteAccountRoute.forms.password.form.passwordConfirm.label", - )} - placeholder={t( - "routes.CompleteAccountRoute.forms.password.form.passwordConfirm.placeholder", - )} + label={t("fields.passwordConfirmation.label", {ns: "common"})} + placeholder={t("fields.passwordConfirmation.placeholder", { + ns: "common", + })} value={formik.values.passwordConfirmation} onChange={formik.handleChange} disabled={formik.isSubmitting} diff --git a/src/route-widgets/CreateReservedAliasRoute/AliasExplanation.tsx b/src/route-widgets/CreateReservedAliasRoute/AliasExplanation.tsx index 6e538ab..1d5f199 100644 --- a/src/route-widgets/CreateReservedAliasRoute/AliasExplanation.tsx +++ b/src/route-widgets/CreateReservedAliasRoute/AliasExplanation.tsx @@ -14,7 +14,7 @@ export interface AliasExplanationProps { } export default function AliasExplanation({local, emails}: AliasExplanationProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("admin-reserved-aliases") const theme = useTheme() const serverSettings = useLoaderData() as ServerSettings @@ -37,7 +37,7 @@ export default function AliasExplanation({local, emails}: AliasExplanationProps) - {t("routes.AdminRoute.forms.reservedAliases.explanation.step1")} + {t("createNew.explanation.step1")} @@ -49,7 +49,7 @@ export default function AliasExplanation({local, emails}: AliasExplanationProps) - {t("routes.AdminRoute.forms.reservedAliases.explanation.step2")} + {t("createNew.explanation.step2")} @@ -76,7 +76,7 @@ export default function AliasExplanation({local, emails}: AliasExplanationProps) - {t("routes.AdminRoute.forms.reservedAliases.explanation.step4")} + {t("createNew.explanation.step4")} diff --git a/src/route-widgets/CreateReservedAliasRoute/UsersSelectField.tsx b/src/route-widgets/CreateReservedAliasRoute/UsersSelectField.tsx index 9fb60f9..4c0609a 100644 --- a/src/route-widgets/CreateReservedAliasRoute/UsersSelectField.tsx +++ b/src/route-widgets/CreateReservedAliasRoute/UsersSelectField.tsx @@ -37,7 +37,7 @@ export default function UsersSelectField({ error, ...props }: UsersSelectFieldProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("admin-reserved-aliases") const meUser = useUser() const {data: {users} = {}} = useQuery( ["getAdminUsers"], @@ -49,7 +49,7 @@ export default function UsersSelectField({ return ( - {t("routes.AdminRoute.forms.reservedAliases.fields.users.label")} + {t("fields.users.label")} {...props} @@ -98,7 +98,7 @@ export default function UsersSelectField({ name="users" id="users" error={error} - label={t("routes.AdminRoute.forms.reservedAliases.fields.users.label")} + label={t("fields.users.label")} > {users ? ( users.map(user => ( @@ -108,12 +108,9 @@ export default function UsersSelectField({ primary={(() => { // Check if user is me if (user.id === meUser.id) { - return t( - "routes.AdminRoute.forms.reservedAliases.fields.users.me", - { - email: user.email.address, - }, - ) + return t("fields.users.me", { + email: user.email.address, + }) } return user.email.address @@ -122,7 +119,7 @@ export default function UsersSelectField({ )) ) : ( - {t("general.loading")} + {t("general.loading", {ns: "common"})} )} {helperText ? {helperText} : null} diff --git a/src/route-widgets/GlobalSettingsRoute/AliasPercentageAmount.tsx b/src/route-widgets/GlobalSettingsRoute/AliasPercentageAmount.tsx index 117a1da..6780bc1 100644 --- a/src/route-widgets/GlobalSettingsRoute/AliasPercentageAmount.tsx +++ b/src/route-widgets/GlobalSettingsRoute/AliasPercentageAmount.tsx @@ -14,14 +14,14 @@ export default function AliasesPercentageAmount({ length, percentage, }: AliasPercentageAmountProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("admin-global-settings") const amount = Math.floor(Math.pow(characters.length, length) * percentage) return ( - {t("routes.AdminRoute.settings.randomAliasesIncreaseExplanation", { + {t("randomAliasesIncreaseExplanation", { originalLength: length, increasedLength: length + 1, amount, diff --git a/src/route-widgets/GlobalSettingsRoute/RandomAliasGenerator.tsx b/src/route-widgets/GlobalSettingsRoute/RandomAliasGenerator.tsx index 37cb4b6..d2be605 100644 --- a/src/route-widgets/GlobalSettingsRoute/RandomAliasGenerator.tsx +++ b/src/route-widgets/GlobalSettingsRoute/RandomAliasGenerator.tsx @@ -18,7 +18,7 @@ export default function RandomAliasGenerator({ length, }: RandomAliasGeneratorProps): ReactElement { const serverSettings = useLoaderData() as ServerSettings - const {t} = useTranslation() + const {t} = useTranslation("admin-global-settings") const theme = useTheme() const generateLocal = useCallback( @@ -39,7 +39,7 @@ export default function RandomAliasGenerator({ return ( - {t("routes.AdminRoute.settings.randomAliasesPreview.title")} + {t("randomAliasesPreview.title")} @@ -51,9 +51,7 @@ export default function RandomAliasGenerator({ - - {t("routes.AdminRoute.settings.randomAliasesPreview.helperText")} - + {t("randomAliasesPreview.helperText")} ) } diff --git a/src/route-widgets/GlobalSettingsRoute/SettingsDisabled.tsx b/src/route-widgets/GlobalSettingsRoute/SettingsDisabled.tsx index 79a15e3..692f9f6 100644 --- a/src/route-widgets/GlobalSettingsRoute/SettingsDisabled.tsx +++ b/src/route-widgets/GlobalSettingsRoute/SettingsDisabled.tsx @@ -5,8 +5,7 @@ import {useTranslation} from "react-i18next" import {Container, Grid, Typography} from "@mui/material" export default function SettingsDisabled(): ReactElement { - console.log("asdas") - const {t} = useTranslation() + const {t} = useTranslation("admin-global-settings") return ( @@ -21,16 +20,14 @@ export default function SettingsDisabled(): ReactElement { > - {t("routes.AdminRoute.settings.disabled.title")} + {t("disabled.title")} - - {t("routes.AdminRoute.settings.disabled.description")} - + {t("disabled.description")} diff --git a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx index 81f91d7..948867a 100644 --- a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx +++ b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx @@ -44,7 +44,7 @@ const DEFAULT_POOLS = createPool({ }) export default function SettingsForm({settings, queryKey}: SettingsFormProps) { - const {t} = useTranslation() + const {t} = useTranslation(["admin-global-settings", "common"]) const {showSuccess, showError} = useErrorSuccessSnacks() const validationSchema = yup.object().shape({ @@ -52,50 +52,33 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { .number() .min(1) .max(1_023) - .label(t("routes.AdminRoute.forms.settings.randomEmailIdMinLength.label")), - randomEmailIdChars: yup - .string() - .label(t("routes.AdminRoute.forms.settings.randomEmailIdChars.label")), + .label(t("fields.randomEmailIdMinLength.label")), + randomEmailIdChars: yup.string().label(t("fields.randomEmailIdChars.label")), randomEmailLengthIncreaseOnPercentage: yup .number() .min(0) .max(1) - .label( - t("routes.AdminRoute.forms.settings.randomEmailLengthIncreaseOnPercentage.label"), - ), + .label(t("fields.randomEmailLengthIncreaseOnPercentage.label")), imageProxyStorageLifeTimeInHours: yup .number() - .label(t("routes.AdminRoute.forms.settings.imageProxyStorageLifeTimeInHours.label")), + .label(t("fields.imageProxyStorageLifeTimeInHours.label")), customEmailSuffixLength: yup .number() .min(1) .max(1_023) - .label(t("routes.AdminRoute.forms.settings.customEmailSuffixLength-label")), - customEmailSuffixChars: yup - .string() - .label(t("routes.AdminRoute.forms.settings.customEmailSuffixChars.label")), + .label(t("fields.customEmailSuffixLength.label")), + customEmailSuffixChars: yup.string().label(t("fields.customEmailSuffixChars.label")), userEmailEnableDisposableEmails: yup .boolean() - .label(t("routes.AdminRoute.forms.settings.userEmailEnableDisposableEmails.label")), + .label(t("fields.userEmailEnableDisposableEmails.label")), userEmailEnableOtherRelays: yup .boolean() - .label(t("routes.AdminRoute.forms.settings.userEmailEnableOtherRelays.label")), - enableImageProxy: yup - .boolean() - .label(t("routes.AdminRoute.forms.settings.enableImageProxy.label")), - enableImageProxyStorage: yup - .boolean() - .label(t("routes.AdminRoute.forms.settings.enableImageProxyStorage.label")), - allowStatistics: yup - .boolean() - .label(t("routes.AdminRoute.forms.settings.allowStatistics.label")), - allowAliasDeletion: yup - .boolean() - .label(t("routes.AdminRoute.forms.settings.allowAliasDeletion.label")), - maxAliasesPerUser: yup - .number() - .label(t("routes.AdminRoute.forms.settings.maxAliasesPerUser.label")) - .min(0), + .label(t("fields.userEmailEnableOtherRelays.label")), + enableImageProxy: yup.boolean().label(t("fields.enableImageProxy.label")), + enableImageProxyStorage: yup.boolean().label(t("fields.enableImageProxyStorage.label")), + allowStatistics: yup.boolean().label(t("fields.allowStatistics.label")), + allowAliasDeletion: yup.boolean().label(t("fields.allowAliasDeletion.label")), + maxAliasesPerUser: yup.number().label(t("fields.maxAliasesPerUser.label")).min(0), } as Record) const {mutateAsync} = useMutation< @@ -109,7 +92,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { return } - showSuccess(t("routes.AdminRoute.settings.successMessage")) + showSuccess(t("updatedSuccessfullyMessage")) queryClient.setQueryData>(queryKey, newSettings) }, @@ -144,12 +127,12 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { - {t("routes.AdminRoute.settings.title")} + {t("title")} - {t("routes.AdminRoute.settings.description")} + {t("description")} @@ -160,9 +143,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { - {t( - "routes.AdminRoute.forms.settings.imageProxyStorageLifeTimeInHours.unit", - { - count: - formik.values - .imageProxyStorageLifeTimeInHours || 0, - }, - )} + {t("fields.imageProxyStorageLifeTimeInHours.unit", { + count: + formik.values + .imageProxyStorageLifeTimeInHours || 0, + })} ), }} @@ -421,9 +373,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { /> } disabled={formik.isSubmitting} - label={t( - "routes.AdminRoute.forms.settings.userEmailEnableDisposableEmails.label", - )} + label={t("fields.userEmailEnableDisposableEmails.label")} /> {(formik.touched.userEmailEnableDisposableEmails && formik.errors.userEmailEnableDisposableEmails) || - t( - "routes.AdminRoute.forms.settings.userEmailEnableDisposableEmails.description", - )} + t("fields.userEmailEnableDisposableEmails.description")} @@ -450,9 +398,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { /> } disabled={formik.isSubmitting} - label={t( - "routes.AdminRoute.forms.settings.userEmailEnableOtherRelays.label", - )} + label={t("fields.userEmailEnableOtherRelays.label")} /> {(formik.touched.userEmailEnableOtherRelays && formik.errors.userEmailEnableOtherRelays) || - t( - "routes.AdminRoute.forms.settings.userEmailEnableOtherRelays.description", - )} + t("fields.userEmailEnableOtherRelays.description")} @@ -481,9 +425,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { /> } disabled={formik.isSubmitting} - label={t( - "routes.AdminRoute.forms.settings.enableImageProxy.label", - )} + label={t("fields.enableImageProxy.label")} /> {(formik.touched.enableImageProxy && formik.errors.enableImageProxy) || - t( - "routes.AdminRoute.forms.settings.enableImageProxy.description", - )} + t("fields.enableImageProxy.description")} @@ -513,9 +453,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { /> } disabled={formik.isSubmitting} - label={t( - "routes.AdminRoute.forms.settings.enableImageProxyStorage.label", - )} + label={t("fields.enableImageProxyStorage.label")} /> {(formik.touched.enableImageProxyStorage && formik.errors.enableImageProxyStorage) || - t( - "routes.AdminRoute.forms.settings.enableImageProxyStorage.description", - )} + t("fields.enableImageProxyStorage.description")} @@ -545,9 +481,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { /> } disabled={formik.isSubmitting} - label={t( - "routes.AdminRoute.forms.settings.allowStatistics.label", - )} + label={t("fields.allowStatistics.label")} /> {(formik.touched.allowStatistics && formik.errors.allowStatistics) || - t( - "routes.AdminRoute.forms.settings.allowStatistics.description", - )} + t("fields.allowStatistics.description")} @@ -574,9 +506,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { /> } disabled={formik.isSubmitting} - label={t( - "routes.AdminRoute.forms.settings.allowAliasDeletion.label", - )} + label={t("fields.allowAliasDeletion.label")} /> {(formik.touched.allowAliasDeletion && formik.errors.allowAliasDeletion) || - t( - "routes.AdminRoute.forms.settings.allowAliasDeletion.description", - )} + t("fields.allowAliasDeletion.description")} @@ -608,7 +536,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { formik.submitForm() }} > - {t("routes.AdminRoute.settings.resetLabel")} + {t("general.resetLabel", {ns: "common"})} @@ -618,7 +546,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { type="submit" startIcon={} > - {t("general.saveLabel")} + {t("general.saveLabel", {ns: "common"})} diff --git a/src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx b/src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx index 8cc1c31..94ffa78 100644 --- a/src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx +++ b/src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx @@ -55,7 +55,7 @@ export default function ConfirmCodeForm({ }: ConfirmCodeFormProps): ReactElement { const settings = useLoaderData() as ServerSettings const expirationTime = isDev ? 70 : settings.emailLoginExpirationInSeconds - const {t} = useTranslation() + const {t} = useTranslation(["login", "common"]) const requestDate = useMemo(() => new Date(), []) const [isExpiringSoon, setIsExpiringSoon] = useState(false) @@ -67,7 +67,7 @@ export default function ConfirmCodeForm({ .max(settings.emailLoginTokenLength) .test( "chars", - t("routes.LoginRoute.forms.confirmCode.form.code.errors.invalidChars") as string, + t("forms.confirmCode.fields.code.errors.invalidChars") as string, code => { if (!code) { return false @@ -78,7 +78,7 @@ export default function ConfirmCodeForm({ return code.split("").every(char => chars.includes(char)) }, ) - .label(t("routes.LoginRoute.forms.confirmCode.form.code.label")), + .label(t("forms.confirmCode.fields.code.label")), }) const {mutateAsync} = useMutation< @@ -165,7 +165,7 @@ export default function ConfirmCodeForm({ > - {t("routes.LoginRoute.forms.confirmCode.title")} + {t("forms.confirmCode.title")} @@ -175,7 +175,7 @@ export default function ConfirmCodeForm({ - {t("routes.LoginRoute.forms.confirmCode.description")} + {t("forms.confirmCode.description")} @@ -196,7 +196,7 @@ export default function ConfirmCodeForm({ } labelPlacement="end" label={t( - "routes.LoginRoute.forms.confirmCode.allowLoginFromDifferentDevices", + "forms.confirmCode.allowLoginFromDifferentDevices", )} /> @@ -206,9 +206,7 @@ export default function ConfirmCodeForm({ fullWidth name="code" id="code" - label={t( - "routes.LoginRoute.forms.confirmCode.form.code.label", - )} + label={t("forms.confirmCode.fields.code.label")} value={formik.values.code} onChange={event => { formik.setFieldValue( @@ -250,7 +248,7 @@ export default function ConfirmCodeForm({ type="submit" startIcon={} > - {t("routes.LoginRoute.forms.confirmCode.continueAction")} + {t("forms.confirmCode.continueActionLabel")} @@ -260,7 +258,7 @@ export default function ConfirmCodeForm({ - {t("routes.LoginRoute.forms.confirmCode.expiringSoon")} + {t("forms.confirmCode.expiringSoonWarning")} diff --git a/src/route-widgets/LoginRoute/ConfirmCodeForm/ResendMailButton.tsx b/src/route-widgets/LoginRoute/ConfirmCodeForm/ResendMailButton.tsx index 3d52a46..152a1ae 100644 --- a/src/route-widgets/LoginRoute/ConfirmCodeForm/ResendMailButton.tsx +++ b/src/route-widgets/LoginRoute/ConfirmCodeForm/ResendMailButton.tsx @@ -19,8 +19,8 @@ export default function ResendMailButton({ email, sameRequestToken, }: ResendMailButtonProps): ReactElement { + const {t} = useTranslation("components") const settings = useLoaderData() as ServerSettings - const {t} = useTranslation() const mutation = useMutation(() => resendEmailLoginCode({ @@ -37,7 +37,7 @@ export default function ResendMailButton({ startIcon={} onClick={() => mutate()} > - {t("components.ResendMailButton.label")} + {t("ResendMailButton.label")} diff --git a/src/route-widgets/LoginRoute/ConfirmFromDifferentDevice.tsx b/src/route-widgets/LoginRoute/ConfirmFromDifferentDevice.tsx index 5f0181e..c05bf8e 100644 --- a/src/route-widgets/LoginRoute/ConfirmFromDifferentDevice.tsx +++ b/src/route-widgets/LoginRoute/ConfirmFromDifferentDevice.tsx @@ -22,7 +22,7 @@ export default function ConfirmFromDifferentDevice({ token, onConfirm, }: ConfirmFromDifferentDeviceProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["login"]) const {mutate, isLoading, isError} = useMutation( () => verifyLoginWithEmail({ @@ -51,14 +51,12 @@ export default function ConfirmFromDifferentDevice({ - {t("routes.LoginRoute.forms.confirmFromDifferentDevice.title")} + {t("forms.confirmFromDifferentDevice.title")} - {t( - "routes.LoginRoute.forms.confirmFromDifferentDevice.description", - )} + {t("forms.confirmFromDifferentDevice.description")} diff --git a/src/route-widgets/LoginRoute/EmailForm.tsx b/src/route-widgets/LoginRoute/EmailForm.tsx index 69f07f6..5d87e7f 100644 --- a/src/route-widgets/LoginRoute/EmailForm.tsx +++ b/src/route-widgets/LoginRoute/EmailForm.tsx @@ -24,7 +24,7 @@ interface Form { } export default function EmailForm({onLogin, email: preFilledEmail}: EmailFormProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["login", "common"]) const $password = useRef(null) const schema = yup.object().shape({ @@ -32,7 +32,7 @@ export default function EmailForm({onLogin, email: preFilledEmail}: EmailFormPro .string() .email() .required() - .label(t("routes.LoginRoute.forms.email.form.email.label")), + .label(t("fields.email.label", {ns: "common"})), }) const {mutateAsync} = useMutation(loginWithEmail, { @@ -63,9 +63,9 @@ export default function EmailForm({onLogin, email: preFilledEmail}: EmailFormPro @@ -77,7 +77,7 @@ export default function EmailForm({onLogin, email: preFilledEmail}: EmailFormPro name="email" id="email" label="Email" - placeholder={t("routes.LoginRoute.forms.email.form.email.placeholder")} + placeholder={t("fields.email.placeholder", {ns: "common"})} inputRef={$password} inputMode="email" value={formik.values.email} diff --git a/src/route-widgets/LoginRoute/OTPForm.tsx b/src/route-widgets/LoginRoute/OTPForm.tsx index 457c21d..a3c88c9 100644 --- a/src/route-widgets/LoginRoute/OTPForm.tsx +++ b/src/route-widgets/LoginRoute/OTPForm.tsx @@ -1,19 +1,21 @@ import * as yup from "yup" import {ReactElement} from "react" -import {useMutation} from "@tanstack/react-query" -import {ServerUser} from "~/server-types" -import {AxiosError} from "axios" -import {verifyOTP} from "~/apis" import {useTranslation} from "react-i18next" import {useFormik} from "formik" +import {BsPhone, BsShieldLockFill} from "react-icons/bs" +import {MdChevronRight} from "react-icons/md" +import {Link as RouterLink} from "react-router-dom" +import {AxiosError} from "axios" + +import {useMutation} from "@tanstack/react-query" +import {LoadingButton} from "@mui/lab" +import {Box, Button, Grid, InputAdornment, TextField, Typography} from "@mui/material" + +import {verifyOTP} from "~/apis" import {parseFastAPIError} from "~/utils" import {MultiStepFormElement} from "~/components" -import {Box, Button, Grid, InputAdornment, TextField, Typography} from "@mui/material" -import {BsPhone, BsShieldLockFill} from "react-icons/bs" -import {LoadingButton} from "@mui/lab" -import {MdChevronRight} from "react-icons/md" import {useErrorSuccessSnacks} from "~/hooks" -import {Link as RouterLink} from "react-router-dom" +import {ServerUser} from "~/server-types" interface Form { code: string @@ -30,7 +32,7 @@ export default function OTPForm({ onConfirm, onCodeUnavailable, }: OTPFormProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["login", "common"]) const {showError} = useErrorSuccessSnacks() const {mutateAsync} = useMutation( code => @@ -42,7 +44,7 @@ export default function OTPForm({ onSuccess: onConfirm, onError: error => { if (error.response?.status === 410 || error.response?.status === 404) { - showError(t("routes.LoginRoute.forms.otp.unavailable").toString()) + showError(t("forms.otp.isUnavailable").toString()) onCodeUnavailable() } }, @@ -50,7 +52,10 @@ export default function OTPForm({ ) const schema = yup.object().shape({ - code: yup.string().required().label(t("routes.LoginRoute.forms.otp.code.label")), + code: yup + .string() + .required() + .label(t("fields.2faCode.label", {ns: "common"})), }) const formik = useFormik({ @@ -81,7 +86,7 @@ export default function OTPForm({ > - {t("routes.LoginRoute.forms.otp.title")} + {t("forms.otp.title")} @@ -91,7 +96,7 @@ export default function OTPForm({ - {t("routes.LoginRoute.forms.otp.description")} + {t("forms.otp.description")} @@ -101,7 +106,8 @@ export default function OTPForm({ fullWidth name="code" id="code" - label={t("routes.LoginRoute.forms.otp.code.label")} + placeholder={t("fields.2faCode.placeholder", {ns: "common"})} + label={t("fields.2faCode.label", {ns: "common"})} value={formik.values.code} onChange={formik.handleChange} disabled={formik.isSubmitting} @@ -125,12 +131,12 @@ export default function OTPForm({ type="submit" startIcon={} > - {t("routes.LoginRoute.forms.otp.submit")} + {t("forms.otp.continueActionLabel")} diff --git a/src/route-widgets/ReportDetailRoute/ExpandedUrlsListItem.tsx b/src/route-widgets/ReportDetailRoute/ExpandedUrlsListItem.tsx index d366d4a..43b48bd 100644 --- a/src/route-widgets/ReportDetailRoute/ExpandedUrlsListItem.tsx +++ b/src/route-widgets/ReportDetailRoute/ExpandedUrlsListItem.tsx @@ -12,13 +12,13 @@ export interface ExpandedUrlsListItemProps { } export default function ExpandedUrlsListItem({urls}: ExpandedUrlsListItemProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("reports") return ( } - title={t("routes.ReportDetailRoute.sections.trackers.results.expandedUrls.text", { + title={t("sections.trackers.results.expandedUrls.text", { count: urls.length, })} > diff --git a/src/route-widgets/ReportDetailRoute/ProxiedImagesListItem.tsx b/src/route-widgets/ReportDetailRoute/ProxiedImagesListItem.tsx index 0a03a86..f5681e0 100644 --- a/src/route-widgets/ReportDetailRoute/ProxiedImagesListItem.tsx +++ b/src/route-widgets/ReportDetailRoute/ProxiedImagesListItem.tsx @@ -17,14 +17,14 @@ export interface ProxiedImagesListItemProps { } export default function ProxiedImagesListItem({images}: ProxiedImagesListItemProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("reports") const serverSettings = useLoaderData() as ServerSettings return ( } - title={t("routes.ReportDetailRoute.sections.trackers.results.proxiedImages.text", { + title={t("sections.trackers.results.proxiedImages.text", { count: images.length, })} > @@ -62,11 +62,11 @@ export default function ProxiedImagesListItem({images}: ProxiedImagesListItemPro ) ) { return t( - "routes.ReportDetailRoute.sections.trackers.results.proxiedImages.status.isStored", + "sections.trackers.results.proxiedImages.status.isStored", ) } else { return t( - "routes.ReportDetailRoute.sections.trackers.results.proxiedImages.status.isProxying", + "sections.trackers.results.proxiedImages.status.isProxying", ) } })()} diff --git a/src/route-widgets/ReportDetailRoute/SinglePixelImageTrackersListItem.tsx b/src/route-widgets/ReportDetailRoute/SinglePixelImageTrackersListItem.tsx index c02e411..decc661 100644 --- a/src/route-widgets/ReportDetailRoute/SinglePixelImageTrackersListItem.tsx +++ b/src/route-widgets/ReportDetailRoute/SinglePixelImageTrackersListItem.tsx @@ -14,7 +14,7 @@ export interface SinglePixelImageTrackersListItemProps { export default function SinglePixelImageTrackersListItem({ images, }: SinglePixelImageTrackersListItemProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("reports") const imagesPerTracker = images.reduce((acc, value) => { acc[value.trackerName] = [...(acc[value.trackerName] || []), value] @@ -26,7 +26,7 @@ export default function SinglePixelImageTrackersListItem({ } - title={t("routes.ReportDetailRoute.sections.trackers.results.imageTrackers.text", { + title={t("sections.trackers.results.imageTrackers.text", { count: images.length, })} > diff --git a/src/route-widgets/ReportsRoute/EmptyStateScreen.tsx b/src/route-widgets/ReportsRoute/EmptyStateScreen.tsx index eb1511c..640cb41 100644 --- a/src/route-widgets/ReportsRoute/EmptyStateScreen.tsx +++ b/src/route-widgets/ReportsRoute/EmptyStateScreen.tsx @@ -5,23 +5,21 @@ import {MdTextSnippet} from "react-icons/md" import {Container, Grid, Typography} from "@mui/material" export default function EmptyStateScreen(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("reports") return ( - {t("routes.ReportsRoute.emptyState.title")} + {t("emptyState.title")} - - {t("routes.ReportsRoute.emptyState.description")} - + {t("emptyState.description")} diff --git a/src/route-widgets/ReportsRoute/ReportInformationItem.tsx b/src/route-widgets/ReportsRoute/ReportInformationItem.tsx index aacbc66..2d423dc 100644 --- a/src/route-widgets/ReportsRoute/ReportInformationItem.tsx +++ b/src/route-widgets/ReportsRoute/ReportInformationItem.tsx @@ -11,18 +11,16 @@ export interface ReportInformationItemProps { } export default function ReportInformationItem({report}: ReportInformationItemProps): ReactElement { + const {t} = useTranslation("reports") const navigate = useNavigate() - const {t} = useTranslation() return ( navigate(`/reports/${report.id}`)}> {t("relations.report.emailMeta.emptySubject")} - ) + report.messageDetails.content.subject || {t("emailMeta.emptySubject")} } - secondary={t("relations.report.emailMeta.flow", { + secondary={t("emailMeta.flow", { from: report.messageDetails.meta.from, to: report.messageDetails.meta.to, })} diff --git a/src/route-widgets/ReservedAliasDetailRoute/AdminUserPicker.tsx b/src/route-widgets/ReservedAliasDetailRoute/AdminUserPicker.tsx index a33e68f..b857356 100644 --- a/src/route-widgets/ReservedAliasDetailRoute/AdminUserPicker.tsx +++ b/src/route-widgets/ReservedAliasDetailRoute/AdminUserPicker.tsx @@ -17,7 +17,7 @@ export default function AdminUserPicker({ onPick, alreadyPicked, }: AdminUserPickerProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("admin-reserved-aliases") const meUser = useUser() const {data: {users: availableUsers} = {}} = useQuery( ["getAdminUsers"], @@ -54,7 +54,7 @@ export default function AdminUserPicker({ {users.map(user => ( {user.id === meUser?.id - ? t("routes.AdminRoute.forms.reservedAliases.fields.users.me", { + ? t("fields.users.me", { email: user.email.address, }) : user.email.address} diff --git a/src/route-widgets/ReservedAliasDetailRoute/AliasActivationSwitch.tsx b/src/route-widgets/ReservedAliasDetailRoute/AliasActivationSwitch.tsx index 6057d11..e2fc46c 100644 --- a/src/route-widgets/ReservedAliasDetailRoute/AliasActivationSwitch.tsx +++ b/src/route-widgets/ReservedAliasDetailRoute/AliasActivationSwitch.tsx @@ -22,7 +22,7 @@ export default function AliasActivationSwitch({ isActive, queryKey, }: AliasActivationSwitch): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("common") const {showError, showSuccess} = useErrorSuccessSnacks() const {isLoading, mutateAsync} = useMutation< ReservedAlias, @@ -76,8 +76,8 @@ export default function AliasActivationSwitch({ showSuccess( isActive - ? t("relations.alias.mutations.success.aliasChangedToDisabled") - : t("relations.alias.mutations.success.aliasChangedToEnabled"), + ? t("messages.alias.changedToDisabled") + : t("messages.alias.changedToEnabled"), ) } catch {} }} diff --git a/src/route-widgets/ReservedAliasDetailRoute/AliasUsersList.tsx b/src/route-widgets/ReservedAliasDetailRoute/AliasUsersList.tsx index 75c549a..524f921 100644 --- a/src/route-widgets/ReservedAliasDetailRoute/AliasUsersList.tsx +++ b/src/route-widgets/ReservedAliasDetailRoute/AliasUsersList.tsx @@ -40,7 +40,7 @@ interface Form { } export default function AliasUsersList({users, queryKey, id}: AliasUsersListProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["admin-reserved-aliases", "common"]) const {showError, showSuccess} = useErrorSuccessSnacks() const {mutateAsync} = useMutation< ReservedAlias, @@ -73,7 +73,7 @@ export default function AliasUsersList({users, queryKey, id}: AliasUsersListProp } }, onSuccess: async newAlias => { - showSuccess(t("relations.alias.mutations.success.aliasUpdated")) + showSuccess(t("messages.alias.updated", {ns: "common"})) await queryClient.cancelQueries(queryKey) @@ -102,7 +102,7 @@ export default function AliasUsersList({users, queryKey, id}: AliasUsersListProp }), }), ) - .label(t("routes.AliasDetailRoute.sections.users.fields.users.label")), + .label(t("fields.users.label")), }) const initialValues: Form = { users: users, @@ -126,7 +126,7 @@ export default function AliasUsersList({users, queryKey, id}: AliasUsersListProp - {t("routes.ReservedAliasDetailRoute.sections.users.title")} + {t("fields.users.label")} diff --git a/src/route-widgets/ReservedAliasesRoute/EmptyStateScreen.tsx b/src/route-widgets/ReservedAliasesRoute/EmptyStateScreen.tsx index ee300e9..b3299c8 100644 --- a/src/route-widgets/ReservedAliasesRoute/EmptyStateScreen.tsx +++ b/src/route-widgets/ReservedAliasesRoute/EmptyStateScreen.tsx @@ -1,11 +1,11 @@ import {ReactElement} from "react" import {useTranslation} from "react-i18next" - -import {Container, Grid, Typography} from "@mui/material" import {BsStarFill} from "react-icons/bs" +import {Container, Grid, Typography} from "@mui/material" + export default function EmptyStateScreen(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("admin-reserved-aliases") return ( @@ -20,16 +20,14 @@ export default function EmptyStateScreen(): ReactElement { > - {t("routes.ReservedAliasesRoute.emptyState.title")} + {t("emptyState.title")} - - {t("routes.ReservedAliasesRoute.emptyState.description")} - + {t("emptyState.description")} diff --git a/src/route-widgets/Settings2FARoute/Delete2FA.tsx b/src/route-widgets/Settings2FARoute/Delete2FA.tsx index eb0c2a0..b1c3ea9 100644 --- a/src/route-widgets/Settings2FARoute/Delete2FA.tsx +++ b/src/route-widgets/Settings2FARoute/Delete2FA.tsx @@ -17,11 +17,11 @@ export interface Delete2FAProps { } export default function Delete2FA({onSuccess}: Delete2FAProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("settings-2fa") const {showSuccess, showError} = useErrorSuccessSnacks() const {mutate} = useMutation(delete2FA, { onSuccess: () => { - showSuccess(t("routes.SettingsRoute.2fa.delete.success")) + showSuccess(t("delete.success")) onSuccess() }, onError: showError, @@ -36,7 +36,7 @@ export default function Delete2FA({onSuccess}: Delete2FAProps): ReactElement { case "showAction": return ( ) @@ -45,7 +45,7 @@ export default function Delete2FA({onSuccess}: Delete2FAProps): ReactElement { @@ -53,7 +53,7 @@ export default function Delete2FA({onSuccess}: Delete2FAProps): ReactElement { onClick={() => setView("askRecoveryCode")} startIcon={} > - {t("routes.SettingsRoute.2fa.delete.askType.recoveryCode")} + {t("delete.steps.askType.recoveryCode")} @@ -65,7 +65,7 @@ export default function Delete2FA({onSuccess}: Delete2FAProps): ReactElement { setValue(e.target.value)} /> @@ -76,7 +76,7 @@ export default function Delete2FA({onSuccess}: Delete2FAProps): ReactElement { variant="contained" startIcon={} > - {t("routes.SettingsRoute.2fa.delete.submit")} + {t("delete.continueActionLabel")} @@ -88,7 +88,7 @@ export default function Delete2FA({onSuccess}: Delete2FAProps): ReactElement { setValue(e.target.value)} /> @@ -99,7 +99,7 @@ export default function Delete2FA({onSuccess}: Delete2FAProps): ReactElement { variant="contained" startIcon={} > - {t("routes.SettingsRoute.2fa.delete.submit")} + {t("delete.continueActionLabel")} diff --git a/src/route-widgets/Settings2FARoute/Setup2FA.tsx b/src/route-widgets/Settings2FARoute/Setup2FA.tsx index 327e772..2b9a311 100644 --- a/src/route-widgets/Settings2FARoute/Setup2FA.tsx +++ b/src/route-widgets/Settings2FARoute/Setup2FA.tsx @@ -18,7 +18,7 @@ export interface Setup2FAProps { } export default function Setup2FA({onSuccess}: Setup2FAProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("settings-2fa") const {showError} = useErrorSuccessSnacks() const { @@ -33,9 +33,7 @@ export default function Setup2FA({onSuccess}: Setup2FAProps): ReactElement { return ( - - {t("routes.SettingsRoute.2fa.setup.description")} - + {t("setup.description")} {secret ? ( @@ -55,7 +53,7 @@ export default function Setup2FA({onSuccess}: Setup2FAProps): ReactElement { variant="contained" startIcon={} > - {t("routes.SettingsRoute.2fa.setup.setupLabel")} + {t("setup.setupLabel")} )} diff --git a/src/route-widgets/Settings2FARoute/VerifyOTPForm.tsx b/src/route-widgets/Settings2FARoute/VerifyOTPForm.tsx index 87082e1..9b395dd 100644 --- a/src/route-widgets/Settings2FARoute/VerifyOTPForm.tsx +++ b/src/route-widgets/Settings2FARoute/VerifyOTPForm.tsx @@ -41,7 +41,7 @@ export default function Settings2FARoute({ onRecreateRequired, secret, }: VerifyOTPFormProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["settings-2fa", "common"]) const {showSuccess, showError} = useErrorSuccessSnacks() const user = useUser() const theme = useTheme() @@ -52,16 +52,19 @@ export default function Settings2FARoute({ code: yup .string() .required() + .matches( + /^[0-9]+$/, + t("fields.2faCode.errors.shouldOnlyBeDigits", {ns: "common"}) as string, + ) .length(6) - .matches(/^[0-9]+$/, t("routes.SettingsRoute.2fa.setup.code.onlyDigits").toString()) - .label(t("routes.SettingsRoute.2fa.setup.code.label")), + .label(t("fields.2faCode.label", {ns: "common"})), }) const {mutateAsync} = useMutation(verify2FASetup, { onSuccess: () => setShowRecoveryCodes(true), onError: error => { if (error.response?.status === 409 || error.response?.status === 410) { - showError(t("routes.SettingsRoute.2fa.setup.expired").toString()) + showError(t("setup.codeExpired").toString()) onRecreateRequired() } else { showError(error) @@ -107,7 +110,7 @@ export default function Settings2FARoute({ error={!!formik.errors.code} helperText={formik.errors.code} name="code" - label={t("routes.SettingsRoute.2fa.setup.code.label")} + label={t("fields.2faCode.label", {ns: "common"})} disabled={formik.isSubmitting} InputProps={{ startAdornment: ( @@ -125,7 +128,7 @@ export default function Settings2FARoute({ variant="contained" loading={formik.isSubmitting} > - {t("routes.SettingsRoute.2fa.setup.submit")} + {t("setup.continueActionLabel")} @@ -133,7 +136,7 @@ export default function Settings2FARoute({ - {t("routes.SettingsRoute.2fa.setup.recoveryCodes.title")} + {t("setup.recoveryCodes.title")} {code}

))} - - {t("routes.SettingsRoute.2fa.setup.recoveryCodes.description")} - + {t("setup.recoveryCodes.description")}
diff --git a/src/route-widgets/SignupRoute/EmailForm/DetectEmailAutofillService.tsx b/src/route-widgets/SignupRoute/EmailForm/DetectEmailAutofillService.tsx index dfb2e81..7cf4284 100644 --- a/src/route-widgets/SignupRoute/EmailForm/DetectEmailAutofillService.tsx +++ b/src/route-widgets/SignupRoute/EmailForm/DetectEmailAutofillService.tsx @@ -12,6 +12,7 @@ import { DialogTitle, Grid, } from "@mui/material" +import {useTranslation} from "react-i18next" export interface DetectEmailAutofillServiceProps { domains: string[] @@ -32,6 +33,8 @@ const STORAGE_KEY = "has-shown-email-autofill-service" export default function DetectEmailAutofillService({ domains, }: DetectEmailAutofillServiceProps): ReactElement { + const {t} = useTranslation("relay-service-detected") + const $hasDetected = useRef(false) const [type, setType] = useState(null) @@ -100,19 +103,14 @@ export default function DetectEmailAutofillService({ return ( setType(null)}> - Email relay service detected + {t("title")} - - We detected that you are using an email relay service to sign up. This - KleckRelay instance does not support relaying to another email relay - service. You can either choose a different instance or sign up with a - different email address. - + {t("description")} - Detected email relay: + {t("detectedExplanation")} {TYPE_NAME_MAP[type!]} @@ -121,7 +119,7 @@ export default function DetectEmailAutofillService({ diff --git a/src/route-widgets/SignupRoute/EmailForm/EmailForm.tsx b/src/route-widgets/SignupRoute/EmailForm/EmailForm.tsx index 27b544f..48d686b 100644 --- a/src/route-widgets/SignupRoute/EmailForm/EmailForm.tsx +++ b/src/route-widgets/SignupRoute/EmailForm/EmailForm.tsx @@ -27,7 +27,7 @@ interface Form { } export default function EmailForm({onSignUp, serverSettings}: EmailFormProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["signup", "common"]) const $password = useRef(null) const schema = yup.object().shape({ @@ -35,7 +35,7 @@ export default function EmailForm({onSignUp, serverSettings}: EmailFormProps): R .string() .email() .required() - .label(t("routes.SignupRoute.forms.email.form.email.label")), + .label(t("fields.email.label", {ns: "common"})), }) const {mutateAsync} = useMutation(signup, { @@ -53,13 +53,13 @@ export default function EmailForm({onSignUp, serverSettings}: EmailFormProps): R if (isDisposable) { setErrors({ - email: "Disposable email addresses are not allowed", + email: t("fields.email.errors.disposable", {ns: "common"}), }) return } } catch { setErrors({ - detail: "An error occurred", + detail: t("messages.errors.unknown", {ns: "common"}), }) return } @@ -82,9 +82,9 @@ export default function EmailForm({onSignUp, serverSettings}: EmailFormProps): R
@@ -95,10 +95,8 @@ export default function EmailForm({onSignUp, serverSettings}: EmailFormProps): R autoFocus name="email" id="email" - label={t("routes.SignupRoute.forms.email.form.email.label")} - placeholder={t( - "routes.SignupRoute.forms.email.form.email.placeholder", - )} + label={t("fields.email.label", {ns: "common"})} + placeholder={t("fields.email.placeholder", {ns: "common"})} inputMode="email" inputRef={$password} value={formik.values.email} diff --git a/src/route-widgets/SignupRoute/YouGotMail/ResendMailButton.tsx b/src/route-widgets/SignupRoute/YouGotMail/ResendMailButton.tsx index a6426dc..81f1451 100644 --- a/src/route-widgets/SignupRoute/YouGotMail/ResendMailButton.tsx +++ b/src/route-widgets/SignupRoute/YouGotMail/ResendMailButton.tsx @@ -19,7 +19,7 @@ export default function ResendMailButton({ email, onEmailAlreadyVerified, }: ResendMailButtonProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("components") const settings = useLoaderData() as ServerSettings const mutation = useMutation( @@ -41,7 +41,7 @@ export default function ResendMailButton({ startIcon={} onClick={() => mutate()} > - {t("components.ResendMailButton.label")} + {t("ResendMailButton.label")} diff --git a/src/route-widgets/SignupRoute/YouGotMail/YouGotMail.tsx b/src/route-widgets/SignupRoute/YouGotMail/YouGotMail.tsx index c01de46..bc94bbd 100644 --- a/src/route-widgets/SignupRoute/YouGotMail/YouGotMail.tsx +++ b/src/route-widgets/SignupRoute/YouGotMail/YouGotMail.tsx @@ -23,7 +23,7 @@ export interface YouGotMailProps { } export default function YouGotMail({email, onGoBack}: YouGotMailProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["signup", "common"]) const [askToEditEmail, setAskToEditEmail] = useState(false) @@ -43,12 +43,12 @@ export default function YouGotMail({email, onGoBack}: YouGotMailProps): ReactEle > - {t("routes.SignupRoute.forms.mailVerification.title")} + {t("forms.mailVerification.title")} - {t("routes.SignupRoute.forms.mailVerification.description")} + {t("forms.mailVerification.description")} @@ -72,20 +72,18 @@ export default function YouGotMail({email, onGoBack}: YouGotMailProps): ReactEle - - {t("routes.SignupRoute.forms.mailVerification.editEmail.title")} - + {t("forms.mailVerification.editEmail.title")} - {t("routes.SignupRoute.forms.mailVerification.editEmail.description")} + {t("forms.mailVerification.editEmail.description")} diff --git a/src/routes/AdminRoute.tsx b/src/routes/AdminRoute.tsx index 994dc15..f87f3f2 100644 --- a/src/routes/AdminRoute.tsx +++ b/src/routes/AdminRoute.tsx @@ -11,7 +11,7 @@ import {useNavigateToNext, useUser} from "~/hooks" import ServerStatus from "~/route-widgets/AdminRoute/ServerStatus" export default function AdminRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("admin") const navigateToNext = useNavigateToNext() const user = useUser() @@ -22,20 +22,20 @@ export default function AdminRoute(): ReactElement { }, [user.isAdmin, navigateToNext]) return ( - + - + - + diff --git a/src/routes/AliasDetailRoute.tsx b/src/routes/AliasDetailRoute.tsx index ce85126..24804c8 100644 --- a/src/routes/AliasDetailRoute.tsx +++ b/src/routes/AliasDetailRoute.tsx @@ -25,7 +25,7 @@ import ChangeAliasActivationStatusSwitch from "~/route-widgets/AliasDetailRoute/ import decryptAliasNotes from "~/apis/helpers/decrypt-alias-notes" export default function AliasDetailRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["aliases", "common"]) const serverSettings = useLoaderData() as ServerSettings const {id: aliasID} = useParams() const {_decryptUsingMasterPassword, encryptionStatus} = useContext(AuthContext) @@ -46,17 +46,17 @@ export default function AliasDetailRoute(): ReactElement { return ( deleteAlias(aliasID!)} - label={t("routes.AliasDetailRoute.actions.delete.label")} - description={t("routes.AliasDetailRoute.actions.delete.description")} - continueLabel={t("routes.AliasDetailRoute.actions.delete.continueAction")} + label={t("actions.delete.label")} + description={t("actions.delete.description")} + continueLabel={t("actions.delete.continueActionLabel")} navigateTo={"/aliases"} - successMessage={t("relations.alias.mutations.success.aliasDeleted")} + successMessage={t("messages.alias.deleted", {ns: "common"})} /> ) } @@ -97,10 +97,7 @@ export default function AliasDetailRoute(): ReactElement { )} , - + , ]} diff --git a/src/routes/AliasesRoute.tsx b/src/routes/AliasesRoute.tsx index 8e74b1d..7bffdfe 100644 --- a/src/routes/AliasesRoute.tsx +++ b/src/routes/AliasesRoute.tsx @@ -36,7 +36,7 @@ enum TypeFilter { } export default function AliasesRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["aliases", "common"]) const [searchValue, setSearchValue] = useState("") const [queryValue, setQueryValue] = useState("") @@ -131,7 +131,7 @@ export default function AliasesRoute(): ReactElement { return ( @@ -146,10 +146,8 @@ export default function AliasesRoute(): ReactElement { setQueryValue(event.target.value) }) }} - label={t("routes.AliasesRoute.pageActions.search.label")} - placeholder={t( - "routes.AliasesRoute.pageActions.search.placeholder", - )} + label={t("fields.search.label", {ns: "common"})} + placeholder={t("pageActions.search.placeholder")} id="search" InputProps={{ startAdornment: ( @@ -166,9 +164,7 @@ export default function AliasesRoute(): ReactElement { + - - {t("routes.AliasesRoute.isInCopyMode")} + {t("isInCopyMode")} diff --git a/src/routes/AuthenticateRoute.tsx b/src/routes/AuthenticateRoute.tsx index 2bd1787..d75d2c5 100644 --- a/src/routes/AuthenticateRoute.tsx +++ b/src/routes/AuthenticateRoute.tsx @@ -8,7 +8,7 @@ import {Box, Button, Grid} from "@mui/material" import {LanguageButton} from "~/components" export default function AuthenticateRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("common") return ( } > - {t("components.AuthenticateRoute.signup")} + {t("routes.signup")} @@ -46,7 +46,7 @@ export default function AuthenticateRoute(): ReactElement { size="small" startIcon={} > - {t("components.AuthenticateRoute.login")} + {t("routes.login")} diff --git a/src/routes/AuthenticatedRoute.tsx b/src/routes/AuthenticatedRoute.tsx index f04ca7c..fb25b95 100644 --- a/src/routes/AuthenticatedRoute.tsx +++ b/src/routes/AuthenticatedRoute.tsx @@ -12,7 +12,7 @@ import NavigationButton, { } from "~/route-widgets/AuthenticateRoute/NavigationButton" export default function AuthenticatedRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("common") const theme = useTheme() const user = useUser() @@ -90,7 +90,7 @@ export default function AuthenticatedRoute(): ReactElement { to="/auth/logout" startIcon={} > - {t("components.AuthenticatedRoute.logout")} + {t("routes.logout")} diff --git a/src/routes/CompleteAccountRoute.tsx b/src/routes/CompleteAccountRoute.tsx index a361946..e4db796 100644 --- a/src/routes/CompleteAccountRoute.tsx +++ b/src/routes/CompleteAccountRoute.tsx @@ -9,7 +9,7 @@ import GenerateEmailReportsForm from "~/route-widgets/CompleteAccountRoute/Gener import PasswordForm from "~/route-widgets/CompleteAccountRoute/PasswordForm" export default function CompleteAccountRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("complete-account") const {encryptionStatus} = useContext(AuthContext) const navigateToNext = useNavigateToNext() @@ -50,13 +50,11 @@ export default function CompleteAccountRoute(): ReactElement { > - {t("routes.CompleteAccountRoute.forms.available.title")} + {t("alreadyCompleted.title")} - - {t("routes.CompleteAccountRoute.forms.available.description")} - + {t("alreadyCompleted.description")} diff --git a/src/routes/CreateReservedAliasRoute.tsx b/src/routes/CreateReservedAliasRoute.tsx index 7e9f933..9d28a2a 100644 --- a/src/routes/CreateReservedAliasRoute.tsx +++ b/src/routes/CreateReservedAliasRoute.tsx @@ -26,7 +26,7 @@ interface Form { } export default function CreateReservedAliasRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["admin-reserved-aliases", "common"]) const {showSuccess} = useErrorSuccessSnacks() const navigateToNext = useNavigateToNext("/admin/reserved-aliases") const {mutateAsync: createAlias} = useMutation< @@ -35,7 +35,7 @@ export default function CreateReservedAliasRoute(): ReactElement { CreateReservedAliasData >(createReservedAlias, { onSuccess: () => { - showSuccess(t("relations.alias.mutations.success.aliasCreation")) + showSuccess(t("messages.alias.created", {ns: "common"})) navigateToNext() }, }) @@ -44,10 +44,8 @@ export default function CreateReservedAliasRoute(): ReactElement { local: yup .string() .required() - .label(t("routes.AdminRoute.forms.reservedAliases.fields.local.label")), - isActive: yup - .boolean() - .label(t("routes.AdminRoute.forms.reservedAliases.fields.isActive.label")), + .label(t("fields.local.label", {ns: "common"})), + isActive: yup.boolean().label(t("fields.active.label")), // Only store IDs of users, as they provide a reference to the user users: yup .array() @@ -60,7 +58,7 @@ export default function CreateReservedAliasRoute(): ReactElement { }), }), ) - .label(t("routes.AdminRoute.forms.reservedAliases.fields.users.label")), + .label(t("fields.users.label")), }) const formik = useFormik({ validationSchema: schema, @@ -87,12 +85,10 @@ export default function CreateReservedAliasRoute(): ReactElement { {[ @@ -110,9 +106,7 @@ export default function CreateReservedAliasRoute(): ReactElement { }} name="local" id="local" - label={t( - "routes.AdminRoute.forms.reservedAliases.fields.local.label", - )} + label={t("fields.local.label", {ns: "common"})} value={formik.values.local} onChange={formik.handleChange} disabled={formik.isSubmitting} diff --git a/src/routes/EnterDecryptionPassword.tsx b/src/routes/EnterDecryptionPassword.tsx index 5ff8445..17c10a1 100644 --- a/src/routes/EnterDecryptionPassword.tsx +++ b/src/routes/EnterDecryptionPassword.tsx @@ -17,7 +17,7 @@ interface Form { } export default function EnterDecryptionPassword(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["decryption", "common"]) const navigate = useNavigate() const navigateToNext = useNavigateToNext() const user = useUser() @@ -29,7 +29,7 @@ export default function EnterDecryptionPassword(): ReactElement { password: yup .string() .required() - .label(t("components.EnterDecryptionPassword.form.password.label")), + .label(t("fields.password.label", {ns: "common"})), }) const formik = useFormik({ @@ -46,9 +46,7 @@ export default function EnterDecryptionPassword(): ReactElement { } catch (error) { // Password is incorrect setErrors({ - password: t( - "components.EnterDecryptionPassword.form.password.errors.invalidPassword", - ), + password: t("fields.password.errors,invalid", {ns: "common"}), }) } }, @@ -67,10 +65,9 @@ export default function EnterDecryptionPassword(): ReactElement { return ( @@ -81,10 +78,8 @@ export default function EnterDecryptionPassword(): ReactElement { autoFocus name="password" id="password" - label={t("components.EnterDecryptionPassword.form.password.label")} - placeholder={t( - "components.EnterDecryptionPassword.form.password.placeholder", - )} + label={t("fields.password.label", {ns: "common"})} + placeholder={t("fields.password.placeholder", {ns: "common"})} value={formik.values.password} onChange={formik.handleChange} disabled={formik.isSubmitting} diff --git a/src/routes/LogoutRoute.tsx b/src/routes/LogoutRoute.tsx index 0d48c58..29021bc 100644 --- a/src/routes/LogoutRoute.tsx +++ b/src/routes/LogoutRoute.tsx @@ -8,7 +8,7 @@ import {useNavigateToNext} from "~/hooks" import {AuthContext} from "~/components" export default function LogoutRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("logout") const navigateToNext = useNavigateToNext("/auth/login") const {logout} = useContext(AuthContext) @@ -23,16 +23,14 @@ export default function LogoutRoute(): ReactElement { - {t("routes.LogoutRoute.title")} + {t("title")} - - {t("routes.LogoutRoute.description")} - + {t("description")} diff --git a/src/routes/Recover2FARoute.tsx b/src/routes/Recover2FARoute.tsx index 9f88c16..e0e2a1c 100644 --- a/src/routes/Recover2FARoute.tsx +++ b/src/routes/Recover2FARoute.tsx @@ -20,7 +20,7 @@ interface Form { } export default function Recover2FARoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["recover-2fa", "common"]) const {showError, showSuccess} = useErrorSuccessSnacks() const {login} = useContext(AuthContext) const navigate = useNavigate() @@ -29,17 +29,17 @@ export default function Recover2FARoute(): ReactElement { try { const user = await getMe() - showSuccess(t("routes.Recover2FARoute.loggedIn").toString()) + showSuccess(t("events.loggedIn").toString()) login(user) navigate("/aliases") } catch (error) { - showSuccess(t("routes.Recover2FARoute.canLoginNow").toString()) + showSuccess(t("events.canLogInNow").toString()) navigate("/auth/login") } }, onError: error => { if (error.response?.status == 401) { - showError(t("routes.Recover2FARoute.unauthorized").toString()) + showError(t("events.unauthorized").toString()) navigate("/auth/login") } else { showError(error) @@ -48,7 +48,10 @@ export default function Recover2FARoute(): ReactElement { }) const schema = yup.object().shape({ - recoveryCode: yup.string().required().label(t("routes.LoginRoute.forms.otp.code.label")), + recoveryCode: yup + .string() + .required() + .label(t("fields.recoveryCode.label", {ns: "common"})), }) const formik = useFormik({ @@ -76,7 +79,7 @@ export default function Recover2FARoute(): ReactElement { > - {t("routes.Recover2FARoute.title")} + {t("title")} @@ -86,17 +89,17 @@ export default function Recover2FARoute(): ReactElement { - {t("routes.Recover2FARoute.description")} + {t("description")} - + } > - {t("routes.Recover2FARoute.forms.submit")} + {t("continueActionLabel")} diff --git a/src/routes/ReportDetailRoute.tsx b/src/routes/ReportDetailRoute.tsx index 7d4b36f..67a2dd9 100644 --- a/src/routes/ReportDetailRoute.tsx +++ b/src/routes/ReportDetailRoute.tsx @@ -21,7 +21,7 @@ import ProxiedImagesListItem from "~/route-widgets/ReportDetailRoute/ProxiedImag import SinglePixelImageTrackersListItem from "~/route-widgets/ReportDetailRoute/SinglePixelImageTrackersListItem" function ReportDetailRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["reports", "common"]) const params = useParams() const query = useQuery(["get_report", params.id], () => @@ -30,16 +30,16 @@ function ReportDetailRoute(): ReactElement { return ( deleteReport(params.id!)} - label={t("routes.ReportDetailRoute.actions.delete.label")} - description={t("routes.ReportDetailRoute.actions.delete.description")} - continueLabel={t("routes.ReportDetailRoute.actions.delete.continueAction")} + label={t("actions.delete.label")} + description={t("delete.description")} + continueLabel={t("actions.delete.continueActionLabel")} navigateTo={"/reports"} - successMessage={t("relations.report.mutations.success.reportDeleted")} + successMessage={t("messages.report.deleted", {ns: "common"})} /> ) } @@ -52,16 +52,14 @@ function ReportDetailRoute(): ReactElement { {[ {[ { @@ -72,9 +70,7 @@ function ReportDetailRoute(): ReactElement { { (report as DecryptedReportContent) @@ -85,7 +81,7 @@ function ReportDetailRoute(): ReactElement { { @@ -99,9 +95,7 @@ function ReportDetailRoute(): ReactElement { = { [SortingView.GroupByAlias]: , } const SORTING_VIEW_NAME_MAP: Record = createEnumMapFromTranslation( - "routes.ReportsRoute.pageActions.sort", + "pageActions.sort.values", SortingView, ) function ReportsRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("reports") const query = useQuery, AxiosError>(["get_reports"], getReports) @@ -40,13 +40,13 @@ function ReportsRoute(): ReactElement { return ( 0 && ( setSortingView(event.target.value as SortingView)} - label="Sorting" + label={t("pageActions.sort.label")} id="sorting" InputProps={{ startAdornment: ( diff --git a/src/routes/ReservedAliasDetailRoute.tsx b/src/routes/ReservedAliasDetailRoute.tsx index 74cbdbb..2d58bb8 100644 --- a/src/routes/ReservedAliasDetailRoute.tsx +++ b/src/routes/ReservedAliasDetailRoute.tsx @@ -14,7 +14,7 @@ import AliasAddress from "~/route-widgets/AliasDetailRoute/AliasAddress" import AliasUsersList from "~/route-widgets/ReservedAliasDetailRoute/AliasUsersList" export default function ReservedAliasDetailRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["admin-reserved-aliases", "common"]) const params = useParams() const queryKey = ["get_reserved_alias", params.id!] @@ -22,20 +22,16 @@ export default function ReservedAliasDetailRoute(): ReactElement { return ( deleteReservedAlias(params.id!)} - label={t("routes.AdminRoute.reservedAlias.actions.delete.label")} - description={t( - "routes.adminRoute.reservedAlias.actions.delete.description", - )} - continueLabel={t( - "routes.AdminRoute.reservedAlias.actions.delete.continueAction", - )} + label={t("actions.delete.label")} + description={t("actions.delete.description")} + continueLabel={t("actions.delete.continueActionLabel")} navigateTo="/admin/reserved-aliases" - successMessage={t("relations.alias.mutations.success.aliasDeleted")} + successMessage={t("messages.alias.deleted", {ns: "common"})} /> ) } diff --git a/src/routes/ReservedAliasesRoute.tsx b/src/routes/ReservedAliasesRoute.tsx index 4b6ca4a..7060f87 100644 --- a/src/routes/ReservedAliasesRoute.tsx +++ b/src/routes/ReservedAliasesRoute.tsx @@ -22,7 +22,7 @@ import {NoSearchResults, QueryResult, SimplePage} from "~/components" import EmptyStateScreen from "~/route-widgets/ReservedAliasesRoute/EmptyStateScreen" export default function ReservedAliasesRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["admin-reserved-aliases", "common"]) const [showSearch, setShowSearch] = useState(false) const [searchValue, setSearchValue] = useState("") const [queryValue, setQueryValue] = useState("") @@ -44,7 +44,7 @@ export default function ReservedAliasesRoute(): ReactElement { return ( - {t("routes.ReservedAliasesRoute.actions.create.label")} + {t("actions.create.label")} } > @@ -108,7 +106,7 @@ export default function ReservedAliasesRoute(): ReactElement { @{alias.domain} } - secondary={t("routes.ReservedAliasesRoute.userAmount", { + secondary={t("userAmount", { count: alias.users.length, })} /> diff --git a/src/routes/Root.tsx b/src/routes/Root.tsx index f560233..0c4b05c 100644 --- a/src/routes/Root.tsx +++ b/src/routes/Root.tsx @@ -1,15 +1,18 @@ import {Outlet} from "react-router-dom" -import React, {ReactElement} from "react" +import React, {ReactElement, Suspense} from "react" import {AppLoadingScreen, AuthContextProvider, ExtensionSignalHandler} from "~/components" +import LoadingPage from "~/components/widgets/LoadingPage" export default function RootRoute(): ReactElement { return ( - - - - - - + }> + + + + + + + ) } diff --git a/src/routes/Settings2FARoute.tsx b/src/routes/Settings2FARoute.tsx index 068dc3c..4c71a7b 100644 --- a/src/routes/Settings2FARoute.tsx +++ b/src/routes/Settings2FARoute.tsx @@ -11,20 +11,18 @@ import Setup2FA from "~/route-widgets/Settings2FARoute/Setup2FA" import getHas2FAEnabled from "~/apis/get-has-2fa-enabled" export default function Settings2FARoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("settings-2fa") const queryKey = ["get_2fa_enabled"] const query = useQuery(queryKey, getHas2FAEnabled) return ( - + query={query}> {has2FAEnabled => has2FAEnabled ? ( - - {t("routes.SettingsRoute.2fa.alreadyEnabled")} - + {t("alreadyEnabled")} diff --git a/src/routes/SettingsAliasPreferencesRoute.tsx b/src/routes/SettingsAliasPreferencesRoute.tsx index e8dd998..eb1609b 100644 --- a/src/routes/SettingsAliasPreferencesRoute.tsx +++ b/src/routes/SettingsAliasPreferencesRoute.tsx @@ -44,30 +44,26 @@ interface Form { } export default function SettingsAliasPreferencesRoute(): ReactElement { + const {t} = useTranslation(["aliases", "settings-preferences", "common"]) const {_updateUser} = useContext(AuthContext) const user = useUser() const {showError, showSuccess} = useErrorSuccessSnacks() - const {t} = useTranslation() const schema = yup.object().shape({ - removeTrackers: yup.boolean().label(t("relations.alias.settings.removeTrackers.label")), - createMailReport: yup - .boolean() - .label(t("relations.alias.settings.createMailReports.label")), - proxyImages: yup.boolean().label(t("relations.alias.settings.proxyImages.label")), + removeTrackers: yup.boolean().label(t("settings.fields.removeTrackers.label")), + createMailReport: yup.boolean().label(t("settings.fields.createMailReport.label")), + proxyImages: yup.boolean().label(t("settings.fields.proxyImages.label")), imageProxyFormat: yup .mixed() .oneOf(Object.values(ImageProxyFormatType)) .required() - .label(t("relations.alias.settings.imageProxyFormat.label")), + .label(t("settings.fields.imageProxyFormat.label")), proxyUserAgent: yup .mixed() .oneOf(Object.values(ProxyUserAgentType)) .required() - .label(t("relations.alias.settings.proxyUserAgent.label")), - expandUrlShorteners: yup - .boolean() - .label(t("relations.alias.settings.expandUrlShorteners.label")), + .label(t("settings.fields.proxyUserAgent.label")), + expandUrlShorteners: yup.boolean().label(t("settings.fields.expandUrlShorteners.label")), }) const {mutateAsync} = useMutation( @@ -121,8 +117,8 @@ export default function SettingsAliasPreferencesRoute(): ReactElement { return ( } labelPlacement="start" - label={t("relations.alias.settings.removeTrackers.label")} + label={t("settings.fields.removeTrackers.label")} /> {(formik.touched.createMailReport && formik.errors.createMailReport) || - t("relations.alias.settings.removeTrackers.helperText")} + t("settings.fields.removeTrackers.helperText")} @@ -174,7 +170,7 @@ export default function SettingsAliasPreferencesRoute(): ReactElement { /> } labelPlacement="start" - label={t("relations.alias.settings.createMailReports.label")} + label={t("settings.fields.createMailReport.label")} /> {(formik.touched.createMailReport && formik.errors.createMailReport) || - t("relations.alias.settings.createMailReports.helperText")} + t("settings.fields.createMailReport.helperText")} @@ -202,7 +198,7 @@ export default function SettingsAliasPreferencesRoute(): ReactElement { /> } labelPlacement="start" - label={t("relations.alias.settings.proxyImages.label")} + label={t("settings.fields.proxyImages.label")} /> {(formik.touched.proxyImages && formik.errors.proxyImages) || - t("relations.alias.settings.proxyImages.helperText")} + t("settings.fields.proxyImages.helperText")} - {t("general.experimentalFeature")} + {t("general.experimentalFeatureExplanation", {ns: "common"})} @@ -242,9 +238,7 @@ export default function SettingsAliasPreferencesRoute(): ReactElement { }} name="imageProxyFormat" id="imageProxyFormat" - label={t( - "relations.alias.settings.imageProxyFormat.label", - )} + label={t("settings.fields.imageProxyFormat.label")} value={formik.values.imageProxyFormat} onChange={formik.handleChange} disabled={formik.isSubmitting} @@ -286,7 +280,7 @@ export default function SettingsAliasPreferencesRoute(): ReactElement { select name="proxyUserAgent" id="proxyUserAgent" - label={t("relations.alias.settings.proxyUserAgent.label")} + label={t("settings.fields.proxyUserAgent.label")} value={formik.values.proxyUserAgent} onChange={formik.handleChange} disabled={formik.isSubmitting} @@ -312,7 +306,7 @@ export default function SettingsAliasPreferencesRoute(): ReactElement { )} > {(formik.touched.proxyUserAgent && formik.errors.proxyUserAgent) || - t("relations.alias.settings.proxyUserAgent.helperText")} + t("settings.fields.proxyUserAgent.helperText")} @@ -330,7 +324,7 @@ export default function SettingsAliasPreferencesRoute(): ReactElement { /> } labelPlacement="start" - label={t("relations.alias.settings.expandUrlShorteners.label")} + label={t("settings.fields.expandUrlShorteners.label")} /> {(formik.touched.expandUrlShorteners && formik.errors.expandUrlShorteners) || - t("relations.alias.settings.expandUrlShorteners.helperText")} + t("settings.fields.expandUrlShorteners.helperText")} - {t("general.experimentalFeature")} + {t("general.experimentalFeatureExplanation", {ns: "common"})} @@ -357,7 +351,7 @@ export default function SettingsAliasPreferencesRoute(): ReactElement { type="submit" startIcon={} > - {t("routes.SettingsRoute.forms.aliasPreferences.saveAction")} + {t("continueActionLabel", {ns: "settings-preferences"})} diff --git a/src/routes/SettingsRoute.tsx b/src/routes/SettingsRoute.tsx index f3e7b27..bb0b06e 100644 --- a/src/routes/SettingsRoute.tsx +++ b/src/routes/SettingsRoute.tsx @@ -9,22 +9,22 @@ import {List, ListItemButton, ListItemIcon, ListItemText} from "@mui/material" import {SimplePageBuilder} from "~/components" export default function SettingsRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("settings") return ( - + - + - + diff --git a/src/routes/VerifyEmailRoute.tsx b/src/routes/VerifyEmailRoute.tsx index 5641ecc..7c28982 100644 --- a/src/routes/VerifyEmailRoute.tsx +++ b/src/routes/VerifyEmailRoute.tsx @@ -17,9 +17,9 @@ import {AuthContext} from "~/components" const emailSchema = yup.string().email() export default function VerifyEmailRoute(): ReactElement { + const {t} = useTranslation("verify-email") const theme = useTheme() const navigate = useNavigate() - const {t} = useTranslation() const {login} = useContext(AuthContext) const [_, setEmail] = useLocalStorage("signup-form-state-email", "") @@ -32,7 +32,7 @@ export default function VerifyEmailRoute(): ReactElement { const tokenSchema = yup .string() .length(serverSettings.emailVerificationLength) - .test("token", t("routes.VerifyEmailRoute.errors.code.invalid") as string, token => { + .test("token", t("errors.invalid") as string, token => { if (!token) { return false } @@ -69,13 +69,13 @@ export default function VerifyEmailRoute(): ReactElement { > - {t("routes.VerifyEmailRoute.title")} + {t("title")} {loading ? ( - {t("routes.VerifyEmailRoute.isLoading")} + {t("isLoading")} ) : ( @@ -85,7 +85,7 @@ export default function VerifyEmailRoute(): ReactElement {
- {t("routes.VerifyEmailRoute.isCodeInvalid")} + {t("errors.invalid")}