Skip to content

Commit 7b1eb4c

Browse files
Further localization for ZXCVBN and API errors (#511)
* SDK-2797: Update ZXCVBN to be localizable and adopt new UI (#509) * Update ZXCVBN to be localizable and adopt new UI * Lint * SDK-2798 Make certain API errors localizable (#510) * SDK-2798 make certain api errors localizable * Lint
1 parent ef08fa2 commit 7b1eb4c

File tree

5 files changed

+555
-7
lines changed

5 files changed

+555
-7
lines changed
21.9 KB
Binary file not shown.

Sources/StytchUI/Components/ZXCVBNIndicator.swift

Lines changed: 113 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,44 @@ struct ZXCVBNIndicator: View {
99
var body: some View {
1010
let emptyColor = UIColor.progressDefault
1111
let filledColor: UIColor = state.score < MAXSCORE ? UIColor.progressDanger : UIColor.progressSuccess
12-
let text = state.score < MAXSCORE ? (state.suggestions ?? []).joined(separator: ", ") : LocalizationManager.stytch_zxcvbn_feedback_success
1312
VStack(alignment: .leading) {
1413
HStack(alignment: .center) {
1514
ForEach(0...MAXSCORE, id: \.self) { index in
1615
let color = state.score >= index ? filledColor : emptyColor
1716
Rectangle().fill(Color(color)).frame(height: 4)
1817
}
1918
}
20-
Text(text)
21-
.fixedSize(horizontal: false, vertical: true)
22-
.font(Font(UIFont.IBMPlexSansRegular(size: 16)))
23-
.multilineTextAlignment(.leading)
24-
.foregroundColor(Color(filledColor))
19+
if state.score < MAXSCORE {
20+
if let warning = state.warning, !warning.isEmpty {
21+
HStack(alignment: .center, spacing: 4) {
22+
Image("crossIcon", bundle: .module).frame(width: 16, height: 16, alignment: .center)
23+
Text(warning.mapToLocalizedString())
24+
.fixedSize(horizontal: false, vertical: true)
25+
.font(Font(UIFont.IBMPlexSansRegular(size: 16)))
26+
.multilineTextAlignment(.leading)
27+
.foregroundColor(Color(filledColor))
28+
}
29+
}
30+
ForEach(state.suggestions ?? [], id: \.self) { suggestion in
31+
HStack(alignment: .firstTextBaseline, spacing: 4) {
32+
Text("")
33+
.frame(width: 16, height: 16, alignment: .center)
34+
.font(Font(UIFont.IBMPlexSansRegular(size: 16)))
35+
.foregroundColor(Color(UIColor.secondaryText))
36+
Text(suggestion.mapToLocalizedString())
37+
.fixedSize(horizontal: false, vertical: true)
38+
.font(Font(UIFont.IBMPlexSansRegular(size: 16)))
39+
.multilineTextAlignment(.leading)
40+
.foregroundColor(Color(UIColor.secondaryText))
41+
}
42+
}
43+
} else {
44+
Text(LocalizationManager.stytch_zxcvbn_feedback_success)
45+
.fixedSize(horizontal: false, vertical: true)
46+
.font(Font(UIFont.IBMPlexSansRegular(size: 16)))
47+
.multilineTextAlignment(.leading)
48+
.foregroundColor(Color(filledColor))
49+
}
2550
}
2651
}
2752

@@ -40,4 +65,86 @@ final class ZXCVBNState: ObservableObject {
4065
@Published var score: Int = 0
4166
@Published var suggestions: [String]?
4267
@Published var warning: String?
68+
69+
init(score: Int = 0, suggestions: [String]? = nil, warning: String? = nil) {
70+
self.score = score
71+
self.suggestions = suggestions
72+
self.warning = warning
73+
}
74+
}
75+
76+
// swiftlint:disable cyclomatic_complexity function_body_length
77+
private extension String {
78+
func mapToLocalizedString() -> String {
79+
switch self {
80+
case "Use a few words, avoid common phrases.":
81+
LocalizationManager.stytch_zxcvbn_suggestion_1
82+
case "No need for symbols, digits, or uppercase letters.":
83+
LocalizationManager.stytch_zxcvbn_suggestion_2
84+
case "Add another word or two. Uncommon words are better.":
85+
LocalizationManager.stytch_zxcvbn_suggestion_3
86+
case "Use a longer keyboard pattern with more turns.":
87+
LocalizationManager.stytch_zxcvbn_suggestion_4
88+
case "Avoid repeated words and characters.":
89+
LocalizationManager.stytch_zxcvbn_suggestion_5
90+
case "Avoid sequences.":
91+
LocalizationManager.stytch_zxcvbn_suggestion_6
92+
case "Avoid recent years.":
93+
LocalizationManager.stytch_zxcvbn_suggestion_7
94+
case "Avoid years that are associated with you.":
95+
LocalizationManager.stytch_zxcvbn_suggestion_8
96+
case "Avoid dates and years that are associated with you.":
97+
LocalizationManager.stytch_zxcvbn_suggestion_9
98+
case "Capitalization doesn\'t help very much.":
99+
LocalizationManager.stytch_zxcvbn_suggestion_10
100+
case "All-uppercase is almost as easy to guess as all-lowercase.":
101+
LocalizationManager.stytch_zxcvbn_suggestion_11
102+
case "Reversed words aren\'t much harder to guess.":
103+
LocalizationManager.stytch_zxcvbn_suggestion_12
104+
case "Predictable substitutions like \'@\' instead of \'a\' don\'t help very much.":
105+
LocalizationManager.stytch_zxcvbn_suggestion_13
106+
case "Short keyboard patterns are easy to guess.":
107+
LocalizationManager.stytch_zxcvbn_suggestion_14
108+
case "Straight rows of keys are easy to guess.":
109+
LocalizationManager.stytch_zxcvbn_suggestion_15
110+
case "Repeats like \"abcabcabc\" are only slightly harder to guess than \"abc\".":
111+
LocalizationManager.stytch_zxcvbn_suggestion_16
112+
case "Repeats like \"aaa\" are easy to guess.":
113+
LocalizationManager.stytch_zxcvbn_suggestion_17
114+
case "Sequences like \"abc\" or \"6543\" are easy to guess.":
115+
LocalizationManager.stytch_zxcvbn_suggestion_18
116+
case "Recent years are easy to guess.":
117+
LocalizationManager.stytch_zxcvbn_suggestion_19
118+
case "Dates are often easy to guess.":
119+
LocalizationManager.stytch_zxcvbn_suggestion_20
120+
case "This is a top-10 common password.":
121+
LocalizationManager.stytch_zxcvbn_suggestion_21
122+
case "This is a top-100 common password.":
123+
LocalizationManager.stytch_zxcvbn_suggestion_22
124+
case "This is a very common password.":
125+
LocalizationManager.stytch_zxcvbn_suggestion_23
126+
case "This is similar to a commonly used password.":
127+
LocalizationManager.stytch_zxcvbn_suggestion_24
128+
case "A word by itself is easy to guess.":
129+
LocalizationManager.stytch_zxcvbn_suggestion_25
130+
case "Names and surnames by themselves are easy to guess.":
131+
LocalizationManager.stytch_zxcvbn_suggestion_26
132+
case "Common names and surnames are easy to guess.":
133+
LocalizationManager.stytch_zxcvbn_suggestion_27
134+
default:
135+
self
136+
}
137+
}
138+
}
139+
140+
#Preview {
141+
let state: ZXCVBNState = .init(
142+
score: 2,
143+
suggestions: [
144+
"Predictable substitutions like \'@\' instead of \'a\' don\'t help very much.",
145+
"Common names and surnames are easy to guess.",
146+
],
147+
warning: "Common names and surnames are easy to guess."
148+
)
149+
ZXCVBNIndicator(state: state)
43150
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import StytchCore
2+
3+
// swiftlint:disable cyclomatic_complexity function_body_length
4+
extension Error {
5+
func getLocalizedErrorMessage() -> String? {
6+
if let apiError = (self as Error).stytchAPIError {
7+
return switch apiError.errorType {
8+
case .unauthorizedCredentials:
9+
LocalizationManager.stytch_error_unauthorized_credentials
10+
case .userUnauthenticated:
11+
LocalizationManager.stytch_error_user_unauthenticated
12+
case .emailNotFound:
13+
LocalizationManager.stytch_error_email_not_found
14+
case .otpCodeNotFound:
15+
LocalizationManager.stytch_error_otp_code_not_found
16+
case .breachedPassword:
17+
LocalizationManager.stytch_error_breached_password
18+
case .noUserPassword:
19+
LocalizationManager.stytch_error_no_user_password
20+
case .invalidCode:
21+
LocalizationManager.stytch_error_invalid_code
22+
case .tooManyRequests:
23+
LocalizationManager.stytch_error_too_many_requests
24+
case .sessionNotFound:
25+
LocalizationManager.stytch_error_session_not_found
26+
case .userLockLimitReached:
27+
LocalizationManager.stytch_error_user_lock_limit_reached
28+
case .resetPassword:
29+
LocalizationManager.stytch_error_reset_password
30+
case .unableToAuthOtpCode:
31+
LocalizationManager.stytch_error_unable_to_auth_otp_code
32+
case .noActiveBiometricRegistrations:
33+
LocalizationManager.stytch_error_no_active_biometric_registrations
34+
case .unableToAuthMagicLink:
35+
LocalizationManager.stytch_error_unable_to_auth_magic_link
36+
case .phoneNumberNotFound:
37+
LocalizationManager.stytch_error_phone_number_not_found
38+
case .invalidPhoneNumberCountryCode:
39+
LocalizationManager.stytch_error_invalid_phone_number_country_code
40+
case .sessionTooOldToResetPassword:
41+
LocalizationManager.stytch_error_session_too_old_to_reset_password
42+
case .invalidEmail:
43+
LocalizationManager.stytch_error_invalid_email
44+
case .unauthorizedAction:
45+
LocalizationManager.stytch_error_unauthorized_action
46+
case .weakPassword:
47+
LocalizationManager.stytch_error_weak_password
48+
case .duplicateEmail:
49+
LocalizationManager.stytch_error_duplicate_email
50+
case .invalidPhoneNumber:
51+
LocalizationManager.stytch_error_invalid_phone_number
52+
case .oauthAuthCodeError:
53+
LocalizationManager.stytch_error_oauth_auth_code_error
54+
case .oauthFlowCallbackError:
55+
LocalizationManager.stytch_error_oauth_flow_callback_error
56+
case .oauthTokenNotFound:
57+
LocalizationManager.stytch_error_oauth_token_not_found
58+
case .pkceMismatch:
59+
LocalizationManager.stytch_error_pkce_mismatch
60+
case .adBlockerDetected:
61+
LocalizationManager.stytch_error_ad_blocker_detected
62+
case .staleFactors:
63+
LocalizationManager.stytch_error_stale_factors
64+
case .internalServerError:
65+
LocalizationManager.stytch_error_internal_server_error
66+
case .invalidMethodId:
67+
LocalizationManager.stytch_error_invalid_method_id
68+
case .unableToAuthBiometricRegistration:
69+
LocalizationManager.stytch_error_unable_to_auth_biometric_registration
70+
case .unsubscribedPhoneNumber:
71+
LocalizationManager.stytch_error_unsubscribed_phone_number
72+
case .emailTemplateNotFound:
73+
LocalizationManager.stytch_error_email_template_not_found
74+
case .pkceExpectedCodeVerifier:
75+
LocalizationManager.stytch_error_pkce_expected_code_verifier
76+
case .captchaRequired:
77+
LocalizationManager.stytch_error_captcha_required
78+
case .inactiveEmail:
79+
LocalizationManager.stytch_error_inactive_email
80+
case .memberPasswordNotFound:
81+
LocalizationManager.stytch_error_member_password_not_found
82+
case .serverUnavailable:
83+
LocalizationManager.stytch_error_server_unavailable
84+
case .tooManyBiometricRegistrationsForUser:
85+
LocalizationManager.stytch_error_too_many_biometric_registrations_for_user
86+
case .duplicatePhoneNumber:
87+
LocalizationManager.stytch_error_duplicate_phone_number
88+
case .oauthInvalidCallbackRequest:
89+
LocalizationManager.stytch_error_oauth_invalid_callback_request
90+
case .intermediateSessionNotFound:
91+
LocalizationManager.stytch_error_intermediate_session_not_found
92+
case .noMatchForProvidedMagicLinkUrl:
93+
LocalizationManager.stytch_error_no_match_for_provided_magic_link_url
94+
case .totpCodeAlreadyAuthenticated:
95+
LocalizationManager.stytch_error_totp_code_already_authenticated
96+
case .invalidSessionDurationMinutes:
97+
LocalizationManager.stytch_error_invalid_session_duration_minutes
98+
case .invalidConsumerEndpoint:
99+
LocalizationManager.stytch_error_invalid_consumer_endpoint
100+
case .crossOrgPasswordsNotEnabled:
101+
LocalizationManager.stytch_error_cross_org_passwords_not_enabled
102+
case .invalidSessionDuration:
103+
LocalizationManager.stytch_error_invalid_session_duration
104+
case .invalidLocale:
105+
LocalizationManager.stytch_error_invalid_locale
106+
case .magicLinkNotFound:
107+
LocalizationManager.stytch_error_magic_link_not_found
108+
default:
109+
nil
110+
}
111+
} else {
112+
return nil
113+
}
114+
}
115+
}

Sources/StytchUI/Extensions/UIViewController+StytchUI.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ extension UIViewController {
55
func presentErrorAlert(error: Error) {
66
presentAlert(
77
title: LocalizationManager.stytch_error_alert_title,
8-
message: (error as? StytchError)?.message ?? error.localizedDescription
8+
message: error.getLocalizedErrorMessage() ?? (error as? StytchError)?.message ?? error.localizedDescription
99
)
1010
}
1111

0 commit comments

Comments
 (0)