I18n auth (#23)

* 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

* Add translation keys in app/auth

* Fix build

---------

Co-authored-by: Lokowitz <marvinlokowitz@gmail.com>
This commit is contained in:
vlalx 2025-05-17 19:11:56 +03:00 committed by GitHub
parent d2d84be99a
commit b8ed5ac1c5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 727 additions and 115 deletions

View file

@ -133,7 +133,7 @@
"shareAccessHint": "Jeder mit diesem Link kann auf die Ressource zugreifen. Teilen Sie sie mit Vorsicht.", "shareAccessHint": "Jeder mit diesem Link kann auf die Ressource zugreifen. Teilen Sie sie mit Vorsicht.",
"shareTokenUsage": "Zugriffstoken-Nutzung anzeigen", "shareTokenUsage": "Zugriffstoken-Nutzung anzeigen",
"createLink": "Link erstellen", "createLink": "Link erstellen",
"resourceNotFound": "Keine Ressourcen gefunden", "resourcesNotFound": "Keine Ressourcen gefunden",
"resourceSearch": "Suche Ressourcen", "resourceSearch": "Suche Ressourcen",
"openMenu": "Menü öffnen", "openMenu": "Menü öffnen",
"resource": "Ressource", "resource": "Ressource",
@ -802,5 +802,89 @@
"redirectUrlAbout": "Über die Weiterleitungs-URL", "redirectUrlAbout": "Über die Weiterleitungs-URL",
"redirectUrlAboutDescription": "Dies ist die URL, zu der Benutzer nach der Authentifizierung weitergeleitet werden. Sie müssen diese URL in den Einstellungen Ihres Identitätsanbieters konfigurieren.", "redirectUrlAboutDescription": "Dies ist die URL, zu der Benutzer nach der Authentifizierung weitergeleitet werden. Sie müssen diese URL in den Einstellungen Ihres Identitätsanbieters konfigurieren.",
"key": "Schlüssel", "key": "Schlüssel",
"createdAt": "Erstellt am" "createdAt": "Erstellt am",
"expiresAt": "Läuft ab am",
"pangolinAuth": "Auth - Pangolin",
"emailInvalid": "Ungültige E-Mail-Adresse",
"verificationCodeLengthRequirements": "Ihr Verifizierungscode muss 8 Zeichen lang sein.",
"errorOccurred": "Ein Fehler ist aufgetreten",
"emailErrorVerify": "E-Mail konnte nicht verifiziert werden:",
"emailVerified": "E-Mail erfolgreich verifiziert! Sie werden weitergeleitet...",
"verificationCodeErrorResend": "Verifizierungscode konnte nicht erneut gesendet werden:",
"verificationCodeResend": "Verifizierungscode erneut gesendet",
"verificationCodeResendDescription": "Wir haben einen neuen Verifizierungscode an Ihre E-Mail-Adresse gesendet. Bitte prüfen Sie Ihren Posteingang.",
"emailVerify": "E-Mail verifizieren",
"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",
"emailVerifyResendProgress": "Wird erneut gesendet...",
"emailVerifyResend": "Keinen Code erhalten? Hier klicken zum erneuten Senden",
"passwordNotMatch": "Passwörter stimmen nicht überein",
"signupError": "Beim Registrieren ist ein Fehler aufgetreten",
"pangolinLogoAlt": "Pangolin Logo",
"inviteAlready": "Sieht aus, als wären Sie eingeladen worden!",
"inviteAlreadyDescription": "Um die Einladung anzunehmen, müssen Sie sich einloggen oder ein Konto erstellen.",
"signupQuestion": "Haben Sie bereits ein Konto?",
"login": "Anmelden",
"resourceNotFound": "Ressource nicht gefunden",
"resourceNotFoundDescription": "Die Ressource, auf die Sie zugreifen möchten, existiert nicht.",
"pincodeRequirementsLength": "PIN muss genau 6 Ziffern lang sein",
"pincodeRequirementsChars": "PIN darf nur Zahlen enthalten",
"passwordRequirementsLength": "Passwort muss mindestens 1 Zeichen lang sein",
"otpEmailRequirementsLength": "OTP muss mindestens 1 Zeichen lang sein",
"otpEmailSent": "OTP gesendet",
"otpEmailSentDescription": "Ein OTP wurde an Ihre E-Mail gesendet",
"otpEmailErrorAuthenticate": "Authentifizierung per E-Mail fehlgeschlagen",
"pincodeErrorAuthenticate": "Authentifizierung per PIN fehlgeschlagen",
"passwordErrorAuthenticate": "Authentifizierung per Passwort fehlgeschlagen",
"poweredBy": "Bereitgestellt von",
"authenticationRequired": "Authentifizierung erforderlich",
"authenticationMethodChoose": "Wählen Sie Ihre bevorzugte Methode für den Zugriff auf {name}",
"authenticationRequest": "Sie müssen sich authentifizieren, um auf {name} zuzugreifen",
"user": "Benutzer",
"pincodeInput": "6-stelliger PIN-Code",
"pincodeSubmit": "Mit PIN anmelden",
"passwordSubmit": "Mit Passwort anmelden",
"otpEmailDescription": "Ein Einmalcode wird an diese E-Mail gesendet.",
"otpEmailSend": "Einmalcode senden",
"otpEmail": "Einmalpasswort (OTP)",
"otpEmailSubmit": "OTP absenden",
"backToEmail": "Zurück zur E-Mail",
"noSupportKey": "Server läuft ohne Unterstützer-Schlüssel.<br/>Erwägen Sie, das Projekt zu unterstützen!",
"accessDenied": "Zugriff verweigert",
"accessDeniedDescription": "Sie haben keine Berechtigung, auf diese Ressource zuzugreifen. Falls dies ein Fehler ist, kontaktieren Sie bitte den Administrator.",
"accessTokenError": "Fehler beim Prüfen des Zugriffstokens",
"accessGranted": "Zugriff gewährt",
"accessUrlInvalid": "Zugriffs-URL ungültig",
"accessGrantedDescription": "Ihnen wurde Zugriff auf diese Ressource gewährt. Sie werden weitergeleitet...",
"accessUrlInvalidDescription": "Diese geteilte Zugriffs-URL ist ungültig. Bitte kontaktieren Sie den Ressourceneigentümer für eine neue URL.",
"tokenInvalid": "Ungültiger Token",
"pincodeInvalid": "Ungültiger Code",
"passwordErrorRequestReset": "Zurücksetzung konnte nicht angefordert werden:",
"passwordErrorReset": "Passwort konnte nicht zurückgesetzt werden:",
"passwordResetSuccess": "Passwort erfolgreich zurückgesetzt! Zurück zur Anmeldung...",
"passwordReset": "Passwort zurücksetzen",
"passwordResetDescription": "Folgen Sie den Schritten, um Ihr Passwort zurückzusetzen",
"passwordResetSent": "Wir senden einen Code zum Zurücksetzen des Passworts an diese E-Mail-Adresse.",
"passwordResetCode": "Reset-Code",
"passwordResetCodeDescription": "Prüfen Sie Ihre E-Mail für den Reset-Code.",
"passwordNew": "Neues Passwort",
"passwordNewConfirm": "Neues Passwort bestätigen",
"pincodeAuth": "Authentifizierungscode",
"pincodeSubmit2": "Code absenden",
"passwordResetSubmit": "Zurücksetzung anfordern",
"passwordBack": "Zurück zum Passwort",
"loginBack": "Zurück zur Anmeldung",
"signup": "Registrieren",
"loginStart": "Melden Sie sich an, um zu beginnen",
"idpOidcTokenValidating": "OIDC-Token wird validiert",
"idpOidcTokenResponse": "OIDC-Token-Antwort validieren",
"idpErrorOidcTokenValidating": "Fehler beim Validieren des OIDC-Tokens",
"idpConnectingTo": "Verbindung zu {name} wird hergestellt",
"idpConnectingToDescription": "Ihre Identität wird überprüft",
"idpConnectingToProcess": "Verbindung wird hergestellt...",
"idpConnectingToFinished": "Verbunden",
"idpErrorConnectingTo": "Es gab ein Problem bei der Verbindung zu {name}. Bitte kontaktieren Sie Ihren Administrator.",
"idpErrorNotFound": "IdP nicht gefunden"
} }

View file

