Skip to content

Commit 2c67c54

Browse files
committed
translate error handling
1 parent 058a27b commit 2c67c54

20 files changed

Lines changed: 578 additions & 355 deletions

locales/de-DE.arb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,5 +263,41 @@
263263
"userCode": "Benutzercode",
264264
"@userCode": {
265265
"description": "Label für Benutzercode Eingabefeld"
266+
},
267+
"errorInvalidUsernameOrPassword": "Ungültiger Benutzername oder Passwort",
268+
"@errorInvalidUsernameOrPassword": {
269+
"description": "Login-Formularfeldfehler"
270+
},
271+
"errorInvalidUsername": "Ungültiger Benutzername",
272+
"@errorInvalidUsername": {
273+
"description": "Benutzername-Feldfehler"
274+
},
275+
"errorInvalidPassword": "Ungültiges Passwort",
276+
"@errorInvalidPassword": {
277+
"description": "Passwort-Feldfehler"
278+
},
279+
"errorInvalidEmail": "Ungültige E-Mail",
280+
"@errorInvalidEmail": {
281+
"description": "E-Mail-Feldfehler"
282+
},
283+
"errorInvalidCode": "Ungültiger Code",
284+
"@errorInvalidCode": {
285+
"description": "Bestätigungscode-Feldfehler"
286+
},
287+
"errorInvalidRecoveryCode": "Ungültiger Wiederherstellungscode",
288+
"@errorInvalidRecoveryCode": {
289+
"description": "Wiederherstellungscode-Feldfehler"
290+
},
291+
"errorInvalidUserCode": "Ungültiger Benutzercode",
292+
"@errorInvalidUserCode": {
293+
"description": "Benutzercode-Feldfehler"
294+
},
295+
"errorPasswordMismatch": "Passwörter stimmen nicht überein",
296+
"@errorPasswordMismatch": {
297+
"description": "Passwort-Bestätigungsfeldfehler"
298+
},
299+
"errorAccountTemporarilyDisabled": "Konto ist vorübergehend deaktiviert. Wenden Sie sich an Ihren Administrator oder versuchen Sie es später erneut.",
300+
"@errorAccountTemporarilyDisabled": {
301+
"description": "Konto deaktiviert Fehler"
266302
}
267303
}

locales/en-US.arb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,5 +263,41 @@
263263
"userCode": "User Code",
264264
"@userCode": {
265265
"description": "Label for user code input field"
266+
},
267+
"errorInvalidUsernameOrPassword": "Invalid username or password",
268+
"@errorInvalidUsernameOrPassword": {
269+
"description": "Login form field error"
270+
},
271+
"errorInvalidUsername": "Invalid username",
272+
"@errorInvalidUsername": {
273+
"description": "Username field error"
274+
},
275+
"errorInvalidPassword": "Invalid password",
276+
"@errorInvalidPassword": {
277+
"description": "Password field error"
278+
},
279+
"errorInvalidEmail": "Invalid email",
280+
"@errorInvalidEmail": {
281+
"description": "Email field error"
282+
},
283+
"errorInvalidCode": "Invalid code",
284+
"@errorInvalidCode": {
285+
"description": "Verification code field error"
286+
},
287+
"errorInvalidRecoveryCode": "Invalid recovery code",
288+
"@errorInvalidRecoveryCode": {
289+
"description": "Recovery code field error"
290+
},
291+
"errorInvalidUserCode": "Invalid user code",
292+
"@errorInvalidUserCode": {
293+
"description": "User code field error"
294+
},
295+
"errorPasswordMismatch": "Passwords do not match",
296+
"@errorPasswordMismatch": {
297+
"description": "Password confirmation field error"
298+
},
299+
"errorAccountTemporarilyDisabled": "Account is temporarily disabled. Contact your administrator or try again later.",
300+
"@errorAccountTemporarilyDisabled": {
301+
"description": "Account disabled error"
266302
}
267303
}

