Skip to content

Commit 0fbac9f

Browse files
authored
Merge pull request #359 from stephenafamo/translate
Add Localizer module to localize various strings
2 parents 47760dc + 6d4f345 commit 0fbac9f

19 files changed

Lines changed: 349 additions & 140 deletions

File tree

auth/auth.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func (a *Auth) LoginPost(w http.ResponseWriter, r *http.Request) error {
6363
pidUser, err := a.Authboss.Storage.Server.Load(r.Context(), pid)
6464
if err == authboss.ErrUserNotFound {
6565
logger.Infof("failed to load user requested by pid: %s", pid)
66-
data := authboss.HTMLData{authboss.DataErr: "Invalid Credentials"}
66+
data := authboss.HTMLData{authboss.DataErr: a.Localizef(r.Context(), authboss.TxtInvalidCredentials)}
6767
return a.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageLogin, data)
6868
} else if err != nil {
6969
return err
@@ -85,7 +85,7 @@ func (a *Auth) LoginPost(w http.ResponseWriter, r *http.Request) error {
8585
}
8686

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

authboss.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,21 @@ func (a *Authboss) VerifyPassword(user AuthableUser, password string) error {
101101
return a.Core.Hasher.CompareHashAndPassword(user.GetPassword(), password)
102102
}
103103

104+
// Localizef is a helper to translate a key using the translator
105+
// If the localizer is nil or returns an empty string,
106+
// then the original text will be returned using [fmt.Sprintf] to interpolate the args.
107+
func (a *Authboss) Localizef(ctx context.Context, key LocalizationKey, args ...any) string {
108+
if a.Config.Core.Localizer == nil {
109+
return fmt.Sprintf(key.Default, args...)
110+
}
111+
112+
if translated := a.Config.Core.Localizer.Localizef(ctx, key, args...); translated != "" {
113+
return translated
114+
}
115+
116+
return fmt.Sprintf(key.Default, args...)
117+
}
118+
104119
// VerifyPassword uses authboss mechanisms to check that a password is correct.
105120
// Returns nil on success otherwise there will be an error. Simply a helper
106121
// to do the bcrypt comparison.
@@ -216,7 +231,7 @@ func MountedMiddleware2(ab *Authboss, mountPathed bool, reqs MWRequirements, fai
216231

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

config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,9 @@ type Config struct {
250250
// also implement the ContextLogger to be able to upgrade to a
251251
// request specific logger.
252252
Logger Logger
253+
254+
// Localizer is used to translate strings into different languages.
255+
Localizer Localizer
253256
}
254257
}
255258

confirm/confirm.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ func (c *Confirm) PreventAuth(w http.ResponseWriter, r *http.Request, handled bo
9393
ro := authboss.RedirectOptions{
9494
Code: http.StatusTemporaryRedirect,
9595
RedirectPath: c.Authboss.Config.Paths.ConfirmNotOK,
96-
Failure: "Your account has not been confirmed, please check your e-mail.",
96+
Failure: c.Localizef(r.Context(), authboss.TxtAccountNotConfirmed),
9797
}
9898
return true, c.Authboss.Config.Core.Redirector.Redirect(w, r, ro)
9999
}
@@ -114,7 +114,7 @@ func (c *Confirm) StartConfirmationWeb(w http.ResponseWriter, r *http.Request, h
114114
ro := authboss.RedirectOptions{
115115
Code: http.StatusTemporaryRedirect,
116116
RedirectPath: c.Authboss.Config.Paths.ConfirmNotOK,
117-
Success: "Please verify your account, an e-mail has been sent to you.",
117+
Success: c.Localizef(r.Context(), authboss.TxtConfirmYourAccount),
118118
}
119119
return true, c.Authboss.Config.Core.Redirector.Redirect(w, r, ro)
120120
}
@@ -157,7 +157,7 @@ func (c *Confirm) SendConfirmEmail(ctx context.Context, to, token string) {
157157
To: []string{to},
158158
From: c.Config.Mail.From,
159159
FromName: c.Config.Mail.FromName,
160-
Subject: c.Config.Mail.SubjectPrefix + "Confirm New Account",
160+
Subject: c.Config.Mail.SubjectPrefix + c.Localizef(ctx, authboss.TxtConfirmEmailSubject),
161161
}
162162

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

237237
ro := authboss.RedirectOptions{
238238
Code: http.StatusTemporaryRedirect,
239-
Success: "You have successfully confirmed your account.",
239+
Success: c.Localizef(r.Context(), authboss.TxtConfrimationSuccess),
240240
RedirectPath: c.Authboss.Config.Paths.ConfirmOK,
241241
}
242242
return c.Authboss.Config.Core.Redirector.Redirect(w, r, ro)
@@ -256,7 +256,7 @@ func (c *Confirm) mailURL(token string) string {
256256
func (c *Confirm) invalidToken(w http.ResponseWriter, r *http.Request) error {
257257
ro := authboss.RedirectOptions{
258258
Code: http.StatusTemporaryRedirect,
259-
Failure: "confirm token is invalid",
259+
Failure: c.Localizef(r.Context(), authboss.TxtInvalidConfirmToken),
260260
RedirectPath: c.Authboss.Config.Paths.ConfirmNotOK,
261261
}
262262
return c.Authboss.Config.Core.Redirector.Redirect(w, r, ro)
@@ -284,7 +284,7 @@ func Middleware(ab *authboss.Authboss) func(http.Handler) http.Handler {
284284
logger.Infof("user %s prevented from accessing %s: not confirmed", user.GetPID(), r.URL.Path)
285285
ro := authboss.RedirectOptions{
286286
Code: http.StatusTemporaryRedirect,
287-
Failure: "Your account has not been confirmed, please check your e-mail.",
287+
Failure: ab.Localizef(r.Context(), authboss.TxtAccountNotConfirmed),
288288
RedirectPath: ab.Config.Paths.ConfirmNotOK,
289289
}
290290
if err := ab.Config.Core.Redirector.Redirect(w, r, ro); err != nil {

confirm/confirm_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ func TestGetValidationFailure(t *testing.T) {
235235
if p := harness.redirector.Options.RedirectPath; p != harness.ab.Paths.ConfirmNotOK {
236236
t.Error("redir path was wrong:", p)
237237
}
238-
if reason := harness.redirector.Options.Failure; reason != "confirm token is invalid" {
238+
if reason := harness.redirector.Options.Failure; reason != harness.ab.Localizef(context.Background(), authboss.TxtInvalidConfirmToken) {
239239
t.Error("reason for failure was wrong:", reason)
240240
}
241241
}
@@ -262,7 +262,7 @@ func TestGetBase64DecodeFailure(t *testing.T) {
262262
if p := harness.redirector.Options.RedirectPath; p != harness.ab.Paths.ConfirmNotOK {
263263
t.Error("redir path was wrong:", p)
264264
}
265-
if reason := harness.redirector.Options.Failure; reason != "confirm token is invalid" {
265+
if reason := harness.redirector.Options.Failure; reason != harness.ab.Localizef(context.Background(), authboss.TxtInvalidConfirmToken) {
266266
t.Error("reason for failure was wrong:", reason)
267267
}
268268
}
@@ -294,7 +294,7 @@ func TestGetUserNotFoundFailure(t *testing.T) {
294294
if p := harness.redirector.Options.RedirectPath; p != harness.ab.Paths.ConfirmNotOK {
295295
t.Error("redir path was wrong:", p)
296296
}
297-
if reason := harness.redirector.Options.Failure; reason != "confirm token is invalid" {
297+
if reason := harness.redirector.Options.Failure; reason != harness.ab.Localizef(context.Background(), authboss.TxtInvalidConfirmToken) {
298298
t.Error("reason for failure was wrong:", reason)
299299
}
300300
}

localizer.go

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
package authboss
2+
3+
import (
4+
"context"
5+
)
6+
7+
type Localizer interface {
8+
// Get the translation for the given text in the given context.
9+
// If no translation is found, an empty string should be returned.
10+
Localizef(ctx context.Context, key LocalizationKey, args ...any) string
11+
}
12+
13+
type LocalizationKey struct {
14+
ID string
15+
Default string
16+
}
17+
18+
var (
19+
TxtSuccess = LocalizationKey{
20+
ID: "Success",
21+
Default: "success",
22+
}
23+
24+
// Used in the auth module
25+
TxtInvalidCredentials = LocalizationKey{
26+
ID: "InvalidCredentials",
27+
Default: "Invalid Credentials",
28+
}
29+
TxtAuthFailed = LocalizationKey{
30+
ID: "AuthFailed",
31+
Default: "Please login",
32+
}
33+
34+
// Used in the register module
35+
TxtUserAlreadyExists = LocalizationKey{
36+
ID: "UserAlreadyExists",
37+
Default: "User already exists",
38+
}
39+
TxtRegisteredAndLoggedIn = LocalizationKey{
40+
ID: "RegisteredAndLoggedIn",
41+
Default: "Account successfully created, you are now logged in",
42+
}
43+
44+
// Used in the confirm module
45+
TxtConfirmYourAccount = LocalizationKey{
46+
ID: "ConfirmYourAccount",
47+
Default: "Please verify your account, an e-mail has been sent to you.",
48+
}
49+
TxtAccountNotConfirmed = LocalizationKey{
50+
ID: "AccountNotConfirmed",
51+
Default: "Your account has not been confirmed, please check your e-mail.",
52+
}
53+
TxtInvalidConfirmToken = LocalizationKey{
54+
ID: "InvalidConfirmToken",
55+
Default: "Your confirmation token is invalid.",
56+
}
57+
TxtConfrimationSuccess = LocalizationKey{
58+
ID: "ConfrimationSuccess",
59+
Default: "You have successfully confirmed your account.",
60+
}
61+
TxtConfirmEmailSubject = LocalizationKey{
62+
ID: "ConfirmEmailSubject",
63+
Default: "Confirm New Account",
64+
}
65+
66+
// Used in the lock module
67+
TxtLocked = LocalizationKey{
68+
ID: "Locked",
69+
Default: "Your account has been locked, please contact the administrator.",
70+
}
71+
72+
// Used in the logout module
73+
TxtLoggedOut = LocalizationKey{
74+
ID: "LoggedOut",
75+
Default: "You have been logged out",
76+
}
77+
78+
// Used in the oauth2 module
79+
TxtOAuth2LoginOK = LocalizationKey{
80+
ID: "OAuth2LoginOK",
81+
Default: "Logged in successfully with %s.",
82+
}
83+
TxtOAuth2LoginNotOK = LocalizationKey{
84+
ID: "OAuth2LoginNotOK",
85+
Default: "%s login cancelled or failed",
86+
}
87+
88+
// Used in the recover module
89+
TxtRecoverInitiateSuccessFlash = LocalizationKey{
90+
ID: "RecoverInitiateSuccessFlash",
91+
Default: "An email has been sent to you with further instructions on how to reset your password.",
92+
}
93+
TxtPasswordResetEmailSubject = LocalizationKey{
94+
ID: "PasswordResetEmailSubject",
95+
Default: "Password Reset",
96+
}
97+
TxtRecoverSuccessMsg = LocalizationKey{
98+
ID: "RecoverSuccessMsg",
99+
Default: "Successfully updated password",
100+
}
101+
TxtRecoverAndLoginSuccessMsg = LocalizationKey{
102+
ID: "RecoverAndLoginSuccessMsg",
103+
Default: "Successfully updated password and logged in",
104+
}
105+
106+
// Used in the otp module
107+
TxtTooManyOTPs = LocalizationKey{
108+
ID: "TooManyOTPs",
109+
Default: "You cannot have more than %d one time passwords",
110+
}
111+
112+
// Used in the 2fa module
113+
TxtEmailVerifyTriggered = LocalizationKey{
114+
ID: "EmailVerifyTriggered",
115+
Default: "An e-mail has been sent to confirm 2FA activation",
116+
}
117+
TxtEmailVerifySubject = LocalizationKey{
118+
ID: "EmailVerifySubject",
119+
Default: "Add 2FA to Account",
120+
}
121+
TxtInvalid2FAVerificationToken = LocalizationKey{
122+
ID: "Invalid2FAVerificationToken",
123+
Default: "Invalid 2FA email verification token",
124+
}
125+
Txt2FAAuthorizationRequired = LocalizationKey{
126+
ID: "2FAAuthorizationRequired",
127+
Default: "You must first authorize adding 2fa by e-mail",
128+
}
129+
TxtInvalid2FACode = LocalizationKey{
130+
ID: "Invalid2FACode",
131+
Default: "2FA code was invalid",
132+
}
133+
TxtRepeated2FACode = LocalizationKey{
134+
ID: "Repeated2FACode",
135+
Default: "2FA code was previously used",
136+
}
137+
TxtTOTP2FANotActive = LocalizationKey{
138+
ID: "TOTP2FANotActive",
139+
Default: "TOTP 2FA is not active",
140+
}
141+
TxtSMSNumberRequired = LocalizationKey{
142+
ID: "SMSNumberRequired",
143+
Default: "You must provide a phone number",
144+
}
145+
TxtSMSWaitToResend = LocalizationKey{
146+
ID: "SMSWaitToResend",
147+
Default: "Please wait a few moments before resending the SMS code",
148+
}
149+
)
150+
151+
// // Translation constants
152+
// const (
153+
// TxtSuccess = "success"
154+
//
155+
// // Used in the auth module
156+
// TxtInvalidCredentials = "Invalid Credentials"
157+
// TxtAuthFailed = "Please login"
158+
//
159+
// // Used in the register module
160+
// TxtUserAlreadyExists = "User already exists"
161+
// TxtRegisteredAndLoggedIn = "Account successfully created, you are now logged in"
162+
//
163+
// // Used in the confirm module
164+
// TxtConfirmYourAccount = "Please verify your account, an e-mail has been sent to you."
165+
// TxtAccountNotConfirmed = "Your account has not been confirmed, please check your e-mail."
166+
// TxtInvalidConfirmToken = "Your confirmation token is invalid."
167+
// TxtConfrimationSuccess = "You have successfully confirmed your account."
168+
// TxtConfirmEmailSubject = "Confirm New Account"
169+
//
170+
// // Used in the lock module
171+
// TxtLocked = "Your account has been locked, please contact the administrator."
172+
//
173+
// // Used in the logout module
174+
// TxtLoggedOut = "You have been logged out"
175+
//
176+
// // Used in the oauth2 module
177+
// TxtOAuth2LoginOK = "Logged in successfully with %s."
178+
// TxtOAuth2LoginNotOK = "%s login cancelled or failed"
179+
//
180+
// // Used in the recover module
181+
// TxtRecoverInitiateSuccessFlash = "An email has been sent to you with further instructions on how to reset your password."
182+
// TxtPasswordResetEmailSubject = "Password Reset"
183+
// TxtRecoverSuccessMsg = "Successfully updated password"
184+
// TxtRecoverAndLoginSuccessMsg = "Successfully updated password and logged in"
185+
//
186+
// // Used in the otp module
187+
// TxtTooManyOTPs = "You cannot have more than %d one time passwords"
188+
//
189+
// // Used in the 2fa module
190+
// TxtEmailVerifyTriggered = "An e-mail has been sent to confirm 2FA activation"
191+
// TxtEmailVerifySubject = "Add 2FA to Account"
192+
// TxtInvalid2FAVerificationToken = "Invalid 2FA email verification token"
193+
// Txt2FAAuthorizationRequired = "You must first authorize adding 2fa by e-mail"
194+
// TxtInvalid2FACode = "2FA code was invalid"
195+
// TxtRepeated2FACode = "2FA code was previously used"
196+
// TxtTOTP2FANotActive = "TOTP 2FA is not active"
197+
// TxtSMSNumberRequired = "You must provide a phone number"
198+
// TxtSMSWaitToResend = "Please wait a few moments before resending the SMS code"
199+
// )

lock/lock.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ func (l *Lock) updateLockedState(w http.ResponseWriter, r *http.Request, wasCorr
9999

100100
ro := authboss.RedirectOptions{
101101
Code: http.StatusTemporaryRedirect,
102-
Failure: "Your account has been locked, please contact the administrator.",
102+
Failure: l.Localizef(r.Context(), authboss.TxtLocked),
103103
RedirectPath: l.Authboss.Config.Paths.LockNotOK,
104104
}
105105
return true, l.Authboss.Config.Core.Redirector.Redirect(w, r, ro)
@@ -158,7 +158,7 @@ func Middleware(ab *authboss.Authboss) func(http.Handler) http.Handler {
158158
logger.Infof("user %s prevented from accessing %s: locked", user.GetPID(), r.URL.Path)
159159
ro := authboss.RedirectOptions{
160160
Code: http.StatusTemporaryRedirect,
161-
Failure: "Your account has been locked, please contact the administrator.",
161+
Failure: ab.Localizef(r.Context(), authboss.TxtLocked),
162162
RedirectPath: ab.Config.Paths.LockNotOK,
163163
}
164164
if err := ab.Config.Core.Redirector.Redirect(w, r, ro); err != nil {

0 commit comments

Comments
 (0)