@ -133,7 +133,7 @@
"shareAccessHint": "Anyone with this link can access the resource. Share it with care.", "shareAccessHint": "Anyone with this link can access the resource. Share it with care.",
"shareTokenUsage": "See Access Token Usage", "shareTokenUsage": "See Access Token Usage",
"createLink": "Create Link", "createLink": "Create Link",
"resourceNotFound": "No resources found", "resourcesNotFound": "No resources found",
"resourceSearch": "Search resources", "resourceSearch": "Search resources",
"openMenu": "Open menu", "openMenu": "Open menu",
"resource": "Resource", "resource": "Resource",
@ -802,5 +802,89 @@
"redirectUrlAbout": "About Redirect URL", "redirectUrlAbout": "About Redirect URL",
"redirectUrlAboutDescription": "This is the URL to which users will be redirected after authentication. You need to configure this URL in your identity provider settings.", "redirectUrlAboutDescription": "This is the URL to which users will be redirected after authentication. You need to configure this URL in your identity provider settings.",
"key": "Key", "key": "Key",
"createdAt": "Created At" "createdAt": "Created At",
"expiresAt": "Expires At",
"pangolinAuth": "Auth - Pangolin",
"emailInvalid": "Invalid email address",
"verificationCodeLengthRequirements": "Your verification code must be 8 characters.",
"errorOccurred": "An error occurred",
"emailErrorVerify": "Failed to verify email:",
"emailVerified": "Email successfully verified! Redirecting you...",
"verificationCodeErrorResend": "Failed to resend verification code:",
"verificationCodeResend": "Verification code resent",
"verificationCodeResendDescription": "We've resent a verification code to your email address. Please check your inbox.",
"emailVerify": "Verify Email",
"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",
"emailVerifyResendProgress": "Resending...",
"emailVerifyResend": "Didn't receive a code? Click here to resend",
"passwordNotMatch": "Passwords do not match",
"signupError": "An error occurred while signing up",
"pangolinLogoAlt": "Pangolin Logo",
"inviteAlready": "Looks like you've been invited!",
"inviteAlreadyDescription": "To accept the invite, you must log in or create an account.",
"signupQuestion": "Already have an account?",
"login": "Log in",
"resourceNotFound": "Resource Not Found",
"resourceNotFoundDescription": "The resource you're trying to access does not exist.",
"pincodeRequirementsLength": "PIN must be exactly 6 digits",
"pincodeRequirementsChars": "PIN must only contain numbers",
"passwordRequirementsLength": "Password must be at least 1 character long",
"otpEmailRequirementsLength": "OTP must be at least 1 character long",
"otpEmailSent": "OTP Sent",
"otpEmailSentDescription": "An OTP has been sent to your email",
"otpEmailErrorAuthenticate": "Failed to authenticate with email",
"pincodeErrorAuthenticate": "Failed to authenticate with pincode",
"passwordErrorAuthenticate": "Failed to authenticate with password",
"poweredBy": "Powered by",
"authenticationRequired": "Authentication Required",
"authenticationMethodChoose": "Choose your preferred method to access {name}",
"authenticationRequest": "You must authenticate to access {name}",
"user": "User",
"pincodeInput": "6-digit PIN Code",
"pincodeSubmit": "Log in with PIN",
"passwordSubmit": "Log In with Password",
"otpEmailDescription": "A one-time code will be sent to this email.",
"otpEmailSend": "Send One-time Code",
"otpEmail": "One-Time Password (OTP)",
"otpEmailSubmit": "Submit OTP",
"backToEmail": "Back to Email",
"noSupportKey": "Server is running without a supporter key.<br/>Consider supporting the project!",
"accessDenied": "Access Denied",
"accessDeniedDescription": "You're not allowed to access this resource. If this is a mistake, please contact the administrator.",
"accessTokenError": "Error checking access token",
"accessGranted": "Access Granted",
"accessUrlInvalid": "Access URL Invalid",
"accessGrantedDescription": "You have been granted access to this resource. Redirecting you...",
"accessUrlInvalidDescription": "This shared access URL is invalid. Please contact the resource owner for a new URL.",
"tokenInvalid": "Invalid token",
"pincodeInvalid": "Invalid code",
"passwordErrorRequestReset": "Failed to request reset:",
"passwordErrorReset": "Failed to reset password:",
"passwordResetSuccess": "Password reset successfully! Back to log in...",
"passwordReset": "Reset Password",
"passwordResetDescription": "Follow the steps to reset your password",
"passwordResetSent": "We'll send a password reset code to this email address.",
"passwordResetCode": "Reset Code",
"passwordResetCodeDescription": "Check your email for the reset code.",
"passwordNew": "New Password",
"passwordNewConfirm": "Confirm New Password",
"pincodeAuth": "Authenticator Code",
"pincodeSubmit2": "Submit Code",
"passwordResetSubmit": "Request Reset",
"passwordBack": "Back to Password",
"loginBack": "Go back to log in",
"signup": "Sign up",
"loginStart": "Log in to get started",
"idpOidcTokenValidating": "Validating OIDC token",
"idpOidcTokenResponse": "Validate OIDC token response",
"idpErrorOidcTokenValidating": "Error validating OIDC token",
"idpConnectingTo": "Connecting to {name}",
"idpConnectingToDescription": "Validating your identity",
"idpConnectingToProcess": "Connecting...",
"idpConnectingToFinished": "Connected",
"idpErrorConnectingTo": "There was a problem connecting to {name}. Please contact your administrator.",
"idpErrorNotFound": "IdP not found"
} }

View file

@ -133,7 +133,7 @@
"shareAccessHint": "N'importe qui avec ce lien peut accéder à la ressource. Partagez-le avec soin.", "shareAccessHint": "N'importe qui avec ce lien peut accéder à la ressource. Partagez-le avec soin.",
"shareTokenUsage": "Voir Utilisation du jeton d'accès", "shareTokenUsage": "Voir Utilisation du jeton d'accès",
"createLink": "Créer un lien", "createLink": "Créer un lien",
"resourceNotFound": "Aucune ressource trouvée", "resourcesNotFound": "Aucune ressource trouvée",
"resourceSearch": "Rechercher des ressources", "resourceSearch": "Rechercher des ressources",
"openMenu": "Ouvrir le menu", "openMenu": "Ouvrir le menu",
"resource": "Ressource", "resource": "Ressource",
@ -802,5 +802,89 @@
"redirectUrlAbout": "À propos de l'URL de redirection", "redirectUrlAbout": "À propos de l'URL de redirection",
"redirectUrlAboutDescription": "C'est l'URL vers laquelle les utilisateurs seront redirigés après l'authentification. Vous devez configurer cette URL dans les paramètres de votre fournisseur d'identité.", "redirectUrlAboutDescription": "C'est l'URL vers laquelle les utilisateurs seront redirigés après l'authentification. Vous devez configurer cette URL dans les paramètres de votre fournisseur d'identité.",
"key": "Clé", "key": "Clé",
"createdAt": "Créé le" "createdAt": "Créé le",
"expiresAt": "Expire le",
"pangolinAuth": "Auth - Pangolin",
"emailInvalid": "Adresse e-mail invalide",
"verificationCodeLengthRequirements": "Votre code de vérification doit comporter 8 caractères.",
"errorOccurred": "Une erreur s'est produite",
"emailErrorVerify": "Échec de la vérification de l'e-mail :",
"emailVerified": "E-mail vérifié avec succès ! Redirection...",
"verificationCodeErrorResend": "Échec du renvoi du code de vérification :",
"verificationCodeResend": "Code de vérification renvoyé",
"verificationCodeResendDescription": "Nous avons renvoyé un code de vérification à votre adresse e-mail. Veuillez vérifier votre boîte de réception.",
"emailVerify": "Vérifier l'e-mail",
"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",
"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",
"signupError": "Une erreur s'est produite lors de l'inscription",
"pangolinLogoAlt": "Logo Pangolin",
"inviteAlready": "On dirait que vous avez été invité !",
"inviteAlreadyDescription": "Pour accepter l'invitation, vous devez vous connecter ou créer un compte.",
"signupQuestion": "Vous avez déjà un compte ?",
"login": "Se connecter",
"resourceNotFound": "Ressource introuvable",
"resourceNotFoundDescription": "La ressource que vous essayez d'accéder n'existe pas.",
"pincodeRequirementsLength": "Le code PIN doit comporter exactement 6 chiffres",
"pincodeRequirementsChars": "Le code PIN ne doit contenir que des chiffres",
"passwordRequirementsLength": "Le mot de passe doit comporter au moins 1 caractère",
"otpEmailRequirementsLength": "L'OTP doit comporter au moins 1 caractère",
"otpEmailSent": "OTP envoyé",
"otpEmailSentDescription": "Un OTP a été envoyé à votre e-mail",
"otpEmailErrorAuthenticate": "Échec de l'authentification par e-mail",
"pincodeErrorAuthenticate": "Échec de l'authentification avec le code PIN",
"passwordErrorAuthenticate": "Échec de l'authentification avec le mot de passe",
"poweredBy": "Propulsé par",
"authenticationRequired": "Authentification requise",
"authenticationMethodChoose": "Choisissez votre méthode préférée pour accéder à {name}",
"authenticationRequest": "Vous devez vous authentifier pour accéder à {name}",
"user": "Utilisateur",
"pincodeInput": "Code PIN à 6 chiffres",
"pincodeSubmit": "Se connecter avec le PIN",
"passwordSubmit": "Se connecter avec le mot de passe",
"otpEmailDescription": "Un code à usage unique sera envoyé à cet e-mail.",
"otpEmailSend": "Envoyer le code à usage unique",
"otpEmail": "Mot de passe à usage unique (OTP)",
"otpEmailSubmit": "Soumettre l'OTP",
"backToEmail": "Retour à l'e-mail",
"noSupportKey": "Le serveur fonctionne sans clé de support.<br/>Envisagez de soutenir le projet !",
"accessDenied": "Accès refusé",
"accessDeniedDescription": "Vous n'êtes pas autorisé à accéder à cette ressource. Si c'est une erreur, veuillez contacter l'administrateur.",
"accessTokenError": "Erreur lors de la vérification du jeton d'accès",
"accessGranted": "Accès accordé",
"accessUrlInvalid": "URL d'accès invalide",
"accessGrantedDescription": "L'accès à cette ressource vous a été accordé. Redirection...",
"accessUrlInvalidDescription": "Cette URL d'accès partagé n'est pas valide. Veuillez contacter le propriétaire de la ressource pour obtenir une nouvelle URL.",
"tokenInvalid": "Jeton invalide",
"pincodeInvalid": "Code invalide",
"passwordErrorRequestReset": "Échec de la demande de réinitialisation :",
"passwordErrorReset": "Échec de la réinitialisation du mot de passe :",
"passwordResetSuccess": "Mot de passe réinitialisé avec succès ! Retour à la connexion...",
"passwordReset": "Réinitialiser le mot de passe",
"passwordResetDescription": "Suivez les étapes pour réinitialiser votre mot de passe",
"passwordResetSent": "Nous allons envoyer un code de réinitialisation à cette adresse e-mail.",
"passwordResetCode": "Code de réinitialisation",
"passwordResetCodeDescription": "Vérifiez votre e-mail pour le code de réinitialisation.",
"passwordNew": "Nouveau mot de passe",
"passwordNewConfirm": "Confirmer le nouveau mot de passe",
"pincodeAuth": "Code d'authentification",
"pincodeSubmit2": "Soumettre le code",
"passwordResetSubmit": "Demander la réinitialisation",
"passwordBack": "Retour au mot de passe",
"loginBack": "Retour à la connexion",
"signup": "S'inscrire",
"loginStart": "Connectez-vous pour commencer",
"idpOidcTokenValidating": "Validation du jeton OIDC",
"idpOidcTokenResponse": "Valider la réponse du jeton OIDC",
"idpErrorOidcTokenValidating": "Erreur lors de la validation du jeton OIDC",
"idpConnectingTo": "Connexion à {name}",
"idpConnectingToDescription": "Validation de votre identité",
"idpConnectingToProcess": "Connexion...",
"idpConnectingToFinished": "Connecté",
"idpErrorConnectingTo": "Un problème est survenu lors de la connexion à {name}. Veuillez contacter votre administrateur.",
"idpErrorNotFound": "IdP introuvable"
} }