src/i18n/translations.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ export type HelpwaveIdTranslationEntries = {
3333
'email': string,
3434
'emailVerificationBody': string,
3535
'emailVerificationBody1': string,
36+
'errorAccountTemporarilyDisabled': string,
37+
'errorInvalidCode': string,
38+
'errorInvalidEmail': string,
39+
'errorInvalidPassword': string,
40+
'errorInvalidRecoveryCode': string,
41+
'errorInvalidUserCode': string,
42+
'errorInvalidUsername': string,
43+
'errorInvalidUsernameOrPassword': string,
44+
'errorPasswordMismatch': string,
3645
'forgotPassword': string,
3746
'frontchannelLogoutMessage': string,
3847
'helpwaveId': string,
@@ -102,6 +111,15 @@ export const helpwaveIdTranslation: Translation<HelpwaveIdTranslationLocales, Pa
102111
'email': `E-Mail`,
103112
'emailVerificationBody': `Bitte überprüfen Sie Ihre E-Mail für einen Bestätigungslink.`,
104113
'emailVerificationBody1': `Bitte überprüfen Sie Ihre E-Mail für einen Bestätigungslink.`,
114+
'errorAccountTemporarilyDisabled': `Konto ist vorübergehend deaktiviert. Wenden Sie sich an Ihren Administrator oder versuchen Sie es später erneut.`,
115+
'errorInvalidCode': `Ungültiger Code`,
116+
'errorInvalidEmail': `Ungültige E-Mail`,
117+
'errorInvalidPassword': `Ungültiges Passwort`,
118+
'errorInvalidRecoveryCode': `Ungültiger Wiederherstellungscode`,
119+
'errorInvalidUserCode': `Ungültiger Benutzercode`,
120+
'errorInvalidUsername': `Ungültiger Benutzername`,
121+
'errorInvalidUsernameOrPassword': `Ungültiger Benutzername oder Passwort`,
122+
'errorPasswordMismatch': `Passwörter stimmen nicht überein`,
105123
'forgotPassword': `Passwort vergessen?`,
106124
'frontchannelLogoutMessage': `Abmelden...`,
107125
'helpwaveId': `helpwave id`,
@@ -174,6 +192,15 @@ export const helpwaveIdTranslation: Translation<HelpwaveIdTranslationLocales, Pa
174192
'email': `Email`,
175193
'emailVerificationBody': `Please check your email for a verification link.`,
176194
'emailVerificationBody1': `Please check your email for a verification link.`,
195+
'errorAccountTemporarilyDisabled': `Account is temporarily disabled. Contact your administrator or try again later.`,
196+
'errorInvalidCode': `Invalid code`,
197+
'errorInvalidEmail': `Invalid email`,
198+
'errorInvalidPassword': `Invalid password`,
199+
'errorInvalidRecoveryCode': `Invalid recovery code`,
200+
'errorInvalidUserCode': `Invalid user code`,
201+
'errorInvalidUsername': `Invalid username`,
202+
'errorInvalidUsernameOrPassword': `Invalid username or password`,
203+
'errorPasswordMismatch': `Passwords do not match`,
177204
'forgotPassword': `Forgot Password?`,
178205
'frontchannelLogoutMessage': `Logging out...`,
179206
'helpwaveId': `helpwave id`,

src/login/pages/Code.tsx

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useI18n } from '../i18n'
55
import Template from 'keycloakify/login/Template'
66
import { PageLayout } from '../components/PageLayout'
77
import { useTranslation } from '../../i18n/useTranslation'
8+
import { useTranslatedFieldError } from '../utils/translateFieldError'
89

