diff --git a/Sources/FaceLiveness/Utilities/LivenessLocalizedStrings.swift b/Sources/FaceLiveness/Utilities/LivenessLocalizedStrings.swift new file mode 100644 index 00000000..ba45e269 --- /dev/null +++ b/Sources/FaceLiveness/Utilities/LivenessLocalizedStrings.swift @@ -0,0 +1,104 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import SwiftUI + +enum LocalizedStrings { + /// en = "Liveness Check" + static let get_ready_page_title = "amplify_ui_liveness_get_ready_page_title".localized() + + /// en = "You will go through a face verification process to prove that you are a real person. Your screen's brightness will temporarily be set to 100% for highest accuracy." + static let get_ready_page_description = "amplify_ui_liveness_get_ready_page_description".localized() + + /// en = "Photosensitivity Warning" + static let get_ready_photosensitivity_title = "amplify_ui_liveness_get_ready_photosensitivity_title".localized() + + /// en = "This check displays colored lights. Use caution if you are photosensitive." + static let get_ready_photosensitivity_description = "amplify_ui_liveness_get_ready_photosensitivity_description".localized() + + /// en = "Photosensitivity Information" + static let get_ready_photosensitivity_icon_a11y = "amplify_ui_liveness_get_ready_photosensitivity_icon_a11y".localized() + + /// en = "Photosensitivity warning" + static let get_ready_photosensitivity_dialog_title = "amplify_ui_liveness_get_ready_photosensitivity_dialog_title".localized() + + /// en = "A small percentage of individuals may experience epileptic seizures when exposed to colored lights. Use caution if you, or anyone in your family, have an epileptic condition." + static let get_ready_photosensitivity_dialog_description = "amplify_ui_liveness_get_ready_photosensitivity_dialog_description".localized() + + /// en = "Follow the instructions to complete the check:" + static let get_ready_steps_title = "amplify_ui_liveness_get_ready_steps_title".localized() + + /// en = "Make sure your face is not covered with sunglasses or a mask." + static let get_ready_face_not_covered = "amplify_ui_liveness_get_ready_face_not_covered".localized() + + /// en = "Move to a well-lit place that is not in direct sunlight." + static let get_ready_lighting = "amplify_ui_liveness_get_ready_lighting".localized() + + /// en = "When an oval appears, fill the oval with your face in it." + static let get_ready_fit_face = "amplify_ui_liveness_get_ready_fit_face".localized() + + /// en = "Begin Check" + static let get_ready_begin_check = "amplify_ui_liveness_get_ready_begin_check".localized() + + /// en = "Illustration demonstrating good fit of face in oval." + static let get_ready_illustration_good_fit_a11y = "amplify_ui_liveness_get_ready_illustration_good_fit_a11y".localized() + + /// en = "Illustration demonstrating face too far from screen." + static let get_ready_illustration_too_far_a11y = "amplify_ui_liveness_get_ready_illustration_too_far_a11y".localized() + + /// en = "REC" + static let challenge_recording_indicator_label = "amplify_ui_liveness_challenge_recording_indicator_label".localized() + + /// en = "Hold face in oval for colored lights." + static let challenge_instruction_hold_face_during_freshness = "amplify_ui_liveness_challenge_instruction_hold_face_during_freshness".localized() + + /// en = "Move back" + static let challenge_instruction_move_face_back = "amplify_ui_liveness_challenge_instruction_move_face_back".localized() + + /// en = "Move closer" + static let challenge_instruction_move_face_closer = "amplify_ui_liveness_challenge_instruction_move_face_closer".localized() + + /// en = "Move closer" + static let challenge_instruction_move_face = "amplify_ui_liveness_challenge_instruction_move_face".localized() + + /// en = "Hold still" + static let challenge_instruction_hold_still = "amplify_ui_liveness_challenge_instruction_hold_still".localized() + + /// en = "Ensure only one face is in front of camera" + static let challenge_instruction_multiple_faces_detected = "amplify_ui_liveness_challenge_instruction_multiple_faces_detected".localized() + + /// en = "Connecting..." + static let challenge_connecting = "amplify_ui_liveness_challenge_connecting".localized() + + /// en = "Verifying" + static let challenge_verifying = "amplify_ui_liveness_challenge_verifying".localized() + + /// en = "Cancel Challenge" + static let challenge_cancel_a11y = "amplify_ui_liveness_challenge_cancel_a11y".localized() + + /// en = "Change Your Camera Settings" + static let camera_setting_alert_title = "amplify_ui_liveness_camera_setting_alert_title".localized() + + /// en = "Allow camera permission in settings." + static let camera_setting_alert_message = "amplify_ui_liveness_camera_setting_alert_message".localized() + + /// en = "Update Setting" + static let camera_setting_alert_update_setting_button_text = "amplify_ui_liveness_camera_setting_alert_update_setting_button_text".localized() + + /// en = "Not Now" + static let camera_setting_alert_not_now_button_text = "amplify_ui_liveness_camera_setting_alert_not_now_button_text".localized() + + /// en = "Close" + static let close_button_a11y = "amplify_ui_liveness_close_button_a11y".localized() + + /// en = "Good fit" + static let get_ready_good_fit_example = "amplify_ui_liveness_get_ready_good_fit_example".localized() + + /// en = "Too far" + static let get_ready_too_far_example = "amplify_ui_liveness_get_ready_too_far_example".localized() + +} diff --git a/Sources/FaceLiveness/Utilities/LocalizedStringKey+Liveness.swift b/Sources/FaceLiveness/Utilities/LocalizedStringKey+Liveness.swift deleted file mode 100644 index 77f7fcff..00000000 --- a/Sources/FaceLiveness/Utilities/LocalizedStringKey+Liveness.swift +++ /dev/null @@ -1,166 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import SwiftUI - -extension LocalizedStringKey { - /// en = "Liveness Check" - static let get_ready_page_title = LocalizedStringKey( - "amplify_ui_liveness_get_ready_page_title" - ) - - /// en = "You will go through a face verification process to prove that you are a real person. Your screen's brightness will temporarily be set to 100% for highest accuracy." - static let get_ready_page_description = LocalizedStringKey( - "amplify_ui_liveness_get_ready_page_description" - ) - - /// en = "Photosensitivity Warning" - static let get_ready_photosensitivity_title = LocalizedStringKey( - "amplify_ui_liveness_get_ready_photosensitivity_title" - ) - - /// en = "This check displays colored lights. Use caution if you are photosensitive." - static let get_ready_photosensitivity_description = LocalizedStringKey( - "amplify_ui_liveness_get_ready_photosensitivity_description" - ) - - /// en = "Photosensitivity Information" - static let get_ready_photosensitivity_icon_a11y = LocalizedStringKey( - "amplify_ui_liveness_get_ready_photosensitivity_icon_a11y" - ) - - /// en = "Photosensitivity warning" - static let get_ready_photosensitivity_dialog_title = LocalizedStringKey( - "amplify_ui_liveness_get_ready_photosensitivity_dialog_title" - ) - - /// en = "A small percentage of individuals may experience epileptic seizures when exposed to colored lights. Use caution if you, or anyone in your family, have an epileptic condition." - static let get_ready_photosensitivity_dialog_description = LocalizedStringKey( - "amplify_ui_liveness_get_ready_photosensitivity_dialog_description" - ) - - /// en = "Follow the instructions to complete the check:" - static let get_ready_steps_title = LocalizedStringKey( - "amplify_ui_liveness_get_ready_steps_title" - ) - - /// en = "Make sure your face is not covered with sunglasses or a mask." - static let get_ready_face_not_covered = LocalizedStringKey( - "amplify_ui_liveness_get_ready_face_not_covered" - ) - - /// en = "Move to a well-lit place that is not in direct sunlight." - static let get_ready_lighting = LocalizedStringKey( - "amplify_ui_liveness_get_ready_lighting" - ) - - /// en = "When an oval appears, fill the oval with your face in it." - static let get_ready_fit_face = LocalizedStringKey( - "amplify_ui_liveness_get_ready_fit_face" - ) - - /// en = "Begin Check" - static let get_ready_begin_check = LocalizedStringKey( - "amplify_ui_liveness_get_ready_begin_check" - ) - - /// en = "Illustration demonstrating good fit of face in oval." - static let get_ready_illustration_good_fit_a11y = LocalizedStringKey( - "amplify_ui_liveness_get_ready_illustration_good_fit_a11y" - ) - - /// en = "Illustration demonstrating face too far from screen." - static let get_ready_illustration_too_far_a11y = LocalizedStringKey( - "amplify_ui_liveness_get_ready_illustration_too_far_a11y" - ) - - /// en = "REC" - static let challenge_recording_indicator_label = LocalizedStringKey( - "amplify_ui_liveness_challenge_recording_indicator_label" - ) - - /// en = "Hold face in oval for colored lights." - static let challenge_instruction_hold_face_during_freshness = LocalizedStringKey( - "amplify_ui_liveness_challenge_instruction_hold_face_during_freshness" - ) - - /// en = "Move back" - static let challenge_instruction_move_face_back = LocalizedStringKey( - "amplify_ui_liveness_challenge_instruction_move_face_back" - ) - - /// en = "Move closer" - static let challenge_instruction_move_face_closer = LocalizedStringKey( - "amplify_ui_liveness_challenge_instruction_move_face_closer" - ) - - /// en = "Move closer" - static let challenge_instruction_move_face = LocalizedStringKey( - "amplify_ui_liveness_challenge_instruction_move_face" - ) - - /// en = "Hold still" - static let challenge_instruction_hold_still = LocalizedStringKey( - "amplify_ui_liveness_challenge_instruction_hold_still" - ) - - /// en = "Ensure only one face is in front of camera" - static let challenge_instruction_multiple_faces_detected = LocalizedStringKey( - "amplify_ui_liveness_challenge_instruction_multiple_faces_detected" - ) - - /// en = "Connecting..." - static let challenge_connecting = LocalizedStringKey( - "amplify_ui_liveness_challenge_connecting" - ) - - /// en = "Verifying" - static let challenge_verifying = LocalizedStringKey( - "amplify_ui_liveness_challenge_verifying" - ) - - /// en = "Cancel Challenge" - static let challenge_cancel_a11y = LocalizedStringKey( - "amplify_ui_liveness_challenge_cancel_a11y" - ) - - /// en = "Change Your Camera Settings" - static let camera_setting_alert_title = LocalizedStringKey( - "amplify_ui_liveness_camera_setting_alert_title" - ) - - /// en = "Allow camera permission in settings." - static let camera_setting_alert_message = LocalizedStringKey( - "amplify_ui_liveness_camera_setting_alert_message" - ) - - /// en = "Update Setting" - static let camera_setting_alert_update_setting_button_text = LocalizedStringKey( - "amplify_ui_liveness_camera_setting_alert_update_setting_button_text" - ) - - /// en = "Not Now" - static let camera_setting_alert_not_now_button_text = LocalizedStringKey( - "amplify_ui_liveness_camera_setting_alert_not_now_button_text" - ) - - /// en = "Close" - static let close_button_a11y = LocalizedStringKey( - "amplify_ui_liveness_close_button_a11y" - ) - - /// en = "Good fit" - static let get_ready_good_fit_example = LocalizedStringKey( - "amplify_ui_liveness_get_ready_good_fit_example" - ) - - /// en = "Too far" - static let get_ready_too_far_example = LocalizedStringKey( - "amplify_ui_liveness_get_ready_too_far_example" - ) - -} diff --git a/Sources/FaceLiveness/Utilities/String+Localizable.swift b/Sources/FaceLiveness/Utilities/String+Localizable.swift new file mode 100644 index 00000000..b81091dc --- /dev/null +++ b/Sources/FaceLiveness/Utilities/String+Localizable.swift @@ -0,0 +1,27 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation + +extension String { + /// Looks for a localized value using this value as the key. + /// If no localization is found in the current app's bundle, + /// it defaults to the one provided by Liveness + func localized(comment: String = "") -> String { + let defaultValue = NSLocalizedString(self, bundle: .module, comment: "") + return NSLocalizedString( + self, + bundle: .main, + value: defaultValue, + comment: "" + ) + } + + func localized(using arguments: CVarArg...) -> String { + return String(format: localized(), arguments) + } +} diff --git a/Sources/FaceLiveness/Views/CloseButton.swift b/Sources/FaceLiveness/Views/CloseButton.swift index 19dddc7c..dee45c40 100644 --- a/Sources/FaceLiveness/Views/CloseButton.swift +++ b/Sources/FaceLiveness/Views/CloseButton.swift @@ -20,7 +20,7 @@ struct CloseButton: View { .frame(width: 44, height: 44) .background(Color.livenessBackground) .clipShape(Circle()) - .accessibilityLabel(Text(.close_button_a11y, bundle: .module)) + .accessibilityLabel(Text(LocalizedStrings.close_button_a11y)) } ) } diff --git a/Sources/FaceLiveness/Views/GetReadyPage/GetReadyPageView.swift b/Sources/FaceLiveness/Views/GetReadyPage/GetReadyPageView.swift index bf8b31e1..404d261e 100644 --- a/Sources/FaceLiveness/Views/GetReadyPage/GetReadyPageView.swift +++ b/Sources/FaceLiveness/Views/GetReadyPage/GetReadyPageView.swift @@ -26,23 +26,23 @@ struct GetReadyPageView: View { VStack { ScrollView { VStack(alignment: .leading) { - Text(.get_ready_page_title, bundle: .module) + Text(LocalizedStrings.get_ready_page_title) .font(.system(size: 34, weight: .semibold)) .accessibilityAddTraits(.isHeader) .padding(.bottom, 8) - Text(.get_ready_page_description, bundle: .module) + Text(LocalizedStrings.get_ready_page_description) .padding(.bottom, 8) WarningBox( - titleText: .get_ready_photosensitivity_title, - bodyText: .get_ready_photosensitivity_description, + titleText: LocalizedStrings.get_ready_photosensitivity_title, + bodyText: LocalizedStrings.get_ready_photosensitivity_description, popoverContent: { photosensitivityWarningPopoverContent } ) .accessibilityElement(children: .combine) .padding(.bottom, 8) - Text(.get_ready_steps_title, bundle: .module) + Text(LocalizedStrings.get_ready_steps_title) .fontWeight(.semibold) .padding(.bottom, 16) @@ -59,15 +59,15 @@ struct GetReadyPageView: View { } .alert(isPresented: $displayingCameraPermissionsNeededAlert) { Alert( - title: Text(.camera_setting_alert_title, bundle: .module), - message: Text(.camera_setting_alert_message, bundle: .module), + title: Text(LocalizedStrings.camera_setting_alert_title), + message: Text(LocalizedStrings.camera_setting_alert_message), primaryButton: .default( - Text(.camera_setting_alert_update_setting_button_text, bundle: .module).bold(), + Text(LocalizedStrings.camera_setting_alert_update_setting_button_text).bold(), action: { goToSettingsAppPage() }), secondaryButton: .default( - Text(.camera_setting_alert_not_now_button_text, bundle: .module) + Text(LocalizedStrings.camera_setting_alert_not_now_button_text) ) ) } @@ -83,7 +83,7 @@ struct GetReadyPageView: View { Button( action: onBegin, label: { - Text(.get_ready_begin_check, bundle: .module) + Text(LocalizedStrings.get_ready_begin_check) .foregroundColor(.livenessPrimaryLabel) .frame(maxWidth: .infinity) } @@ -98,21 +98,21 @@ struct GetReadyPageView: View { private var photosensitivityWarningPopoverContent: some View { VStack { - Text(.get_ready_photosensitivity_dialog_title, bundle: .module) + Text(LocalizedStrings.get_ready_photosensitivity_dialog_title) .font(.system(size: 20, weight: .medium)) .frame(alignment: .center) .padding() - Text(.get_ready_photosensitivity_dialog_description, bundle: .module) + Text(LocalizedStrings.get_ready_photosensitivity_dialog_description) .padding() Spacer() } } private func steps() -> some View { - func step(number: Int, text: LocalizedStringKey) -> some View { + func step(number: Int, text: String) -> some View { HStack(alignment: .top) { Text("\(number).") - Text(text, bundle: .module) + Text(text) } } @@ -120,13 +120,13 @@ struct GetReadyPageView: View { alignment: .leading, spacing: 16 ) { - step(number: 1, text: .get_ready_fit_face) + step(number: 1, text: LocalizedStrings.get_ready_fit_face) .accessibilityElement(children: .combine) - step(number: 2, text: .get_ready_face_not_covered) + step(number: 2, text: LocalizedStrings.get_ready_face_not_covered) .accessibilityElement(children: .combine) - step(number: 3, text: .get_ready_lighting) + step(number: 3, text: LocalizedStrings.get_ready_lighting) .accessibilityElement(children: .combine) } } diff --git a/Sources/FaceLiveness/Views/GetReadyPage/OvalIllustrationExamples.swift b/Sources/FaceLiveness/Views/GetReadyPage/OvalIllustrationExamples.swift index 763ff611..b188cff8 100644 --- a/Sources/FaceLiveness/Views/GetReadyPage/OvalIllustrationExamples.swift +++ b/Sources/FaceLiveness/Views/GetReadyPage/OvalIllustrationExamples.swift @@ -12,23 +12,23 @@ struct OvalIllustrationExamples: View { HStack(spacing: 16) { OvalIllustrationView( icon: .checkmark(backgroundColor: .hex("#365E3D")), - text: { Text(.get_ready_good_fit_example, bundle: .module) }, + text: { Text(LocalizedStrings.get_ready_good_fit_example) }, primaryColor: .hex("#365E3D"), secondaryColor: .hex("#D6F5DB"), illustration: { Image("illustration_face_good_fit", bundle: .module) } ) .accessibilityElement(children: .ignore) - .accessibilityLabel(Text(.get_ready_illustration_good_fit_a11y, bundle: .module)) + .accessibilityLabel(Text(LocalizedStrings.get_ready_illustration_good_fit_a11y)) OvalIllustrationView( icon: .xmark(backgroundColor: .hex("#660000")), - text: { Text(.get_ready_too_far_example, bundle: .module) }, + text: { Text(LocalizedStrings.get_ready_too_far_example) }, primaryColor: .hex("#660000"), secondaryColor: .hex("#F5BCBC"), illustration: { Image("illustration_face_too_far", bundle: .module) } ) .accessibilityElement(children: .ignore) - .accessibilityLabel(Text(.get_ready_illustration_too_far_a11y, bundle: .module)) + .accessibilityLabel(Text(LocalizedStrings.get_ready_illustration_too_far_a11y)) Spacer() } diff --git a/Sources/FaceLiveness/Views/Instruction/InstructionContainerView.swift b/Sources/FaceLiveness/Views/Instruction/InstructionContainerView.swift index 028eb386..a1ae560d 100644 --- a/Sources/FaceLiveness/Views/Instruction/InstructionContainerView.swift +++ b/Sources/FaceLiveness/Views/Instruction/InstructionContainerView.swift @@ -15,7 +15,7 @@ struct InstructionContainerView: View { switch viewModel.livenessState.state { case .displayingFreshness: InstructionView( - text: .challenge_instruction_hold_still, + text: LocalizedStrings.challenge_instruction_hold_still, backgroundColor: .livenessPrimaryBackground, textColor: .livenessPrimaryLabel, font: .title @@ -23,17 +23,13 @@ struct InstructionContainerView: View { .onAppear { UIAccessibility.post( notification: .announcement, - argument: NSLocalizedString( - "amplify_ui_liveness_challenge_instruction_hold_still", - bundle: .module, - comment: "" - ) + argument: LocalizedStrings.challenge_instruction_hold_still ) } case .awaitingFaceInOvalMatch(.faceTooClose, _): InstructionView( - text: .challenge_instruction_move_face_back, + text: LocalizedStrings.challenge_instruction_move_face_back, backgroundColor: .livenessErrorBackground, textColor: .livenessErrorLabel, font: .title @@ -41,11 +37,7 @@ struct InstructionContainerView: View { .onAppear { UIAccessibility.post( notification: .announcement, - argument: NSLocalizedString( - "amplify_ui_liveness_challenge_instruction_move_face_back", - bundle: .module, - comment: "" - ) + argument: LocalizedStrings.challenge_instruction_move_face_back ) } @@ -67,7 +59,7 @@ struct InstructionContainerView: View { .frame(width: 200, height: 30) case .recording(ovalDisplayed: true): InstructionView( - text: .challenge_instruction_move_face_closer, + text: LocalizedStrings.challenge_instruction_move_face_closer, backgroundColor: .livenessPrimaryBackground, textColor: .livenessPrimaryLabel, font: .title @@ -75,11 +67,7 @@ struct InstructionContainerView: View { .onAppear { UIAccessibility.post( notification: .announcement, - argument: NSLocalizedString( - "amplify_ui_liveness_challenge_instruction_move_face_closer", - bundle: .module, - comment: "" - ) + argument: LocalizedStrings.challenge_instruction_move_face_closer ) } @@ -98,17 +86,13 @@ struct InstructionContainerView: View { ) case .completedDisplayingFreshness: InstructionView( - text: .challenge_verifying, + text: LocalizedStrings.challenge_verifying, backgroundColor: .livenessBackground ) .onAppear { UIAccessibility.post( notification: .announcement, - argument: NSLocalizedString( - "amplify_ui_liveness_challenge_verifying", - bundle: .module, - comment: "" - ) + argument: LocalizedStrings.challenge_verifying ) } default: diff --git a/Sources/FaceLiveness/Views/Instruction/InstructionView.swift b/Sources/FaceLiveness/Views/Instruction/InstructionView.swift index 4fe7fb5e..5311387b 100644 --- a/Sources/FaceLiveness/Views/Instruction/InstructionView.swift +++ b/Sources/FaceLiveness/Views/Instruction/InstructionView.swift @@ -8,13 +8,13 @@ import SwiftUI struct InstructionView: View { - let text: LocalizedStringKey + let text: String let backgroundColor: Color var textColor: Color = .livenessLabel var font: Font = .body var body: some View { - Text(text, bundle: .module) + Text(text) .foregroundColor(textColor) .font(font) .padding(12) diff --git a/Sources/FaceLiveness/Views/RecordingButton.swift b/Sources/FaceLiveness/Views/RecordingButton.swift index 0b55ee96..0a157278 100644 --- a/Sources/FaceLiveness/Views/RecordingButton.swift +++ b/Sources/FaceLiveness/Views/RecordingButton.swift @@ -13,7 +13,7 @@ struct RecordingButton: View { Circle() .foregroundColor(.hex("#F92626")) .frame(width: 17, height: 17) - Text(.challenge_recording_indicator_label, bundle: .module) + Text(LocalizedStrings.challenge_recording_indicator_label) .font(.system(size: 12)) .fontWeight(.bold) } diff --git a/Sources/FaceLiveness/Views/WarningBox.swift b/Sources/FaceLiveness/Views/WarningBox.swift index fe46b9cd..050c2aa4 100644 --- a/Sources/FaceLiveness/Views/WarningBox.swift +++ b/Sources/FaceLiveness/Views/WarningBox.swift @@ -9,13 +9,13 @@ import SwiftUI struct WarningBox: View { @State var isPresentingPopover = false - let titleText: LocalizedStringKey - let bodyText: LocalizedStringKey + let titleText: String + let bodyText: String let popoverContent: PopoverView init( - titleText: LocalizedStringKey, - bodyText: LocalizedStringKey, + titleText: String, + bodyText: String, @ViewBuilder popoverContent: () -> PopoverView ) { self.titleText = titleText @@ -26,11 +26,11 @@ struct WarningBox: View { var body: some View { HStack { VStack(alignment: .leading) { - Text(titleText, bundle: .module) + Text(titleText) .fontWeight(.semibold) .foregroundColor(.livenessWarningLabel) - Text(bodyText, bundle: .module) + Text(bodyText) .foregroundColor(.livenessWarningLabel) } Spacer() diff --git a/Tests/IntegrationTestApp/IntegrationTestApp/Localizable.xcstrings b/Tests/IntegrationTestApp/IntegrationTestApp/Localizable.xcstrings new file mode 100644 index 00000000..8a470103 --- /dev/null +++ b/Tests/IntegrationTestApp/IntegrationTestApp/Localizable.xcstrings @@ -0,0 +1,5 @@ +{ + "sourceLanguage" : "en", + "strings" : {}, + "version" : "1.0" +}