View file

@ -133,7 +133,7 @@
"shareAccessHint": "Chiunque abbia questo link può accedere alla risorsa. Condividilo con cura.", "shareAccessHint": "Chiunque abbia questo link può accedere alla risorsa. Condividilo con cura.",
"shareTokenUsage": "Vedi Utilizzo Token Di Accesso", "shareTokenUsage": "Vedi Utilizzo Token Di Accesso",
"createLink": "Crea Collegamento", "createLink": "Crea Collegamento",
"resourceNotFound": "Nessuna risorsa trovata", "resourcesNotFound": "Nessuna risorsa trovata",
"resourceSearch": "Cerca risorse", "resourceSearch": "Cerca risorse",
"openMenu": "Apri menu", "openMenu": "Apri menu",
"resource": "Risorsa", "resource": "Risorsa",
@ -802,5 +802,89 @@
"redirectUrlAbout": "Informazioni sull'URL di Reindirizzamento", "redirectUrlAbout": "Informazioni sull'URL di Reindirizzamento",
"redirectUrlAboutDescription": "Questo è l'URL a cui gli utenti verranno reindirizzati dopo l'autenticazione. Devi configurare questo URL nelle impostazioni del tuo provider di identità.", "redirectUrlAboutDescription": "Questo è l'URL a cui gli utenti verranno reindirizzati dopo l'autenticazione. Devi configurare questo URL nelle impostazioni del tuo provider di identità.",
"key": "Chiave", "key": "Chiave",
"createdAt": "Creato Il" "createdAt": "Creato Il",
"expiresAt": "Scade Il",
"pangolinAuth": "Auth - Pangolin",
"emailInvalid": "Indirizzo email non valido",
"verificationCodeLengthRequirements": "Il tuo codice di verifica deve essere di 8 caratteri.",
"errorOccurred": "Si è verificato un errore",
"emailErrorVerify": "Impossibile verificare l'email:",
"emailVerified": "Email verificata con successo! Reindirizzamento in corso...",
"verificationCodeErrorResend": "Impossibile reinviare il codice di verifica:",
"verificationCodeResend": "Codice di verifica reinviato",
"verificationCodeResendDescription": "Abbiamo reinviato un codice di verifica al tuo indirizzo email. Controlla la tua casella di posta.",
"emailVerify": "Verifica Email",
"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",
"emailVerifyResendProgress": "Reinvio in corso...",
"emailVerifyResend": "Non hai ricevuto il codice? Clicca qui per reinviare",
"passwordNotMatch": "Le password non coincidono",
"signupError": "Si è verificato un errore durante la registrazione",
"pangolinLogoAlt": "Logo Pangolin",
"inviteAlready": "Sembra che sei stato invitato!",
"inviteAlreadyDescription": "Per accettare l'invito, devi accedere o creare un account.",
"signupQuestion": "Hai già un account?",
"login": "Accedi",
"resourceNotFound": "Risorsa Non Trovata",
"resourceNotFoundDescription": "La risorsa che stai cercando di accedere non esiste.",
"pincodeRequirementsLength": "Il PIN deve essere esattamente di 6 cifre",
"pincodeRequirementsChars": "Il PIN deve contenere solo numeri",
"passwordRequirementsLength": "La password deve essere lunga almeno 1 carattere",
"otpEmailRequirementsLength": "L'OTP deve essere lungo almeno 1 carattere",
"otpEmailSent": "OTP Inviato",
"otpEmailSentDescription": "Un OTP è stato inviato alla tua email",
"otpEmailErrorAuthenticate": "Impossibile autenticare con l'email",
"pincodeErrorAuthenticate": "Impossibile autenticare con il codice PIN",
"passwordErrorAuthenticate": "Impossibile autenticare con la password",
"poweredBy": "Offerto da",
"authenticationRequired": "Autenticazione Richiesta",
"authenticationMethodChoose": "Scegli il tuo metodo preferito per accedere a {name}",
"authenticationRequest": "Devi autenticarti per accedere a {name}",
"user": "Utente",
"pincodeInput": "Codice PIN a 6 cifre",
"pincodeSubmit": "Accedi con PIN",
"passwordSubmit": "Accedi con Password",
"otpEmailDescription": "Un codice usa e getta verrà inviato a questa email.",
"otpEmailSend": "Invia Codice Usa e Getta",
"otpEmail": "Password Usa e Getta (OTP)",
"otpEmailSubmit": "Invia OTP",
"backToEmail": "Torna all'Email",
"noSupportKey": "Il server è in esecuzione senza una chiave di supporto.<br/>Considera di supportare il progetto!",
"accessDenied": "Accesso Negato",
"accessDeniedDescription": "Non sei autorizzato ad accedere a questa risorsa. Se ritieni che sia un errore, contatta l'amministratore.",
"accessTokenError": "Errore nel controllo del token di accesso",
"accessGranted": "Accesso Concesso",
"accessUrlInvalid": "URL di Accesso Non Valido",
"accessGrantedDescription": "Ti è stato concesso l'accesso a questa risorsa. Reindirizzamento in corso...",
"accessUrlInvalidDescription": "Questo URL di accesso condiviso non è valido. Contatta il proprietario della risorsa per un nuovo URL.",
"tokenInvalid": "Token non valido",
"pincodeInvalid": "Codice non valido",
"passwordErrorRequestReset": "Impossibile richiedere il reset:",
"passwordErrorReset": "Impossibile reimpostare la password:",
"passwordResetSuccess": "Password reimpostata con successo! Torna al login...",
"passwordReset": "Reimposta Password",
"passwordResetDescription": "Segui i passaggi per reimpostare la tua password",
"passwordResetSent": "Invieremo un codice di reset della password a questo indirizzo email.",
"passwordResetCode": "Codice di Reset",
"passwordResetCodeDescription": "Controlla la tua email per il codice di reset.",
"passwordNew": "Nuova Password",
"passwordNewConfirm": "Conferma Nuova Password",
"pincodeAuth": "Codice Autenticatore",
"pincodeSubmit2": "Invia Codice",
"passwordResetSubmit": "Richiedi Reset",
"passwordBack": "Torna alla Password",
"loginBack": "Torna al login",
"signup": "Registrati",
"loginStart": "Accedi per iniziare",
"idpOidcTokenValidating": "Convalida token OIDC",
"idpOidcTokenResponse": "Convalida risposta token OIDC",
"idpErrorOidcTokenValidating": "Errore nella convalida del token OIDC",
"idpConnectingTo": "Connessione a {name}",
"idpConnectingToDescription": "Convalida della tua identità",
"idpConnectingToProcess": "Connessione in corso...",
"idpConnectingToFinished": "Connesso",
"idpErrorConnectingTo": "Si è verificato un problema durante la connessione a {name}. Contatta il tuo amministratore.",
"idpErrorNotFound": "IdP non trovato"
} }

View file