910
type CodeProps = {
1011
kcContext: Extract<KcContext, { pageId: 'code.ftl' }>,
@@ -13,6 +14,7 @@ type CodeProps = {
1314
export default function Code({ kcContext }: CodeProps) {
1415
const { i18n } = useI18n({ kcContext })
1516
const t = useTranslation()
17+
const translateError = useTranslatedFieldError()
1618
const [code, setCode] = useState('')
1719

1820
const codeError = kcContext.messagesPerField?.existsError('code')
@@ -54,25 +56,27 @@ export default function Code({ kcContext }: CodeProps) {
5456
method="post"
5557
style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}
5658
>
57-
<FormFieldLayout
58-
label={t('loginOtp') || t('otp')}
59-
invalidDescription={codeError}
60-
required
61-
>
62-
{({ id, ariaAttributes }) => (
63-
<Input
64-
id={id}
65-
name="code"
66-
type="text"
67-
value={code}
68-
onChange={(e) => setCode(e.target.value)}
69-
autoFocus
70-
autoComplete="one-time-code"
71-
required
72-
{...ariaAttributes}
73-
/>
74-
)}
75-
</FormFieldLayout>
59+
<div className="mb-6">
60+
<FormFieldLayout
61+
label={t('loginOtp') || t('otp')}
62+
invalidDescription={translateError(codeError)}
63+
required
64+
>
65+
{({ id, ariaAttributes }) => (
66+
<Input
67+
id={id}
68+
name="code"
69+
type="text"
70+
value={code}
71+
onChange={(e) => setCode(e.target.value)}
72+
autoFocus
73+
autoComplete="one-time-code"
74+
required
75+
{...ariaAttributes}
76+
/>
77+
)}
78+
</FormFieldLayout>
79+
</div>
7680

7781
<Button type="submit" color="primary">
7882
{t('doSubmit')}

src/login/pages/ForgotPassword.tsx

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Template from 'keycloakify/login/Template'
66
import { PageLayout } from '../components/PageLayout'
77
import { ArrowLeft } from 'lucide-react'
88
import { useTranslation } from '../../i18n/useTranslation'
9+
import { useTranslatedFieldError } from '../utils/translateFieldError'
910

1011
type ForgotPasswordProps = {
1112
kcContext: Extract<KcContext, { pageId: 'login-reset-password.ftl' }>,
@@ -14,6 +15,7 @@ type ForgotPasswordProps = {
1415
export default function ForgotPassword({ kcContext }: ForgotPasswordProps) {
1516
const { i18n } = useI18n({ kcContext })
1617
const t = useTranslation()
18+
const translateError = useTranslatedFieldError()
1719
const [username, setUsername] = useState(kcContext.auth?.attemptedUsername ?? '')
1820

1921
const usernameError = kcContext.messagesPerField?.existsError('username')
@@ -62,29 +64,31 @@ export default function ForgotPassword({ kcContext }: ForgotPasswordProps) {
6264
method="post"
6365
style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}
6466
>
65-
<FormFieldLayout
66-
label={t(
67-
kcContext.realm?.loginWithEmailAllowed
68-
? 'usernameOrEmail'
69-
: 'username'
70-
)}
71-
invalidDescription={usernameError}
72-
required
73-
>
74-
{({ id, ariaAttributes }) => (
75-
<Input
76-
id={id}
77-
name="username"
78-
type="text"
79-
value={username}
80-
onChange={(e) => setUsername(e.target.value)}
81-
autoFocus
82-
autoComplete="username"
83-
required
84-
{...ariaAttributes}
85-
/>
86-
)}
87-
</FormFieldLayout>
67+
<div className="mb-6">
68+
<FormFieldLayout
69+
label={t(
70+
kcContext.realm?.loginWithEmailAllowed
71+
? 'usernameOrEmail'
72+
: 'username'
73+
)}
74+
invalidDescription={translateError(usernameError)}
75+
required
76+
>
77+
{({ id, ariaAttributes }) => (
78+
<Input
79+
id={id}
80+
name="username"
81+
type="text"
82+
value={username}
83+
onChange={(e) => setUsername(e.target.value)}
84+
autoFocus
85+
autoComplete="username"
86+
required
87+
{...ariaAttributes}
88+
/>
89+
)}
90+
</FormFieldLayout>
91+
</div>
8892

8993
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
9094
<Button type="submit" color="primary">

src/login/pages/IdpReviewUserProfile.tsx

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useI18n } from '../i18n'
55
import Template from 'keycloakify/login/Template'
66
import { PageLayout } from '../components/PageLayout'
77
import { useTranslation } from '../../i18n/useTranslation'
8+
import { useTranslatedFieldError } from '../utils/translateFieldError'
89

910
type IdpReviewUserProfileProps = {
1011
kcContext: Extract<KcContext, { pageId: 'idp-review-user-profile.ftl' }>,
@@ -13,6 +14,7 @@ type IdpReviewUserProfileProps = {
1314
export default function IdpReviewUserProfile({ kcContext }: IdpReviewUserProfileProps) {
1415
const { i18n } = useI18n({ kcContext })
1516
const t = useTranslation()
17+
const translateError = useTranslatedFieldError()
1618

1719
const profile = kcContext.profile
1820
const attributes = profile?.attributesByName ?? {}
@@ -41,27 +43,28 @@ export default function IdpReviewUserProfile({ kcContext }: IdpReviewUserProfile
4143
const inputType = fieldType === 'email' ? 'email' : 'text'
4244

4345
return (
44-
<FormFieldLayout
45-
key={attrName}
46-
label={attr.displayName ?? attrName}
47-
invalidDescription={getFieldError(attrName)}
48-
required={attr.required}
49-
>
50-
{({ id, ariaAttributes }) => (
51-
<Input
52-
id={id}
53-
name={attrName}
54-
type={inputType}
55-
value={formData[attrName] ?? ''}
56-
onChange={(e) => setFormData({ ...formData, [attrName]: e.target.value })}
57-
autoComplete={attr.autocomplete ?? 'off'}
58-
required={attr.required}
59-
readOnly={attr.readOnly}
60-
disabled={attr.readOnly}
61-
{...ariaAttributes}
62-
/>
63-
)}
64-
</FormFieldLayout>
46+
<div key={attrName} className="mb-6">
47+
<FormFieldLayout
48+
label={attr.displayName ?? attrName}
49+
invalidDescription={translateError(getFieldError(attrName))}
50+
required={attr.required}
51+
>
52+
{({ id, ariaAttributes }) => (
53+
<Input
54+
id={id}
55+
name={attrName}
56+
type={inputType}
57+
value={formData[attrName] ?? ''}
58+
onChange={(e) => setFormData({ ...formData, [attrName]: e.target.value })}
59+
autoComplete={attr.autocomplete ?? 'off'}
60+
required={attr.required}
61+
readOnly={attr.readOnly}
62+
disabled={attr.readOnly}
63+
{...ariaAttributes}
64+
/>
65+
)}
66+
</FormFieldLayout>
67+
</div>
6568
)
6669
}
6770

0 commit comments

Comments
 (0)