Skip to content

Commit

Permalink
Merge pull request #359 from stephenafamo/translate
Browse files Browse the repository at this point in the history
Add Localizer module to localize various strings
  • Loading branch information
aarondl committed Dec 30, 2023
2 parents 47760dc + 6d4f345 commit 0fbac9f
Show file tree
Hide file tree
Showing 19 changed files with 349 additions and 140 deletions.
4 changes: 2 additions & 2 deletions auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func (a *Auth) LoginPost(w http.ResponseWriter, r *http.Request) error {
pidUser, err := a.Authboss.Storage.Server.Load(r.Context(), pid)
if err == authboss.ErrUserNotFound {
logger.Infof("failed to load user requested by pid: %s", pid)
data := authboss.HTMLData{authboss.DataErr: "Invalid Credentials"}
data := authboss.HTMLData{authboss.DataErr: a.Localizef(r.Context(), authboss.TxtInvalidCredentials)}
return a.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageLogin, data)
} else if err != nil {
return err
Expand All @@ -85,7 +85,7 @@ func (a *Auth) LoginPost(w http.ResponseWriter, r *http.Request) error {
}

logger.Infof("user %s failed to log in", pid)
data := authboss.HTMLData{authboss.DataErr: "Invalid Credentials"}
data := authboss.HTMLData{authboss.DataErr: a.Localizef(r.Context(), authboss.TxtInvalidCredentials)}
return a.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageLogin, data)
}

Expand Down
17 changes: 16 additions & 1 deletion authboss.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,21 @@ func (a *Authboss) VerifyPassword(user AuthableUser, password string) error {
return a.Core.Hasher.CompareHashAndPassword(user.GetPassword(), password)
}

// Localizef is a helper to translate a key using the translator
// If the localizer is nil or returns an empty string,
// then the original text will be returned using [fmt.Sprintf] to interpolate the args.
func (a *Authboss) Localizef(ctx context.Context, key LocalizationKey, args ...any) string {
if a.Config.Core.Localizer == nil {
return fmt.Sprintf(key.Default, args...)
}

if translated := a.Config.Core.Localizer.Localizef(ctx, key, args...); translated != "" {
return translated
}

return fmt.Sprintf(key.Default, args...)
}

// VerifyPassword uses authboss mechanisms to check that a password is correct.
// Returns nil on success otherwise there will be an error. Simply a helper
// to do the bcrypt comparison.
Expand Down Expand Up @@ -216,7 +231,7 @@ func MountedMiddleware2(ab *Authboss, mountPathed bool, reqs MWRequirements, fai

ro := RedirectOptions{
Code: http.StatusTemporaryRedirect,
Failure: "please re-login",
Failure: ab.Localizef(r.Context(), TxtAuthFailed),
RedirectPath: path.Join(ab.Config.Paths.Mount, fmt.Sprintf("/login?%s", vals.Encode())),
}

Expand Down
3 changes: 3 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,9 @@ type Config struct {
// also implement the ContextLogger to be able to upgrade to a
// request specific logger.
Logger Logger

// Localizer is used to translate strings into different languages.
Localizer Localizer
}
}

Expand Down
12 changes: 6 additions & 6 deletions confirm/confirm.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (c *Confirm) PreventAuth(w http.ResponseWriter, r *http.Request, handled bo
ro := authboss.RedirectOptions{
Code: http.StatusTemporaryRedirect,
RedirectPath: c.Authboss.Config.Paths.ConfirmNotOK,
Failure: "Your account has not been confirmed, please check your e-mail.",
Failure: c.Localizef(r.Context(), authboss.TxtAccountNotConfirmed),
}
return true, c.Authboss.Config.Core.Redirector.Redirect(w, r, ro)
}
Expand All @@ -114,7 +114,7 @@ func (c *Confirm) StartConfirmationWeb(w http.ResponseWriter, r *http.Request, h
ro := authboss.RedirectOptions{
Code: http.StatusTemporaryRedirect,
RedirectPath: c.Authboss.Config.Paths.ConfirmNotOK,
Success: "Please verify your account, an e-mail has been sent to you.",
Success: c.Localizef(r.Context(), authboss.TxtConfirmYourAccount),
}
return true, c.Authboss.Config.Core.Redirector.Redirect(w, r, ro)
}
Expand Down Expand Up @@ -157,7 +157,7 @@ func (c *Confirm) SendConfirmEmail(ctx context.Context, to, token string) {
To: []string{to},
From: c.Config.Mail.From,
FromName: c.Config.Mail.FromName,
Subject: c.Config.Mail.SubjectPrefix + "Confirm New Account",
Subject: c.Config.Mail.SubjectPrefix + c.Localizef(ctx, authboss.TxtConfirmEmailSubject),
}

logger.Infof("sending confirm e-mail to: %s", to)
Expand Down Expand Up @@ -236,7 +236,7 @@ func (c *Confirm) Get(w http.ResponseWriter, r *http.Request) error {

ro := authboss.RedirectOptions{
Code: http.StatusTemporaryRedirect,
Success: "You have successfully confirmed your account.",
Success: c.Localizef(r.Context(), authboss.TxtConfrimationSuccess),
RedirectPath: c.Authboss.Config.Paths.ConfirmOK,
}
return c.Authboss.Config.Core.Redirector.Redirect(w, r, ro)
Expand All @@ -256,7 +256,7 @@ func (c *Confirm) mailURL(token string) string {
func (c *Confirm) invalidToken(w http.ResponseWriter, r *http.Request) error {
ro := authboss.RedirectOptions{
Code: http.StatusTemporaryRedirect,
Failure: "confirm token is invalid",
Failure: c.Localizef(r.Context(), authboss.TxtInvalidConfirmToken),
RedirectPath: c.Authboss.Config.Paths.ConfirmNotOK,
}
return c.Authboss.Config.Core.Redirector.Redirect(w, r, ro)
Expand Down Expand Up @@ -284,7 +284,7 @@ func Middleware(ab *authboss.Authboss) func(http.Handler) http.Handler {
logger.Infof("user %s prevented from accessing %s: not confirmed", user.GetPID(), r.URL.Path)
ro := authboss.RedirectOptions{
Code: http.StatusTemporaryRedirect,
Failure: "Your account has not been confirmed, please check your e-mail.",
Failure: ab.Localizef(r.Context(), authboss.TxtAccountNotConfirmed),
RedirectPath: ab.Config.Paths.ConfirmNotOK,
}
if err := ab.Config.Core.Redirector.Redirect(w, r, ro); err != nil {
Expand Down
6 changes: 3 additions & 3 deletions confirm/confirm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ func TestGetValidationFailure(t *testing.T) {
if p := harness.redirector.Options.RedirectPath; p != harness.ab.Paths.ConfirmNotOK {
t.Error("redir path was wrong:", p)
}
if reason := harness.redirector.Options.Failure; reason != "confirm token is invalid" {
if reason := harness.redirector.Options.Failure; reason != harness.ab.Localizef(context.Background(), authboss.TxtInvalidConfirmToken) {
t.Error("reason for failure was wrong:", reason)
}
}
Expand All @@ -262,7 +262,7 @@ func TestGetBase64DecodeFailure(t *testing.T) {
if p := harness.redirector.Options.RedirectPath; p != harness.ab.Paths.ConfirmNotOK {
t.Error("redir path was wrong:", p)
}
if reason := harness.redirector.Options.Failure; reason != "confirm token is invalid" {
if reason := harness.redirector.Options.Failure; reason != harness.ab.Localizef(context.Background(), authboss.TxtInvalidConfirmToken) {
t.Error("reason for failure was wrong:", reason)
}
}
Expand Down Expand Up @@ -294,7 +294,7 @@ func TestGetUserNotFoundFailure(t *testing.T) {
if p := harness.redirector.Options.RedirectPath; p != harness.ab.Paths.ConfirmNotOK {
t.Error("redir path was wrong:", p)
}
if reason := harness.redirector.Options.Failure; reason != "confirm token is invalid" {
if reason := harness.redirector.Options.Failure; reason != harness.ab.Localizef(context.Background(), authboss.TxtInvalidConfirmToken) {
t.Error("reason for failure was wrong:", reason)
}
}
Expand Down
199 changes: 199 additions & 0 deletions localizer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
package authboss

import (
"context"
)

type Localizer interface {
// Get the translation for the given text in the given context.
// If no translation is found, an empty string should be returned.
Localizef(ctx context.Context, key LocalizationKey, args ...any) string
}

type LocalizationKey struct {
ID string
Default string
}

var (
TxtSuccess = LocalizationKey{
ID: "Success",
Default: "success",
}

// Used in the auth module
TxtInvalidCredentials = LocalizationKey{
ID: "InvalidCredentials",
Default: "Invalid Credentials",
}
TxtAuthFailed = LocalizationKey{
ID: "AuthFailed",
Default: "Please login",
}

// Used in the register module
TxtUserAlreadyExists = LocalizationKey{
ID: "UserAlreadyExists",
Default: "User already exists",
}
TxtRegisteredAndLoggedIn = LocalizationKey{
ID: "RegisteredAndLoggedIn",
Default: "Account successfully created, you are now logged in",
}

// Used in the confirm module
TxtConfirmYourAccount = LocalizationKey{
ID: "ConfirmYourAccount",
Default: "Please verify your account, an e-mail has been sent to you.",
}
TxtAccountNotConfirmed = LocalizationKey{
ID: "AccountNotConfirmed",
Default: "Your account has not been confirmed, please check your e-mail.",
}
TxtInvalidConfirmToken = LocalizationKey{
ID: "InvalidConfirmToken",
Default: "Your confirmation token is invalid.",
}
TxtConfrimationSuccess = LocalizationKey{
ID: "ConfrimationSuccess",
Default: "You have successfully confirmed your account.",
}
TxtConfirmEmailSubject = LocalizationKey{
ID: "ConfirmEmailSubject",
Default: "Confirm New Account",
}

// Used in the lock module
TxtLocked = LocalizationKey{
ID: "Locked",
Default: "Your account has been locked, please contact the administrator.",
}

// Used in the logout module
TxtLoggedOut = LocalizationKey{
ID: "LoggedOut",
Default: "You have been logged out",
}

// Used in the oauth2 module
TxtOAuth2LoginOK = LocalizationKey{
ID: "OAuth2LoginOK",
Default: "Logged in successfully with %s.",
}
TxtOAuth2LoginNotOK = LocalizationKey{
ID: "OAuth2LoginNotOK",
Default: "%s login cancelled or failed",
}

// Used in the recover module
TxtRecoverInitiateSuccessFlash = LocalizationKey{
ID: "RecoverInitiateSuccessFlash",
Default: "An email has been sent to you with further instructions on how to reset your password.",
}
TxtPasswordResetEmailSubject = LocalizationKey{
ID: "PasswordResetEmailSubject",
Default: "Password Reset",
}
TxtRecoverSuccessMsg = LocalizationKey{
ID: "RecoverSuccessMsg",
Default: "Successfully updated password",
}
TxtRecoverAndLoginSuccessMsg = LocalizationKey{
ID: "RecoverAndLoginSuccessMsg",
Default: "Successfully updated password and logged in",
}

// Used in the otp module
TxtTooManyOTPs = LocalizationKey{
ID: "TooManyOTPs",
Default: "You cannot have more than %d one time passwords",
}

// Used in the 2fa module
TxtEmailVerifyTriggered = LocalizationKey{
ID: "EmailVerifyTriggered",
Default: "An e-mail has been sent to confirm 2FA activation",
}
TxtEmailVerifySubject = LocalizationKey{
ID: "EmailVerifySubject",
Default: "Add 2FA to Account",
}
TxtInvalid2FAVerificationToken = LocalizationKey{
ID: "Invalid2FAVerificationToken",
Default: "Invalid 2FA email verification token",
}
Txt2FAAuthorizationRequired = LocalizationKey{
ID: "2FAAuthorizationRequired",
Default: "You must first authorize adding 2fa by e-mail",
}
TxtInvalid2FACode = LocalizationKey{
ID: "Invalid2FACode",
Default: "2FA code was invalid",
}
TxtRepeated2FACode = LocalizationKey{
ID: "Repeated2FACode",
Default: "2FA code was previously used",
}
TxtTOTP2FANotActive = LocalizationKey{
ID: "TOTP2FANotActive",
Default: "TOTP 2FA is not active",
}
TxtSMSNumberRequired = LocalizationKey{
ID: "SMSNumberRequired",
Default: "You must provide a phone number",
}
TxtSMSWaitToResend = LocalizationKey{
ID: "SMSWaitToResend",
Default: "Please wait a few moments before resending the SMS code",
}
)

// // Translation constants
// const (
// TxtSuccess = "success"
//
// // Used in the auth module
// TxtInvalidCredentials = "Invalid Credentials"
// TxtAuthFailed = "Please login"
//
// // Used in the register module
// TxtUserAlreadyExists = "User already exists"
// TxtRegisteredAndLoggedIn = "Account successfully created, you are now logged in"
//
// // Used in the confirm module
// TxtConfirmYourAccount = "Please verify your account, an e-mail has been sent to you."
// TxtAccountNotConfirmed = "Your account has not been confirmed, please check your e-mail."
// TxtInvalidConfirmToken = "Your confirmation token is invalid."
// TxtConfrimationSuccess = "You have successfully confirmed your account."
// TxtConfirmEmailSubject = "Confirm New Account"
//
// // Used in the lock module
// TxtLocked = "Your account has been locked, please contact the administrator."
//
// // Used in the logout module
// TxtLoggedOut = "You have been logged out"
//
// // Used in the oauth2 module
// TxtOAuth2LoginOK = "Logged in successfully with %s."
// TxtOAuth2LoginNotOK = "%s login cancelled or failed"
//
// // Used in the recover module
// TxtRecoverInitiateSuccessFlash = "An email has been sent to you with further instructions on how to reset your password."
// TxtPasswordResetEmailSubject = "Password Reset"
// TxtRecoverSuccessMsg = "Successfully updated password"
// TxtRecoverAndLoginSuccessMsg = "Successfully updated password and logged in"
//
// // Used in the otp module
// TxtTooManyOTPs = "You cannot have more than %d one time passwords"
//
// // Used in the 2fa module
// TxtEmailVerifyTriggered = "An e-mail has been sent to confirm 2FA activation"
// TxtEmailVerifySubject = "Add 2FA to Account"
// TxtInvalid2FAVerificationToken = "Invalid 2FA email verification token"
// Txt2FAAuthorizationRequired = "You must first authorize adding 2fa by e-mail"
// TxtInvalid2FACode = "2FA code was invalid"
// TxtRepeated2FACode = "2FA code was previously used"
// TxtTOTP2FANotActive = "TOTP 2FA is not active"
// TxtSMSNumberRequired = "You must provide a phone number"
// TxtSMSWaitToResend = "Please wait a few moments before resending the SMS code"
// )
4 changes: 2 additions & 2 deletions lock/lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func (l *Lock) updateLockedState(w http.ResponseWriter, r *http.Request, wasCorr

ro := authboss.RedirectOptions{
Code: http.StatusTemporaryRedirect,
Failure: "Your account has been locked, please contact the administrator.",
Failure: l.Localizef(r.Context(), authboss.TxtLocked),
RedirectPath: l.Authboss.Config.Paths.LockNotOK,
}
return true, l.Authboss.Config.Core.Redirector.Redirect(w, r, ro)
Expand Down Expand Up @@ -158,7 +158,7 @@ func Middleware(ab *authboss.Authboss) func(http.Handler) http.Handler {
logger.Infof("user %s prevented from accessing %s: locked", user.GetPID(), r.URL.Path)
ro := authboss.RedirectOptions{
Code: http.StatusTemporaryRedirect,
Failure: "Your account has been locked, please contact the administrator.",
Failure: ab.Localizef(r.Context(), authboss.TxtLocked),
RedirectPath: ab.Config.Paths.LockNotOK,
}
if err := ab.Config.Core.Redirector.Redirect(w, r, ro); err != nil {
Expand Down
Loading

0 comments on commit 0fbac9f

Please sign in to comment.