@ -133,7 +133,7 @@
"shareAccessHint": "Każdy z tym linkiem może uzyskać dostęp do zasobu. Podziel się nim ostrożnie.", "shareAccessHint": "Każdy z tym linkiem może uzyskać dostęp do zasobu. Podziel się nim ostrożnie.",
"shareTokenUsage": "Zobacz użycie tokenu dostępu", "shareTokenUsage": "Zobacz użycie tokenu dostępu",
"createLink": "Utwórz link", "createLink": "Utwórz link",
"resourceNotFound": "Nie znaleziono zasobów", "resourcesNotFound": "Nie znaleziono zasobów",
"resourceSearch": "Szukaj zasobów", "resourceSearch": "Szukaj zasobów",
"openMenu": "Otwórz menu", "openMenu": "Otwórz menu",
"resource": "Zasoby", "resource": "Zasoby",
@ -802,5 +802,89 @@
"redirectUrlAbout": "O URL przekierowania", "redirectUrlAbout": "O URL przekierowania",
"redirectUrlAboutDescription": "Jest to URL, na który użytkownicy zostaną przekierowani po uwierzytelnieniu. Musisz skonfigurować ten URL w ustawieniach swojego dostawcy tożsamości.", "redirectUrlAboutDescription": "Jest to URL, na który użytkownicy zostaną przekierowani po uwierzytelnieniu. Musisz skonfigurować ten URL w ustawieniach swojego dostawcy tożsamości.",
"key": "Klucz", "key": "Klucz",
"createdAt": "Utworzono" "createdAt": "Utworzono",
"expiresAt": "Wygasa w dniu",
"pangolinAuth": "Autoryzacja - Pangolin",
"emailInvalid": "Nieprawidłowy adres e-mail",
"verificationCodeLengthRequirements": "Twój kod weryfikacyjny musi mieć 8 znaków.",
"errorOccurred": "Wystąpił błąd",
"emailErrorVerify": "Nie udało się zweryfikować adresu e-mail:",
"emailVerified": "E-mail został pomyślnie zweryfikowany! Przekierowywanie...",
"verificationCodeErrorResend": "Nie udało się ponownie wysłać kodu weryfikacyjnego:",
"verificationCodeResend": "Kod weryfikacyjny wysłany ponownie",
"verificationCodeResendDescription": "Wysłaliśmy ponownie kod weryfikacyjny na Twój adres e-mail. Sprawdź swoją skrzynkę odbiorczą.",
"emailVerify": "Zweryfikuj e-mail",
"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",
"emailVerifyResendProgress": "Ponowne wysyłanie...",
"emailVerifyResend": "Nie otrzymałeś kodu? Kliknij tutaj, aby wysłać ponownie",
"passwordNotMatch": "Hasła nie są zgodne",
"signupError": "Wystąpił błąd podczas rejestracji",
"pangolinLogoAlt": "Logo Pangolin",
"inviteAlready": "Wygląda na to, że zostałeś już zaproszony!",
"inviteAlreadyDescription": "Aby zaakceptować zaproszenie, musisz się zalogować lub utworzyć konto.",
"signupQuestion": "Masz już konto?",
"login": "Zaloguj się",
"resourceNotFound": "Nie znaleziono zasobu",
"resourceNotFoundDescription": "Zasób, do którego próbujesz uzyskać dostęp, nie istnieje.",
"pincodeRequirementsLength": "PIN musi składać się dokładnie z 6 cyfr",
"pincodeRequirementsChars": "PIN może zawierać tylko cyfry",
"passwordRequirementsLength": "Hasło musi mieć co najmniej 1 znak",
"otpEmailRequirementsLength": "Kod jednorazowy musi mieć co najmniej 1 znak",
"otpEmailSent": "Kod jednorazowy wysłany",
"otpEmailSentDescription": "Kod jednorazowy został wysłany na Twój e-mail",
"otpEmailErrorAuthenticate": "Nie udało się uwierzytelnić za pomocą e-maila",
"pincodeErrorAuthenticate": "Nie udało się uwierzytelnić za pomocą kodu PIN",
"passwordErrorAuthenticate": "Nie udało się uwierzytelnić za pomocą hasła",
"poweredBy": "Obsługiwane przez",
"authenticationRequired": "Wymagane uwierzytelnienie",
"authenticationMethodChoose": "Wybierz preferowaną metodę dostępu do {name}",
"authenticationRequest": "Musisz się uwierzytelnić, aby uzyskać dostęp do {name}",
"user": "Użytkownik",
"pincodeInput": "6-cyfrowy kod PIN",
"pincodeSubmit": "Zaloguj się kodem PIN",
"passwordSubmit": "Zaloguj się hasłem",
"otpEmailDescription": "Kod jednorazowy zostanie wysłany na ten adres e-mail.",
"otpEmailSend": "Wyślij kod jednorazowy",
"otpEmail": "Hasło jednorazowe (OTP)",
"otpEmailSubmit": "Wyślij OTP",
"backToEmail": "Powrót do e-maila",
"noSupportKey": "Serwer działa bez klucza wspierającego.<br/>Rozważ wsparcie projektu!",
"accessDenied": "Odmowa dostępu",
"accessDeniedDescription": "Nie masz uprawnień dostępu do tego zasobu. Jeśli to pomyłka, skontaktuj się z administratorem.",
"accessTokenError": "Błąd sprawdzania tokena dostępu",
"accessGranted": "Dostęp przyznany",
"accessUrlInvalid": "Nieprawidłowy URL dostępu",
"accessGrantedDescription": "Otrzymałeś dostęp do tego zasobu. Przekierowywanie...",
"accessUrlInvalidDescription": "Ten udostępniony URL dostępu jest nieprawidłowy. Skontaktuj się z właścicielem zasobu, aby otrzymać nowy URL.",
"tokenInvalid": "Nieprawidłowy token",
"pincodeInvalid": "Nieprawidłowy kod",
"passwordErrorRequestReset": "Nie udało się zażądać resetowania:",
"passwordErrorReset": "Nie udało się zresetować hasła:",
"passwordResetSuccess": "Hasło zostało pomyślnie zresetowane! Powrót do logowania...",
"passwordReset": "Zresetuj hasło",
"passwordResetDescription": "Wykonaj kroki, aby zresetować hasło",
"passwordResetSent": "Wyślemy kod resetowania hasła na ten adres e-mail.",
"passwordResetCode": "Kod resetowania",
"passwordResetCodeDescription": "Sprawdź swój e-mail, aby znaleźć kod resetowania.",
"passwordNew": "Nowe hasło",
"passwordNewConfirm": "Potwierdź nowe hasło",
"pincodeAuth": "Kod uwierzytelniający",
"pincodeSubmit2": "Wyślij kod",
"passwordResetSubmit": "Zażądaj resetowania",
"passwordBack": "Powrót do hasła",
"loginBack": "Wróć do logowania",
"signup": "Zarejestruj się",
"loginStart": "Zaloguj się, aby rozpocząć",
"idpOidcTokenValidating": "Walidacja tokena OIDC",
"idpOidcTokenResponse": "Zweryfikuj odpowiedź tokena OIDC",
"idpErrorOidcTokenValidating": "Błąd walidacji tokena OIDC",
"idpConnectingTo": "Łączenie z {name}",
"idpConnectingToDescription": "Weryfikacja tożsamości",
"idpConnectingToProcess": "Łączenie...",
"idpConnectingToFinished": "Połączono",
"idpErrorConnectingTo": "Wystąpił problem z połączeniem z {name}. Skontaktuj się z administratorem.",
"idpErrorNotFound": "Nie znaleziono IdP"
} }

View file

@ -133,7 +133,7 @@
"shareAccessHint": "Qualquer um com este link pode acessar o recurso. Compartilhe com cuidado.", "shareAccessHint": "Qualquer um com este link pode acessar o recurso. Compartilhe com cuidado.",
"shareTokenUsage": "Ver Uso do Token de Acesso", "shareTokenUsage": "Ver Uso do Token de Acesso",
"createLink": "Criar Link", "createLink": "Criar Link",
"resourceNotFound": "Nenhum recurso encontrado", "resourcesNotFound": "Nenhum recurso encontrado",
"resourceSearch": "Recursos de pesquisa", "resourceSearch": "Recursos de pesquisa",
"openMenu": "Abrir menu", "openMenu": "Abrir menu",
"resource": "Recurso", "resource": "Recurso",
@ -802,5 +802,89 @@
"redirectUrlAbout": "Sobre o URL de Redirecionamento", "redirectUrlAbout": "Sobre o URL de Redirecionamento",
"redirectUrlAboutDescription": "Este é o URL para o qual os utilizadores serão redirecionados após a autenticação. Precisa configurar este URL nas configurações do seu provedor de identidade.", "redirectUrlAboutDescription": "Este é o URL para o qual os utilizadores serão redirecionados após a autenticação. Precisa configurar este URL nas configurações do seu provedor de identidade.",
"key": "Chave", "key": "Chave",
"createdAt": "Criado Em" "createdAt": "Criado Em",
"expiresAt": "Expira em",
"pangolinAuth": "Autenticação - Pangolin",
"emailInvalid": "Endereço de email inválido",
"verificationCodeLengthRequirements": "O seu código de verificação deve ter 8 caracteres.",
"errorOccurred": "Ocorreu um erro",
"emailErrorVerify": "Falha ao verificar o email:",
"emailVerified": "Email verificado com sucesso! Redirecionando...",
"verificationCodeErrorResend": "Falha ao reenviar o código de verificação:",
"verificationCodeResend": "Código de verificação reenviado",
"verificationCodeResendDescription": "Reenviámos um código de verificação para o seu email. Por favor, verifique a sua caixa de entrada.",
"emailVerify": "Verificar Email",
"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",
"emailVerifyResendProgress": "A reenviar...",
"emailVerifyResend": "Não recebeu um código? Clique aqui para reenviar",
"passwordNotMatch": "As palavras-passe não correspondem",
"signupError": "Ocorreu um erro durante o registo",
"pangolinLogoAlt": "Logótipo Pangolin",
"inviteAlready": "Parece que já foi convidado!",
"inviteAlreadyDescription": "Para aceitar o convite, deve iniciar sessão ou criar uma conta.",
"signupQuestion": "Já tem uma conta?",
"login": "Iniciar sessão",
"resourceNotFound": "Recurso Não Encontrado",
"resourceNotFoundDescription": "O recurso que está a tentar aceder não existe.",
"pincodeRequirementsLength": "O PIN deve ter exatamente 6 dígitos",
"pincodeRequirementsChars": "O PIN deve conter apenas números",
"passwordRequirementsLength": "A palavra-passe deve ter pelo menos 1 caractere",
"otpEmailRequirementsLength": "O OTP deve ter pelo menos 1 caractere",
"otpEmailSent": "OTP Enviado",
"otpEmailSentDescription": "Um OTP foi enviado para o seu email",
"otpEmailErrorAuthenticate": "Falha na autenticação por email",
"pincodeErrorAuthenticate": "Falha na autenticação com PIN",
"passwordErrorAuthenticate": "Falha na autenticação com palavra-passe",
"poweredBy": "Desenvolvido por",
"authenticationRequired": "Autenticação Necessária",
"authenticationMethodChoose": "Escolha o seu método preferido para aceder a {name}",
"authenticationRequest": "Deve autenticar-se para aceder a {name}",
"user": "Utilizador",
"pincodeInput": "Código PIN de 6 dígitos",
"pincodeSubmit": "Iniciar sessão com PIN",
"passwordSubmit": "Iniciar Sessão com Palavra-passe",
"otpEmailDescription": "Um código único será enviado para este email.",
"otpEmailSend": "Enviar Código Único",
"otpEmail": "Palavra-passe Única (OTP)",
"otpEmailSubmit": "Submeter OTP",
"backToEmail": "Voltar ao Email",
"noSupportKey": "O servidor está a funcionar sem uma chave de suporte.<br/>Considere apoiar o projeto!",
"accessDenied": "Acesso Negado",
"accessDeniedDescription": "Não tem permissão para aceder a este recurso. Se isto for um erro, contacte o administrador.",
"accessTokenError": "Erro ao verificar o token de acesso",
"accessGranted": "Acesso Concedido",
"accessUrlInvalid": "URL de Acesso Inválido",
"accessGrantedDescription": "Foi-lhe concedido acesso a este recurso. A redirecionar...",
"accessUrlInvalidDescription": "Este URL de acesso partilhado é inválido. Por favor, contacte o proprietário do recurso para obter um novo URL.",
"tokenInvalid": "Token inválido",
"pincodeInvalid": "Código inválido",
"passwordErrorRequestReset": "Falha ao solicitar redefinição:",
"passwordErrorReset": "Falha ao redefinir palavra-passe:",
"passwordResetSuccess": "Palavra-passe redefinida com sucesso! Voltar ao início de sessão...",
"passwordReset": "Redefinir Palavra-passe",
"passwordResetDescription": "Siga os passos para redefinir a sua palavra-passe",
"passwordResetSent": "Enviaremos um código de redefinição de palavra-passe para este email.",
"passwordResetCode": "Código de Redefinição",
"passwordResetCodeDescription": "Verifique o seu email para obter o código de redefinição.",
"passwordNew": "Nova Palavra-passe",
"passwordNewConfirm": "Confirmar Nova Palavra-passe",
"pincodeAuth": "Código do Autenticador",
"pincodeSubmit2": "Submeter Código",
"passwordResetSubmit": "Solicitar Redefinição",
"passwordBack": "Voltar à Palavra-passe",
"loginBack": "Voltar ao início de sessão",
"signup": "Registar",
"loginStart": "Inicie sessão para começar",
"idpOidcTokenValidating": "A validar token OIDC",
"idpOidcTokenResponse": "Validar resposta do token OIDC",
"idpErrorOidcTokenValidating": "Erro ao validar token OIDC",
"idpConnectingTo": "A ligar a {name}",
"idpConnectingToDescription": "A validar a sua identidade",
"idpConnectingToProcess": "A conectar...",
"idpConnectingToFinished": "Conectado",
"idpErrorConnectingTo": "Ocorreu um problema ao ligar a {name}. Por favor, contacte o seu administrador.",
"idpErrorNotFound": "IdP não encontrado"
} }

