mirror of
https://github.com/fosrl/pangolin.git
synced 2025-07-28 14:44:55 +02:00
I18n components (#27)
* New translation keys in en-US locale * New translation keys in de-DE locale * New translation keys in fr-FR locale * New translation keys in it-IT locale * New translation keys in pl-PL locale * New translation keys in pt-PT locale * New translation keys in tr-TR locale * Move into function * Replace string matching to boolean check * Add FIXIT in UsersTable * Use localization for size units * Missed and restored translation keys * fixup! New translation keys in tr-TR locale * Add translation keys in components
This commit is contained in:
parent
af3694da34
commit
ea24759bb3
42 changed files with 1419 additions and 329 deletions
|
@ -816,7 +816,7 @@
|
|||
"emailVerifyDescription": "Geben Sie den an Ihre E-Mail-Adresse gesendeten Verifizierungscode ein.",
|
||||
"verificationCode": "Verifizierungscode",
|
||||
"verificationCodeEmailSent": "Wir haben einen Verifizierungscode an Ihre E-Mail-Adresse gesendet.",
|
||||
"emailVerifySubmit": "Absenden",
|
||||
"submit": "Absenden",
|
||||
"emailVerifyResendProgress": "Wird erneut gesendet...",
|
||||
"emailVerifyResend": "Keinen Code erhalten? Hier klicken zum erneuten Senden",
|
||||
"passwordNotMatch": "Passwörter stimmen nicht überein",
|
||||
|
@ -907,5 +907,157 @@
|
|||
"usersAll": "Alle Benutzer",
|
||||
"license": "Lizenz",
|
||||
"pangolinDashboard": "Dashboard - Pangolin",
|
||||
"noResults": "Keine Ergebnisse gefunden."
|
||||
"noResults": "Keine Ergebnisse gefunden.",
|
||||
"terabytes": "{count} TB",
|
||||
"gigabytes": "{count} GB",
|
||||
"megabytes": "{count} MB",
|
||||
"tagsEntered": "Eingegebene Tags",
|
||||
"tagsEnteredDescription": "Dies sind die von Ihnen eingegebenen Tags.",
|
||||
"tagsWarnCannotBeLessThanZero": "maxTags und minTags können nicht kleiner als 0 sein",
|
||||
"tagsWarnNotAllowedAutocompleteOptions": "Tag ist laut Autovervollständigungsoptionen nicht erlaubt",
|
||||
"tagsWarnInvalid": "Ungültiger Tag laut validateTag",
|
||||
"tagWarnTooShort": "Tag {tagText} ist zu kurz",
|
||||
"tagWarnTooLong": "Tag {tagText} ist zu lang",
|
||||
"tagsWarnReachedMaxNumber": "Maximale Anzahl erlaubter Tags erreicht",
|
||||
"tagWarnDuplicate": "Doppelter Tag {tagText} nicht hinzugefügt",
|
||||
"supportKeyInvalid": "Ungültiger Schlüssel",
|
||||
"supportKeyInvalidDescription": "Ihr Unterstützer-Schlüssel ist ungültig.",
|
||||
"supportKeyValid": "Gültiger Schlüssel",
|
||||
"supportKeyValidDescription": "Ihr Unterstützer-Schlüssel wurde validiert. Danke für Ihre Unterstützung!",
|
||||
"supportKeyErrorValidationDescription": "Unterstützer-Schlüssel konnte nicht validiert werden.",
|
||||
"supportKey": "Unterstütze die Entwicklung und adoptiere ein Pangolin!",
|
||||
"supportKeyDescription": "Kaufen Sie einen Unterstützer-Schlüssel, um uns bei der Weiterentwicklung von Pangolin für die Community zu helfen. Ihr Beitrag ermöglicht es uns, mehr Zeit in die Wartung und neue Funktionen für alle zu investieren. Wir werden dies nie für Paywalls nutzen. Dies ist unabhängig von der Commercial Edition.",
|
||||
"supportKeyPet": "Sie können auch Ihr eigenes Pangolin-Haustier adoptieren und kennenlernen!",
|
||||
"supportKeyPurchase": "Zahlungen werden über GitHub abgewickelt. Danach können Sie Ihren Schlüssel auf",
|
||||
"supportKeyPurchaseLink": "unserer Website",
|
||||
"supportKeyPurchase2": "abrufen und hier einlösen.",
|
||||
"supportKeyLearnMore": "Mehr erfahren.",
|
||||
"supportKeyOptions": "Bitte wählen Sie die Option, die am besten zu Ihnen passt.",
|
||||
"supportKetOptionFull": "Voller Unterstützer",
|
||||
"forWholeServer": "Für den gesamten Server",
|
||||
"lifetimePurchase": "Lebenslanger Kauf",
|
||||
"supporterStatus": "Unterstützer-Status",
|
||||
"buy": "Kaufen",
|
||||
"supportKeyOptionLimited": "Eingeschränkter Unterstützer",
|
||||
"forFiveUsers": "Für 5 oder weniger Benutzer",
|
||||
"supportKeyRedeem": "Unterstützer-Schlüssel einlösen",
|
||||
"supportKeyHideSevenDays": "7 Tage ausblenden",
|
||||
"supportKeyEnter": "Unterstützer-Schlüssel eingeben",
|
||||
"supportKeyEnterDescription": "Treffen Sie Ihr eigenes Pangolin-Haustier!",
|
||||
"githubUsername": "GitHub Benutzername",
|
||||
"supportKeyInput": "Unterstützer-Schlüssel",
|
||||
"supportKeyBuy": "Unterstützer-Schlüssel kaufen",
|
||||
"logoutError": "Fehler beim Abmelden",
|
||||
"signingAs": "Angemeldet als",
|
||||
"serverAdmin": "Server-Administrator",
|
||||
"otpEnable": "Zwei-Faktor aktivieren",
|
||||
"otpDisable": "Zwei-Faktor deaktivieren",
|
||||
"logout": "Abmelden",
|
||||
"licenseTierProfessionalRequired": "Professional Edition erforderlich",
|
||||
"licenseTierProfessionalRequiredDescription": "Diese Funktion ist nur in der Professional Edition verfügbar.",
|
||||
"actionGetOrg": "Organisation abrufen",
|
||||
"actionUpdateOrg": "Organisation aktualisieren",
|
||||
"actionGetOrgUser": "Organisationsbenutzer abrufen",
|
||||
"actionListOrgDomains": "Organisationsdomänen auflisten",
|
||||
"actionCreateSite": "Site erstellen",
|
||||
"actionDeleteSite": "Site löschen",
|
||||
"actionGetSite": "Site abrufen",
|
||||
"actionListSites": "Sites auflisten",
|
||||
"actionUpdateSite": "Site aktualisieren",
|
||||
"actionListSiteRoles": "Erlaubte Site-Rollen auflisten",
|
||||
"actionCreateResource": "Ressource erstellen",
|
||||
"actionDeleteResource": "Ressource löschen",
|
||||
"actionGetResource": "Ressource abrufen",
|
||||
"actionListResource": "Ressourcen auflisten",
|
||||
"actionUpdateResource": "Ressource aktualisieren",
|
||||
"actionListResourceUsers": "Ressourcenbenutzer auflisten",
|
||||
"actionSetResourceUsers": "Ressourcenbenutzer festlegen",
|
||||
"actionSetAllowedResourceRoles": "Erlaubte Ressourcenrollen festlegen",
|
||||
"actionListAllowedResourceRoles": "Erlaubte Ressourcenrollen auflisten",
|
||||
"actionSetResourcePassword": "Ressourcenpasswort festlegen",
|
||||
"actionSetResourcePincode": "Ressourcen-PIN festlegen",
|
||||
"actionSetResourceEmailWhitelist": "Ressourcen-E-Mail-Whitelist festlegen",
|
||||
"actionGetResourceEmailWhitelist": "Ressourcen-E-Mail-Whitelist abrufen",
|
||||
"actionCreateTarget": "Ziel erstellen",
|
||||
"actionDeleteTarget": "Ziel löschen",
|
||||
"actionGetTarget": "Ziel abrufen",
|
||||
"actionListTargets": "Ziele auflisten",
|
||||
"actionUpdateTarget": "Ziel aktualisieren",
|
||||
"actionCreateRole": "Rolle erstellen",
|
||||
"actionDeleteRole": "Rolle löschen",
|
||||
"actionGetRole": "Rolle abrufen",
|
||||
"actionListRole": "Rollen auflisten",
|
||||
"actionUpdateRole": "Rolle aktualisieren",
|
||||
"actionListAllowedRoleResources": "Erlaubte Rollenressourcen auflisten",
|
||||
"actionInviteUser": "Benutzer einladen",
|
||||
"actionRemoveUser": "Benutzer entfernen",
|
||||
"actionListUsers": "Benutzer auflisten",
|
||||
"actionAddUserRole": "Benutzerrolle hinzufügen",
|
||||
"actionGenerateAccessToken": "Zugriffstoken generieren",
|
||||
"actionDeleteAccessToken": "Zugriffstoken löschen",
|
||||
"actionListAccessTokens": "Zugriffstoken auflisten",
|
||||
"actionCreateResourceRule": "Ressourcenregel erstellen",
|
||||
"actionDeleteResourceRule": "Ressourcenregel löschen",
|
||||
"actionListResourceRules": "Ressourcenregeln auflisten",
|
||||
"actionUpdateResourceRule": "Ressourcenregel aktualisieren",
|
||||
"actionListOrgs": "Organisationen auflisten",
|
||||
"actionCheckOrgId": "ID prüfen",
|
||||
"actionCreateOrg": "Organisation erstellen",
|
||||
"actionDeleteOrg": "Organisation löschen",
|
||||
"actionListApiKeys": "API-Schlüssel auflisten",
|
||||
"actionListApiKeyActions": "API-Schlüsselaktionen auflisten",
|
||||
"actionSetApiKeyActions": "Erlaubte API-Schlüsselaktionen festlegen",
|
||||
"actionCreateApiKey": "API-Schlüssel erstellen",
|
||||
"actionDeleteApiKey": "API-Schlüssel löschen",
|
||||
"actionCreateIdp": "IDP erstellen",
|
||||
"actionUpdateIdp": "IDP aktualisieren",
|
||||
"actionDeleteIdp": "IDP löschen",
|
||||
"actionListIdps": "IDP auflisten",
|
||||
"actionGetIdp": "IDP abrufen",
|
||||
"actionCreateIdpOrg": "IDP-Organisationsrichtlinie erstellen",
|
||||
"actionDeleteIdpOrg": "IDP-Organisationsrichtlinie löschen",
|
||||
"actionListIdpOrgs": "IDP-Organisationen auflisten",
|
||||
"actionUpdateIdpOrg": "IDP-Organisation aktualisieren",
|
||||
"noneSelected": "Keine ausgewählt",
|
||||
"orgNotFound2": "Keine Organisationen gefunden.",
|
||||
"searchProgress": "Suche...",
|
||||
"create": "Erstellen",
|
||||
"orgs": "Organisationen",
|
||||
"loginError": "Beim Anmelden ist ein Fehler aufgetreten",
|
||||
"passwordForgot": "Passwort vergessen?",
|
||||
"otpAuth": "Zwei-Faktor-Authentifizierung",
|
||||
"otpAuthDescription": "Geben Sie den Code aus Ihrer Authenticator-App oder einen Ihrer einmaligen Backup-Codes ein.",
|
||||
"otpAuthSubmit": "Code absenden",
|
||||
"idpContinue": "Oder weiter mit",
|
||||
"otpAuthBack": "Zurück zur Anmeldung",
|
||||
"navbar": "Navigationsmenü",
|
||||
"navbarDescription": "Hauptnavigationsmenü für die Anwendung",
|
||||
"navbarDocsLink": "Dokumentation",
|
||||
"commercialEdition": "Commercial Edition",
|
||||
"otpErrorEnable": "2FA konnte nicht aktiviert werden",
|
||||
"otpErrorEnableDescription": "Beim Aktivieren der 2FA ist ein Fehler aufgetreten",
|
||||
"otpSetupCheckCode": "Bitte geben Sie einen 6-stelligen Code ein",
|
||||
"otpSetupCheckCodeRetry": "Ungültiger Code. Bitte versuchen Sie es erneut.",
|
||||
"otpSetup": "Zwei-Faktor-Authentifizierung aktivieren",
|
||||
"otpSetupDescription": "Sichern Sie Ihr Konto mit einer zusätzlichen Schutzebene",
|
||||
"otpSetupScanQr": "Scannen Sie diesen QR-Code mit Ihrer Authenticator-App oder geben Sie den Geheimschlüssel manuell ein:",
|
||||
"otpSetupSecretCode": "Authenticator-Code",
|
||||
"otpSetupSuccess": "Zwei-Faktor-Authentifizierung aktiviert",
|
||||
"otpSetupSuccessStoreBackupCodes": "Ihr Konto ist jetzt sicherer. Vergessen Sie nicht, Ihre Backup-Codes zu speichern.",
|
||||
"otpErrorDisable": "2FA konnte nicht deaktiviert werden",
|
||||
"otpErrorDisableDescription": "Beim Deaktivieren der 2FA ist ein Fehler aufgetreten",
|
||||
"otpRemove": "Zwei-Faktor-Authentifizierung deaktivieren",
|
||||
"otpRemoveDescription": "Deaktivieren Sie die Zwei-Faktor-Authentifizierung für Ihr Konto",
|
||||
"otpRemoveSuccess": "Zwei-Faktor-Authentifizierung deaktiviert",
|
||||
"otpRemoveSuccessMessage": "Die Zwei-Faktor-Authentifizierung wurde für Ihr Konto deaktiviert. Sie können sie jederzeit wieder aktivieren.",
|
||||
"otpRemoveSubmit": "2FA deaktivieren",
|
||||
"paginator": "Seite {current} von {last}",
|
||||
"paginatorToFirst": "Zur ersten Seite",
|
||||
"paginatorToPrevious": "Zur vorherigen Seite",
|
||||
"paginatorToNext": "Zur nächsten Seite",
|
||||
"paginatorToLast": "Zur letzten Seite",
|
||||
"copyText": "Text kopieren",
|
||||
"copyTextFailed": "Text konnte nicht kopiert werden: ",
|
||||
"copyTextClipboard": "In die Zwischenablage kopieren",
|
||||
"inviteErrorInvalidConfirmation": "Ungültige Bestätigung"
|
||||
}
|
||||
|
|
|
@ -816,7 +816,7 @@
|
|||
"emailVerifyDescription": "Enter the verification code sent to your email address.",
|
||||
"verificationCode": "Verification Code",
|
||||
"verificationCodeEmailSent": "We sent a verification code to your email address.",
|
||||
"emailVerifySubmit": "Submit",
|
||||
"submit": "Submit",
|
||||
"emailVerifyResendProgress": "Resending...",
|
||||
"emailVerifyResend": "Didn't receive a code? Click here to resend",
|
||||
"passwordNotMatch": "Passwords do not match",
|
||||
|
@ -907,5 +907,157 @@
|
|||
"usersAll": "All Users",
|
||||
"license": "License",
|
||||
"pangolinDashboard": "Dashboard - Pangolin",
|
||||
"noResults": "No results found."
|
||||
"noResults": "No results found.",
|
||||
"terabytes": "{count} TB",
|
||||
"gigabytes": "{count} GB",
|
||||
"megabytes": "{count} MB",
|
||||
"tagsEntered": "Entered Tags",
|
||||
"tagsEnteredDescription": "These are the tags you`ve entered.",
|
||||
"tagsWarnCannotBeLessThanZero": "maxTags and minTags cannot be less than 0",
|
||||
"tagsWarnNotAllowedAutocompleteOptions": "Tag not allowed as per autocomplete options",
|
||||
"tagsWarnInvalid": "Invalid tag as per validateTag",
|
||||
"tagWarnTooShort": "Tag {tagText} is too short",
|
||||
"tagWarnTooLong": "Tag {tagText} is too long",
|
||||
"tagsWarnReachedMaxNumber": "Reached the maximum number of tags allowed",
|
||||
"tagWarnDuplicate": "Duplicate tag {tagText} not added",
|
||||
"supportKeyInvalid": "Invalid Key",
|
||||
"supportKeyInvalidDescription": "Your supporter key is invalid.",
|
||||
"supportKeyValid": "Valid Key",
|
||||
"supportKeyValidDescription": "Your supporter key has been validated. Thank you for your support!",
|
||||
"supportKeyErrorValidationDescription": "Failed to validate supporter key.",
|
||||
"supportKey": "Support Development and Adopt a Pangolin!",
|
||||
"supportKeyDescription": "Purchase a supporter key to help us continue developing Pangolin for the community. Your contribution allows us to commit more time to maintain and add new features to the application for everyone. We will never use this to paywall features. This is separate from any Commercial Edition.",
|
||||
"supportKeyPet": "You will also get to adopt and meet your very own pet Pangolin!",
|
||||
"supportKeyPurchase": "Payments are processed via GitHub. Afterward, you can retrieve your key on",
|
||||
"supportKeyPurchaseLink": "our website",
|
||||
"supportKeyPurchase2": "and redeem it here.",
|
||||
"supportKeyLearnMore": "Learn more.",
|
||||
"supportKeyOptions": "Please select the option that best suits you.",
|
||||
"supportKetOptionFull": "Full Supporter",
|
||||
"forWholeServer": "For the whole server",
|
||||
"lifetimePurchase": "Lifetime purchase",
|
||||
"supporterStatus": "Supporter status",
|
||||
"buy": "Buy",
|
||||
"supportKeyOptionLimited": "Limited Supporter",
|
||||
"forFiveUsers": "For 5 or less users",
|
||||
"supportKeyRedeem": "Redeem Supporter Key",
|
||||
"supportKeyHideSevenDays": "Hide for 7 days",
|
||||
"supportKeyEnter": "Enter Supporter Key",
|
||||
"supportKeyEnterDescription": "Meet your very own pet Pangolin!",
|
||||
"githubUsername": "GitHub Username",
|
||||
"supportKeyInput": "Supporter Key",
|
||||
"supportKeyBuy": "Buy Supporter Key",
|
||||
"logoutError": "Error logging out",
|
||||
"signingAs": "Signed in as",
|
||||
"serverAdmin": "Server Admin",
|
||||
"otpEnable": "Enable Two-factor",
|
||||
"otpDisable": "Disable Two-factor",
|
||||
"logout": "Log Out",
|
||||
"licenseTierProfessionalRequired": "Professional Edition Required",
|
||||
"licenseTierProfessionalRequiredDescription": "This feature is only available in the Professional Edition.",
|
||||
"actionGetOrg": "Get Organization",
|
||||
"actionUpdateOrg": "Update Organization",
|
||||
"actionGetOrgUser": "Get Organization User",
|
||||
"actionListOrgDomains": "List Organization Domains",
|
||||
"actionCreateSite": "Create Site",
|
||||
"actionDeleteSite": "Delete Site",
|
||||
"actionGetSite": "Get Site",
|
||||
"actionListSites": "List Sites",
|
||||
"actionUpdateSite": "Update Site",
|
||||
"actionListSiteRoles": "List Allowed Site Roles",
|
||||
"actionCreateResource": "Create Resource",
|
||||
"actionDeleteResource": "Delete Resource",
|
||||
"actionGetResource": "Get Resource",
|
||||
"actionListResource": "List Resources",
|
||||
"actionUpdateResource": "Update Resource",
|
||||
"actionListResourceUsers": "List Resource Users",
|
||||
"actionSetResourceUsers": "Set Resource Users",
|
||||
"actionSetAllowedResourceRoles": "Set Allowed Resource Roles",
|
||||
"actionListAllowedResourceRoles": "List Allowed Resource Roles",
|
||||
"actionSetResourcePassword": "Set Resource Password",
|
||||
"actionSetResourcePincode": "Set Resource Pincode",
|
||||
"actionSetResourceEmailWhitelist": "Set Resource Email Whitelist",
|
||||
"actionGetResourceEmailWhitelist": "Get Resource Email Whitelist",
|
||||
"actionCreateTarget": "Create Target",
|
||||
"actionDeleteTarget": "Delete Target",
|
||||
"actionGetTarget": "Get Target",
|
||||
"actionListTargets": "List Targets",
|
||||
"actionUpdateTarget": "Update Target",
|
||||
"actionCreateRole": "Create Role",
|
||||
"actionDeleteRole": "Delete Role",
|
||||
"actionGetRole": "Get Role",
|
||||
"actionListRole": "List Roles",
|
||||
"actionUpdateRole": "Update Role",
|
||||
"actionListAllowedRoleResources": "List Allowed Role Resources",
|
||||
"actionInviteUser": "Invite User",
|
||||
"actionRemoveUser": "Remove User",
|
||||
"actionListUsers": "List Users",
|
||||
"actionAddUserRole": "Add User Role",
|
||||
"actionGenerateAccessToken": "Generate Access Token",
|
||||
"actionDeleteAccessToken": "Delete Access Token",
|
||||
"actionListAccessTokens": "List Access Tokens",
|
||||
"actionCreateResourceRule": "Create Resource Rule",
|
||||
"actionDeleteResourceRule": "Delete Resource Rule",
|
||||
"actionListResourceRules": "List Resource Rules",
|
||||
"actionUpdateResourceRule": "Update Resource Rule",
|
||||
"actionListOrgs": "List Organizations",
|
||||
"actionCheckOrgId": "Check ID",
|
||||
"actionCreateOrg": "Create Organization",
|
||||
"actionDeleteOrg": "Delete Organization",
|
||||
"actionListApiKeys": "List API Keys",
|
||||
"actionListApiKeyActions": "List API Key Actions",
|
||||
"actionSetApiKeyActions": "Set API Key Allowed Actions",
|
||||
"actionCreateApiKey": "Create API Key",
|
||||
"actionDeleteApiKey": "Delete API Key",
|
||||
"actionCreateIdp": "Create IDP",
|
||||
"actionUpdateIdp": "Update IDP",
|
||||
"actionDeleteIdp": "Delete IDP",
|
||||
"actionListIdps": "List IDP",
|
||||
"actionGetIdp": "Get IDP",
|
||||
"actionCreateIdpOrg": "Create IDP Org Policy",
|
||||
"actionDeleteIdpOrg": "Delete IDP Org Policy",
|
||||
"actionListIdpOrgs": "List IDP Orgs",
|
||||
"actionUpdateIdpOrg": "Update IDP Org",
|
||||
"noneSelected": "None selected",
|
||||
"orgNotFound2": "No organizations found.",
|
||||
"searchProgress": "Search...",
|
||||
"create": "Create",
|
||||
"orgs": "Organizations",
|
||||
"loginError": "An error occurred while logging in",
|
||||
"passwordForgot": "Forgot your password?",
|
||||
"otpAuth": "Two-Factor Authentication",
|
||||
"otpAuthDescription": "Enter the code from your authenticator app or one of your single-use backup codes.",
|
||||
"otpAuthSubmit": "Submit Code",
|
||||
"idpContinue": "Or continue with",
|
||||
"otpAuthBack": "Back to Log In",
|
||||
"navbar": "Navigation Menu",
|
||||
"navbarDescription": "Main navigation menu for the application",
|
||||
"navbarDocsLink": "Documentation",
|
||||
"commercialEdition": "Commercial Edition",
|
||||
"otpErrorEnable": "Unable to enable 2FA",
|
||||
"otpErrorEnableDescription": "An error occurred while enabling 2FA",
|
||||
"otpSetupCheckCode": "Please enter a 6-digit code",
|
||||
"otpSetupCheckCodeRetry": "Invalid code. Please try again.",
|
||||
"otpSetup": "Enable Two-factor Authentication",
|
||||
"otpSetupDescription": "Secure your account with an extra layer of protection",
|
||||
"otpSetupScanQr": "Scan this QR code with your authenticator app or enter the secret key manually:",
|
||||
"otpSetupSecretCode": "Authenticator Code",
|
||||
"otpSetupSuccess": "Two-Factor Authentication Enabled",
|
||||
"otpSetupSuccessStoreBackupCodes": "Your account is now more secure. Don't forget to save your backup codes.",
|
||||
"otpErrorDisable": "Unable to disable 2FA",
|
||||
"otpErrorDisableDescription": "An error occurred while disabling 2FA",
|
||||
"otpRemove": "Disable Two-factor Authentication",
|
||||
"otpRemoveDescription": "Disable two-factor authentication for your account",
|
||||
"otpRemoveSuccess": "Two-Factor Authentication Disabled",
|
||||
"otpRemoveSuccessMessage": "Two-factor authentication has been disabled for your account. You can enable it again at any time.",
|
||||
"otpRemoveSubmit": "Disable 2FA",
|
||||
"paginator": "Page {current} of {last}",
|
||||
"paginatorToFirst": "Go to first page",
|
||||
"paginatorToPrevious": "Go to previous page",
|
||||
"paginatorToNext": "Go to next page",
|
||||
"paginatorToLast": "Go to last page",
|
||||
"copyText": "Copy text",
|
||||
"copyTextFailed": "Failed to copy text: ",
|
||||
"copyTextClipboard": "Copy to clipboard",
|
||||
"inviteErrorInvalidConfirmation": "Invalid confirmation"
|
||||
}
|
||||
|
|
|
@ -816,7 +816,7 @@
|
|||
"emailVerifyDescription": "Entrez le code de vérification envoyé à votre adresse e-mail.",
|
||||
"verificationCode": "Code de vérification",
|
||||
"verificationCodeEmailSent": "Nous avons envoyé un code de vérification à votre adresse e-mail.",
|
||||
"emailVerifySubmit": "Soumettre",
|
||||
"submit": "Soumettre",
|
||||
"emailVerifyResendProgress": "Renvoi en cours...",
|
||||
"emailVerifyResend": "Vous n'avez pas reçu de code ? Cliquez ici pour renvoyer",
|
||||
"passwordNotMatch": "Les mots de passe ne correspondent pas",
|
||||
|
@ -907,5 +907,157 @@
|
|||
"usersAll": "Tous les utilisateurs",
|
||||
"license": "Licence",
|
||||
"pangolinDashboard": "Tableau de bord - Pangolin",
|
||||
"noResults": "Aucun résultat trouvé."
|
||||
"noResults": "Aucun résultat trouvé.",
|
||||
"terabytes": "{count} To",
|
||||
"gigabytes": "{count} Go",
|
||||
"megabytes": "{count} Mo",
|
||||
"tagsEntered": "Tags saisis",
|
||||
"tagsEnteredDescription": "Ce sont les tags que vous avez saisis.",
|
||||
"tagsWarnCannotBeLessThanZero": "maxTags et minTags ne peuvent pas être inférieurs à 0",
|
||||
"tagsWarnNotAllowedAutocompleteOptions": "Tag non autorisé selon les options d'autocomplétion",
|
||||
"tagsWarnInvalid": "Tag invalide selon validateTag",
|
||||
"tagWarnTooShort": "Le tag {tagText} est trop court",
|
||||
"tagWarnTooLong": "Le tag {tagText} est trop long",
|
||||
"tagsWarnReachedMaxNumber": "Nombre maximum de tags autorisés atteint",
|
||||
"tagWarnDuplicate": "Tag en double {tagText} non ajouté",
|
||||
"supportKeyInvalid": "Clé invalide",
|
||||
"supportKeyInvalidDescription": "Votre clé de support est invalide.",
|
||||
"supportKeyValid": "Clé valide",
|
||||
"supportKeyValidDescription": "Votre clé de support a été validée. Merci pour votre soutien !",
|
||||
"supportKeyErrorValidationDescription": "Échec de la validation de la clé de support.",
|
||||
"supportKey": "Soutenez le développement et adoptez un Pangolin !",
|
||||
"supportKeyDescription": "Achetez une clé de support pour nous aider à continuer le développement de Pangolin pour la communauté. Votre contribution nous permet de consacrer plus de temps à maintenir et ajouter de nouvelles fonctionnalités à l'application pour tous. Nous n'utiliserons jamais cela pour verrouiller des fonctionnalités. Ceci est distinct de toute Édition Commerciale.",
|
||||
"supportKeyPet": "Vous pourrez aussi adopter et rencontrer votre propre Pangolin de compagnie !",
|
||||
"supportKeyPurchase": "Les paiements sont traités via GitHub. Ensuite, vous pourrez récupérer votre clé sur",
|
||||
"supportKeyPurchaseLink": "notre site web",
|
||||
"supportKeyPurchase2": "et l'utiliser ici.",
|
||||
"supportKeyLearnMore": "En savoir plus.",
|
||||
"supportKeyOptions": "Veuillez sélectionner l'option qui vous convient le mieux.",
|
||||
"supportKetOptionFull": "Support complet",
|
||||
"forWholeServer": "Pour tout le serveur",
|
||||
"lifetimePurchase": "Achat à vie",
|
||||
"supporterStatus": "Statut de supporter",
|
||||
"buy": "Acheter",
|
||||
"supportKeyOptionLimited": "Support limité",
|
||||
"forFiveUsers": "Pour 5 utilisateurs ou moins",
|
||||
"supportKeyRedeem": "Utiliser une clé de support",
|
||||
"supportKeyHideSevenDays": "Masquer pendant 7 jours",
|
||||
"supportKeyEnter": "Saisir la clé de support",
|
||||
"supportKeyEnterDescription": "Rencontrez votre propre Pangolin de compagnie !",
|
||||
"githubUsername": "Nom d'utilisateur GitHub",
|
||||
"supportKeyInput": "Clé de support",
|
||||
"supportKeyBuy": "Acheter une clé de support",
|
||||
"logoutError": "Erreur lors de la déconnexion",
|
||||
"signingAs": "Connecté en tant que",
|
||||
"serverAdmin": "Admin Serveur",
|
||||
"otpEnable": "Activer l'authentification à deux facteurs",
|
||||
"otpDisable": "Désactiver l'authentification à deux facteurs",
|
||||
"logout": "Déconnexion",
|
||||
"licenseTierProfessionalRequired": "Édition Professionnelle Requise",
|
||||
"licenseTierProfessionalRequiredDescription": "Cette fonctionnalité n'est disponible que dans l'Édition Professionnelle.",
|
||||
"actionGetOrg": "Obtenir l'organisation",
|
||||
"actionUpdateOrg": "Mettre à jour l'organisation",
|
||||
"actionGetOrgUser": "Obtenir l'utilisateur de l'organisation",
|
||||
"actionListOrgDomains": "Lister les domaines de l'organisation",
|
||||
"actionCreateSite": "Créer un site",
|
||||
"actionDeleteSite": "Supprimer un site",
|
||||
"actionGetSite": "Obtenir un site",
|
||||
"actionListSites": "Lister les sites",
|
||||
"actionUpdateSite": "Mettre à jour un site",
|
||||
"actionListSiteRoles": "Lister les rôles autorisés du site",
|
||||
"actionCreateResource": "Créer une ressource",
|
||||
"actionDeleteResource": "Supprimer une ressource",
|
||||
"actionGetResource": "Obtenir une ressource",
|
||||
"actionListResource": "Lister les ressources",
|
||||
"actionUpdateResource": "Mettre à jour une ressource",
|
||||
"actionListResourceUsers": "Lister les utilisateurs de la ressource",
|
||||
"actionSetResourceUsers": "Définir les utilisateurs de la ressource",
|
||||
"actionSetAllowedResourceRoles": "Définir les rôles autorisés de la ressource",
|
||||
"actionListAllowedResourceRoles": "Lister les rôles autorisés de la ressource",
|
||||
"actionSetResourcePassword": "Définir le mot de passe de la ressource",
|
||||
"actionSetResourcePincode": "Définir le code PIN de la ressource",
|
||||
"actionSetResourceEmailWhitelist": "Définir la liste blanche des emails de la ressource",
|
||||
"actionGetResourceEmailWhitelist": "Obtenir la liste blanche des emails de la ressource",
|
||||
"actionCreateTarget": "Créer une cible",
|
||||
"actionDeleteTarget": "Supprimer une cible",
|
||||
"actionGetTarget": "Obtenir une cible",
|
||||
"actionListTargets": "Lister les cibles",
|
||||
"actionUpdateTarget": "Mettre à jour une cible",
|
||||
"actionCreateRole": "Créer un rôle",
|
||||
"actionDeleteRole": "Supprimer un rôle",
|
||||
"actionGetRole": "Obtenir un rôle",
|
||||
"actionListRole": "Lister les rôles",
|
||||
"actionUpdateRole": "Mettre à jour un rôle",
|
||||
"actionListAllowedRoleResources": "Lister les ressources autorisées du rôle",
|
||||
"actionInviteUser": "Inviter un utilisateur",
|
||||
"actionRemoveUser": "Supprimer un utilisateur",
|
||||
"actionListUsers": "Lister les utilisateurs",
|
||||
"actionAddUserRole": "Ajouter un rôle utilisateur",
|
||||
"actionGenerateAccessToken": "Générer un jeton d'accès",
|
||||
"actionDeleteAccessToken": "Supprimer un jeton d'accès",
|
||||
"actionListAccessTokens": "Lister les jetons d'accès",
|
||||
"actionCreateResourceRule": "Créer une règle de ressource",
|
||||
"actionDeleteResourceRule": "Supprimer une règle de ressource",
|
||||
"actionListResourceRules": "Lister les règles de ressource",
|
||||
"actionUpdateResourceRule": "Mettre à jour une règle de ressource",
|
||||
"actionListOrgs": "Lister les organisations",
|
||||
"actionCheckOrgId": "Vérifier l'ID",
|
||||
"actionCreateOrg": "Créer une organisation",
|
||||
"actionDeleteOrg": "Supprimer une organisation",
|
||||
"actionListApiKeys": "Lister les clés API",
|
||||
"actionListApiKeyActions": "Lister les actions des clés API",
|
||||
"actionSetApiKeyActions": "Définir les actions autorisées des clés API",
|
||||
"actionCreateApiKey": "Créer une clé API",
|
||||
"actionDeleteApiKey": "Supprimer une clé API",
|
||||
"actionCreateIdp": "Créer un IDP",
|
||||
"actionUpdateIdp": "Mettre à jour un IDP",
|
||||
"actionDeleteIdp": "Supprimer un IDP",
|
||||
"actionListIdps": "Lister les IDP",
|
||||
"actionGetIdp": "Obtenir un IDP",
|
||||
"actionCreateIdpOrg": "Créer une politique d'organisation IDP",
|
||||
"actionDeleteIdpOrg": "Supprimer une politique d'organisation IDP",
|
||||
"actionListIdpOrgs": "Lister les organisations IDP",
|
||||
"actionUpdateIdpOrg": "Mettre à jour une organisation IDP",
|
||||
"noneSelected": "Aucune sélection",
|
||||
"orgNotFound2": "Aucune organisation trouvée.",
|
||||
"searchProgress": "Rechercher...",
|
||||
"create": "Créer",
|
||||
"orgs": "Organisations",
|
||||
"loginError": "Une erreur s'est produite lors de la connexion",
|
||||
"passwordForgot": "Mot de passe oublié ?",
|
||||
"otpAuth": "Authentification à deux facteurs",
|
||||
"otpAuthDescription": "Entrez le code de votre application d'authentification ou l'un de vos codes de secours à usage unique.",
|
||||
"otpAuthSubmit": "Soumettre le code",
|
||||
"idpContinue": "Ou continuer avec",
|
||||
"otpAuthBack": "Retour à la connexion",
|
||||
"navbar": "Menu de navigation",
|
||||
"navbarDescription": "Menu de navigation principal de l'application",
|
||||
"navbarDocsLink": "Documentation",
|
||||
"commercialEdition": "Édition Commerciale",
|
||||
"otpErrorEnable": "Impossible d'activer l'A2F",
|
||||
"otpErrorEnableDescription": "Une erreur s'est produite lors de l'activation de l'A2F",
|
||||
"otpSetupCheckCode": "Veuillez entrer un code à 6 chiffres",
|
||||
"otpSetupCheckCodeRetry": "Code invalide. Veuillez réessayer.",
|
||||
"otpSetup": "Activer l'authentification à deux facteurs",
|
||||
"otpSetupDescription": "Sécurisez votre compte avec une couche de protection supplémentaire",
|
||||
"otpSetupScanQr": "Scannez ce code QR avec votre application d'authentification ou entrez la clé secrète manuellement :",
|
||||
"otpSetupSecretCode": "Code d'authentification",
|
||||
"otpSetupSuccess": "Authentification à deux facteurs activée",
|
||||
"otpSetupSuccessStoreBackupCodes": "Votre compte est maintenant plus sécurisé. N'oubliez pas de sauvegarder vos codes de secours.",
|
||||
"otpErrorDisable": "Impossible de désactiver l'A2F",
|
||||
"otpErrorDisableDescription": "Une erreur s'est produite lors de la désactivation de l'A2F",
|
||||
"otpRemove": "Désactiver l'authentification à deux facteurs",
|
||||
"otpRemoveDescription": "Désactiver l'authentification à deux facteurs pour votre compte",
|
||||
"otpRemoveSuccess": "Authentification à deux facteurs désactivée",
|
||||
"otpRemoveSuccessMessage": "L'authentification à deux facteurs a été désactivée pour votre compte. Vous pouvez la réactiver à tout moment.",
|
||||
"otpRemoveSubmit": "Désactiver l'A2F",
|
||||
"paginator": "Page {current} sur {last}",
|
||||
"paginatorToFirst": "Aller à la première page",
|
||||
"paginatorToPrevious": "Aller à la page précédente",
|
||||
"paginatorToNext": "Aller à la page suivante",
|
||||
"paginatorToLast": "Aller à la dernière page",
|
||||
"copyText": "Copier le texte",
|
||||
"copyTextFailed": "Échec de la copie du texte : ",
|
||||
"copyTextClipboard": "Copier dans le presse-papiers",
|
||||
"inviteErrorInvalidConfirmation": "Confirmation invalide"
|
||||
}
|
||||
|
|
|
@ -816,7 +816,7 @@
|
|||
"emailVerifyDescription": "Inserisci il codice di verifica inviato al tuo indirizzo email.",
|
||||
"verificationCode": "Codice di Verifica",
|
||||
"verificationCodeEmailSent": "Abbiamo inviato un codice di verifica al tuo indirizzo email.",
|
||||
"emailVerifySubmit": "Invia",
|
||||
"submit": "Invia",
|
||||
"emailVerifyResendProgress": "Reinvio in corso...",
|
||||
"emailVerifyResend": "Non hai ricevuto il codice? Clicca qui per reinviare",
|
||||
"passwordNotMatch": "Le password non coincidono",
|
||||
|
@ -907,5 +907,157 @@
|
|||
"usersAll": "Tutti Gli Utenti",
|
||||
"license": "Licenza",
|
||||
"pangolinDashboard": "Cruscotto - Pangolino",
|
||||
"noResults": "Nessun risultato trovato."
|
||||
"noResults": "Nessun risultato trovato.",
|
||||
"terabytes": "{count} TB",
|
||||
"gigabytes": "{count} GB",
|
||||
"megabytes": "{count} MB",
|
||||
"tagsEntered": "Tag Inseriti",
|
||||
"tagsEnteredDescription": "Questi sono i tag che hai inserito.",
|
||||
"tagsWarnCannotBeLessThanZero": "maxTags e minTags non possono essere minori di 0",
|
||||
"tagsWarnNotAllowedAutocompleteOptions": "Tag non consentito come da opzioni di autocompletamento",
|
||||
"tagsWarnInvalid": "Tag non valido secondo validateTag",
|
||||
"tagWarnTooShort": "Il tag {tagText} è troppo corto",
|
||||
"tagWarnTooLong": "Il tag {tagText} è troppo lungo",
|
||||
"tagsWarnReachedMaxNumber": "Raggiunto il numero massimo di tag consentiti",
|
||||
"tagWarnDuplicate": "Tag duplicato {tagText} non aggiunto",
|
||||
"supportKeyInvalid": "Chiave Non Valida",
|
||||
"supportKeyInvalidDescription": "La tua chiave di supporto non è valida.",
|
||||
"supportKeyValid": "Chiave Valida",
|
||||
"supportKeyValidDescription": "La tua chiave di supporto è stata convalidata. Grazie per il tuo sostegno!",
|
||||
"supportKeyErrorValidationDescription": "Impossibile convalidare la chiave di supporto.",
|
||||
"supportKey": "Supporta lo Sviluppo e Adotta un Pangolino!",
|
||||
"supportKeyDescription": "Acquista una chiave di supporto per aiutarci a continuare a sviluppare Pangolin per la comunità. Il tuo contributo ci permette di dedicare più tempo alla manutenzione e all'aggiunta di nuove funzionalità per tutti. Non useremo mai questo per bloccare le funzionalità. Questo è separato da qualsiasi Edizione Commerciale.",
|
||||
"supportKeyPet": "Potrai anche adottare e incontrare il tuo pangolino personale!",
|
||||
"supportKeyPurchase": "I pagamenti sono elaborati tramite GitHub. Successivamente, potrai recuperare la tua chiave su",
|
||||
"supportKeyPurchaseLink": "il nostro sito web",
|
||||
"supportKeyPurchase2": "e riscattarla qui.",
|
||||
"supportKeyLearnMore": "Scopri di più.",
|
||||
"supportKeyOptions": "Seleziona l'opzione più adatta a te.",
|
||||
"supportKetOptionFull": "Supporto Completo",
|
||||
"forWholeServer": "Per l'intero server",
|
||||
"lifetimePurchase": "Acquisto a vita",
|
||||
"supporterStatus": "Stato supportatore",
|
||||
"buy": "Acquista",
|
||||
"supportKeyOptionLimited": "Supporto Limitato",
|
||||
"forFiveUsers": "Per 5 o meno utenti",
|
||||
"supportKeyRedeem": "Riscatta Chiave di Supporto",
|
||||
"supportKeyHideSevenDays": "Nascondi per 7 giorni",
|
||||
"supportKeyEnter": "Inserisci Chiave di Supporto",
|
||||
"supportKeyEnterDescription": "Incontra il tuo pangolino personale!",
|
||||
"githubUsername": "Username GitHub",
|
||||
"supportKeyInput": "Chiave di Supporto",
|
||||
"supportKeyBuy": "Acquista Chiave di Supporto",
|
||||
"logoutError": "Errore durante il logout",
|
||||
"signingAs": "Accesso come",
|
||||
"serverAdmin": "Amministratore Server",
|
||||
"otpEnable": "Abilita Autenticazione a Due Fattori",
|
||||
"otpDisable": "Disabilita Autenticazione a Due Fattori",
|
||||
"logout": "Disconnetti",
|
||||
"licenseTierProfessionalRequired": "Edizione Professional Richiesta",
|
||||
"licenseTierProfessionalRequiredDescription": "Questa funzionalità è disponibile solo nell'Edizione Professional.",
|
||||
"actionGetOrg": "Ottieni Organizzazione",
|
||||
"actionUpdateOrg": "Aggiorna Organizzazione",
|
||||
"actionGetOrgUser": "Ottieni Utente Organizzazione",
|
||||
"actionListOrgDomains": "Elenca Domini Organizzazione",
|
||||
"actionCreateSite": "Crea Sito",
|
||||
"actionDeleteSite": "Elimina Sito",
|
||||
"actionGetSite": "Ottieni Sito",
|
||||
"actionListSites": "Elenca Siti",
|
||||
"actionUpdateSite": "Aggiorna Sito",
|
||||
"actionListSiteRoles": "Elenca Ruoli Sito Consentiti",
|
||||
"actionCreateResource": "Crea Risorsa",
|
||||
"actionDeleteResource": "Elimina Risorsa",
|
||||
"actionGetResource": "Ottieni Risorsa",
|
||||
"actionListResource": "Elenca Risorse",
|
||||
"actionUpdateResource": "Aggiorna Risorsa",
|
||||
"actionListResourceUsers": "Elenca Utenti Risorsa",
|
||||
"actionSetResourceUsers": "Imposta Utenti Risorsa",
|
||||
"actionSetAllowedResourceRoles": "Imposta Ruoli Risorsa Consentiti",
|
||||
"actionListAllowedResourceRoles": "Elenca Ruoli Risorsa Consentiti",
|
||||
"actionSetResourcePassword": "Imposta Password Risorsa",
|
||||
"actionSetResourcePincode": "Imposta Codice PIN Risorsa",
|
||||
"actionSetResourceEmailWhitelist": "Imposta Lista Autorizzazioni Email Risorsa",
|
||||
"actionGetResourceEmailWhitelist": "Ottieni Lista Autorizzazioni Email Risorsa",
|
||||
"actionCreateTarget": "Crea Target",
|
||||
"actionDeleteTarget": "Elimina Target",
|
||||
"actionGetTarget": "Ottieni Target",
|
||||
"actionListTargets": "Elenca Target",
|
||||
"actionUpdateTarget": "Aggiorna Target",
|
||||
"actionCreateRole": "Crea Ruolo",
|
||||
"actionDeleteRole": "Elimina Ruolo",
|
||||
"actionGetRole": "Ottieni Ruolo",
|
||||
"actionListRole": "Elenca Ruoli",
|
||||
"actionUpdateRole": "Aggiorna Ruolo",
|
||||
"actionListAllowedRoleResources": "Elenca Risorse Ruolo Consentite",
|
||||
"actionInviteUser": "Invita Utente",
|
||||
"actionRemoveUser": "Rimuovi Utente",
|
||||
"actionListUsers": "Elenca Utenti",
|
||||
"actionAddUserRole": "Aggiungi Ruolo Utente",
|
||||
"actionGenerateAccessToken": "Genera Token di Accesso",
|
||||
"actionDeleteAccessToken": "Elimina Token di Accesso",
|
||||
"actionListAccessTokens": "Elenca Token di Accesso",
|
||||
"actionCreateResourceRule": "Crea Regola Risorsa",
|
||||
"actionDeleteResourceRule": "Elimina Regola Risorsa",
|
||||
"actionListResourceRules": "Elenca Regole Risorsa",
|
||||
"actionUpdateResourceRule": "Aggiorna Regola Risorsa",
|
||||
"actionListOrgs": "Elenca Organizzazioni",
|
||||
"actionCheckOrgId": "Controlla ID",
|
||||
"actionCreateOrg": "Crea Organizzazione",
|
||||
"actionDeleteOrg": "Elimina Organizzazione",
|
||||
"actionListApiKeys": "Elenca Chiavi API",
|
||||
"actionListApiKeyActions": "Elenca Azioni Chiave API",
|
||||
"actionSetApiKeyActions": "Imposta Azioni Consentite Chiave API",
|
||||
"actionCreateApiKey": "Crea Chiave API",
|
||||
"actionDeleteApiKey": "Elimina Chiave API",
|
||||
"actionCreateIdp": "Crea IDP",
|
||||
"actionUpdateIdp": "Aggiorna IDP",
|
||||
"actionDeleteIdp": "Elimina IDP",
|
||||
"actionListIdps": "Elenca IDP",
|
||||
"actionGetIdp": "Ottieni IDP",
|
||||
"actionCreateIdpOrg": "Crea Politica Org IDP",
|
||||
"actionDeleteIdpOrg": "Elimina Politica Org IDP",
|
||||
"actionListIdpOrgs": "Elenca Org IDP",
|
||||
"actionUpdateIdpOrg": "Aggiorna Org IDP",
|
||||
"noneSelected": "Nessuna selezione",
|
||||
"orgNotFound2": "Nessuna organizzazione trovata.",
|
||||
"searchProgress": "Ricerca...",
|
||||
"create": "Crea",
|
||||
"orgs": "Organizzazioni",
|
||||
"loginError": "Si è verificato un errore durante l'accesso",
|
||||
"passwordForgot": "Password dimenticata?",
|
||||
"otpAuth": "Autenticazione a Due Fattori",
|
||||
"otpAuthDescription": "Inserisci il codice dalla tua app di autenticazione o uno dei tuoi codici di backup monouso.",
|
||||
"otpAuthSubmit": "Invia Codice",
|
||||
"idpContinue": "O continua con",
|
||||
"otpAuthBack": "Torna al Login",
|
||||
"navbar": "Menu di Navigazione",
|
||||
"navbarDescription": "Menu di navigazione principale dell'applicazione",
|
||||
"navbarDocsLink": "Documentazione",
|
||||
"commercialEdition": "Edizione Commerciale",
|
||||
"otpErrorEnable": "Impossibile abilitare 2FA",
|
||||
"otpErrorEnableDescription": "Si è verificato un errore durante l'abilitazione di 2FA",
|
||||
"otpSetupCheckCode": "Inserisci un codice a 6 cifre",
|
||||
"otpSetupCheckCodeRetry": "Codice non valido. Riprova.",
|
||||
"otpSetup": "Abilita Autenticazione a Due Fattori",
|
||||
"otpSetupDescription": "Proteggi il tuo account con un livello extra di protezione",
|
||||
"otpSetupScanQr": "Scansiona questo codice QR con la tua app di autenticazione o inserisci manualmente la chiave segreta:",
|
||||
"otpSetupSecretCode": "Codice Autenticatore",
|
||||
"otpSetupSuccess": "Autenticazione a Due Fattori Abilitata",
|
||||
"otpSetupSuccessStoreBackupCodes": "Il tuo account è ora più sicuro. Non dimenticare di salvare i tuoi codici di backup.",
|
||||
"otpErrorDisable": "Impossibile disabilitare 2FA",
|
||||
"otpErrorDisableDescription": "Si è verificato un errore durante la disabilitazione di 2FA",
|
||||
"otpRemove": "Disabilita Autenticazione a Due Fattori",
|
||||
"otpRemoveDescription": "Disabilita l'autenticazione a due fattori per il tuo account",
|
||||
"otpRemoveSuccess": "Autenticazione a Due Fattori Disabilitata",
|
||||
"otpRemoveSuccessMessage": "L'autenticazione a due fattori è stata disabilitata per il tuo account. Puoi riattivarla in qualsiasi momento.",
|
||||
"otpRemoveSubmit": "Disabilita 2FA",
|
||||
"paginator": "Pagina {current} di {last}",
|
||||
"paginatorToFirst": "Vai alla prima pagina",
|
||||
"paginatorToPrevious": "Vai alla pagina precedente",
|
||||
"paginatorToNext": "Vai alla pagina successiva",
|
||||
"paginatorToLast": "Vai all'ultima pagina",
|
||||
"copyText": "Copia testo",
|
||||
"copyTextFailed": "Impossibile copiare il testo: ",
|
||||
"copyTextClipboard": "Copia negli appunti",
|
||||
"inviteErrorInvalidConfirmation": "Conferma non valida"
|
||||
}
|
||||
|
|
|
@ -816,7 +816,7 @@
|
|||
"emailVerifyDescription": "Wprowadź kod weryfikacyjny wysłany na Twój adres e-mail.",
|
||||
"verificationCode": "Kod weryfikacyjny",
|
||||
"verificationCodeEmailSent": "Wysłaliśmy kod weryfikacyjny na Twój adres e-mail.",
|
||||
"emailVerifySubmit": "Wyślij",
|
||||
"submit": "Wyślij",
|
||||
"emailVerifyResendProgress": "Ponowne wysyłanie...",
|
||||
"emailVerifyResend": "Nie otrzymałeś kodu? Kliknij tutaj, aby wysłać ponownie",
|
||||
"passwordNotMatch": "Hasła nie są zgodne",
|
||||
|
@ -907,5 +907,157 @@
|
|||
"usersAll": "Wszyscy użytkownicy",
|
||||
"license": "Licencja",
|
||||
"pangolinDashboard": "Panel - Pangolin",
|
||||
"noResults": "Nie znaleziono wyników."
|
||||
"noResults": "Nie znaleziono wyników.",
|
||||
"terabytes": "{count} TB",
|
||||
"gigabytes": "{count} GB",
|
||||
"megabytes": "{count} MB",
|
||||
"tagsEntered": "Wprowadzone tagi",
|
||||
"tagsEnteredDescription": "To są wprowadzone przez ciebie tagi.",
|
||||
"tagsWarnCannotBeLessThanZero": "maxTags i minTags nie mogą być mniejsze od 0",
|
||||
"tagsWarnNotAllowedAutocompleteOptions": "Tag niedozwolony zgodnie z opcjami autouzupełniania",
|
||||
"tagsWarnInvalid": "Nieprawidłowy tag według validateTag",
|
||||
"tagWarnTooShort": "Tag {tagText} jest za krótki",
|
||||
"tagWarnTooLong": "Tag {tagText} jest za długi",
|
||||
"tagsWarnReachedMaxNumber": "Osiągnięto maksymalną dozwoloną liczbę tagów",
|
||||
"tagWarnDuplicate": "Zduplikowany tag {tagText} nie został dodany",
|
||||
"supportKeyInvalid": "Nieprawidłowy klucz",
|
||||
"supportKeyInvalidDescription": "Twój klucz wspierający jest nieprawidłowy.",
|
||||
"supportKeyValid": "Prawidłowy klucz",
|
||||
"supportKeyValidDescription": "Twój klucz wspierający został zweryfikowany. Dziękujemy za wsparcie!",
|
||||
"supportKeyErrorValidationDescription": "Nie udało się zweryfikować klucza wspierającego.",
|
||||
"supportKey": "Wesprzyj rozwój i adoptuj Pangolina!",
|
||||
"supportKeyDescription": "Kup klucz wspierający, aby pomóc nam w dalszym rozwijaniu Pangolina dla społeczności. Twój wkład pozwala nam poświęcić więcej czasu na utrzymanie i dodawanie nowych funkcji do aplikacji dla wszystkich. Nigdy nie wykorzystamy tego do blokowania funkcji za paywallem. Jest to oddzielne od wydania komercyjnego.",
|
||||
"supportKeyPet": "Będziesz mógł także zaadoptować i poznać swojego własnego zwierzaka Pangolina!",
|
||||
"supportKeyPurchase": "Płatności są przetwarzane przez GitHub. Następnie możesz pobrać swój klucz na",
|
||||
"supportKeyPurchaseLink": "naszej stronie",
|
||||
"supportKeyPurchase2": "i wykorzystać go tutaj.",
|
||||
"supportKeyLearnMore": "Dowiedz się więcej.",
|
||||
"supportKeyOptions": "Wybierz opcję, która najbardziej ci odpowiada.",
|
||||
"supportKetOptionFull": "Pełne wsparcie",
|
||||
"forWholeServer": "Dla całego serwera",
|
||||
"lifetimePurchase": "Zakup dożywotni",
|
||||
"supporterStatus": "Status wspierającego",
|
||||
"buy": "Kup",
|
||||
"supportKeyOptionLimited": "Ograniczone wsparcie",
|
||||
"forFiveUsers": "Dla 5 lub mniej użytkowników",
|
||||
"supportKeyRedeem": "Wykorzystaj klucz wspierający",
|
||||
"supportKeyHideSevenDays": "Ukryj na 7 dni",
|
||||
"supportKeyEnter": "Wprowadź klucz wspierający",
|
||||
"supportKeyEnterDescription": "Poznaj swojego własnego zwierzaka Pangolina!",
|
||||
"githubUsername": "Nazwa użytkownika GitHub",
|
||||
"supportKeyInput": "Klucz wspierający",
|
||||
"supportKeyBuy": "Kup klucz wspierający",
|
||||
"logoutError": "Błąd podczas wylogowywania",
|
||||
"signingAs": "Zalogowany jako",
|
||||
"serverAdmin": "Administrator serwera",
|
||||
"otpEnable": "Włącz uwierzytelnianie dwuskładnikowe",
|
||||
"otpDisable": "Wyłącz uwierzytelnianie dwuskładnikowe",
|
||||
"logout": "Wyloguj się",
|
||||
"licenseTierProfessionalRequired": "Wymagana edycja Professional",
|
||||
"licenseTierProfessionalRequiredDescription": "Ta funkcja jest dostępna tylko w edycji Professional.",
|
||||
"actionGetOrg": "Pobierz organizację",
|
||||
"actionUpdateOrg": "Aktualizuj organizację",
|
||||
"actionGetOrgUser": "Pobierz użytkownika organizacji",
|
||||
"actionListOrgDomains": "Lista domen organizacji",
|
||||
"actionCreateSite": "Utwórz witrynę",
|
||||
"actionDeleteSite": "Usuń witrynę",
|
||||
"actionGetSite": "Pobierz witrynę",
|
||||
"actionListSites": "Lista witryn",
|
||||
"actionUpdateSite": "Aktualizuj witrynę",
|
||||
"actionListSiteRoles": "Lista dozwolonych ról witryny",
|
||||
"actionCreateResource": "Utwórz zasób",
|
||||
"actionDeleteResource": "Usuń zasób",
|
||||
"actionGetResource": "Pobierz zasób",
|
||||
"actionListResource": "Lista zasobów",
|
||||
"actionUpdateResource": "Aktualizuj zasób",
|
||||
"actionListResourceUsers": "Lista użytkowników zasobu",
|
||||
"actionSetResourceUsers": "Ustaw użytkowników zasobu",
|
||||
"actionSetAllowedResourceRoles": "Ustaw dozwolone role zasobu",
|
||||
"actionListAllowedResourceRoles": "Lista dozwolonych ról zasobu",
|
||||
"actionSetResourcePassword": "Ustaw hasło zasobu",
|
||||
"actionSetResourcePincode": "Ustaw kod PIN zasobu",
|
||||
"actionSetResourceEmailWhitelist": "Ustaw białą listę email zasobu",
|
||||
"actionGetResourceEmailWhitelist": "Pobierz białą listę email zasobu",
|
||||
"actionCreateTarget": "Utwórz cel",
|
||||
"actionDeleteTarget": "Usuń cel",
|
||||
"actionGetTarget": "Pobierz cel",
|
||||
"actionListTargets": "Lista celów",
|
||||
"actionUpdateTarget": "Aktualizuj cel",
|
||||
"actionCreateRole": "Utwórz rolę",
|
||||
"actionDeleteRole": "Usuń rolę",
|
||||
"actionGetRole": "Pobierz rolę",
|
||||
"actionListRole": "Lista ról",
|
||||
"actionUpdateRole": "Aktualizuj rolę",
|
||||
"actionListAllowedRoleResources": "Lista dozwolonych zasobów roli",
|
||||
"actionInviteUser": "Zaproś użytkownika",
|
||||
"actionRemoveUser": "Usuń użytkownika",
|
||||
"actionListUsers": "Lista użytkowników",
|
||||
"actionAddUserRole": "Dodaj rolę użytkownika",
|
||||
"actionGenerateAccessToken": "Wygeneruj token dostępu",
|
||||
"actionDeleteAccessToken": "Usuń token dostępu",
|
||||
"actionListAccessTokens": "Lista tokenów dostępu",
|
||||
"actionCreateResourceRule": "Utwórz regułę zasobu",
|
||||
"actionDeleteResourceRule": "Usuń regułę zasobu",
|
||||
"actionListResourceRules": "Lista reguł zasobu",
|
||||
"actionUpdateResourceRule": "Aktualizuj regułę zasobu",
|
||||
"actionListOrgs": "Lista organizacji",
|
||||
"actionCheckOrgId": "Sprawdź ID",
|
||||
"actionCreateOrg": "Utwórz organizację",
|
||||
"actionDeleteOrg": "Usuń organizację",
|
||||
"actionListApiKeys": "Lista kluczy API",
|
||||
"actionListApiKeyActions": "Lista akcji klucza API",
|
||||
"actionSetApiKeyActions": "Ustaw dozwolone akcje klucza API",
|
||||
"actionCreateApiKey": "Utwórz klucz API",
|
||||
"actionDeleteApiKey": "Usuń klucz API",
|
||||
"actionCreateIdp": "Utwórz IDP",
|
||||
"actionUpdateIdp": "Aktualizuj IDP",
|
||||
"actionDeleteIdp": "Usuń IDP",
|
||||
"actionListIdps": "Lista IDP",
|
||||
"actionGetIdp": "Pobierz IDP",
|
||||
"actionCreateIdpOrg": "Utwórz politykę organizacji IDP",
|
||||
"actionDeleteIdpOrg": "Usuń politykę organizacji IDP",
|
||||
"actionListIdpOrgs": "Lista organizacji IDP",
|
||||
"actionUpdateIdpOrg": "Aktualizuj organizację IDP",
|
||||
"noneSelected": "Nie wybrano",
|
||||
"orgNotFound2": "Nie znaleziono organizacji.",
|
||||
"searchProgress": "Szukaj...",
|
||||
"create": "Utwórz",
|
||||
"orgs": "Organizacje",
|
||||
"loginError": "Wystąpił błąd podczas logowania",
|
||||
"passwordForgot": "Zapomniałeś hasła?",
|
||||
"otpAuth": "Uwierzytelnianie dwuskładnikowe",
|
||||
"otpAuthDescription": "Wprowadź kod z aplikacji uwierzytelniającej lub jeden z jednorazowych kodów zapasowych.",
|
||||
"otpAuthSubmit": "Wyślij kod",
|
||||
"idpContinue": "Lub kontynuuj z",
|
||||
"otpAuthBack": "Powrót do logowania",
|
||||
"navbar": "Menu nawigacyjne",
|
||||
"navbarDescription": "Główne menu nawigacyjne aplikacji",
|
||||
"navbarDocsLink": "Dokumentacja",
|
||||
"commercialEdition": "Edycja komercyjna",
|
||||
"otpErrorEnable": "Nie można włączyć 2FA",
|
||||
"otpErrorEnableDescription": "Wystąpił błąd podczas włączania 2FA",
|
||||
"otpSetupCheckCode": "Wprowadź 6-cyfrowy kod",
|
||||
"otpSetupCheckCodeRetry": "Nieprawidłowy kod. Spróbuj ponownie.",
|
||||
"otpSetup": "Włącz uwierzytelnianie dwuskładnikowe",
|
||||
"otpSetupDescription": "Zabezpiecz swoje konto dodatkową warstwą ochrony",
|
||||
"otpSetupScanQr": "Zeskanuj ten kod QR za pomocą aplikacji uwierzytelniającej lub wprowadź klucz tajny ręcznie:",
|
||||
"otpSetupSecretCode": "Kod uwierzytelniający",
|
||||
"otpSetupSuccess": "Włączono uwierzytelnianie dwuskładnikowe",
|
||||
"otpSetupSuccessStoreBackupCodes": "Twoje konto jest teraz bezpieczniejsze. Nie zapomnij zapisać kodów zapasowych.",
|
||||
"otpErrorDisable": "Nie można wyłączyć 2FA",
|
||||
"otpErrorDisableDescription": "Wystąpił błąd podczas wyłączania 2FA",
|
||||
"otpRemove": "Wyłącz uwierzytelnianie dwuskładnikowe",
|
||||
"otpRemoveDescription": "Wyłącz uwierzytelnianie dwuskładnikowe dla swojego konta",
|
||||
"otpRemoveSuccess": "Wyłączono uwierzytelnianie dwuskładnikowe",
|
||||
"otpRemoveSuccessMessage": "Uwierzytelnianie dwuskładnikowe zostało wyłączone dla Twojego konta. Możesz je włączyć ponownie w dowolnym momencie.",
|
||||
"otpRemoveSubmit": "Wyłącz 2FA",
|
||||
"paginator": "Strona {current} z {last}",
|
||||
"paginatorToFirst": "Przejdź do pierwszej strony",
|
||||
"paginatorToPrevious": "Przejdź do poprzedniej strony",
|
||||
"paginatorToNext": "Przejdź do następnej strony",
|
||||
"paginatorToLast": "Przejdź do ostatniej strony",
|
||||
"copyText": "Kopiuj tekst",
|
||||
"copyTextFailed": "Nie udało się skopiować tekstu: ",
|
||||
"copyTextClipboard": "Kopiuj do schowka",
|
||||
"inviteErrorInvalidConfirmation": "Nieprawidłowe potwierdzenie"
|
||||
}
|
||||
|
|
|
@ -816,7 +816,7 @@
|
|||
"emailVerifyDescription": "Insira o código de verificação enviado para o seu email.",
|
||||
"verificationCode": "Código de Verificação",
|
||||
"verificationCodeEmailSent": "Enviámos um código de verificação para o seu email.",
|
||||
"emailVerifySubmit": "Submeter",
|
||||
"submit": "Submeter",
|
||||
"emailVerifyResendProgress": "A reenviar...",
|
||||
"emailVerifyResend": "Não recebeu um código? Clique aqui para reenviar",
|
||||
"passwordNotMatch": "As palavras-passe não correspondem",
|
||||
|
@ -907,5 +907,157 @@
|
|||
"usersAll": "Todos os Usuários",
|
||||
"license": "Licença",
|
||||
"pangolinDashboard": "Painel - Pangolin",
|
||||
"noResults": "Nenhum resultado encontrado."
|
||||
"noResults": "Nenhum resultado encontrado.",
|
||||
"terabytes": "{count} TB",
|
||||
"gigabytes": "{count} GB",
|
||||
"megabytes": "{count} MB",
|
||||
"tagsEntered": "Tags Inseridas",
|
||||
"tagsEnteredDescription": "Estas são as tags que você inseriu.",
|
||||
"tagsWarnCannotBeLessThanZero": "maxTags e minTags não podem ser menores que 0",
|
||||
"tagsWarnNotAllowedAutocompleteOptions": "Tag não permitida conforme as opções de autocompletar",
|
||||
"tagsWarnInvalid": "Tag inválida conforme validateTag",
|
||||
"tagWarnTooShort": "A tag {tagText} é muito curta",
|
||||
"tagWarnTooLong": "A tag {tagText} é muito longa",
|
||||
"tagsWarnReachedMaxNumber": "Atingido o número máximo de tags permitidas",
|
||||
"tagWarnDuplicate": "Tag duplicada {tagText} não adicionada",
|
||||
"supportKeyInvalid": "Chave Inválida",
|
||||
"supportKeyInvalidDescription": "A sua chave de suporte é inválida.",
|
||||
"supportKeyValid": "Chave Válida",
|
||||
"supportKeyValidDescription": "A sua chave de suporte foi validada. Obrigado pelo seu apoio!",
|
||||
"supportKeyErrorValidationDescription": "Falha ao validar a chave de suporte.",
|
||||
"supportKey": "Apoie o Desenvolvimento e Adote um Pangolim!",
|
||||
"supportKeyDescription": "Compre uma chave de suporte para nos ajudar a continuar desenvolvendo o Pangolin para a comunidade. A sua contribuição permite-nos dedicar mais tempo para manter e adicionar novos recursos à aplicação para todos. Nunca usaremos isto para restringir recursos. Isto é separado de qualquer Edição Comercial.",
|
||||
"supportKeyPet": "Também poderá adotar e conhecer o seu próprio Pangolim de estimação!",
|
||||
"supportKeyPurchase": "Os pagamentos são processados via GitHub. Depois, pode obter a sua chave em",
|
||||
"supportKeyPurchaseLink": "nosso site",
|
||||
"supportKeyPurchase2": "e resgatá-la aqui.",
|
||||
"supportKeyLearnMore": "Saiba mais.",
|
||||
"supportKeyOptions": "Por favor, selecione a opção que melhor se adequa a si.",
|
||||
"supportKetOptionFull": "Apoiante Completo",
|
||||
"forWholeServer": "Para todo o servidor",
|
||||
"lifetimePurchase": "Compra vitalícia",
|
||||
"supporterStatus": "Estado de apoiante",
|
||||
"buy": "Comprar",
|
||||
"supportKeyOptionLimited": "Apoiante Limitado",
|
||||
"forFiveUsers": "Para 5 ou menos utilizadores",
|
||||
"supportKeyRedeem": "Resgatar Chave de Apoiante",
|
||||
"supportKeyHideSevenDays": "Ocultar por 7 dias",
|
||||
"supportKeyEnter": "Inserir Chave de Apoiante",
|
||||
"supportKeyEnterDescription": "Conheça o seu próprio Pangolim de estimação!",
|
||||
"githubUsername": "Nome de Utilizador GitHub",
|
||||
"supportKeyInput": "Chave de Apoiante",
|
||||
"supportKeyBuy": "Comprar Chave de Apoiante",
|
||||
"logoutError": "Erro ao terminar sessão",
|
||||
"signingAs": "Sessão iniciada como",
|
||||
"serverAdmin": "Administrador do Servidor",
|
||||
"otpEnable": "Ativar Autenticação de Dois Fatores",
|
||||
"otpDisable": "Desativar Autenticação de Dois Fatores",
|
||||
"logout": "Terminar Sessão",
|
||||
"licenseTierProfessionalRequired": "Edição Profissional Necessária",
|
||||
"licenseTierProfessionalRequiredDescription": "Esta funcionalidade só está disponível na Edição Profissional.",
|
||||
"actionGetOrg": "Obter Organização",
|
||||
"actionUpdateOrg": "Atualizar Organização",
|
||||
"actionGetOrgUser": "Obter Utilizador da Organização",
|
||||
"actionListOrgDomains": "Listar Domínios da Organização",
|
||||
"actionCreateSite": "Criar Site",
|
||||
"actionDeleteSite": "Eliminar Site",
|
||||
"actionGetSite": "Obter Site",
|
||||
"actionListSites": "Listar Sites",
|
||||
"actionUpdateSite": "Atualizar Site",
|
||||
"actionListSiteRoles": "Listar Funções Permitidas do Site",
|
||||
"actionCreateResource": "Criar Recurso",
|
||||
"actionDeleteResource": "Eliminar Recurso",
|
||||
"actionGetResource": "Obter Recurso",
|
||||
"actionListResource": "Listar Recursos",
|
||||
"actionUpdateResource": "Atualizar Recurso",
|
||||
"actionListResourceUsers": "Listar Utilizadores do Recurso",
|
||||
"actionSetResourceUsers": "Definir Utilizadores do Recurso",
|
||||
"actionSetAllowedResourceRoles": "Definir Funções Permitidas do Recurso",
|
||||
"actionListAllowedResourceRoles": "Listar Funções Permitidas do Recurso",
|
||||
"actionSetResourcePassword": "Definir Palavra-passe do Recurso",
|
||||
"actionSetResourcePincode": "Definir Código PIN do Recurso",
|
||||
"actionSetResourceEmailWhitelist": "Definir Lista Permitida de Emails do Recurso",
|
||||
"actionGetResourceEmailWhitelist": "Obter Lista Permitida de Emails do Recurso",
|
||||
"actionCreateTarget": "Criar Alvo",
|
||||
"actionDeleteTarget": "Eliminar Alvo",
|
||||
"actionGetTarget": "Obter Alvo",
|
||||
"actionListTargets": "Listar Alvos",
|
||||
"actionUpdateTarget": "Atualizar Alvo",
|
||||
"actionCreateRole": "Criar Função",
|
||||
"actionDeleteRole": "Eliminar Função",
|
||||
"actionGetRole": "Obter Função",
|
||||
"actionListRole": "Listar Funções",
|
||||
"actionUpdateRole": "Atualizar Função",
|
||||
"actionListAllowedRoleResources": "Listar Recursos Permitidos da Função",
|
||||
"actionInviteUser": "Convidar Utilizador",
|
||||
"actionRemoveUser": "Remover Utilizador",
|
||||
"actionListUsers": "Listar Utilizadores",
|
||||
"actionAddUserRole": "Adicionar Função ao Utilizador",
|
||||
"actionGenerateAccessToken": "Gerar Token de Acesso",
|
||||
"actionDeleteAccessToken": "Eliminar Token de Acesso",
|
||||
"actionListAccessTokens": "Listar Tokens de Acesso",
|
||||
"actionCreateResourceRule": "Criar Regra de Recurso",
|
||||
"actionDeleteResourceRule": "Eliminar Regra de Recurso",
|
||||
"actionListResourceRules": "Listar Regras de Recurso",
|
||||
"actionUpdateResourceRule": "Atualizar Regra de Recurso",
|
||||
"actionListOrgs": "Listar Organizações",
|
||||
"actionCheckOrgId": "Verificar ID",
|
||||
"actionCreateOrg": "Criar Organização",
|
||||
"actionDeleteOrg": "Eliminar Organização",
|
||||
"actionListApiKeys": "Listar Chaves API",
|
||||
"actionListApiKeyActions": "Listar Ações da Chave API",
|
||||
"actionSetApiKeyActions": "Definir Ações Permitidas da Chave API",
|
||||
"actionCreateApiKey": "Criar Chave API",
|
||||
"actionDeleteApiKey": "Eliminar Chave API",
|
||||
"actionCreateIdp": "Criar IDP",
|
||||
"actionUpdateIdp": "Atualizar IDP",
|
||||
"actionDeleteIdp": "Eliminar IDP",
|
||||
"actionListIdps": "Listar IDP",
|
||||
"actionGetIdp": "Obter IDP",
|
||||
"actionCreateIdpOrg": "Criar Política de Organização IDP",
|
||||
"actionDeleteIdpOrg": "Eliminar Política de Organização IDP",
|
||||
"actionListIdpOrgs": "Listar Organizações IDP",
|
||||
"actionUpdateIdpOrg": "Atualizar Organização IDP",
|
||||
"noneSelected": "Nenhum selecionado",
|
||||
"orgNotFound2": "Nenhuma organização encontrada.",
|
||||
"searchProgress": "Pesquisar...",
|
||||
"create": "Criar",
|
||||
"orgs": "Organizações",
|
||||
"loginError": "Ocorreu um erro ao iniciar sessão",
|
||||
"passwordForgot": "Esqueceu a sua palavra-passe?",
|
||||
"otpAuth": "Autenticação de Dois Fatores",
|
||||
"otpAuthDescription": "Insira o código da sua aplicação de autenticação ou um dos seus códigos de backup de uso único.",
|
||||
"otpAuthSubmit": "Submeter Código",
|
||||
"idpContinue": "Ou continuar com",
|
||||
"otpAuthBack": "Voltar ao Início de Sessão",
|
||||
"navbar": "Menu de Navegação",
|
||||
"navbarDescription": "Menu de navegação principal da aplicação",
|
||||
"navbarDocsLink": "Documentação",
|
||||
"commercialEdition": "Edição Comercial",
|
||||
"otpErrorEnable": "Não foi possível ativar 2FA",
|
||||
"otpErrorEnableDescription": "Ocorreu um erro ao ativar 2FA",
|
||||
"otpSetupCheckCode": "Por favor, insira um código de 6 dígitos",
|
||||
"otpSetupCheckCodeRetry": "Código inválido. Por favor, tente novamente.",
|
||||
"otpSetup": "Ativar Autenticação de Dois Fatores",
|
||||
"otpSetupDescription": "Proteja a sua conta com uma camada extra de proteção",
|
||||
"otpSetupScanQr": "Digitalize este código QR com a sua aplicação de autenticação ou insira a chave secreta manualmente:",
|
||||
"otpSetupSecretCode": "Código de Autenticação",
|
||||
"otpSetupSuccess": "Autenticação de Dois Fatores Ativada",
|
||||
"otpSetupSuccessStoreBackupCodes": "A sua conta está agora mais segura. Não se esqueça de guardar os seus códigos de backup.",
|
||||
"otpErrorDisable": "Não foi possível desativar 2FA",
|
||||
"otpErrorDisableDescription": "Ocorreu um erro ao desativar 2FA",
|
||||
"otpRemove": "Desativar Autenticação de Dois Fatores",
|
||||
"otpRemoveDescription": "Desativar a autenticação de dois fatores para a sua conta",
|
||||
"otpRemoveSuccess": "Autenticação de Dois Fatores Desativada",
|
||||
"otpRemoveSuccessMessage": "A autenticação de dois fatores foi desativada para a sua conta. Pode ativá-la novamente a qualquer momento.",
|
||||
"otpRemoveSubmit": "Desativar 2FA",
|
||||
"paginator": "Página {current} de {last}",
|
||||
"paginatorToFirst": "Ir para a primeira página",
|
||||
"paginatorToPrevious": "Ir para a página anterior",
|
||||
"paginatorToNext": "Ir para a próxima página",
|
||||
"paginatorToLast": "Ir para a última página",
|
||||
"copyText": "Copiar texto",
|
||||
"copyTextFailed": "Falha ao copiar texto: ",
|
||||
"copyTextClipboard": "Copiar para a área de transferência",
|
||||
"inviteErrorInvalidConfirmation": "Confirmação inválida"
|
||||
}
|
||||
|
|
|
@ -816,7 +816,7 @@
|
|||
"emailVerifyDescription": "Enter the verification code sent to your email address.",
|
||||
"verificationCode": "Verification Code",
|
||||
"verificationCodeEmailSent": "We sent a verification code to your email address.",
|
||||
"emailVerifySubmit": "Submit",
|
||||
"submit": "Submit",
|
||||
"emailVerifyResendProgress": "Resending...",
|
||||
"emailVerifyResend": "Didn't receive a code? Click here to resend",
|
||||
"passwordNotMatch": "Passwords do not match",
|
||||
|
@ -907,5 +907,157 @@
|
|||
"usersAll": "All Users",
|
||||
"license": "License",
|
||||
"pangolinDashboard": "Dashboard - Pangolin",
|
||||
"noResults": "No results found."
|
||||
"noResults": "No results found.",
|
||||
"terabytes": "{count} TB",
|
||||
"gigabytes": "{count} GB",
|
||||
"megabytes": "{count} MB",
|
||||
"tagsEntered": "Entered Tags",
|
||||
"tagsEnteredDescription": "These are the tags you`ve entered.",
|
||||
"tagsWarnCannotBeLessThanZero": "maxTags and minTags cannot be less than 0",
|
||||
"tagsWarnNotAllowedAutocompleteOptions": "Tag not allowed as per autocomplete options",
|
||||
"tagsWarnInvalid": "Invalid tag as per validateTag",
|
||||
"tagWarnTooShort": "Tag {tagText} is too short",
|
||||
"tagWarnTooLong": "Tag {tagText} is too long",
|
||||
"tagsWarnReachedMaxNumber": "Reached the maximum number of tags allowed",
|
||||
"tagWarnDuplicate": "Duplicate tag {tagText} not added",
|
||||
"supportKeyInvalid": "Invalid Key",
|
||||
"supportKeyInvalidDescription": "Your supporter key is invalid.",
|
||||
"supportKeyValid": "Valid Key",
|
||||
"supportKeyValidDescription": "Your supporter key has been validated. Thank you for your support!",
|
||||
"supportKeyErrorValidationDescription": "Failed to validate supporter key.",
|
||||
"supportKey": "Support Development and Adopt a Pangolin!",
|
||||
"supportKeyDescription": "Purchase a supporter key to help us continue developing Pangolin for the community. Your contribution allows us to commit more time to maintain and add new features to the application for everyone. We will never use this to paywall features. This is separate from any Commercial Edition.",
|
||||
"supportKeyPet": "You will also get to adopt and meet your very own pet Pangolin!",
|
||||
"supportKeyPurchase": "Payments are processed via GitHub. Afterward, you can retrieve your key on",
|
||||
"supportKeyPurchaseLink": "our website",
|
||||
"supportKeyPurchase2": "and redeem it here.",
|
||||
"supportKeyLearnMore": "Learn more.",
|
||||
"supportKeyOptions": "Please select the option that best suits you.",
|
||||
"supportKetOptionFull": "Full Supporter",
|
||||
"forWholeServer": "For the whole server",
|
||||
"lifetimePurchase": "Lifetime purchase",
|
||||
"supporterStatus": "Supporter status",
|
||||
"buy": "Buy",
|
||||
"supportKeyOptionLimited": "Limited Supporter",
|
||||
"forFiveUsers": "For 5 or less users",
|
||||
"supportKeyRedeem": "Redeem Supporter Key",
|
||||
"supportKeyHideSevenDays": "Hide for 7 days",
|
||||
"supportKeyEnter": "Enter Supporter Key",
|
||||
"supportKeyEnterDescription": "Meet your very own pet Pangolin!",
|
||||
"githubUsername": "GitHub Username",
|
||||
"supportKeyInput": "Supporter Key",
|
||||
"supportKeyBuy": "Buy Supporter Key",
|
||||
"logoutError": "Error logging out",
|
||||
"signingAs": "Signed in as",
|
||||
"serverAdmin": "Server Admin",
|
||||
"otpEnable": "Enable Two-factor",
|
||||
"otpDisable": "Disable Two-factor",
|
||||
"logout": "Log Out",
|
||||
"licenseTierProfessionalRequired": "Professional Edition Required",
|
||||
"licenseTierProfessionalRequiredDescription": "This feature is only available in the Professional Edition.",
|
||||
"actionGetOrg": "Get Organization",
|
||||
"actionUpdateOrg": "Update Organization",
|
||||
"actionGetOrgUser": "Get Organization User",
|
||||
"actionListOrgDomains": "List Organization Domains",
|
||||
"actionCreateSite": "Create Site",
|
||||
"actionDeleteSite": "Delete Site",
|
||||
"actionGetSite": "Get Site",
|
||||
"actionListSites": "List Sites",
|
||||
"actionUpdateSite": "Update Site",
|
||||
"actionListSiteRoles": "List Allowed Site Roles",
|
||||
"actionCreateResource": "Create Resource",
|
||||
"actionDeleteResource": "Delete Resource",
|
||||
"actionGetResource": "Get Resource",
|
||||
"actionListResource": "List Resources",
|
||||
"actionUpdateResource": "Update Resource",
|
||||
"actionListResourceUsers": "List Resource Users",
|
||||
"actionSetResourceUsers": "Set Resource Users",
|
||||
"actionSetAllowedResourceRoles": "Set Allowed Resource Roles",
|
||||
"actionListAllowedResourceRoles": "List Allowed Resource Roles",
|
||||
"actionSetResourcePassword": "Set Resource Password",
|
||||
"actionSetResourcePincode": "Set Resource Pincode",
|
||||
"actionSetResourceEmailWhitelist": "Set Resource Email Whitelist",
|
||||
"actionGetResourceEmailWhitelist": "Get Resource Email Whitelist",
|
||||
"actionCreateTarget": "Create Target",
|
||||
"actionDeleteTarget": "Delete Target",
|
||||
"actionGetTarget": "Get Target",
|
||||
"actionListTargets": "List Targets",
|
||||
"actionUpdateTarget": "Update Target",
|
||||
"actionCreateRole": "Create Role",
|
||||
"actionDeleteRole": "Delete Role",
|
||||
"actionGetRole": "Get Role",
|
||||
"actionListRole": "List Roles",
|
||||
"actionUpdateRole": "Update Role",
|
||||
"actionListAllowedRoleResources": "List Allowed Role Resources",
|
||||
"actionInviteUser": "Invite User",
|
||||
"actionRemoveUser": "Remove User",
|
||||
"actionListUsers": "List Users",
|
||||
"actionAddUserRole": "Add User Role",
|
||||
"actionGenerateAccessToken": "Generate Access Token",
|
||||
"actionDeleteAccessToken": "Delete Access Token",
|
||||
"actionListAccessTokens": "List Access Tokens",
|
||||
"actionCreateResourceRule": "Create Resource Rule",
|
||||
"actionDeleteResourceRule": "Delete Resource Rule",
|
||||
"actionListResourceRules": "List Resource Rules",
|
||||
"actionUpdateResourceRule": "Update Resource Rule",
|
||||
"actionListOrgs": "List Organizations",
|
||||
"actionCheckOrgId": "Check ID",
|
||||
"actionCreateOrg": "Create Organization",
|
||||
"actionDeleteOrg": "Delete Organization",
|
||||
"actionListApiKeys": "List API Keys",
|
||||
"actionListApiKeyActions": "List API Key Actions",
|
||||
"actionSetApiKeyActions": "Set API Key Allowed Actions",
|
||||
"actionCreateApiKey": "Create API Key",
|
||||
"actionDeleteApiKey": "Delete API Key",
|
||||
"actionCreateIdp": "Create IDP",
|
||||
"actionUpdateIdp": "Update IDP",
|
||||
"actionDeleteIdp": "Delete IDP",
|
||||
"actionListIdps": "List IDP",
|
||||
"actionGetIdp": "Get IDP",
|
||||
"actionCreateIdpOrg": "Create IDP Org Policy",
|
||||
"actionDeleteIdpOrg": "Delete IDP Org Policy",
|
||||
"actionListIdpOrgs": "List IDP Orgs",
|
||||
"actionUpdateIdpOrg": "Update IDP Org",
|
||||
"noneSelected": "None selected",
|
||||
"orgNotFound2": "No organizations found.",
|
||||
"searchProgress": "Search...",
|
||||
"create": "Create",
|
||||
"orgs": "Organizations",
|
||||
"loginError": "An error occurred while logging in",
|
||||
"passwordForgot": "Forgot your password?",
|
||||
"otpAuth": "Two-Factor Authentication",
|
||||
"otpAuthDescription": "Enter the code from your authenticator app or one of your single-use backup codes.",
|
||||
"otpAuthSubmit": "Submit Code",
|
||||
"idpContinue": "Or continue with",
|
||||
"otpAuthBack": "Back to Log In",
|
||||
"navbar": "Navigation Menu",
|
||||
"navbarDescription": "Main navigation menu for the application",
|
||||
"navbarDocsLink": "Documentation",
|
||||
"commercialEdition": "Commercial Edition",
|
||||
"otpErrorEnable": "Unable to enable 2FA",
|
||||
"otpErrorEnableDescription": "An error occurred while enabling 2FA",
|
||||
"otpSetupCheckCode": "Please enter a 6-digit code",
|
||||
"otpSetupCheckCodeRetry": "Invalid code. Please try again.",
|
||||
"otpSetup": "Enable Two-factor Authentication",
|
||||
"otpSetupDescription": "Secure your account with an extra layer of protection",
|
||||
"otpSetupScanQr": "Scan this QR code with your authenticator app or enter the secret key manually:",
|
||||
"otpSetupSecretCode": "Authenticator Code",
|
||||
"otpSetupSuccess": "Two-Factor Authentication Enabled",
|
||||
"otpSetupSuccessStoreBackupCodes": "Your account is now more secure. Don't forget to save your backup codes.",
|
||||
"otpErrorDisable": "Unable to disable 2FA",
|
||||
"otpErrorDisableDescription": "An error occurred while disabling 2FA",
|
||||
"otpRemove": "Disable Two-factor Authentication",
|
||||
"otpRemoveDescription": "Disable two-factor authentication for your account",
|
||||
"otpRemoveSuccess": "Two-Factor Authentication Disabled",
|
||||
"otpRemoveSuccessMessage": "Two-factor authentication has been disabled for your account. You can enable it again at any time.",
|
||||
"otpRemoveSubmit": "Disable 2FA",
|
||||
"paginator": "Page {current} of {last}",
|
||||
"paginatorToFirst": "Go to first page",
|
||||
"paginatorToPrevious": "Go to previous page",
|
||||
"paginatorToNext": "Go to next page",
|
||||
"paginatorToLast": "Go to last page",
|
||||
"copyText": "Copy text",
|
||||
"copyTextFailed": "Failed to copy text: ",
|
||||
"copyTextClipboard": "Copy to clipboard",
|
||||
"inviteErrorInvalidConfirmation": "Invalid confirmation"
|
||||
}
|
||||
|
|
|
@ -39,11 +39,6 @@ type CreateRoleFormProps = {
|
|||
afterCreate?: (res: CreateRoleResponse) => Promise<void>;
|
||||
};
|
||||
|
||||
const formSchema = z.object({
|
||||
name: z.string({ message: "Name is required" }).max(32),
|
||||
description: z.string().max(255).optional()
|
||||
});
|
||||
|
||||
export default function CreateRoleForm({
|
||||
open,
|
||||
setOpen,
|
||||
|
@ -52,6 +47,11 @@ export default function CreateRoleForm({
|
|||
const { org } = useOrgContext();
|
||||
const t = useTranslations();
|
||||
|
||||
const formSchema = z.object({
|
||||
name: z.string({ message: t('nameRequired') }).max(32),
|
||||
description: z.string().max(255).optional()
|
||||
});
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const api = createApiClient(useEnvContext());
|
||||
|
|
|
@ -47,10 +47,6 @@ type CreateRoleFormProps = {
|
|||
afterDelete?: () => void;
|
||||
};
|
||||
|
||||
const formSchema = z.object({
|
||||
newRoleId: z.string({ message: "New role is required" })
|
||||
});
|
||||
|
||||
export default function DeleteRoleForm({
|
||||
open,
|
||||
roleToDelete,
|
||||
|
@ -65,6 +61,10 @@ export default function DeleteRoleForm({
|
|||
|
||||
const api = createApiClient(useEnvContext());
|
||||
|
||||
const formSchema = z.object({
|
||||
newRoleId: z.string({ message: t('accessRoleErrorNewRequired') })
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchRoles() {
|
||||
const res = await api
|
||||
|
|
|
@ -24,7 +24,7 @@ export function RolesDataTable<TData, TValue>({
|
|||
<DataTable
|
||||
columns={columns}
|
||||
data={data}
|
||||
title="Roles"
|
||||
title={t('roles')}
|
||||
searchPlaceholder={t('accessRolesSearch')}
|
||||
searchColumn="name"
|
||||
onAdd={createRole}
|
||||
|
|
|
@ -222,7 +222,7 @@ export default function UsersTable({ users: u }: UsersTableProps) {
|
|||
toast({
|
||||
variant: "default",
|
||||
title: t('userOrgRemoved'),
|
||||
description: t('userOrgRemovedDescription', {email: selectedUser.email})
|
||||
description: t('userOrgRemovedDescription', {email: selectedUser.email}) // FIXME
|
||||
});
|
||||
|
||||
setUsers((prev) =>
|
||||
|
@ -244,7 +244,7 @@ export default function UsersTable({ users: u }: UsersTableProps) {
|
|||
dialog={
|
||||
<div className="space-y-4">
|
||||
<p>
|
||||
{t('userQuestionOrgRemove', {email: selectedUser?.email || selectedUser?.name || selectedUser?.username})}
|
||||
{t('userQuestionOrgRemove', {email: selectedUser?.email || selectedUser?.name || selectedUser?.username})} // FIXME
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
|
|
@ -42,11 +42,6 @@ import { createApiClient } from "@app/lib/api";
|
|||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const formSchema = z.object({
|
||||
username: z.string(),
|
||||
roleId: z.string().min(1, { message: "Please select a role" })
|
||||
});
|
||||
|
||||
export default function AccessControlsPage() {
|
||||
const { orgUser: user } = userOrgUserContext();
|
||||
|
||||
|
@ -57,6 +52,13 @@ export default function AccessControlsPage() {
|
|||
const [loading, setLoading] = useState(false);
|
||||
const [roles, setRoles] = useState<{ roleId: number; name: string }[]>([]);
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
const formSchema = z.object({
|
||||
username: z.string(),
|
||||
roleId: z.string().min(1, { message: t('accessRoleSelectPlease') })
|
||||
});
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
|
@ -65,8 +67,6 @@ export default function AccessControlsPage() {
|
|||
}
|
||||
});
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchRoles() {
|
||||
const res = await api
|
||||
|
|
|
@ -60,24 +60,6 @@ interface IdpOption {
|
|||
type: string;
|
||||
}
|
||||
|
||||
const internalFormSchema = z.object({
|
||||
email: z.string().email({ message: "Invalid email address" }),
|
||||
validForHours: z.string().min(1, { message: "Please select a duration" }),
|
||||
roleId: z.string().min(1, { message: "Please select a role" })
|
||||
});
|
||||
|
||||
const externalFormSchema = z.object({
|
||||
username: z.string().min(1, { message: "Username is required" }),
|
||||
email: z
|
||||
.string()
|
||||
.email({ message: "Invalid email address" })
|
||||
.optional()
|
||||
.or(z.literal("")),
|
||||
name: z.string().optional(),
|
||||
roleId: z.string().min(1, { message: "Please select a role" }),
|
||||
idpId: z.string().min(1, { message: "Please select an identity provider" })
|
||||
});
|
||||
|
||||
const formatIdpType = (type: string) => {
|
||||
switch (type.toLowerCase()) {
|
||||
case "oidc":
|
||||
|
@ -104,6 +86,24 @@ export default function Page() {
|
|||
const [selectedIdp, setSelectedIdp] = useState<IdpOption | null>(null);
|
||||
const [dataLoaded, setDataLoaded] = useState(false);
|
||||
|
||||
const internalFormSchema = z.object({
|
||||
email: z.string().email({ message: t('emailInvalid') }),
|
||||
validForHours: z.string().min(1, { message: t('inviteValidityDuration') }),
|
||||
roleId: z.string().min(1, { message: t('accessRoleSelectPlease') })
|
||||
});
|
||||
|
||||
const externalFormSchema = z.object({
|
||||
username: z.string().min(1, { message: t('usernameRequired') }),
|
||||
email: z
|
||||
.string()
|
||||
.email({ message: t('emailInvalid') })
|
||||
.optional()
|
||||
.or(z.literal("")),
|
||||
name: z.string().optional(),
|
||||
roleId: z.string().min(1, { message: t('accessRoleSelectPlease') }),
|
||||
idpId: z.string().min(1, { message: t('idpSelectPlease') })
|
||||
});
|
||||
|
||||
const validFor = [
|
||||
{ hours: 24, name: t('day', {count: 1}) },
|
||||
{ hours: 48, name: t('day', {count: 2}) },
|
||||
|
|
|
@ -58,35 +58,6 @@ import CopyTextBox from "@app/components/CopyTextBox";
|
|||
import PermissionsSelectBox from "@app/components/PermissionsSelectBox";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const createFormSchema = z.object({
|
||||
name: z
|
||||
.string()
|
||||
.min(2, {
|
||||
message: "Name must be at least 2 characters."
|
||||
})
|
||||
.max(255, {
|
||||
message: "Name must not be longer than 255 characters."
|
||||
})
|
||||
});
|
||||
|
||||
type CreateFormValues = z.infer<typeof createFormSchema>;
|
||||
|
||||
const copiedFormSchema = z
|
||||
.object({
|
||||
copied: z.boolean()
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
return data.copied;
|
||||
},
|
||||
{
|
||||
message: "You must confirm that you have copied the API key.",
|
||||
path: ["copied"]
|
||||
}
|
||||
);
|
||||
|
||||
type CopiedFormValues = z.infer<typeof copiedFormSchema>;
|
||||
|
||||
export default function Page() {
|
||||
const { env } = useEnvContext();
|
||||
const api = createApiClient({ env });
|
||||
|
@ -101,6 +72,35 @@ export default function Page() {
|
|||
Record<string, boolean>
|
||||
>({});
|
||||
|
||||
const createFormSchema = z.object({
|
||||
name: z
|
||||
.string()
|
||||
.min(2, {
|
||||
message: t('nameMin', {len: 2})
|
||||
})
|
||||
.max(255, {
|
||||
message: t('nameMax', {len: 255})
|
||||
})
|
||||
});
|
||||
|
||||
type CreateFormValues = z.infer<typeof createFormSchema>;
|
||||
|
||||
const copiedFormSchema = z
|
||||
.object({
|
||||
copied: z.boolean()
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
return data.copied;
|
||||
},
|
||||
{
|
||||
message: t('apiKeysConfirmCopy2'),
|
||||
path: ["copied"]
|
||||
}
|
||||
);
|
||||
|
||||
type CopiedFormValues = z.infer<typeof copiedFormSchema>;
|
||||
|
||||
const form = useForm<CreateFormValues>({
|
||||
resolver: zodResolver(createFormSchema),
|
||||
defaultValues: {
|
||||
|
|
|
@ -162,9 +162,10 @@ export default function ResourceAuthenticationPage() {
|
|||
rolesResponse.data.data.roles
|
||||
.map((role) => ({
|
||||
id: role.roleId.toString(),
|
||||
text: role.name
|
||||
text: role.name,
|
||||
isAdmin: role.isAdmin
|
||||
}))
|
||||
.filter((role) => role.text !== "Admin")
|
||||
.filter((role) => !role.isAdmin)
|
||||
);
|
||||
|
||||
usersRolesForm.setValue(
|
||||
|
@ -172,9 +173,10 @@ export default function ResourceAuthenticationPage() {
|
|||
resourceRolesResponse.data.data.roles
|
||||
.map((i) => ({
|
||||
id: i.roleId.toString(),
|
||||
text: i.name
|
||||
text: i.name,
|
||||
isAdmin: i.isAdmin
|
||||
}))
|
||||
.filter((role) => role.text !== "Admin")
|
||||
.filter((role) => !role.isAdmin)
|
||||
);
|
||||
|
||||
setAllUsers(
|
||||
|
|
|
@ -88,17 +88,6 @@ type LocalRule = ArrayElement<ListResourceRulesResponse["rules"]> & {
|
|||
updated?: boolean;
|
||||
};
|
||||
|
||||
const RuleAction = {
|
||||
ACCEPT: "Always Allow",
|
||||
DROP: "Always Deny"
|
||||
} as const;
|
||||
|
||||
const RuleMatch = {
|
||||
PATH: "Path",
|
||||
IP: "IP",
|
||||
CIDR: "IP Range"
|
||||
} as const;
|
||||
|
||||
export default function ResourceRules(props: {
|
||||
params: Promise<{ resourceId: number }>;
|
||||
}) {
|
||||
|
@ -113,6 +102,17 @@ export default function ResourceRules(props: {
|
|||
const router = useRouter();
|
||||
const t = useTranslations();
|
||||
|
||||
const RuleAction = {
|
||||
ACCEPT: t('alwaysAllow'),
|
||||
DROP: t('alwaysDeny')
|
||||
} as const;
|
||||
|
||||
const RuleMatch = {
|
||||
PATH: t('path'),
|
||||
IP: "IP",
|
||||
CIDR: t('ipAddressRange')
|
||||
} as const;
|
||||
|
||||
const addRuleForm = useForm({
|
||||
resolver: zodResolver(addRuleSchema),
|
||||
defaultValues: {
|
||||
|
|
|
@ -204,7 +204,7 @@ export default function CreateShareLinkForm({
|
|||
validForSeconds: neverExpire ? undefined : timeInSeconds,
|
||||
title:
|
||||
values.title ||
|
||||
`${values.resourceName || "Resource" + values.resourceId} Share Link`
|
||||
t('shareLink', {resource: (values.resourceName || "Resource" + values.resourceId)})
|
||||
}
|
||||
)
|
||||
.catch((e) => {
|
||||
|
|
|
@ -69,10 +69,10 @@ export default function ShareLinksTable({
|
|||
async function deleteSharelink(id: string) {
|
||||
await api.delete(`/access-token/${id}`).catch((e) => {
|
||||
toast({
|
||||
title: "Failed to delete link",
|
||||
title: t('shareErrorDelete'),
|
||||
description: formatAxiosError(
|
||||
e,
|
||||
"An error occurred deleting link"
|
||||
t('shareErrorDeleteMessage')
|
||||
)
|
||||
});
|
||||
});
|
||||
|
@ -81,8 +81,8 @@ export default function ShareLinksTable({
|
|||
setRows(newRows);
|
||||
|
||||
toast({
|
||||
title: "Link deleted",
|
||||
description: "The link has been deleted"
|
||||
title: t('shareDeleted'),
|
||||
description: t('shareDeletedDescription')
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -229,11 +229,11 @@ export default function CreateSiteForm({
|
|||
nice: data.niceId.toString(),
|
||||
mbIn:
|
||||
data.type == "wireguard" || data.type == "newt"
|
||||
? "0 MB"
|
||||
? t('megabytes', {count: 0})
|
||||
: "-",
|
||||
mbOut:
|
||||
data.type == "wireguard" || data.type == "newt"
|
||||
? "0 MB"
|
||||
? t('megabytes', {count: 0})
|
||||
: "-",
|
||||
orgId: orgId as string,
|
||||
type: data.type as any,
|
||||
|
@ -273,8 +273,6 @@ PersistentKeepalive = 5`
|
|||
|
||||
const newtConfigDockerRun = `docker run -it fosrl/newt --id ${siteDefaults?.newtId} --secret ${siteDefaults?.newtSecret} --endpoint ${env.app.dashboardUrl}`;
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
return loadingPage ? (
|
||||
<LoaderPlaceholder height="300px" />
|
||||
) : (
|
||||
|
@ -313,7 +311,7 @@ PersistentKeepalive = 5`
|
|||
onValueChange={field.onChange}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select method" />
|
||||
<SelectValue placeholder={t('methodSelect')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="local">
|
||||
|
|
|
@ -67,10 +67,10 @@ export default function GeneralPage() {
|
|||
.catch((e) => {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Failed to update site",
|
||||
title: t('siteErrorUpdate'),
|
||||
description: formatAxiosError(
|
||||
e,
|
||||
"An error occurred while updating the site."
|
||||
t('siteErrorUpdateDescription')
|
||||
)
|
||||
});
|
||||
});
|
||||
|
@ -78,8 +78,8 @@ export default function GeneralPage() {
|
|||
updateSite({ name: data.name });
|
||||
|
||||
toast({
|
||||
title: "Site updated",
|
||||
description: "The site has been updated."
|
||||
title: t('siteUpdated'),
|
||||
description: t('siteUpdatedDescription')
|
||||
});
|
||||
|
||||
setLoading(false);
|
||||
|
|
|
@ -381,8 +381,8 @@ WantedBy=default.target`
|
|||
if (!siteDefaults || !wgConfig) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error creating site",
|
||||
description: "Key pair or site defaults not found"
|
||||
title: t('siteErrorCreate'),
|
||||
description: t('siteErrorCreateKeyPair')
|
||||
});
|
||||
setCreateLoading(false);
|
||||
return;
|
||||
|
@ -399,8 +399,8 @@ WantedBy=default.target`
|
|||
if (!siteDefaults) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error creating site",
|
||||
description: "Site defaults not found"
|
||||
title: t('siteErrorCreate'),
|
||||
description: t('siteErrorCreateDefaults')
|
||||
});
|
||||
setCreateLoading(false);
|
||||
return;
|
||||
|
@ -422,7 +422,7 @@ WantedBy=default.target`
|
|||
.catch((e) => {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error creating site",
|
||||
title: t('siteErrorCreate'),
|
||||
description: formatAxiosError(e)
|
||||
});
|
||||
});
|
||||
|
|
|
@ -24,16 +24,18 @@ export default async function SitesPage(props: SitesPageProps) {
|
|||
sites = res.data.data.sites;
|
||||
} catch (e) {}
|
||||
|
||||
const t = await getTranslations();
|
||||
|
||||
function formatSize(mb: number, type: string): string {
|
||||
if (type === "local") {
|
||||
return "-"; // because we are not able to track the data use in a local site right now
|
||||
}
|
||||
if (mb >= 1024 * 1024) {
|
||||
return `${(mb / (1024 * 1024)).toFixed(2)} TB`;
|
||||
return t('terabytes', {count: (mb / (1024 * 1024)).toFixed(2)});
|
||||
} else if (mb >= 1024) {
|
||||
return `${(mb / 1024).toFixed(2)} GB`;
|
||||
return t('gigabytes', {count: (mb / 1024).toFixed(2)});
|
||||
} else {
|
||||
return `${mb.toFixed(2)} MB`;
|
||||
return t('megabytes', {count: mb.toFixed(2)});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,8 +52,6 @@ export default async function SitesPage(props: SitesPageProps) {
|
|||
};
|
||||
});
|
||||
|
||||
const t = await getTranslations();
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <SitesSplashCard /> */}
|
||||
|
|
|
@ -124,7 +124,7 @@ export default function Page() {
|
|||
.catch((e) => {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error creating API key",
|
||||
title: t('apiKeysErrorCreate'),
|
||||
description: formatAxiosError(e)
|
||||
});
|
||||
});
|
||||
|
@ -145,10 +145,10 @@ export default function Page() {
|
|||
)
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error("Error setting permissions", e);
|
||||
console.error(t('apiKeysErrorSetPermission'), e);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error setting permissions",
|
||||
title: t('apiKeysErrorSetPermission'),
|
||||
description: formatAxiosError(e)
|
||||
});
|
||||
});
|
||||
|
|
|
@ -41,7 +41,7 @@ export default function UsersTable({ users }: Props) {
|
|||
const deleteUser = (id: string) => {
|
||||
api.delete(`/user/${id}`)
|
||||
.catch((e) => {
|
||||
console.error("Error deleting user", e);
|
||||
console.error(t('userErrorDelete'), e);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: t('userErrorDelete'),
|
||||
|
|
|
@ -226,7 +226,7 @@ export default function VerifyEmailForm({
|
|||
{isSubmitting && (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
)}
|
||||
{t('emailVerifySubmit')}
|
||||
{t('submit')}
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
|
|
|
@ -43,6 +43,7 @@ import { useOrgContext } from "@app/hooks/useOrgContext";
|
|||
import { Description } from "@radix-ui/react-toast";
|
||||
import { createApiClient } from "@app/lib/api";
|
||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
type InviteUserFormProps = {
|
||||
open: boolean;
|
||||
|
@ -67,9 +68,11 @@ export default function InviteUserForm({
|
|||
|
||||
const api = createApiClient(useEnvContext());
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
const formSchema = z.object({
|
||||
string: z.string().refine((val) => val === string, {
|
||||
message: "Invalid confirmation"
|
||||
message: t('inviteErrorInvalidConfirmation')
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -129,7 +132,7 @@ export default function InviteUserForm({
|
|||
</CredenzaBody>
|
||||
<CredenzaFooter>
|
||||
<CredenzaClose asChild>
|
||||
<Button variant="outline">Close</Button>
|
||||
<Button variant="outline">{t('close')}</Button>
|
||||
</CredenzaClose>
|
||||
<Button
|
||||
type="submit"
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import { useState, useRef } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Copy, Check } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
type CopyTextBoxProps = {
|
||||
text?: string;
|
||||
|
@ -19,6 +20,7 @@ export default function CopyTextBox({
|
|||
}: CopyTextBoxProps) {
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
const textRef = useRef<HTMLPreElement>(null);
|
||||
const t = useTranslations();
|
||||
|
||||
const copyToClipboard = async () => {
|
||||
if (textRef.current) {
|
||||
|
@ -27,7 +29,7 @@ export default function CopyTextBox({
|
|||
setIsCopied(true);
|
||||
setTimeout(() => setIsCopied(false), 2000);
|
||||
} catch (err) {
|
||||
console.error("Failed to copy text: ", err);
|
||||
console.error(t('copyTextFailed'), err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -52,7 +54,7 @@ export default function CopyTextBox({
|
|||
type="button"
|
||||
className="absolute top-0.5 right-0 z-10 bg-card"
|
||||
onClick={copyToClipboard}
|
||||
aria-label="Copy to clipboard"
|
||||
aria-label={t('copyTextClipboard')}
|
||||
>
|
||||
{isCopied ? (
|
||||
<Check className="h-4 w-4 text-green-500" />
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Check, Copy } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
type CopyToClipboardProps = {
|
||||
text: string;
|
||||
|
@ -22,6 +23,8 @@ const CopyToClipboard = ({ text, displayText, isLink }: CopyToClipboardProps) =>
|
|||
|
||||
const displayValue = displayText ?? text;
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-2 max-w-full">
|
||||
{isLink ? (
|
||||
|
@ -60,7 +63,7 @@ const CopyToClipboard = ({ text, displayText, isLink }: CopyToClipboardProps) =>
|
|||
) : (
|
||||
<Check className="text-green-500 h-4 w-4" />
|
||||
)}
|
||||
<span className="sr-only">Copy text</span>
|
||||
<span className="sr-only">{t('copyText')}</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
SelectTrigger,
|
||||
SelectValue
|
||||
} from "@app/components/ui/select";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface DataTablePaginationProps<TData> {
|
||||
table: Table<TData>;
|
||||
|
@ -22,6 +23,8 @@ interface DataTablePaginationProps<TData> {
|
|||
export function DataTablePagination<TData>({
|
||||
table
|
||||
}: DataTablePaginationProps<TData>) {
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between text-muted-foreground">
|
||||
<div className="flex items-center space-x-2">
|
||||
|
@ -48,8 +51,7 @@ export function DataTablePagination<TData>({
|
|||
|
||||
<div className="flex items-center space-x-3 lg:space-x-8">
|
||||
<div className="flex items-center justify-center text-sm font-medium">
|
||||
Page {table.getState().pagination.pageIndex + 1} of{" "}
|
||||
{table.getPageCount()}
|
||||
{t('paginator', {current: table.getState().pagination.pageIndex + 1, last: table.getPageCount()})}
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
|
@ -58,7 +60,7 @@ export function DataTablePagination<TData>({
|
|||
onClick={() => table.setPageIndex(0)}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
<span className="sr-only">Go to first page</span>
|
||||
<span className="sr-only">{t('paginatorToFirst')}</span>
|
||||
<DoubleArrowLeftIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
|
@ -67,7 +69,7 @@ export function DataTablePagination<TData>({
|
|||
onClick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
<span className="sr-only">Go to previous page</span>
|
||||
<span className="sr-only">{t('paginatorToPrevious')}</span>
|
||||
<ChevronLeftIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
|
@ -76,7 +78,7 @@ export function DataTablePagination<TData>({
|
|||
onClick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
<span className="sr-only">Go to next page</span>
|
||||
<span className="sr-only">{t('paginatorToNext')}</span>
|
||||
<ChevronRightIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
|
@ -87,7 +89,7 @@ export function DataTablePagination<TData>({
|
|||
}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
<span className="sr-only">Go to last page</span>
|
||||
<span className="sr-only">{t('paginatorToLast')}</span>
|
||||
<DoubleArrowRightIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
@ -32,6 +32,7 @@ import { toast } from "@app/hooks/useToast";
|
|||
import { formatAxiosError } from "@app/lib/api";
|
||||
import { useUserContext } from "@app/hooks/useUserContext";
|
||||
import { CheckCircle2 } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const disableSchema = z.object({
|
||||
password: z.string().min(1, { message: "Password is required" }),
|
||||
|
@ -60,6 +61,8 @@ export default function Disable2FaForm({ open, setOpen }: Disable2FaProps) {
|
|||
}
|
||||
});
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
const request2fa = async (values: z.infer<typeof disableSchema>) => {
|
||||
setLoading(true);
|
||||
|
||||
|
@ -70,10 +73,10 @@ export default function Disable2FaForm({ open, setOpen }: Disable2FaProps) {
|
|||
} as Disable2faBody)
|
||||
.catch((e) => {
|
||||
toast({
|
||||
title: "Unable to disable 2FA",
|
||||
title: t('otpErrorDisable'),
|
||||
description: formatAxiosError(
|
||||
e,
|
||||
"An error occurred while disabling 2FA"
|
||||
t('otpErrorDisableDescription')
|
||||
),
|
||||
variant: "destructive"
|
||||
});
|
||||
|
@ -109,10 +112,10 @@ export default function Disable2FaForm({ open, setOpen }: Disable2FaProps) {
|
|||
<CredenzaContent>
|
||||
<CredenzaHeader>
|
||||
<CredenzaTitle>
|
||||
Disable Two-factor Authentication
|
||||
{t('otpRemove')}
|
||||
</CredenzaTitle>
|
||||
<CredenzaDescription>
|
||||
Disable two-factor authentication for your account
|
||||
{t('otpRemoveDescription')}
|
||||
</CredenzaDescription>
|
||||
</CredenzaHeader>
|
||||
<CredenzaBody>
|
||||
|
@ -129,7 +132,7 @@ export default function Disable2FaForm({ open, setOpen }: Disable2FaProps) {
|
|||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormLabel>{t('password')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
|
@ -147,7 +150,7 @@ export default function Disable2FaForm({ open, setOpen }: Disable2FaProps) {
|
|||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Authenticator Code
|
||||
{t('otpSetupSecretCode')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
|
@ -168,19 +171,17 @@ export default function Disable2FaForm({ open, setOpen }: Disable2FaProps) {
|
|||
size={48}
|
||||
/>
|
||||
<p className="font-semibold text-lg">
|
||||
Two-Factor Authentication Disabled
|
||||
{t('otpRemoveSuccess')}
|
||||
</p>
|
||||
<p>
|
||||
Two-factor authentication has been disabled for
|
||||
your account. You can enable it again at any
|
||||
time.
|
||||
{t('otpRemoveSuccessMessage')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</CredenzaBody>
|
||||
<CredenzaFooter>
|
||||
<CredenzaClose asChild>
|
||||
<Button variant="outline">Close</Button>
|
||||
<Button variant="outline">{t('close')}</Button>
|
||||
</CredenzaClose>
|
||||
{step === "password" && (
|
||||
<Button
|
||||
|
@ -189,7 +190,7 @@ export default function Disable2FaForm({ open, setOpen }: Disable2FaProps) {
|
|||
loading={loading}
|
||||
disabled={loading}
|
||||
>
|
||||
Disable 2FA
|
||||
{t('otpRemoveSubmit')}
|
||||
</Button>
|
||||
)}
|
||||
</CredenzaFooter>
|
||||
|
|
|
@ -40,6 +40,7 @@ import { formatAxiosError } from "@app/lib/api";
|
|||
import CopyTextBox from "@app/components/CopyTextBox";
|
||||
import { QRCodeCanvas, QRCodeSVG } from "qrcode.react";
|
||||
import { useUserContext } from "@app/hooks/useUserContext";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const enableSchema = z.object({
|
||||
password: z.string().min(1, { message: "Password is required" })
|
||||
|
@ -82,6 +83,8 @@ export default function Enable2FaForm({ open, setOpen }: Enable2FaProps) {
|
|||
}
|
||||
});
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
const request2fa = async (values: z.infer<typeof enableSchema>) => {
|
||||
setLoading(true);
|
||||
|
||||
|
@ -94,10 +97,10 @@ export default function Enable2FaForm({ open, setOpen }: Enable2FaProps) {
|
|||
)
|
||||
.catch((e) => {
|
||||
toast({
|
||||
title: "Unable to enable 2FA",
|
||||
title: t('otpErrorEnable'),
|
||||
description: formatAxiosError(
|
||||
e,
|
||||
"An error occurred while enabling 2FA"
|
||||
t('otpErrorEnableDescription')
|
||||
),
|
||||
variant: "destructive"
|
||||
});
|
||||
|
@ -121,10 +124,10 @@ export default function Enable2FaForm({ open, setOpen }: Enable2FaProps) {
|
|||
} as VerifyTotpBody)
|
||||
.catch((e) => {
|
||||
toast({
|
||||
title: "Unable to enable 2FA",
|
||||
title: t('otpErrorEnable'),
|
||||
description: formatAxiosError(
|
||||
e,
|
||||
"An error occurred while enabling 2FA"
|
||||
t('otpErrorEnableDescription')
|
||||
),
|
||||
variant: "destructive"
|
||||
});
|
||||
|
@ -141,14 +144,14 @@ export default function Enable2FaForm({ open, setOpen }: Enable2FaProps) {
|
|||
|
||||
const handleVerify = () => {
|
||||
if (verificationCode.length !== 6) {
|
||||
setError("Please enter a 6-digit code");
|
||||
setError(t('otpSetupCheckCode'));
|
||||
return;
|
||||
}
|
||||
if (verificationCode === "123456") {
|
||||
setSuccess(true);
|
||||
setStep(3);
|
||||
} else {
|
||||
setError("Invalid code. Please try again.");
|
||||
setError(t('otpSetupCheckCodeRetry'));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -176,10 +179,10 @@ export default function Enable2FaForm({ open, setOpen }: Enable2FaProps) {
|
|||
<CredenzaContent>
|
||||
<CredenzaHeader>
|
||||
<CredenzaTitle>
|
||||
Enable Two-factor Authentication
|
||||
{t('otpSetup')}
|
||||
</CredenzaTitle>
|
||||
<CredenzaDescription>
|
||||
Secure your account with an extra layer of protection
|
||||
{t('otpSetupDescription')}
|
||||
</CredenzaDescription>
|
||||
</CredenzaHeader>
|
||||
<CredenzaBody>
|
||||
|
@ -196,7 +199,7 @@ export default function Enable2FaForm({ open, setOpen }: Enable2FaProps) {
|
|||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormLabel>{t('password')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
|
@ -215,8 +218,7 @@ export default function Enable2FaForm({ open, setOpen }: Enable2FaProps) {
|
|||
{step === 2 && (
|
||||
<div className="space-y-4">
|
||||
<p>
|
||||
Scan this QR code with your authenticator app or
|
||||
enter the secret key manually:
|
||||
{t('otpSetupScanQr')}
|
||||
</p>
|
||||
<div className="h-[250px] mx-auto flex items-center justify-center">
|
||||
<QRCodeCanvas value={secretUri} size={200} />
|
||||
|
@ -243,7 +245,7 @@ export default function Enable2FaForm({ open, setOpen }: Enable2FaProps) {
|
|||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Authenticator Code
|
||||
{t('otpSetupSecretCode')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
|
@ -268,11 +270,10 @@ export default function Enable2FaForm({ open, setOpen }: Enable2FaProps) {
|
|||
size={48}
|
||||
/>
|
||||
<p className="font-semibold text-lg">
|
||||
Two-Factor Authentication Enabled
|
||||
{t('otpSetupSuccess')}
|
||||
</p>
|
||||
<p>
|
||||
Your account is now more secure. Don't forget to
|
||||
save your backup codes.
|
||||
{t('otpSetupSuccessStoreBackupCodes')}
|
||||
</p>
|
||||
|
||||
<div className="max-w-md mx-auto">
|
||||
|
@ -298,7 +299,7 @@ export default function Enable2FaForm({ open, setOpen }: Enable2FaProps) {
|
|||
}
|
||||
}}
|
||||
>
|
||||
Submit
|
||||
{t('submit')}
|
||||
</Button>
|
||||
)}
|
||||
</CredenzaFooter>
|
||||
|
|
|
@ -7,6 +7,7 @@ import { cn } from "@app/lib/cn";
|
|||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { Badge } from "@app/components/ui/badge";
|
||||
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export type HorizontalTabs = Array<{
|
||||
title: string;
|
||||
|
@ -29,6 +30,7 @@ export function HorizontalTabs({
|
|||
const pathname = usePathname();
|
||||
const params = useParams();
|
||||
const { licenseStatus, isUnlocked } = useLicenseStatusContext();
|
||||
const t = useTranslations();
|
||||
|
||||
function hydrateHref(href: string) {
|
||||
return href
|
||||
|
@ -86,7 +88,7 @@ export function HorizontalTabs({
|
|||
variant="outlinePrimary"
|
||||
className="ml-2"
|
||||
>
|
||||
Professional
|
||||
{t('licenseBadge')}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -23,6 +23,7 @@ import Link from "next/link";
|
|||
import { usePathname } from "next/navigation";
|
||||
import { useUserContext } from "@app/hooks/useUserContext";
|
||||
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
|
@ -60,6 +61,7 @@ export function Layout({
|
|||
const isAdminPage = pathname?.startsWith("/admin");
|
||||
const { user } = useUserContext();
|
||||
const { isUnlocked } = useLicenseStatusContext();
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen overflow-hidden">
|
||||
|
@ -84,11 +86,10 @@ export function Layout({
|
|||
className="w-64 p-0 flex flex-col h-full"
|
||||
>
|
||||
<SheetTitle className="sr-only">
|
||||
Navigation Menu
|
||||
{t('navbar')}
|
||||
</SheetTitle>
|
||||
<SheetDescription className="sr-only">
|
||||
Main navigation menu for the
|
||||
application
|
||||
{t('navbarDescription')}
|
||||
</SheetDescription>
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="p-4">
|
||||
|
@ -114,7 +115,7 @@ export function Layout({
|
|||
}
|
||||
>
|
||||
<Server className="h-4 w-4" />
|
||||
Server Admin
|
||||
{t('serverAdmin')}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
@ -161,7 +162,7 @@ export function Layout({
|
|||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Documentation
|
||||
{t('navbarDocsLink')}
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
|
@ -193,7 +194,7 @@ export function Layout({
|
|||
className="flex items-center gap-3 text-muted-foreground hover:text-foreground transition-colors px-3 py-2 rounded-md w-full"
|
||||
>
|
||||
<Server className="h-4 w-4" />
|
||||
Server Admin
|
||||
{t('serverAdmin')}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
@ -210,8 +211,8 @@ export function Layout({
|
|||
className="flex items-center justify-center gap-1"
|
||||
>
|
||||
{!isUnlocked()
|
||||
? "Community Edition"
|
||||
: "Commercial Edition"}
|
||||
? t('communityEdition')
|
||||
: t('commercialEdition')}
|
||||
<ExternalLink size={12} />
|
||||
</Link>
|
||||
</div>
|
||||
|
|
|
@ -40,6 +40,7 @@ import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp";
|
|||
import Image from "next/image";
|
||||
import { GenerateOidcUrlResponse } from "@server/routers/idp";
|
||||
import { Separator } from "./ui/separator";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export type LoginFormIDP = {
|
||||
idpId: number;
|
||||
|
@ -91,6 +92,8 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
|
|||
}
|
||||
});
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
async function onSubmit(values: any) {
|
||||
const { email, password } = form.getValues();
|
||||
const { code } = mfaForm.getValues();
|
||||
|
@ -106,7 +109,7 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
|
|||
.catch((e) => {
|
||||
console.error(e);
|
||||
setError(
|
||||
formatAxiosError(e, "An error occurred while logging in")
|
||||
formatAxiosError(e, t('loginError'))
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -151,7 +154,7 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
|
|||
console.log(res);
|
||||
|
||||
if (!res) {
|
||||
setError("An error occurred while logging in");
|
||||
setError(t('loginError'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -177,7 +180,7 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
|
|||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormLabel>{t('email')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
|
@ -192,7 +195,7 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
|
|||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormLabel>{t('password')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
|
@ -209,7 +212,7 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
|
|||
href={`/auth/reset-password${form.getValues().email ? `?email=${form.getValues().email}` : ""}`}
|
||||
className="text-sm text-muted-foreground"
|
||||
>
|
||||
Forgot your password?
|
||||
{t('passwordForgot')}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -222,11 +225,10 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
|
|||
<>
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-medium">
|
||||
Two-Factor Authentication
|
||||
{t('otpAuth')}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Enter the code from your authenticator app or one of
|
||||
your single-use backup codes.
|
||||
{t('otpAuthDescription')}
|
||||
</p>
|
||||
</div>
|
||||
<Form {...mfaForm}>
|
||||
|
@ -302,7 +304,7 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
|
|||
loading={loading}
|
||||
disabled={loading}
|
||||
>
|
||||
Submit Code
|
||||
{t('otpAuthSubmit')}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
|
@ -316,7 +318,7 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
|
|||
disabled={loading}
|
||||
>
|
||||
<LockIcon className="w-4 h-4 mr-2" />
|
||||
Log In
|
||||
{t('login')}
|
||||
</Button>
|
||||
|
||||
{hasIdp && (
|
||||
|
@ -327,7 +329,7 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
|
|||
</div>
|
||||
<div className="relative flex justify-center text-xs uppercase">
|
||||
<span className="px-2 bg-card text-muted-foreground">
|
||||
Or continue with
|
||||
{t('idpContinue')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -360,7 +362,7 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
|
|||
mfaForm.reset();
|
||||
}}
|
||||
>
|
||||
Back to Log In
|
||||
{t('otpAuthBack')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -22,6 +22,7 @@ import { Check, ChevronsUpDown, Plus } from "lucide-react";
|
|||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { useUserContext } from "@app/hooks/useUserContext";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface OrgSelectorProps {
|
||||
orgId?: string;
|
||||
|
@ -33,6 +34,7 @@ export function OrgSelector({ orgId, orgs }: OrgSelectorProps) {
|
|||
const [open, setOpen] = useState(false);
|
||||
const router = useRouter();
|
||||
const { env } = useEnvContext();
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
|
@ -47,7 +49,7 @@ export function OrgSelector({ orgId, orgs }: OrgSelectorProps) {
|
|||
<div className="flex items-center justify-between w-full">
|
||||
<div className="flex flex-col items-start">
|
||||
<span className="font-bold text-sm">
|
||||
Organization
|
||||
{t('org')}
|
||||
</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{orgId
|
||||
|
@ -56,7 +58,7 @@ export function OrgSelector({ orgId, orgs }: OrgSelectorProps) {
|
|||
org.orgId ===
|
||||
orgId
|
||||
)?.name
|
||||
: "None selected"}
|
||||
: t('noneSelected')}
|
||||
</span>
|
||||
</div>
|
||||
<ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
|
||||
|
@ -65,14 +67,14 @@ export function OrgSelector({ orgId, orgs }: OrgSelectorProps) {
|
|||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[180px] p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder="Search..." />
|
||||
<CommandInput placeholder={t('searchProgress')} />
|
||||
<CommandEmpty>
|
||||
No organizations found.
|
||||
{t('orgNotFound2')}
|
||||
</CommandEmpty>
|
||||
{(!env.flags.disableUserCreateOrg ||
|
||||
user.serverAdmin) && (
|
||||
<>
|
||||
<CommandGroup heading="Create">
|
||||
<CommandGroup heading={t('create')}>
|
||||
<CommandList>
|
||||
<CommandItem
|
||||
onSelect={(
|
||||
|
@ -84,14 +86,14 @@ export function OrgSelector({ orgId, orgs }: OrgSelectorProps) {
|
|||
}}
|
||||
>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
New Organization
|
||||
{t('setupNewOrg')}
|
||||
</CommandItem>
|
||||
</CommandList>
|
||||
</CommandGroup>
|
||||
<CommandSeparator />
|
||||
</>
|
||||
)}
|
||||
<CommandGroup heading="Organizations">
|
||||
<CommandGroup heading={t('orgs')}>
|
||||
<CommandList>
|
||||
{orgs?.map((org) => (
|
||||
<CommandItem
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
InfoSections,
|
||||
InfoSectionTitle
|
||||
} from "@app/components/InfoSection";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
type PermissionsSelectBoxProps = {
|
||||
root?: boolean;
|
||||
|
@ -15,101 +16,103 @@ type PermissionsSelectBoxProps = {
|
|||
};
|
||||
|
||||
function getActionsCategories(root: boolean) {
|
||||
const t = useTranslations();
|
||||
|
||||
const actionsByCategory: Record<string, Record<string, string>> = {
|
||||
Organization: {
|
||||
"Get Organization": "getOrg",
|
||||
"Update Organization": "updateOrg",
|
||||
"Get Organization User": "getOrgUser",
|
||||
"List Organization Domains": "listOrgDomains",
|
||||
[t('actionGetOrg')]: "getOrg",
|
||||
[t('actionUpdateOrg')]: "updateOrg",
|
||||
[t('actionGetOrgUser')]: "getOrgUser",
|
||||
[t('actionListOrgDomains')]: "listOrgDomains",
|
||||
},
|
||||
|
||||
Site: {
|
||||
"Create Site": "createSite",
|
||||
"Delete Site": "deleteSite",
|
||||
"Get Site": "getSite",
|
||||
"List Sites": "listSites",
|
||||
"Update Site": "updateSite",
|
||||
"List Allowed Site Roles": "listSiteRoles"
|
||||
[t('actionCreateSite')]: "createSite",
|
||||
[t('actionDeleteSite')]: "deleteSite",
|
||||
[t('actionGetSite')]: "getSite",
|
||||
[t('actionListSites')]: "listSites",
|
||||
[t('actionUpdateSite')]: "updateSite",
|
||||
[t('actionListSiteRoles')]: "listSiteRoles"
|
||||
},
|
||||
|
||||
Resource: {
|
||||
"Create Resource": "createResource",
|
||||
"Delete Resource": "deleteResource",
|
||||
"Get Resource": "getResource",
|
||||
"List Resources": "listResources",
|
||||
"Update Resource": "updateResource",
|
||||
"List Resource Users": "listResourceUsers",
|
||||
"Set Resource Users": "setResourceUsers",
|
||||
"Set Allowed Resource Roles": "setResourceRoles",
|
||||
"List Allowed Resource Roles": "listResourceRoles",
|
||||
"Set Resource Password": "setResourcePassword",
|
||||
"Set Resource Pincode": "setResourcePincode",
|
||||
"Set Resource Email Whitelist": "setResourceWhitelist",
|
||||
"Get Resource Email Whitelist": "getResourceWhitelist"
|
||||
[t('actionCreateResource')]: "createResource",
|
||||
[t('actionDeleteResource')]: "deleteResource",
|
||||
[t('actionGetResource')]: "getResource",
|
||||
[t('actionListResource')]: "listResources",
|
||||
[t('actionUpdateResource')]: "updateResource",
|
||||
[t('actionListResourceUsers')]: "listResourceUsers",
|
||||
[t('actionSetResourceUsers')]: "setResourceUsers",
|
||||
[t('actionSetAllowedResourceRoles')]: "setResourceRoles",
|
||||
[t('actionListAllowedResourceRoles')]: "listResourceRoles",
|
||||
[t('actionSetResourcePassword')]: "setResourcePassword",
|
||||
[t('actionSetResourcePincode')]: "setResourcePincode",
|
||||
[t('actionSetResourceEmailWhitelist')]: "setResourceWhitelist",
|
||||
[t('actionGetResourceEmailWhitelist')]: "getResourceWhitelist"
|
||||
},
|
||||
|
||||
Target: {
|
||||
"Create Target": "createTarget",
|
||||
"Delete Target": "deleteTarget",
|
||||
"Get Target": "getTarget",
|
||||
"List Targets": "listTargets",
|
||||
"Update Target": "updateTarget"
|
||||
[t('actionCreateTarget')]: "createTarget",
|
||||
[t('actionDeleteTarget')]: "deleteTarget",
|
||||
[t('actionGetTarget')]: "getTarget",
|
||||
[t('actionListTargets')]: "listTargets",
|
||||
[t('actionUpdateTarget')]: "updateTarget"
|
||||
},
|
||||
|
||||
Role: {
|
||||
"Create Role": "createRole",
|
||||
"Delete Role": "deleteRole",
|
||||
"Get Role": "getRole",
|
||||
"List Roles": "listRoles",
|
||||
"Update Role": "updateRole",
|
||||
"List Allowed Role Resources": "listRoleResources"
|
||||
[t('actionCreateRole')]: "createRole",
|
||||
[t('actionDeleteRole')]: "deleteRole",
|
||||
[t('actionGetRole')]: "getRole",
|
||||
[t('actionListRole')]: "listRoles",
|
||||
[t('actionUpdateRole')]: "updateRole",
|
||||
[t('actionListAllowedRoleResources')]: "listRoleResources"
|
||||
},
|
||||
|
||||
User: {
|
||||
"Invite User": "inviteUser",
|
||||
"Remove User": "removeUser",
|
||||
"List Users": "listUsers",
|
||||
"Add User Role": "addUserRole"
|
||||
[t('actionInviteUser')]: "inviteUser",
|
||||
[t('actionRemoveUser')]: "removeUser",
|
||||
[t('actionListUsers')]: "listUsers",
|
||||
[t('actionAddUserRole')]: "addUserRole"
|
||||
},
|
||||
|
||||
"Access Token": {
|
||||
"Generate Access Token": "generateAccessToken",
|
||||
"Delete Access Token": "deleteAcessToken",
|
||||
"List Access Tokens": "listAccessTokens"
|
||||
[t('actionGenerateAccessToken')]: "generateAccessToken",
|
||||
[t('actionDeleteAccessToken')]: "deleteAcessToken",
|
||||
[t('actionListAccessTokens')]: "listAccessTokens"
|
||||
},
|
||||
|
||||
"Resource Rule": {
|
||||
"Create Resource Rule": "createResourceRule",
|
||||
"Delete Resource Rule": "deleteResourceRule",
|
||||
"List Resource Rules": "listResourceRules",
|
||||
"Update Resource Rule": "updateResourceRule"
|
||||
[t('actionCreateResourceRule')]: "createResourceRule",
|
||||
[t('actionDeleteResourceRule')]: "deleteResourceRule",
|
||||
[t('actionListResourceRules')]: "listResourceRules",
|
||||
[t('actionUpdateResourceRule')]: "updateResourceRule"
|
||||
}
|
||||
};
|
||||
|
||||
if (root) {
|
||||
actionsByCategory["Organization"] = {
|
||||
"List Organizations": "listOrgs",
|
||||
"Check ID": "checkOrgId",
|
||||
"Create Organization": "createOrg",
|
||||
"Delete Organization": "deleteOrg",
|
||||
"List API Keys": "listApiKeys",
|
||||
"List API Key Actions": "listApiKeyActions",
|
||||
"Set API Key Allowed Actions": "setApiKeyActions",
|
||||
"Create API Key": "createApiKey",
|
||||
"Delete API Key": "deleteApiKey",
|
||||
[t('actionListOrgs')]: "listOrgs",
|
||||
[t('actionCheckOrgId')]: "checkOrgId",
|
||||
[t('actionCreateOrg')]: "createOrg",
|
||||
[t('actionDeleteOrg')]: "deleteOrg",
|
||||
[t('actionListApiKeys')]: "listApiKeys",
|
||||
[t('actionListApiKeyActions')]: "listApiKeyActions",
|
||||
[t('actionSetApiKeyActions')]: "setApiKeyActions",
|
||||
[t('actionCreateApiKey')]: "createApiKey",
|
||||
[t('actionDeleteApiKey')]: "deleteApiKey",
|
||||
...actionsByCategory["Organization"]
|
||||
};
|
||||
|
||||
actionsByCategory["Identity Provider (IDP)"] = {
|
||||
"Create IDP": "createIdp",
|
||||
"Update IDP": "updateIdp",
|
||||
"Delete IDP": "deleteIdp",
|
||||
"List IDP": "listIdps",
|
||||
"Get IDP": "getIdp",
|
||||
"Create IDP Org Policy": "createIdpOrg",
|
||||
"Delete IDP Org Policy": "deleteIdpOrg",
|
||||
"List IDP Orgs": "listIdpOrgs",
|
||||
"Update IDP Org": "updateIdpOrg"
|
||||
[t('actionCreateIdp')]: "createIdp",
|
||||
[t('actionUpdateIdp')]: "updateIdp",
|
||||
[t('actionDeleteIdp')]: "deleteIdp",
|
||||
[t('actionListIdps')]: "listIdps",
|
||||
[t('actionGetIdp')]: "getIdp",
|
||||
[t('actionCreateIdpOrg')]: "createIdpOrg",
|
||||
[t('actionDeleteIdpOrg')]: "deleteIdpOrg",
|
||||
[t('actionListIdpOrgs')]: "listIdpOrgs",
|
||||
[t('actionUpdateIdpOrg')]: "updateIdpOrg"
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { cn } from "@app/lib/cn";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
type ProfessionalContentOverlayProps = {
|
||||
children: React.ReactNode;
|
||||
|
@ -11,6 +12,8 @@ export function ProfessionalContentOverlay({
|
|||
children,
|
||||
isProfessional = false
|
||||
}: ProfessionalContentOverlayProps) {
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
|
@ -22,11 +25,10 @@ export function ProfessionalContentOverlay({
|
|||
<div className="absolute inset-0 flex items-center justify-center bg-background/80 z-50">
|
||||
<div className="text-center p-6 bg-primary/10 rounded-lg">
|
||||
<h3 className="text-lg font-semibold mb-2">
|
||||
Professional Edition Required
|
||||
{t('licenseTierProfessionalRequired')}
|
||||
</h3>
|
||||
<p className="text-muted-foreground">
|
||||
This feature is only available in the Professional
|
||||
Edition.
|
||||
{t('licenseTierProfessionalRequiredDescription')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -24,6 +24,7 @@ import Enable2FaForm from "./Enable2FaForm";
|
|||
import SupporterStatus from "./SupporterStatus";
|
||||
import { UserType } from "@server/types/UserTypes";
|
||||
import LocaleSwitcher from '@app/components/LocaleSwitcher';
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
|
||||
export default function ProfileIcon() {
|
||||
|
@ -40,6 +41,8 @@ export default function ProfileIcon() {
|
|||
const [openEnable2fa, setOpenEnable2fa] = useState(false);
|
||||
const [openDisable2fa, setOpenDisable2fa] = useState(false);
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
function getInitials() {
|
||||
return (user.email || user.name || user.username)
|
||||
.substring(0, 1)
|
||||
|
@ -54,10 +57,10 @@ export default function ProfileIcon() {
|
|||
function logout() {
|
||||
api.post("/auth/logout")
|
||||
.catch((e) => {
|
||||
console.error("Error logging out", e);
|
||||
console.error(t('logoutError'), e);
|
||||
toast({
|
||||
title: "Error logging out",
|
||||
description: formatAxiosError(e, "Error logging out")
|
||||
title: t('logoutError'),
|
||||
description: formatAxiosError(e, t('logoutError'))
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
|
@ -94,7 +97,7 @@ export default function ProfileIcon() {
|
|||
<DropdownMenuLabel className="font-normal">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<p className="text-sm font-medium leading-none">
|
||||
Signed in as
|
||||
{t('signingAs')}
|
||||
</p>
|
||||
<p className="text-xs leading-none text-muted-foreground">
|
||||
{user.email || user.name || user.username}
|
||||
|
@ -102,11 +105,11 @@ export default function ProfileIcon() {
|
|||
</div>
|
||||
{user.serverAdmin ? (
|
||||
<p className="text-xs leading-none text-muted-foreground mt-2">
|
||||
Server Admin
|
||||
{t('serverAdmin')}
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-xs leading-none text-muted-foreground mt-2">
|
||||
{user.idpName || "Internal"}
|
||||
{user.idpName || t('idpNameInternal')}
|
||||
</p>
|
||||
)}
|
||||
</DropdownMenuLabel>
|
||||
|
@ -117,14 +120,14 @@ export default function ProfileIcon() {
|
|||
<DropdownMenuItem
|
||||
onClick={() => setOpenEnable2fa(true)}
|
||||
>
|
||||
<span>Enable Two-factor</span>
|
||||
<span>{t('otpEnable')}</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{user.twoFactorEnabled && (
|
||||
<DropdownMenuItem
|
||||
onClick={() => setOpenDisable2fa(true)}
|
||||
>
|
||||
<span>Disable Two-factor</span>
|
||||
<span>{t('otpDisable')}</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
|
@ -166,7 +169,7 @@ export default function ProfileIcon() {
|
|||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={() => logout()}>
|
||||
{/* <LogOut className="mr-2 h-4 w-4" /> */}
|
||||
<span>Log Out</span>
|
||||
<span>{t('logout')}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
|
|
@ -8,6 +8,7 @@ import { ChevronDown, ChevronRight } from "lucide-react";
|
|||
import { useUserContext } from "@app/hooks/useUserContext";
|
||||
import { Badge } from "@app/components/ui/badge";
|
||||
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export interface SidebarNavItem {
|
||||
href: string;
|
||||
|
@ -65,6 +66,8 @@ export function SidebarNav({
|
|||
|
||||
const { user } = useUserContext();
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
function hydrateHref(val: string): string {
|
||||
return val
|
||||
.replace("{orgId}", orgId)
|
||||
|
@ -144,7 +147,7 @@ export function SidebarNav({
|
|||
variant="outlinePrimary"
|
||||
className="ml-2"
|
||||
>
|
||||
Professional
|
||||
{t('licenseBadge')}
|
||||
</Badge>
|
||||
)}
|
||||
</Link>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import Image from "next/image";
|
||||
import { Separator } from "@app/components/ui/separator";
|
||||
import { useSupporterStatusContext } from "@app/hooks/useSupporterStatusContext";
|
||||
import { useState } from "react";
|
||||
import { useState, useTransition } from "react";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
|
@ -48,6 +48,7 @@ import {
|
|||
} from "./ui/card";
|
||||
import { Check, ExternalLink } from "lucide-react";
|
||||
import confetti from "canvas-confetti";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const formSchema = z.object({
|
||||
githubUsername: z
|
||||
|
@ -73,6 +74,8 @@ export default function SupporterStatus() {
|
|||
}
|
||||
});
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
async function hide() {
|
||||
await api.post("/supporter-key/hide");
|
||||
|
||||
|
@ -95,8 +98,8 @@ export default function SupporterStatus() {
|
|||
if (!data || !data.valid) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Invalid Key",
|
||||
description: "Your supporter key is invalid."
|
||||
title: t('supportKeyInvalid'),
|
||||
description: t('supportKeyInvalidDescription')
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -104,9 +107,8 @@ export default function SupporterStatus() {
|
|||
// Trigger the toast
|
||||
toast({
|
||||
variant: "default",
|
||||
title: "Valid Key",
|
||||
description:
|
||||
"Your supporter key has been validated. Thank you for your support!"
|
||||
title: t('supportKeyValid'),
|
||||
description: t('supportKeyValidDescription')
|
||||
});
|
||||
|
||||
// Fireworks-style confetti
|
||||
|
@ -162,7 +164,7 @@ export default function SupporterStatus() {
|
|||
} catch (error) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error",
|
||||
title: t('error'),
|
||||
description: formatAxiosError(
|
||||
error,
|
||||
"Failed to validate supporter key."
|
||||
|
@ -183,55 +185,47 @@ export default function SupporterStatus() {
|
|||
<CredenzaContent className="max-w-3xl">
|
||||
<CredenzaHeader>
|
||||
<CredenzaTitle>
|
||||
Support Development and Adopt a Pangolin!
|
||||
{t('supportKey')}
|
||||
</CredenzaTitle>
|
||||
</CredenzaHeader>
|
||||
<CredenzaBody>
|
||||
<p>
|
||||
Purchase a supporter key to help us continue
|
||||
developing Pangolin for the community. Your
|
||||
contribution allows us to commit more time to
|
||||
maintain and add new features to the application for
|
||||
everyone. We will never use this to paywall
|
||||
features. This is separate from any Commercial
|
||||
Edition.
|
||||
{t('supportKeyDescription')}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You will also get to adopt and meet your very own
|
||||
pet Pangolin!
|
||||
{t('supportKeyPet')}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Payments are processed via GitHub. Afterward, you
|
||||
can retrieve your key on{" "}
|
||||
{t('supportKeyPurchase')}{" "}
|
||||
<Link
|
||||
href="https://supporters.fossorial.io/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline"
|
||||
>
|
||||
our website
|
||||
{t('supportKeyPurchaseLink')}
|
||||
</Link>{" "}
|
||||
and redeem it here.{" "}
|
||||
{t('supportKeyPurchase2')}{" "}
|
||||
<Link
|
||||
href="https://docs.fossorial.io/supporter-program"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline"
|
||||
>
|
||||
Learn more.
|
||||
{t('supportKeyLearnMore')}
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<div className="py-6">
|
||||
<p className="mb-3 text-center">
|
||||
Please select the option that best suits you.
|
||||
{t('supportKeyOptions')}
|
||||
</p>
|
||||
<div className="grid md:grid-cols-2 grid-cols-1 gap-8">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Full Supporter</CardTitle>
|
||||
<CardTitle>{t('supportKetOptionFull')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-4xl mb-6">$95</p>
|
||||
|
@ -239,19 +233,19 @@ export default function SupporterStatus() {
|
|||
<li className="flex items-center gap-2">
|
||||
<Check className="h-6 w-6 text-green-500" />
|
||||
<span className="text-muted-foreground">
|
||||
For the whole server
|
||||
{t('forWholeServer')}
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<Check className="h-6 w-6 text-green-500" />
|
||||
<span className="text-muted-foreground">
|
||||
Lifetime purchase
|
||||
{t('lifetimePurchase')}
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<Check className="h-6 w-6 text-green-500" />
|
||||
<span className="text-muted-foreground">
|
||||
Supporter status
|
||||
{t('supporterStatus')}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -264,7 +258,7 @@ export default function SupporterStatus() {
|
|||
className="w-full"
|
||||
>
|
||||
<Button className="w-full">
|
||||
Buy
|
||||
{t('buy')}
|
||||
</Button>
|
||||
</Link>
|
||||
</CardFooter>
|
||||
|
@ -274,7 +268,7 @@ export default function SupporterStatus() {
|
|||
className={`${supporterStatus?.tier === "Limited Supporter" ? "opacity-50" : ""}`}
|
||||
>
|
||||
<CardHeader>
|
||||
<CardTitle>Limited Supporter</CardTitle>
|
||||
<CardTitle>{t('supportKeyOptionLimited')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-4xl mb-6">$25</p>
|
||||
|
@ -282,19 +276,19 @@ export default function SupporterStatus() {
|
|||
<li className="flex items-center gap-2">
|
||||
<Check className="h-6 w-6 text-green-500" />
|
||||
<span className="text-muted-foreground">
|
||||
For 5 or less users
|
||||
{t('forFiveUsers')}
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<Check className="h-6 w-6 text-green-500" />
|
||||
<span className="text-muted-foreground">
|
||||
Lifetime purchase
|
||||
{t('lifetimePurchase')}
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<Check className="h-6 w-6 text-green-500" />
|
||||
<span className="text-muted-foreground">
|
||||
Supporter status
|
||||
{t('supporterStatus')}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -309,7 +303,7 @@ export default function SupporterStatus() {
|
|||
className="w-full"
|
||||
>
|
||||
<Button className="w-full">
|
||||
Buy
|
||||
{t('buy')}
|
||||
</Button>
|
||||
</Link>
|
||||
) : (
|
||||
|
@ -320,7 +314,7 @@ export default function SupporterStatus() {
|
|||
"Limited Supporter"
|
||||
}
|
||||
>
|
||||
Buy
|
||||
{t('buy')}
|
||||
</Button>
|
||||
)}
|
||||
</CardFooter>
|
||||
|
@ -336,20 +330,20 @@ export default function SupporterStatus() {
|
|||
setKeyOpen(true);
|
||||
}}
|
||||
>
|
||||
Redeem Supporter Key
|
||||
{t('supportKeyRedeem')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="w-full"
|
||||
onClick={() => hide()}
|
||||
>
|
||||
Hide for 7 days
|
||||
{t('supportKeyHideSevenDays')}
|
||||
</Button>
|
||||
</div>
|
||||
</CredenzaBody>
|
||||
<CredenzaFooter>
|
||||
<CredenzaClose asChild>
|
||||
<Button variant="outline">Close</Button>
|
||||
<Button variant="outline">{t('close')}</Button>
|
||||
</CredenzaClose>
|
||||
</CredenzaFooter>
|
||||
</CredenzaContent>
|
||||
|
@ -363,9 +357,9 @@ export default function SupporterStatus() {
|
|||
>
|
||||
<CredenzaContent>
|
||||
<CredenzaHeader>
|
||||
<CredenzaTitle>Enter Supporter Key</CredenzaTitle>
|
||||
<CredenzaTitle>{t('supportKeyEnter')}</CredenzaTitle>
|
||||
<CredenzaDescription>
|
||||
Meet your very own pet Pangolin!
|
||||
{t('supportKeyEnterDescription')}
|
||||
</CredenzaDescription>
|
||||
</CredenzaHeader>
|
||||
<CredenzaBody>
|
||||
|
@ -381,7 +375,7 @@ export default function SupporterStatus() {
|
|||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
GitHub Username
|
||||
{t('githubUsername')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
|
@ -395,7 +389,7 @@ export default function SupporterStatus() {
|
|||
name="key"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Supporter Key</FormLabel>
|
||||
<FormLabel>{t('supportKeyInput')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
|
@ -408,10 +402,10 @@ export default function SupporterStatus() {
|
|||
</CredenzaBody>
|
||||
<CredenzaFooter>
|
||||
<CredenzaClose asChild>
|
||||
<Button variant="outline">Close</Button>
|
||||
<Button variant="outline">{t('close')}</Button>
|
||||
</CredenzaClose>
|
||||
<Button type="submit" form="form">
|
||||
Submit
|
||||
{t('submit')}
|
||||
</Button>
|
||||
</CredenzaFooter>
|
||||
</CredenzaContent>
|
||||
|
@ -426,7 +420,7 @@ export default function SupporterStatus() {
|
|||
setPurchaseOptionsOpen(true);
|
||||
}}
|
||||
>
|
||||
Buy Supporter Key
|
||||
{t('supportKeyBuy')}
|
||||
</Button>
|
||||
) : null}
|
||||
</>
|
||||
|
|
|
@ -10,6 +10,7 @@ import { TagList } from "./tag-list";
|
|||
import { tagVariants } from "./tag";
|
||||
import { Autocomplete } from "./autocomplete";
|
||||
import { cn } from "@app/lib/cn";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export enum Delimiter {
|
||||
Comma = ",",
|
||||
|
@ -166,11 +167,13 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>(
|
|||
);
|
||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
if (
|
||||
(maxTags !== undefined && maxTags < 0) ||
|
||||
(props.minTags !== undefined && props.minTags < 0)
|
||||
) {
|
||||
console.warn("maxTags and minTags cannot be less than 0");
|
||||
console.warn(t('tagsWarnCannotBeLessThanZero'));
|
||||
// error
|
||||
return null;
|
||||
}
|
||||
|
@ -194,24 +197,22 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>(
|
|||
(option) => option.text === newTagText
|
||||
)
|
||||
) {
|
||||
console.warn(
|
||||
"Tag not allowed as per autocomplete options"
|
||||
);
|
||||
console.warn(t('tagsWarnNotAllowedAutocompleteOptions'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (validateTag && !validateTag(newTagText)) {
|
||||
console.warn("Invalid tag as per validateTag");
|
||||
console.warn(t('tagsWarnInvalid'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (minLength && newTagText.length < minLength) {
|
||||
console.warn(`Tag "${newTagText}" is too short`);
|
||||
console.warn(t('tagWarnTooShort', {tagText: newTagText}));
|
||||
return;
|
||||
}
|
||||
|
||||
if (maxLength && newTagText.length > maxLength) {
|
||||
console.warn(`Tag "${newTagText}" is too long`);
|
||||
console.warn(t('tagWarnTooLong', {tagText: newTagText}));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -228,12 +229,10 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>(
|
|||
setTags((prevTags) => [...prevTags, newTag]);
|
||||
onTagAdd?.(newTagText);
|
||||
} else {
|
||||
console.warn(
|
||||
"Reached the maximum number of tags allowed"
|
||||
);
|
||||
console.warn(t('tagsWarnReachedMaxNumber'));
|
||||
}
|
||||
} else {
|
||||
console.warn(`Duplicate tag "${newTagText}" not added`);
|
||||
console.warn(t('tagWarnDuplicate', {tagText: newTagText}));
|
||||
}
|
||||
});
|
||||
setInputValue("");
|
||||
|
@ -259,12 +258,12 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>(
|
|||
}
|
||||
|
||||
if (minLength && newTagText.length < minLength) {
|
||||
console.warn("Tag is too short");
|
||||
console.warn(t('tagWarnTooShort'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (maxLength && newTagText.length > maxLength) {
|
||||
console.warn("Tag is too long");
|
||||
console.warn(t('tagWarnTooLong'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -309,7 +308,7 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>(
|
|||
}
|
||||
|
||||
if (minLength && newTagText.length < minLength) {
|
||||
console.warn("Tag is too short");
|
||||
console.warn(t('tagWarnTooShort'));
|
||||
// error
|
||||
return;
|
||||
}
|
||||
|
@ -317,7 +316,7 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>(
|
|||
// Validate maxLength
|
||||
if (maxLength && newTagText.length > maxLength) {
|
||||
// error
|
||||
console.warn("Tag is too long");
|
||||
console.warn(t('tagWarnTooLong'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import { TagInputStyleClassesProps, type Tag as TagType } from "./tag-input";
|
|||
import { TagList, TagListProps } from "./tag-list";
|
||||
import { Button } from "../ui/button";
|
||||
import { cn } from "@app/lib/cn";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
type TagPopoverProps = {
|
||||
children: React.ReactNode;
|
||||
|
@ -41,6 +42,8 @@ export const TagPopover: React.FC<TagPopoverProps> = ({
|
|||
const [inputFocused, setInputFocused] = useState(false);
|
||||
const [sideOffset, setSideOffset] = useState<number>(0);
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
if (triggerContainerRef.current && triggerRef.current) {
|
||||
|
@ -183,10 +186,10 @@ export const TagPopover: React.FC<TagPopoverProps> = ({
|
|||
>
|
||||
<div className="space-y-1">
|
||||
<h4 className="text-sm font-medium leading-none">
|
||||
Entered Tags
|
||||
{t('tagsEntered')}
|
||||
</h4>
|
||||
<p className="text-sm text-muted-foregrounsd text-left">
|
||||
These are the tags you've entered.
|
||||
{t('tagsEnteredDescription')}
|
||||
</p>
|
||||
</div>
|
||||
<TagList
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue