From 343d8cde1eb9b16a110887c85eef3eb7c68c4005 Mon Sep 17 00:00:00 2001 From: Gerson Noboa Date: Wed, 13 Sep 2023 11:05:12 +0300 Subject: [PATCH] Introduce string providing mechanism The proposal is that the Core SDK will return to us a dictionary with the keys and values just as they come from the backend. This is also exactly how the strings are saved in the `Localizable.strings` file, accessed through the `Localization` enum. When the Core SDK is configured and is able to provide the dictionary, we save it to a StringProviding struct. When a string is accessed through the use of `Localization`, then `Localization` knows what is the key of the string, and also specifies a fallback in case reading from the file fails. So with this same key, we can go to the StringProviding, consult the dictionary, and if the string is not there, use the file fallback, and if not use the string fallback. This means that with this simple PR, once the Core SDK provides the dictionary, we support custom locales for the whole Widgets SDK with minimal effort. The alternative is to create a model which we need to keep in sync between Core and Widgets, which seems like a big mess. MOB-2282 --- GliaWidgets.xcodeproj/project.pbxproj | 20 ++++++ .../Localization+StringProviding.swift | 22 +++++++ GliaWidgets/Localization.swift | 21 ------ GliaWidgets/Public/Glia/Glia.swift | 14 ++-- GliaWidgets/StringProviding.swift | 5 ++ .../Resources/LocalizationTests.swift | 66 +++++++++++++++++++ swiftgen-strings.stencil | 27 -------- 7 files changed, 123 insertions(+), 52 deletions(-) create mode 100644 GliaWidgets/Localization+StringProviding.swift create mode 100644 GliaWidgets/StringProviding.swift create mode 100644 GliaWidgetsTests/Resources/LocalizationTests.swift diff --git a/GliaWidgets.xcodeproj/project.pbxproj b/GliaWidgets.xcodeproj/project.pbxproj index d7852d6d6..fb08ad5f7 100644 --- a/GliaWidgets.xcodeproj/project.pbxproj +++ b/GliaWidgets.xcodeproj/project.pbxproj @@ -183,6 +183,8 @@ 311CAFCD29F8FAE20067B59F /* SecureConversations.TranscriptModel+CustomCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311CAFCC29F8FAE20067B59F /* SecureConversations.TranscriptModel+CustomCard.swift */; }; 313EBD552943116E008E9597 /* SecureConversations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313EBD542943116E008E9597 /* SecureConversations.swift */; }; 3142696A29FFB712003DF62E /* Interactor.Failing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3142696929FFB712003DF62E /* Interactor.Failing.swift */; }; + 3146C9432AB1851C0047D8CC /* LocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3146C9422AB1851C0047D8CC /* LocalizationTests.swift */; }; + 3146C9492AB18AC70047D8CC /* Localization+StringProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3146C9482AB18AC70047D8CC /* Localization+StringProviding.swift */; }; 315BAB1A29ADFEBC00FF284B /* ConfirmationStyle+TitleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 315BAB1929ADFEBC00FF284B /* ConfirmationStyle+TitleStyle.swift */; }; 315BAB1C29ADFEC800FF284B /* ConfirmationStyle+SubtitleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 315BAB1B29ADFEC800FF284B /* ConfirmationStyle+SubtitleStyle.swift */; }; 315BAB1E29ADFED800FF284B /* ConfirmationStyle+CheckMessagesButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 315BAB1D29ADFED800FF284B /* ConfirmationStyle+CheckMessagesButtonStyle.swift */; }; @@ -195,6 +197,7 @@ 3197F7B429F7C26A008EE9F7 /* SecureConversations.ChatWithTranscriptViewModel+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3197F7B329F7C26A008EE9F7 /* SecureConversations.ChatWithTranscriptViewModel+Hashable.swift */; }; 3197F7B629F7C2E5008EE9F7 /* SecureConversations.SecureChatModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3197F7B529F7C2E5008EE9F7 /* SecureConversations.SecureChatModel.swift */; }; 3197F7B829F7C318008EE9F7 /* SecureConversations.CommonEngagementModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3197F7B729F7C318008EE9F7 /* SecureConversations.CommonEngagementModel.swift */; }; + 31B1F8A92AB093ED009EC5AD /* StringProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31B1F8A82AB093ED009EC5AD /* StringProviding.swift */; }; 31D286AD2A00DD2C009192A6 /* SecureConversations.ConfirmationViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D286AC2A00DD2C009192A6 /* SecureConversations.ConfirmationViewModelTests.swift */; }; 31D286AF2A00DE2B009192A6 /* SecureConversations.ConfirmationViewModel.Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D286AE2A00DE2B009192A6 /* SecureConversations.ConfirmationViewModel.Mock.swift */; }; 31DB0C01287C2EFC00FB288E /* StaticValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31DB0C00287C2EFC00FB288E /* StaticValues.swift */; }; @@ -915,6 +918,8 @@ 311CAFCC29F8FAE20067B59F /* SecureConversations.TranscriptModel+CustomCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SecureConversations.TranscriptModel+CustomCard.swift"; sourceTree = ""; }; 313EBD542943116E008E9597 /* SecureConversations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureConversations.swift; sourceTree = ""; }; 3142696929FFB712003DF62E /* Interactor.Failing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Interactor.Failing.swift; sourceTree = ""; }; + 3146C9422AB1851C0047D8CC /* LocalizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationTests.swift; sourceTree = ""; }; + 3146C9482AB18AC70047D8CC /* Localization+StringProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Localization+StringProviding.swift"; sourceTree = ""; }; 315BAB1929ADFEBC00FF284B /* ConfirmationStyle+TitleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConfirmationStyle+TitleStyle.swift"; sourceTree = ""; }; 315BAB1B29ADFEC800FF284B /* ConfirmationStyle+SubtitleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConfirmationStyle+SubtitleStyle.swift"; sourceTree = ""; }; 315BAB1D29ADFED800FF284B /* ConfirmationStyle+CheckMessagesButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConfirmationStyle+CheckMessagesButtonStyle.swift"; sourceTree = ""; }; @@ -927,6 +932,7 @@ 3197F7B329F7C26A008EE9F7 /* SecureConversations.ChatWithTranscriptViewModel+Hashable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SecureConversations.ChatWithTranscriptViewModel+Hashable.swift"; sourceTree = ""; }; 3197F7B529F7C2E5008EE9F7 /* SecureConversations.SecureChatModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureConversations.SecureChatModel.swift; sourceTree = ""; }; 3197F7B729F7C318008EE9F7 /* SecureConversations.CommonEngagementModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureConversations.CommonEngagementModel.swift; sourceTree = ""; }; + 31B1F8A82AB093ED009EC5AD /* StringProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringProviding.swift; sourceTree = ""; }; 31D286AC2A00DD2C009192A6 /* SecureConversations.ConfirmationViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureConversations.ConfirmationViewModelTests.swift; sourceTree = ""; }; 31D286AE2A00DE2B009192A6 /* SecureConversations.ConfirmationViewModel.Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureConversations.ConfirmationViewModel.Mock.swift; sourceTree = ""; }; 31DB0C00287C2EFC00FB288E /* StaticValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticValues.swift; sourceTree = ""; }; @@ -1657,7 +1663,9 @@ 1A205D5C25655CB1003AA3CD /* Info.plist */, 1A60AFAE256680EF00E53F53 /* L10n.swift */, 31E35AB92A8648E9006EC7FB /* Localization.swift */, + 3146C9482AB18AC70047D8CC /* Localization+StringProviding.swift */, 31882C9A2AA21B71009DE4BD /* Localization+Templates.swift */, + 31B1F8A82AB093ED009EC5AD /* StringProviding.swift */, 31DB0C00287C2EFC00FB288E /* StaticValues.swift */, ); path = GliaWidgets; @@ -1666,6 +1674,7 @@ 1A205D6525655CB2003AA3CD /* GliaWidgetsTests */ = { isa = PBXGroup; children = ( + 3146C9412AB1850A0047D8CC /* Resources */, 7552DFB22A6FBC6E0093519B /* CoreSdk */, 7552DFAF2A6FB37E0093519B /* ChatMessage */, 846A5C3729D18D220049B29F /* ScreenShareHandler */, @@ -2569,6 +2578,14 @@ path = ChatTranscript; sourceTree = ""; }; + 3146C9412AB1850A0047D8CC /* Resources */ = { + isa = PBXGroup; + children = ( + 3146C9422AB1851C0047D8CC /* LocalizationTests.swift */, + ); + path = Resources; + sourceTree = ""; + }; 315BAB1829ADFE9E00FF284B /* ConfirmationStyle */ = { isa = PBXGroup; children = ( @@ -4289,6 +4306,7 @@ 846A5C4029ED83C50049B29F /* CallVisualizer.Coordinator.DelegateEvent.swift in Sources */, 845876B4282AA296007AC3DF /* ButtonView.Props.Accessibility.swift in Sources */, 1A0C9AE025C9624500815406 /* ObservableValue.swift in Sources */, + 31B1F8A92AB093ED009EC5AD /* StringProviding.swift in Sources */, 75940962298D3889008B173A /* MessageMetadata.swift in Sources */, 7594094D298D37E8008B173A /* Glia.Environment.Mock.swift in Sources */, 1A60AFB9256682AF00E53F53 /* FlowCoordinator.swift in Sources */, @@ -4555,6 +4573,7 @@ 3100EEF2293E214B00D57F71 /* SecureConversations.Coordinator.swift in Sources */, 1AA738AE2578E0D500E1120F /* ConnectAnimationView.swift in Sources */, 754CC61627E2816F005676E9 /* Survey.InputQuestionView.swift in Sources */, + 3146C9492AB18AC70047D8CC /* Localization+StringProviding.swift in Sources */, 9A1992DF27D62C2E00161AAE /* ImageView.Cache.Mock.swift in Sources */, 8491AF0D2A7A9CB900CC3E72 /* Theme.VisitorChatMessageStyle.swift in Sources */, 1A2DA72D25EF9DD900032611 /* FileUpload.swift in Sources */, @@ -4788,6 +4807,7 @@ 84681A952A61844000DD7406 /* ChatViewModelTests+Gva.swift in Sources */, 847A7643285A1914004044D1 /* FileUploadListViewModelTests.swift in Sources */, 9A1992E727D66C7400161AAE /* UIKitBased.Failing.swift in Sources */, + 3146C9432AB1851C0047D8CC /* LocalizationTests.swift in Sources */, 846A5C3929D18D400049B29F /* ScreenShareHandlerTests.swift in Sources */, 9AE05CB62805D2CB00871321 /* Interactor.Environment.Failing.swift in Sources */, 846429862A45DB4100943BD6 /* AlertViewController.Kind+Mock.swift in Sources */, diff --git a/GliaWidgets/Localization+StringProviding.swift b/GliaWidgets/Localization+StringProviding.swift new file mode 100644 index 000000000..0e79e20c3 --- /dev/null +++ b/GliaWidgets/Localization+StringProviding.swift @@ -0,0 +1,22 @@ +import Foundation + +extension Localization { + static func tr( + _ table: String, + _ key: String, + _ args: CVarArg..., + fallback value: String, + stringProviding: StringProviding? = Glia.sharedInstance.stringProviding, + bundleManaging: BundleManaging = .live + ) -> String { + guard + let stringProviding, + let remoteString = stringProviding.getRemoteString(key) + else { + let format = bundleManaging.current().localizedString(forKey: key, value: value, table: table) + return String(format: format, locale: Locale.current, arguments: args) + } + + return remoteString + } +} diff --git a/GliaWidgets/Localization.swift b/GliaWidgets/Localization.swift index ae8e858d5..995ab6b96 100644 --- a/GliaWidgets/Localization.swift +++ b/GliaWidgets/Localization.swift @@ -640,25 +640,4 @@ internal enum Localization { // swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length // swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces -// MARK: - Implementation Details - -extension Localization { - private static func tr(_ table: String, _ key: String, _ args: CVarArg..., fallback value: String) -> String { - let format = BundleToken.bundle.localizedString(forKey: key, value: value, table: table) - return String(format: format, locale: Locale.current, arguments: args) - } -} - -// swiftlint:disable convenience_type -private final class BundleToken { - static let bundle: Bundle = { - #if SWIFT_PACKAGE - return Bundle.module - #else - return Bundle(for: BundleToken.self) - #endif - }() -} -// swiftlint:enable convenience_type - // swiftlint:enable all diff --git a/GliaWidgets/Public/Glia/Glia.swift b/GliaWidgets/Public/Glia/Glia.swift index 38d0efd25..8b543847e 100644 --- a/GliaWidgets/Public/Glia/Glia.swift +++ b/GliaWidgets/Public/Glia/Glia.swift @@ -57,6 +57,8 @@ public class Glia { /// Used to monitor engagement state changes. public var onEvent: ((GliaEvent) -> Void)? + var stringProviding: StringProviding? + public lazy var callVisualizer = CallVisualizer( environment: .init( data: environment.data, @@ -128,10 +130,14 @@ public class Glia { if let callback = completion { createdInteractor.withConfiguration { [weak createdInteractor] in guard let interactor = createdInteractor else { return } - interactor.state = GliaCore.sharedInstance - .getCurrentEngagement()?.engagedOperator - .map(InteractorState.engaged) ?? interactor.state - callback() + + // TODO: Configure string providing from Core SDK here. + + interactor.state = GliaCore.sharedInstance + .getCurrentEngagement()?.engagedOperator + .map(InteractorState.engaged) ?? interactor.state + + callback() } } diff --git a/GliaWidgets/StringProviding.swift b/GliaWidgets/StringProviding.swift new file mode 100644 index 000000000..cf21cda81 --- /dev/null +++ b/GliaWidgets/StringProviding.swift @@ -0,0 +1,5 @@ +import Foundation + +struct StringProviding { + var getRemoteString: ((String) -> String?) +} diff --git a/GliaWidgetsTests/Resources/LocalizationTests.swift b/GliaWidgetsTests/Resources/LocalizationTests.swift new file mode 100644 index 000000000..14a08f0c2 --- /dev/null +++ b/GliaWidgetsTests/Resources/LocalizationTests.swift @@ -0,0 +1,66 @@ +import Foundation +import XCTest +@testable import GliaWidgets + +final class LocalizationTests: XCTestCase { + let testString = "Glia" + + func test_stringProvider() { + let stringProviding = StringProviding(getRemoteString: { _ in self.testString }) + + let localizationString = Localization.tr( + "", + "", + fallback: "", + stringProviding: stringProviding + ) + + XCTAssertEqual(localizationString, testString) + } + + func test_fallback() { + let localizationString = Localization.tr( + "", + "", + fallback: testString + ) + + XCTAssertEqual(localizationString, testString) + } + + func test_fallbackWhenStringProvidingReturnsNil() { + let stringProviding = StringProviding(getRemoteString: { _ in nil }) + + let localizationString = Localization.tr( + "", + "", + fallback: testString, + stringProviding: stringProviding + ) + + XCTAssertEqual(localizationString, testString) + } + + func test_fileFromString() { + let localizationString = Localization.tr( + "Localizable", + "alert.action.settings", + fallback: "" + ) + + XCTAssertEqual(localizationString, "Settings") + } + + func test_fileFromStringWhenStringProvidingReturnsNil() { + let stringProviding = StringProviding(getRemoteString: { _ in nil }) + + let localizationString = Localization.tr( + "Localizable", + "alert.action.settings", + fallback: "", + stringProviding: stringProviding + ) + + XCTAssertEqual(localizationString, "Settings") + } +} diff --git a/swiftgen-strings.stencil b/swiftgen-strings.stencil index 62c97052d..3a56d2857 100644 --- a/swiftgen-strings.stencil +++ b/swiftgen-strings.stencil @@ -79,33 +79,6 @@ import Foundation } // swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length // swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces - -// MARK: - Implementation Details - -extension {{enumName}} { - private static func tr(_ table: String, _ key: String, _ args: CVarArg..., fallback value: String) -> String { - {% if param.lookupFunction %} - let format = {{ param.lookupFunction }}(key, table, value) - {% else %} - let format = {{param.bundle|default:"BundleToken.bundle"}}.localizedString(forKey: key, value: value, table: table) - {% endif %} - return String(format: format, locale: Locale.current, arguments: args) - } -} -{% if not param.bundle and not param.lookupFunction %} - -// swiftlint:disable convenience_type -private final class BundleToken { - static let bundle: Bundle = { - #if SWIFT_PACKAGE - return Bundle.module - #else - return Bundle(for: BundleToken.self) - #endif - }() -} -// swiftlint:enable convenience_type -{% endif %} {% else %} // No string found {% endif %}