View file

@ -133,7 +133,7 @@
"shareAccessHint": "Anyone with this link can access the resource. Share it with care.", "shareAccessHint": "Anyone with this link can access the resource. Share it with care.",
"shareTokenUsage": "See Access Token Usage", "shareTokenUsage": "See Access Token Usage",
"createLink": "Create Link", "createLink": "Create Link",
"resourceNotFound": "No resources found", "resourcesNotFound": "No resources found",
"resourceSearch": "Search resources", "resourceSearch": "Search resources",
"openMenu": "Open menu", "openMenu": "Open menu",
"resource": "Resource", "resource": "Resource",
@ -802,5 +802,89 @@
"redirectUrlAbout": "About Redirect URL", "redirectUrlAbout": "About Redirect URL",
"redirectUrlAboutDescription": "This is the URL to which users will be redirected after authentication. You need to configure this URL in your identity provider settings.", "redirectUrlAboutDescription": "This is the URL to which users will be redirected after authentication. You need to configure this URL in your identity provider settings.",
"key": "Key", "key": "Key",
"createdAt": "Created At" "createdAt": "Created At",
"expiresAt": "Expires At",
"pangolinAuth": "Auth - Pangolin",
"emailInvalid": "Invalid email address",
"verificationCodeLengthRequirements": "Your verification code must be 8 characters.",
"errorOccurred": "An error occurred",
"emailErrorVerify": "Failed to verify email:",
"emailVerified": "Email successfully verified! Redirecting you...",
"verificationCodeErrorResend": "Failed to resend verification code:",
"verificationCodeResend": "Verification code resent",
"verificationCodeResendDescription": "We've resent a verification code to your email address. Please check your inbox.",
"emailVerify": "Verify Email",
"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",
"emailVerifyResendProgress": "Resending...",
"emailVerifyResend": "Didn't receive a code? Click here to resend",
"passwordNotMatch": "Passwords do not match",
"signupError": "An error occurred while signing up",
"pangolinLogoAlt": "Pangolin Logo",
"inviteAlready": "Looks like you've been invited!",
"inviteAlreadyDescription": "To accept the invite, you must log in or create an account.",
"signupQuestion": "Already have an account?",
"login": "Log in",
"resourceNotFound": "Resource Not Found",
"resourceNotFoundDescription": "The resource you're trying to access does not exist.",
"pincodeRequirementsLength": "PIN must be exactly 6 digits",
"pincodeRequirementsChars": "PIN must only contain numbers",
"passwordRequirementsLength": "Password must be at least 1 character long",
"otpEmailRequirementsLength": "OTP must be at least 1 character long",
"otpEmailSent": "OTP Sent",
"otpEmailSentDescription": "An OTP has been sent to your email",
"otpEmailErrorAuthenticate": "Failed to authenticate with email",
"pincodeErrorAuthenticate": "Failed to authenticate with pincode",
"passwordErrorAuthenticate": "Failed to authenticate with password",
"poweredBy": "Powered by",
"authenticationRequired": "Authentication Required",
"authenticationMethodChoose": "Choose your preferred method to access {name}",
"authenticationRequest": "You must authenticate to access {name}",
"user": "User",
"pincodeInput": "6-digit PIN Code",
"pincodeSubmit": "Log in with PIN",
"passwordSubmit": "Log In with Password",
"otpEmailDescription": "A one-time code will be sent to this email.",
"otpEmailSend": "Send One-time Code",
"otpEmail": "One-Time Password (OTP)",
"otpEmailSubmit": "Submit OTP",
"backToEmail": "Back to Email",
"noSupportKey": "Server is running without a supporter key.<br/>Consider supporting the project!",
"accessDenied": "Access Denied",
"accessDeniedDescription": "You're not allowed to access this resource. If this is a mistake, please contact the administrator.",
"accessTokenError": "Error checking access token",
"accessGranted": "Access Granted",
"accessUrlInvalid": "Access URL Invalid",
"accessGrantedDescription": "You have been granted access to this resource. Redirecting you...",
"accessUrlInvalidDescription": "This shared access URL is invalid. Please contact the resource owner for a new URL.",
"tokenInvalid": "Invalid token",
"pincodeInvalid": "Invalid code",
"passwordErrorRequestReset": "Failed to request reset:",
"passwordErrorReset": "Failed to reset password:",
"passwordResetSuccess": "Password reset successfully! Back to log in...",
"passwordReset": "Reset Password",
"passwordResetDescription": "Follow the steps to reset your password",
"passwordResetSent": "We'll send a password reset code to this email address.",
"passwordResetCode": "Reset Code",
"passwordResetCodeDescription": "Check your email for the reset code.",
"passwordNew": "New Password",
"passwordNewConfirm": "Confirm New Password",
"pincodeAuth": "Authenticator Code",
"pincodeSubmit2": "Submit Code",
"passwordResetSubmit": "Request Reset",
"passwordBack": "Back to Password",
"loginBack": "Go back to log in",
"signup": "Sign up",
"loginStart": "Log in to get started",
"idpOidcTokenValidating": "Validating OIDC token",
"idpOidcTokenResponse": "Validate OIDC token response",
"idpErrorOidcTokenValidating": "Error validating OIDC token",
"idpConnectingTo": "Connecting to {name}",
"idpConnectingToDescription": "Validating your identity",
"idpConnectingToProcess": "Connecting...",
"idpConnectingToFinished": "Connected",
"idpErrorConnectingTo": "There was a problem connecting to {name}. Please contact your administrator.",
"idpErrorNotFound": "IdP not found"
} }

View file

@ -311,7 +311,7 @@ export default function CreateShareLinkForm({
<CommandInput placeholder={t('resourceSearch')} /> <CommandInput placeholder={t('resourceSearch')} />
<CommandList> <CommandList>
<CommandEmpty> <CommandEmpty>
{t('resourceNotFound')} {t('resourcesNotFound')}
</CommandEmpty> </CommandEmpty>
<CommandGroup> <CommandGroup>
{resources.map( {resources.map(

View file

@ -57,9 +57,11 @@ const createSiteFormSchema = z.object({
.string() .string()
.min(2, { .min(2, {
message: "Name must be at least 2 characters." message: "Name must be at least 2 characters."
message: "Name must be at least 2 characters."
}) })
.max(30, { .max(30, {
message: "Name must not be longer than 30 characters." message: "Name must not be longer than 30 characters."
message: "Name must not be longer than 30 characters."
}), }),
method: z.enum(["wireguard", "newt", "local"]) method: z.enum(["wireguard", "newt", "local"])
}); });
@ -273,6 +275,8 @@ PersistentKeepalive = 5`
const newtConfigDockerRun = `docker run -it fosrl/newt --id ${siteDefaults?.newtId} --secret ${siteDefaults?.newtSecret} --endpoint ${env.app.dashboardUrl}`; const newtConfigDockerRun = `docker run -it fosrl/newt --id ${siteDefaults?.newtId} --secret ${siteDefaults?.newtSecret} --endpoint ${env.app.dashboardUrl}`;
const t = useTranslations();
return loadingPage ? ( return loadingPage ? (
<LoaderPlaceholder height="300px" /> <LoaderPlaceholder height="300px" />
) : ( ) : (

View file

@ -16,6 +16,7 @@ import {
import { Alert, AlertDescription } from "@/components/ui/alert"; import { Alert, AlertDescription } from "@/components/ui/alert";
import { Loader2, CheckCircle2, AlertCircle } from "lucide-react"; import { Loader2, CheckCircle2, AlertCircle } from "lucide-react";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext"; import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
import { useTranslations } from "next-intl";
type ValidateOidcTokenParams = { type ValidateOidcTokenParams = {
orgId: string; orgId: string;
@ -36,11 +37,13 @@ export default function ValidateOidcToken(props: ValidateOidcTokenParams) {
const { licenseStatus, isLicenseViolation } = useLicenseStatusContext(); const { licenseStatus, isLicenseViolation } = useLicenseStatusContext();
const t = useTranslations();
useEffect(() => { useEffect(() => {
async function validate() { async function validate() {
setLoading(true); setLoading(true);
console.log("Validating OIDC token", { console.log(t('idpOidcTokenValidating'), {
code: props.code, code: props.code,
expectedState: props.expectedState, expectedState: props.expectedState,
stateCookie: props.stateCookie stateCookie: props.stateCookie
@ -59,7 +62,7 @@ export default function ValidateOidcToken(props: ValidateOidcTokenParams) {
storedState: props.stateCookie storedState: props.stateCookie
}); });
console.log("Validate OIDC token response", res.data); console.log(t('idpOidcTokenResponse'), res.data);
const redirectUrl = res.data.data.redirectUrl; const redirectUrl = res.data.data.redirectUrl;
@ -76,7 +79,7 @@ export default function ValidateOidcToken(props: ValidateOidcTokenParams) {
router.push(res.data.data.redirectUrl); router.push(res.data.data.redirectUrl);
} }
} catch (e) { } catch (e) {
setError(formatAxiosError(e, "Error validating OIDC token")); setError(formatAxiosError(e, t('idpErrorOidcTokenValidating')));
} finally { } finally {
setLoading(false); setLoading(false);
} }
@ -89,20 +92,20 @@ export default function ValidateOidcToken(props: ValidateOidcTokenParams) {
<div className="flex items-center justify-center min-h-screen"> <div className="flex items-center justify-center min-h-screen">
<Card className="w-full max-w-md"> <Card className="w-full max-w-md">
<CardHeader> <CardHeader>
<CardTitle>Connecting to {props.idp.name}</CardTitle> <CardTitle>{t('idpConnectingTo', {name: props.idp.name})}</CardTitle>
<CardDescription>Validating your identity</CardDescription> <CardDescription>{t('idpConnectingToDescription')}</CardDescription>
</CardHeader> </CardHeader>
<CardContent className="flex flex-col items-center space-y-4"> <CardContent className="flex flex-col items-center space-y-4">
{loading && ( {loading && (
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Loader2 className="h-5 w-5 animate-spin" /> <Loader2 className="h-5 w-5 animate-spin" />
<span>Connecting...</span> <span>{t('idpConnectingToProcess')}</span>
</div> </div>
)} )}
{!loading && !error && ( {!loading && !error && (
<div className="flex items-center space-x-2 text-green-600"> <div className="flex items-center space-x-2 text-green-600">
<CheckCircle2 className="h-5 w-5" /> <CheckCircle2 className="h-5 w-5" />
<span>Connected</span> <span>{t('idpConnectingToFinished')}</span>
</div> </div>
)} )}
{error && ( {error && (
@ -110,9 +113,7 @@ export default function ValidateOidcToken(props: ValidateOidcTokenParams) {
<AlertCircle className="h-5 w-5" /> <AlertCircle className="h-5 w-5" />
<AlertDescription className="flex flex-col space-y-2"> <AlertDescription className="flex flex-col space-y-2">
<span> <span>
There was a problem connecting to{" "} {t('idpErrorConnectingTo', {name: props.idp.name})}
{props.idp.name}. Please contact your
administrator.
</span> </span>
<span className="text-xs">{error}</span> <span className="text-xs">{error}</span>
</AlertDescription> </AlertDescription>

View file

@ -3,6 +3,7 @@ import ValidateOidcToken from "./ValidateOidcToken";
import { idp } from "@server/db/schemas"; import { idp } from "@server/db/schemas";
import db from "@server/db"; import db from "@server/db";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { useTranslations } from "next-intl";
export default async function Page(props: { export default async function Page(props: {
params: Promise<{ orgId: string; idpId: string }>; params: Promise<{ orgId: string; idpId: string }>;
@ -23,8 +24,10 @@ export default async function Page(props: {
.from(idp) .from(idp)
.where(eq(idp.idpId, parseInt(params.idpId!))); .where(eq(idp.idpId, parseInt(params.idpId!)));
const t = useTranslations();
if (!idpRes) { if (!idpRes) {
return <div>IdP not found</div>; return <div>{t('idpErrorNotFound')}</div>;
} }
return ( return (

View file

@ -8,6 +8,7 @@ import { AxiosResponse } from "axios";
import { ExternalLink } from "lucide-react"; import { ExternalLink } from "lucide-react";
import { Metadata } from "next"; import { Metadata } from "next";
import { cache } from "react"; import { cache } from "react";
import { useTranslations } from "next-intl";
export const metadata: Metadata = { export const metadata: Metadata = {
title: `Auth - Pangolin`, title: `Auth - Pangolin`,
@ -21,6 +22,7 @@ type AuthLayoutProps = {
export default async function AuthLayout({ children }: AuthLayoutProps) { export default async function AuthLayout({ children }: AuthLayoutProps) {
const getUser = cache(verifySession); const getUser = cache(verifySession);
const user = await getUser(); const user = await getUser();
const t = useTranslations();
const licenseStatusRes = await cache( const licenseStatusRes = await cache(
async () => async () =>

View file

@ -14,6 +14,7 @@ import { useRouter } from "next/navigation";
import { useEffect } from "react"; import { useEffect } from "react";
import Image from "next/image"; import Image from "next/image";
import { cleanRedirect } from "@app/lib/cleanRedirect"; import { cleanRedirect } from "@app/lib/cleanRedirect";
import { useTranslations } from "next-intl";
type DashboardLoginFormProps = { type DashboardLoginFormProps = {
redirect?: string; redirect?: string;
@ -38,23 +39,25 @@ export default function DashboardLoginForm({
// logout(); // logout();
// }); // });
const t = useTranslations();
return ( return (
<Card className="w-full max-w-md"> <Card className="w-full max-w-md">
<CardHeader> <CardHeader>
<div className="flex flex-row items-center justify-center"> <div className="flex flex-row items-center justify-center">
<Image <Image
src={`/logo/pangolin_orange.svg`} src={`/logo/pangolin_orange.svg`}
alt="Pangolin Logo" alt={t('pangolinLogoAlt')}
width="100" width="100"
height="100" height="100"
/> />
</div> </div>
<div className="text-center space-y-1"> <div className="text-center space-y-1">
<h1 className="text-2xl font-bold mt-1"> <h1 className="text-2xl font-bold mt-1">
Welcome to Pangolin {t('welcome')}
</h1> </h1>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Log in to get started {t('loginStart')}
</p> </p>
</div> </div>
</CardHeader> </CardHeader>

View file

@ -9,6 +9,7 @@ import { cleanRedirect } from "@app/lib/cleanRedirect";
import db from "@server/db"; import db from "@server/db";
import { idp } from "@server/db/schemas"; import { idp } from "@server/db/schemas";
import { LoginFormIDP } from "@app/components/LoginForm"; import { LoginFormIDP } from "@app/components/LoginForm";
import { useTranslations } from "next-intl";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
@ -40,6 +41,8 @@ export default async function Page(props: {
name: idp.name name: idp.name
})) as LoginFormIDP[]; })) as LoginFormIDP[];
const t = useTranslations();
return ( return (
<> <>
{isInvite && ( {isInvite && (
@ -47,11 +50,10 @@ export default async function Page(props: {
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<Mail className="w-12 h-12 mb-4 text-primary" /> <Mail className="w-12 h-12 mb-4 text-primary" />
<h2 className="text-2xl font-bold mb-2 text-center"> <h2 className="text-2xl font-bold mb-2 text-center">
Looks like you've been invited! {t('inviteAlready')}
</h2> </h2>
<p className="text-center"> <p className="text-center">
To accept the invite, you must log in or create an {t('inviteAlreadyDescription')}
account.
</p> </p>
</div> </div>
</div> </div>
@ -70,7 +72,7 @@ export default async function Page(props: {
} }
className="underline" className="underline"
> >
Sign up {t('signup')}
</Link> </Link>
</p> </p>
)} )}

View file

@ -44,6 +44,7 @@ import { useEnvContext } from "@app/hooks/useEnvContext";
import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp"; import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp";
import { passwordSchema } from "@server/auth/passwordSchema"; import { passwordSchema } from "@server/auth/passwordSchema";
import { cleanRedirect } from "@app/lib/cleanRedirect"; import { cleanRedirect } from "@app/lib/cleanRedirect";
import { useTranslations } from "next-intl";
const requestSchema = z.object({ const requestSchema = z.object({
email: z.string().email() email: z.string().email()
@ -122,6 +123,8 @@ export default function ResetPasswordForm({
} }
}); });
const t = useTranslations();
async function onRequest(data: z.infer<typeof requestSchema>) { async function onRequest(data: z.infer<typeof requestSchema>) {
const { email } = data; const { email } = data;
@ -200,9 +203,9 @@ export default function ResetPasswordForm({
<div> <div>
<Card className="w-full max-w-md"> <Card className="w-full max-w-md">
<CardHeader> <CardHeader>
<CardTitle>Reset Password</CardTitle> <CardTitle>{t('passwordReset')}</CardTitle>
<CardDescription> <CardDescription>
Follow the steps to reset your password {t('passwordResetDescription')}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
@ -221,14 +224,13 @@ export default function ResetPasswordForm({
name="email" name="email"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Email</FormLabel> <FormLabel>{t('email')}</FormLabel>
<FormControl> <FormControl>
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
<FormDescription> <FormDescription>
We'll send a password reset {t('passwordResetSent')}
code to this email address.
</FormDescription> </FormDescription>
</FormItem> </FormItem>
)} )}
@ -249,7 +251,7 @@ export default function ResetPasswordForm({
name="email" name="email"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Email</FormLabel> <FormLabel>{t('email')}</FormLabel>
<FormControl> <FormControl>
<Input <Input
{...field} {...field}
@ -268,7 +270,7 @@ export default function ResetPasswordForm({
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel> <FormLabel>
Reset Code {t('passwordResetCode')}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input <Input
@ -278,8 +280,7 @@ export default function ResetPasswordForm({
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
<FormDescription> <FormDescription>
Check your email for the {t('passwordResetCodeDescription')}
reset code.
</FormDescription> </FormDescription>
</FormItem> </FormItem>
)} )}
@ -292,7 +293,7 @@ export default function ResetPasswordForm({
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel> <FormLabel>
New Password {t('passwordNew')}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input <Input
@ -310,7 +311,7 @@ export default function ResetPasswordForm({
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel> <FormLabel>
Confirm New Password {t('passwordNewConfirm')}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input <Input
@ -339,7 +340,7 @@ export default function ResetPasswordForm({
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel> <FormLabel>
Authenticator Code {t('pincodeAuth')}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<div className="flex justify-center"> <div className="flex justify-center">
@ -407,8 +408,8 @@ export default function ResetPasswordForm({
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> <Loader2 className="mr-2 h-4 w-4 animate-spin" />
)} )}
{state === "reset" {state === "reset"
? "Reset Password" ? t('passwordReset')
: "Submit Code"} : t('pincodeSubmit2')}
</Button> </Button>
)} )}
@ -422,7 +423,7 @@ export default function ResetPasswordForm({
{isSubmitting && ( {isSubmitting && (
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> <Loader2 className="mr-2 h-4 w-4 animate-spin" />
)} )}
Request Reset {t('passwordResetSubmit')}
</Button> </Button>
)} )}
@ -436,7 +437,7 @@ export default function ResetPasswordForm({
mfaForm.reset(); mfaForm.reset();
}} }}
> >
Back to Password {t('passwordBack')}
</Button> </Button>
)} )}
@ -450,7 +451,7 @@ export default function ResetPasswordForm({
form.reset(); form.reset();
}} }}
> >
Back to Email {t('backToEmail')}
</Button> </Button>
)} )}
</div> </div>

View file

@ -4,6 +4,7 @@ import { cache } from "react";
import ResetPasswordForm from "./ResetPasswordForm"; import ResetPasswordForm from "./ResetPasswordForm";
import Link from "next/link"; import Link from "next/link";
import { cleanRedirect } from "@app/lib/cleanRedirect"; import { cleanRedirect } from "@app/lib/cleanRedirect";
import { useTranslations } from "next-intl";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
@ -27,6 +28,8 @@ export default async function Page(props: {
redirectUrl = cleanRedirect(searchParams.redirect); redirectUrl = cleanRedirect(searchParams.redirect);
} }
const t = useTranslations();
return ( return (
<> <>
<ResetPasswordForm <ResetPasswordForm
@ -44,7 +47,7 @@ export default async function Page(props: {
} }
className="underline" className="underline"
> >
Go back to log in {t('loginBack')}
</Link> </Link>
</p> </p>
</> </>

View file

@ -13,6 +13,7 @@ import { AuthWithAccessTokenResponse } from "@server/routers/resource";
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";
import Link from "next/link"; import Link from "next/link";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useTranslations } from "next-intl";
type AccessTokenProps = { type AccessTokenProps = {
token: string; token: string;
@ -29,6 +30,8 @@ export default function AccessToken({
const { env } = useEnvContext(); const { env } = useEnvContext();
const api = createApiClient({ env }); const api = createApiClient({ env });
const t = useTranslations();
function appendRequestToken(url: string, token: string) { function appendRequestToken(url: string, token: string) {
const fullUrl = new URL(url); const fullUrl = new URL(url);
fullUrl.searchParams.append( fullUrl.searchParams.append(
@ -76,7 +79,7 @@ export default function AccessToken({
); );
} }
} catch (e) { } catch (e) {
console.error("Error checking access token", e); console.error(t('accessTokenError'), e);
} finally { } finally {
setLoading(false); setLoading(false);
} }
@ -115,9 +118,9 @@ export default function AccessToken({
function renderTitle() { function renderTitle() {
if (isValid) { if (isValid) {
return "Access Granted"; return t('accessGranted');
} else { } else {
return "Access URL Invalid"; return t('accessUrlInvalid');
} }
} }
@ -125,18 +128,16 @@ export default function AccessToken({
if (isValid) { if (isValid) {
return ( return (
<div> <div>
You have been granted access to this resource. Redirecting {t('accessGrantedDescription')}
you...
</div> </div>
); );
} else { } else {
return ( return (
<div> <div>
This shared access URL is invalid. Please contact the {t('accessUrlInvalidDescription')}
resource owner for a new URL.
<div className="text-center mt-4"> <div className="text-center mt-4">
<Button> <Button>
<Link href="/">Go Home</Link> <Link href="/">{t('goHome')}</Link>
</Button> </Button>
</div> </div>
</div> </div>

View file

@ -9,21 +9,23 @@ import {
CardTitle, CardTitle,
} from "@app/components/ui/card"; } from "@app/components/ui/card";
import Link from "next/link"; import Link from "next/link";
import { useTranslations } from "next-intl";
export default function ResourceAccessDenied() { export default function ResourceAccessDenied() {
const t = useTranslations();
return ( return (
<Card className="w-full max-w-md"> <Card className="w-full max-w-md">
<CardHeader> <CardHeader>
<CardTitle className="text-center text-2xl font-bold"> <CardTitle className="text-center text-2xl font-bold">
Access Denied {t('accessDenied')}
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
You're not allowed to access this resource. If this is a mistake, {t('accessDeniedDescription')}
please contact the administrator.
<div className="text-center mt-4"> <div className="text-center mt-4">
<Button> <Button>
<Link href="/">Go Home</Link> <Link href="/">{t('goHome')}</Link>
</Button> </Button>
</div> </div>
</CardContent> </CardContent>

View file

@ -44,6 +44,7 @@ import { useEnvContext } from "@app/hooks/useEnvContext";
import { toast } from "@app/hooks/useToast"; import { toast } from "@app/hooks/useToast";
import Link from "next/link"; import Link from "next/link";
import { useSupporterStatusContext } from "@app/hooks/useSupporterStatusContext"; import { useSupporterStatusContext } from "@app/hooks/useSupporterStatusContext";
import { useTranslations } from "next-intl";
const pinSchema = z.object({ const pinSchema = z.object({
pin: z pin: z
@ -170,6 +171,8 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
return fullUrl.toString(); return fullUrl.toString();
} }
const t = useTranslations();
const onWhitelistSubmit = (values: any) => { const onWhitelistSubmit = (values: any) => {
setLoadingLogin(true); setLoadingLogin(true);
api.post<AxiosResponse<AuthWithWhitelistResponse>>( api.post<AxiosResponse<AuthWithWhitelistResponse>>(
@ -183,8 +186,8 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
setOtpState("otp_sent"); setOtpState("otp_sent");
submitOtpForm.setValue("email", values.email); submitOtpForm.setValue("email", values.email);
toast({ toast({
title: "OTP Sent", title: t('otpEmailSent'),
description: "An OTP has been sent to your email" description: t('otpEmailSentDescription')
}); });
return; return;
} }
@ -200,7 +203,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
.catch((e) => { .catch((e) => {
console.error(e); console.error(e);
setWhitelistError( setWhitelistError(
formatAxiosError(e, "Failed to authenticate with email") formatAxiosError(e, t('otpEmailErrorAuthenticate'))
); );
}) })
.then(() => setLoadingLogin(false)); .then(() => setLoadingLogin(false));
@ -225,7 +228,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
.catch((e) => { .catch((e) => {
console.error(e); console.error(e);
setPincodeError( setPincodeError(
formatAxiosError(e, "Failed to authenticate with pincode") formatAxiosError(e, t('pincodeErrorAuthenticate'))
); );
}) })
.then(() => setLoadingLogin(false)); .then(() => setLoadingLogin(false));
@ -253,7 +256,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
.catch((e) => { .catch((e) => {
console.error(e); console.error(e);
setPasswordError( setPasswordError(
formatAxiosError(e, "Failed to authenticate with password") formatAxiosError(e, t('passwordErrorAuthenticate'))
); );
}) })
.finally(() => setLoadingLogin(false)); .finally(() => setLoadingLogin(false));
@ -280,7 +283,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
<div> <div>
<div className="text-center mb-2"> <div className="text-center mb-2">
<span className="text-sm text-muted-foreground"> <span className="text-sm text-muted-foreground">
Powered by{" "} {t('poweredBy')}{" "}
<Link <Link
href="https://github.com/fosrl/pangolin" href="https://github.com/fosrl/pangolin"
target="_blank" target="_blank"
@ -293,11 +296,11 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
</div> </div>
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>Authentication Required</CardTitle> <CardTitle>{t('authenticationRequired')}</CardTitle>
<CardDescription> <CardDescription>
{numMethods > 1 {numMethods > 1
? `Choose your preferred method to access ${props.resource.name}` ? t('authenticationMethodChoose', {name: props.resource.name})
: `You must authenticate to access ${props.resource.name}`} : t('authenticationRequest', {name: props.resource.name})}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
@ -327,19 +330,19 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
{props.methods.password && ( {props.methods.password && (
<TabsTrigger value="password"> <TabsTrigger value="password">
<Key className="w-4 h-4 mr-1" />{" "} <Key className="w-4 h-4 mr-1" />{" "}
Password {t('password')}
</TabsTrigger> </TabsTrigger>
)} )}
{props.methods.sso && ( {props.methods.sso && (
<TabsTrigger value="sso"> <TabsTrigger value="sso">
<User className="w-4 h-4 mr-1" />{" "} <User className="w-4 h-4 mr-1" />{" "}
User {t('user')}
</TabsTrigger> </TabsTrigger>
)} )}
{props.methods.whitelist && ( {props.methods.whitelist && (
<TabsTrigger value="whitelist"> <TabsTrigger value="whitelist">
<AtSign className="w-4 h-4 mr-1" />{" "} <AtSign className="w-4 h-4 mr-1" />{" "}
Email {t('email')}
</TabsTrigger> </TabsTrigger>
)} )}
</TabsList> </TabsList>
@ -362,7 +365,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel> <FormLabel>
6-digit PIN Code {t('pincodeInput')}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<div className="flex justify-center"> <div className="flex justify-center">
@ -431,7 +434,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
disabled={loadingLogin} disabled={loadingLogin}
> >
<LockIcon className="w-4 h-4 mr-2" /> <LockIcon className="w-4 h-4 mr-2" />
Log in with PIN {t('pincodeSubmit')}
</Button> </Button>
</form> </form>
</Form> </Form>
@ -457,7 +460,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel> <FormLabel>
Password {t('password')}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input <Input
@ -485,7 +488,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
disabled={loadingLogin} disabled={loadingLogin}
> >
<LockIcon className="w-4 h-4 mr-2" /> <LockIcon className="w-4 h-4 mr-2" />
Log In with Password {t('passwordSubmit')}
</Button> </Button>
</form> </form>
</Form> </Form>
@ -526,7 +529,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel> <FormLabel>
Email {t('email')}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input <Input
@ -535,10 +538,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
/> />
</FormControl> </FormControl>
<FormDescription> <FormDescription>
A one-time {t('otpEmailDescription')}
code will be
sent to this
email.
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -560,7 +560,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
disabled={loadingLogin} disabled={loadingLogin}
> >
<Send className="w-4 h-4 mr-2" /> <Send className="w-4 h-4 mr-2" />
Send One-time Code {t('otpEmailSend')}
</Button> </Button>
</form> </form>
</Form> </Form>
@ -582,9 +582,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel> <FormLabel>
One-Time {t('otpEmail')}
Password
(OTP)
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input <Input
@ -612,7 +610,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
disabled={loadingLogin} disabled={loadingLogin}
> >
<LockIcon className="w-4 h-4 mr-2" /> <LockIcon className="w-4 h-4 mr-2" />
Submit OTP {t('otpEmailSubmit')}
</Button> </Button>
<Button <Button
@ -624,7 +622,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
submitOtpForm.reset(); submitOtpForm.reset();
}} }}
> >
Back to Email {t('backToEmail')}
</Button> </Button>
</form> </form>
</Form> </Form>
@ -637,9 +635,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
{supporterStatus?.visible && ( {supporterStatus?.visible && (
<div className="text-center mt-2"> <div className="text-center mt-2">
<span className="text-sm text-muted-foreground opacity-50"> <span className="text-sm text-muted-foreground opacity-50">
Server is running without a supporter key. {t('noSupportKey')}
<br />
Consider supporting the project!
</span> </span>
</div> </div>
)} )}

View file

@ -7,20 +7,23 @@ import {
CardTitle, CardTitle,
} from "@app/components/ui/card"; } from "@app/components/ui/card";
import Link from "next/link"; import Link from "next/link";
import { useTranslations } from "next-intl";
export default async function ResourceNotFound() { export default async function ResourceNotFound() {
const t = useTranslations();
return ( return (
<Card className="w-full max-w-md"> <Card className="w-full max-w-md">
<CardHeader> <CardHeader>
<CardTitle className="text-center text-2xl font-bold"> <CardTitle className="text-center text-2xl font-bold">
Resource Not Found {t('resourceNotFound')}
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
The resource you're trying to access does not exist. {t('resourceNotFoundDescription')}
<div className="text-center mt-4"> <div className="text-center mt-4">
<Button> <Button>
<Link href="/">Go Home</Link> <Link href="/">{t('goHome')}</Link>
</Button> </Button>
</div> </div>
</CardContent> </CardContent>

View file

@ -71,6 +71,8 @@ export default function SignupForm({
} }
}); });
const t = useTranslations();
async function onSubmit(values: z.infer<typeof formSchema>) { async function onSubmit(values: z.infer<typeof formSchema>) {
const { email, password } = values; const { email, password } = values;
@ -113,15 +115,13 @@ export default function SignupForm({
setLoading(false); setLoading(false);
} }
const t = useTranslations();
return ( return (
<Card className="w-full max-w-md"> <Card className="w-full max-w-md">
<CardHeader> <CardHeader>
<div className="flex flex-row items-center justify-center"> <div className="flex flex-row items-center justify-center">
<Image <Image
src={`/logo/pangolin_orange.svg`} src={`/logo/pangolin_orange.svg`}
alt="Pangolin Logo" alt={t('pangolinLogoAlt')}
width="100" width="100"
height="100" height="100"
/> />

View file

@ -6,6 +6,7 @@ import { Mail } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { cache } from "react"; import { cache } from "react";
import { useTranslations } from "next-intl";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
@ -20,6 +21,8 @@ export default async function Page(props: {
const isInvite = searchParams?.redirect?.includes("/invite"); const isInvite = searchParams?.redirect?.includes("/invite");
const t = useTranslations();
if (env.flags.disableSignupWithoutInvite && !isInvite) { if (env.flags.disableSignupWithoutInvite && !isInvite) {
redirect("/"); redirect("/");
} }
@ -54,11 +57,10 @@ export default async function Page(props: {
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<Mail className="w-12 h-12 mb-4 text-primary" /> <Mail className="w-12 h-12 mb-4 text-primary" />
<h2 className="text-2xl font-bold mb-2 text-center"> <h2 className="text-2xl font-bold mb-2 text-center">
Looks like you've been invited! {t('inviteAlready')}
</h2> </h2>
<p className="text-center"> <p className="text-center">
To accept the invite, you must log in or create an {t('inviteAlreadyDescription')}
account.
</p> </p>
</div> </div>
</div> </div>
@ -71,7 +73,7 @@ export default async function Page(props: {
/> />
<p className="text-center text-muted-foreground mt-4"> <p className="text-center text-muted-foreground mt-4">
Already have an account?{" "} {t('signupQuestion')}{" "}
<Link <Link
href={ href={
!redirectUrl !redirectUrl
@ -80,7 +82,7 @@ export default async function Page(props: {
} }
className="underline" className="underline"
> >
Log in {t('login')}
</Link> </Link>
</p> </p>
</> </>

View file

@ -37,6 +37,7 @@ import { formatAxiosError } from "@app/lib/api";;
import { createApiClient } from "@app/lib/api"; import { createApiClient } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext"; import { useEnvContext } from "@app/hooks/useEnvContext";
import { cleanRedirect } from "@app/lib/cleanRedirect"; import { cleanRedirect } from "@app/lib/cleanRedirect";
import { useTranslations } from "next-intl";
const FormSchema = z.object({ const FormSchema = z.object({
email: z.string().email({ message: "Invalid email address" }), email: z.string().email({ message: "Invalid email address" }),
@ -71,6 +72,8 @@ export default function VerifyEmailForm({
}, },
}); });
const t = useTranslations();
async function onSubmit(data: z.infer<typeof FormSchema>) { async function onSubmit(data: z.infer<typeof FormSchema>) {
setIsSubmitting(true); setIsSubmitting(true);
@ -114,8 +117,7 @@ export default function VerifyEmailForm({
toast({ toast({
variant: "default", variant: "default",
title: "Verification code resent", title: "Verification code resent",
description: description: "We've resent a verification code to your email address. Please check your inbox.",
"We've resent a verification code to your email address. Please check your inbox.",
}); });
} }
@ -126,9 +128,9 @@ export default function VerifyEmailForm({
<div> <div>
<Card className="w-full max-w-md"> <Card className="w-full max-w-md">
<CardHeader> <CardHeader>
<CardTitle>Verify Email</CardTitle> <CardTitle>{t('emailVerify')}</CardTitle>
<CardDescription> <CardDescription>
Enter the verification code sent to your email address. {t('emailVerifyDescription')}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
@ -142,7 +144,7 @@ export default function VerifyEmailForm({
name="email" name="email"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Email</FormLabel> <FormLabel>{t('email')}</FormLabel>
<FormControl> <FormControl>
<Input <Input
{...field} {...field}
@ -159,7 +161,7 @@ export default function VerifyEmailForm({
name="pin" name="pin"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Verification Code</FormLabel> <FormLabel>{t('verificationCode')}</FormLabel>
<FormControl> <FormControl>
<div className="flex justify-center"> <div className="flex justify-center">
<InputOTP <InputOTP
@ -197,8 +199,7 @@ export default function VerifyEmailForm({
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
<FormDescription> <FormDescription>
We sent a verification code to your {t('verificationCodeEmailSent')}
email address.
</FormDescription> </FormDescription>
</FormItem> </FormItem>
)} )}
@ -226,7 +227,7 @@ export default function VerifyEmailForm({
{isSubmitting && ( {isSubmitting && (
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> <Loader2 className="mr-2 h-4 w-4 animate-spin" />
)} )}
Submit {t('emailVerifySubmit')}
</Button> </Button>
</form> </form>
</Form> </Form>
@ -241,8 +242,8 @@ export default function VerifyEmailForm({
disabled={isResending} disabled={isResending}
> >
{isResending {isResending
? "Resending..." ? t('emailVerifyResendProgress')
: "Didn't receive a code? Click here to resend"} : t('emailVerifyResend')}
</Button> </Button>
</div> </div>
</div> </div>