From 2834eed2b3d72806b63179147df939c6391c9109 Mon Sep 17 00:00:00 2001 From: Mel <78050250+mludowise-stripe@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:53:08 -0700 Subject: [PATCH 1/2] [Connect] Locale fixes (#4120) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary 1. Moves `toLanguageTag()` out of `StripePaymentSheet` and `StripeFinancialConnections` (they were duplicated) and into `StripeCore` so it can be used across PaymentSheet, FinancialConnections, and Connect. 2. Fixes a bug in `toLanguageTag()` where extended locales were including `@`, which is not a valid BCP 47 locale identifier. Ex: Using a different calendar -> `en-US@calendar=japanese` Ex: On iOS 16+ when specifying a different language region from device region (e.g. Language=English (UK) and Region=United States) -> `en-GB@rg=uszzzz` 3. Fixes a bug in `toLanguageTag()` where Language=Chinese, Traditional and Region=Hong Kong (China) was dropping the script from the locale (`zh-HK` instead of `zh-Hant-HK`) 4. Updates Connect to use `toLanguageTag()` instead of our custom `webIdentifier` locale helper when passing the locale identifier to the web view. ## Motivation https://jira.corp.stripe.com/browse/MXMOBILE-2840 Fixes formatting the locale when sending it to the Connect web view for languages that specify a script or when the device region is different from the language region. Examples: * If the selected language was "English (UK)" but the device region was "United States", we were previously sending "en-US" when the correct language would have been "en-UK" * If the selected language was "Chinese (Traditional)" with region "Taiwan (China)", we were previously sending "zh_TW" when it should have been "zh-Hant" or "zh-Hant-TW". By not including the script ("Hant"), the web view was defaulting to simplified Chinese. ## Testing - [x] Unit tests (Locale+StripeTests.swift) - [x] Manual testing (see below) ### iOS 17 | Scenario | Screenshot | | ------- | ---------- | | Region=US, Language=English → US english: | | | Region=US, Language=English (UK) → British English: | | | Region=Taiwan (China), Language=Chinese, Traditional → Traditional Chinese: | | | Region=Hong Kong (China), Language=Chinese, Traditional → Traditional Chinese: | | | Region=Taiwan (China), Language=Chinese, Simplified → Simplified Chinese: | | | Region=China mainland, Language=Chinese, Simplified → Simplified Chinese: | | | Region=China mainland, Language=Chinese, Traditional → Traditional Chinese: | | ### iOS 15: | Scenario | Screenshot | | ------- | ---------- | | Region=US, Language=English → US english: | | | Region=United Kingdom, Language=English → British English: | | | Region=Taiwan, Language=Chinese, Traditional → Traditional Chinese: | | | Region=Hong Kong, Language=Chinese, Traditional → Traditional Chinese: | | | Region=Taiwan, Language=Chinese, Simplified → Simplified Chinese: | | | Region=China mainland, Language=Chinese, Simplified → Simplified Chinese: | | | Region=China mainland, Language=Chinese, Traditional → Traditional Chinese: | | --------- Co-authored-by: Yuki --- .../StripeConnectExample/Info.plist | 94 +++++++++---------- .../StripeConnect.xcodeproj/project.pbxproj | 10 +- .../Extensions/Locale+extension.swift | 20 ---- .../Webview/ConnectComponentWebView.swift | 6 +- .../StripeCore.xcodeproj/project.pbxproj | 4 + .../Source/Categories/Locale+StripeCore.swift | 50 ++++++++++ .../Categories/Locale+StripeTests.swift | 74 +++++++++++++++ .../project.pbxproj | 4 - .../Source/Helpers/Locale+Extensions.swift | 33 ------- .../project.pbxproj | 4 - .../Internal/Link/Utils/Locale+Link.swift | 35 ------- 11 files changed, 181 insertions(+), 153 deletions(-) delete mode 100644 StripeConnect/StripeConnect/Source/Internal/Extensions/Locale+extension.swift create mode 100644 StripeCore/StripeCoreTests/Categories/Locale+StripeTests.swift delete mode 100644 StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/Locale+Extensions.swift delete mode 100644 StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Utils/Locale+Link.swift diff --git a/Example/StripeConnectExample/StripeConnectExample/Info.plist b/Example/StripeConnectExample/StripeConnectExample/Info.plist index ba2d7071d64..55fec2539f3 100644 --- a/Example/StripeConnectExample/StripeConnectExample/Info.plist +++ b/Example/StripeConnectExample/StripeConnectExample/Info.plist @@ -8,53 +8,53 @@ CFBundleLocalizations - bg-BG - zh-Hans - zh-Hant-HK - zh-Hant-TW - hr-HR - cs-CZ - da-DK - nl-NL - en-AU - en-IN - en-IE - en-NZ - en-SG - en-GB - en-US - et-EE - fil-PH - fi-FI - fr-CA - fr-FR - de-DE - el-GR - hu-HU - id-ID - it-IT - ja-JP - ko-KR - lv-LV - lt-LT - ms-MY - mt-MT - nb-NO - pl-PL - pt-BR - pt-PT - ro-RO - sk-SK - sl-SI - es-AR - es-BR - es-419 - es-MX - es-ES - sv-SE - th-TH - tr-TR - vi-VN + bg_BG + zh_Hans + zh-Hant_HK + zh-Hant_TW + hr_HR + cs_CZ + da_DK + nl_NL + en_AU + en_IN + en_IE + en_NZ + en_SG + en_GB + en_US + et_EE + fil_PH + fi_FI + fr_CA + fr_FR + de_DE + el_GR + hu_HU + id_ID + it_IT + ja_JP + ko_KR + lv_LV + lt_LT + ms_MY + mt_MT + nb_NO + pl_PL + pt_BR + pt_PT + ro_RO + sk_SK + sl_SI + es_AR + es_BR + es_419 + es_MX + es_ES + sv_SE + th_TH + tr_TR + vi_VN CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) diff --git a/StripeConnect/StripeConnect.xcodeproj/project.pbxproj b/StripeConnect/StripeConnect.xcodeproj/project.pbxproj index 6ccc3902278..68af8a0abc0 100644 --- a/StripeConnect/StripeConnect.xcodeproj/project.pbxproj +++ b/StripeConnect/StripeConnect.xcodeproj/project.pbxproj @@ -51,7 +51,6 @@ 416E9E862C76B35E00A0B917 /* PayoutsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 416E9E852C76B35E00A0B917 /* PayoutsViewController.swift */; }; 416E9E892C76B36F00A0B917 /* PayoutsViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 416E9E872C76B36F00A0B917 /* PayoutsViewControllerTests.swift */; }; 416E9ECF2C77EAA400A0B917 /* EmbeddedComponentError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 416E9ECE2C77EAA400A0B917 /* EmbeddedComponentError.swift */; }; - 416E9ED22C77F6E000A0B917 /* Locale+extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 416E9ED12C77F6E000A0B917 /* Locale+extension.swift */; }; 416E9ED42C77F90600A0B917 /* WKScriptMessage+extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 416E9ED32C77F90600A0B917 /* WKScriptMessage+extension.swift */; }; 4171B1592C9A5EEC00547F7D /* AccountOnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4171B1582C9A5EEC00547F7D /* AccountOnboardingViewController.swift */; }; 41810D692C88C4B100F10EB7 /* AppearanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41810D682C88C4B100F10EB7 /* AppearanceTests.swift */; }; @@ -89,8 +88,8 @@ 41D17A6E2C5A7429007C6EE6 /* Version.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 41D17A632C5A7429007C6EE6 /* Version.xcconfig */; }; E6165CBF2CA7BF2200B76DA5 /* FetchInitComponentPropsMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6165CBE2CA7BF2200B76DA5 /* FetchInitComponentPropsMessageHandler.swift */; }; E6165CC12CA7D09900B76DA5 /* FetchInitComponentPropsMessageHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6165CC02CA7D09900B76DA5 /* FetchInitComponentPropsMessageHandlerTests.swift */; }; - E65691222CA52D5900E0DB00 /* StripeConnect+Exports.swift in Sources */ = {isa = PBXBuildFile; fileRef = E65691212CA52D5900E0DB00 /* StripeConnect+Exports.swift */; }; E65691202CA5248300E0DB00 /* AccountManagementViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E656911F2CA5248300E0DB00 /* AccountManagementViewControllerTests.swift */; }; + E65691222CA52D5900E0DB00 /* StripeConnect+Exports.swift in Sources */ = {isa = PBXBuildFile; fileRef = E65691212CA52D5900E0DB00 /* StripeConnect+Exports.swift */; }; E65691252CA52F9D00E0DB00 /* NotificationBannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E65691232CA52F8600E0DB00 /* NotificationBannerViewController.swift */; }; E65691272CA533CD00E0DB00 /* OnNotificationsChangeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = E65691262CA533CD00E0DB00 /* OnNotificationsChangeHandler.swift */; }; E688AE002CADD8C400951D97 /* NotificationBannerViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E688ADFF2CADD8C400951D97 /* NotificationBannerViewControllerTests.swift */; }; @@ -157,7 +156,6 @@ 416E9E852C76B35E00A0B917 /* PayoutsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PayoutsViewController.swift; sourceTree = ""; }; 416E9E872C76B36F00A0B917 /* PayoutsViewControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PayoutsViewControllerTests.swift; sourceTree = ""; }; 416E9ECE2C77EAA400A0B917 /* EmbeddedComponentError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmbeddedComponentError.swift; sourceTree = ""; }; - 416E9ED12C77F6E000A0B917 /* Locale+extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locale+extension.swift"; sourceTree = ""; }; 416E9ED32C77F90600A0B917 /* WKScriptMessage+extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WKScriptMessage+extension.swift"; sourceTree = ""; }; 4171B1582C9A5EEC00547F7D /* AccountOnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountOnboardingViewController.swift; sourceTree = ""; }; 41810D682C88C4B100F10EB7 /* AppearanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceTests.swift; sourceTree = ""; }; @@ -195,14 +193,14 @@ 41D17A622C5A7429007C6EE6 /* StripeiOS-Shared.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "StripeiOS-Shared.xcconfig"; sourceTree = ""; }; 41D17A632C5A7429007C6EE6 /* Version.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Version.xcconfig; sourceTree = ""; }; E6165CBE2CA7BF2200B76DA5 /* FetchInitComponentPropsMessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchInitComponentPropsMessageHandler.swift; sourceTree = ""; }; + E6165CC02CA7D09900B76DA5 /* FetchInitComponentPropsMessageHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchInitComponentPropsMessageHandlerTests.swift; sourceTree = ""; }; E656911F2CA5248300E0DB00 /* AccountManagementViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountManagementViewControllerTests.swift; sourceTree = ""; }; + E65691212CA52D5900E0DB00 /* StripeConnect+Exports.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StripeConnect+Exports.swift"; sourceTree = ""; }; E65691232CA52F8600E0DB00 /* NotificationBannerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationBannerViewController.swift; sourceTree = ""; }; E65691262CA533CD00E0DB00 /* OnNotificationsChangeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnNotificationsChangeHandler.swift; sourceTree = ""; }; E688ADFF2CADD8C400951D97 /* NotificationBannerViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationBannerViewControllerTests.swift; sourceTree = ""; }; E688AE022CADE36900951D97 /* OnNotificationsChangeHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnNotificationsChangeHandlerTests.swift; sourceTree = ""; }; E6C5F5F52C9FEE0200861709 /* AccountManagementViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountManagementViewController.swift; sourceTree = ""; }; - E6165CC02CA7D09900B76DA5 /* FetchInitComponentPropsMessageHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchInitComponentPropsMessageHandlerTests.swift; sourceTree = ""; }; - E65691212CA52D5900E0DB00 /* StripeConnect+Exports.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StripeConnect+Exports.swift"; sourceTree = ""; }; E6F485F72C9E35A5000D914F /* PaymentDetailsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentDetailsViewController.swift; sourceTree = ""; }; E6F485FB2C9E360A000D914F /* ConnectJSURLParams.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectJSURLParams.swift; sourceTree = ""; }; E6F485FD2C9E36B2000D914F /* PaymentDetailsViewControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentDetailsViewControllerTests.swift; sourceTree = ""; }; @@ -351,7 +349,6 @@ 416E9ED02C77F6C100A0B917 /* Extensions */ = { isa = PBXGroup; children = ( - 416E9ED12C77F6E000A0B917 /* Locale+extension.swift */, 416E9ED32C77F90600A0B917 /* WKScriptMessage+extension.swift */, 41542A682C88B6F2004E728E /* JSONEncoder+extension.swift */, 41542A6A2C88B79E004E728E /* JSONSerialization+extension.swift */, @@ -703,7 +700,6 @@ E6C5F5F62C9FEE0200861709 /* AccountManagementViewController.swift in Sources */, 413987C82C63F34B001D375E /* DebugMessageHandler.swift in Sources */, 410D0FCC2C6CFFDB009B0E26 /* AccountSessionClaimedMessageHandler.swift in Sources */, - 416E9ED22C77F6E000A0B917 /* Locale+extension.swift in Sources */, 41BCCFED2C8B34F600797E01 /* StringCodingKey.swift in Sources */, 4186664E2C66ACB3003DB62E /* OnLoadErrorMessageHandler.swift in Sources */, 410D0FE52C6D32F0009B0E26 /* ApplicationURLOpener.swift in Sources */, diff --git a/StripeConnect/StripeConnect/Source/Internal/Extensions/Locale+extension.swift b/StripeConnect/StripeConnect/Source/Internal/Extensions/Locale+extension.swift deleted file mode 100644 index f16d3842af8..00000000000 --- a/StripeConnect/StripeConnect/Source/Internal/Extensions/Locale+extension.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// Locale+extension.swift -// StripeConnect -// -// Created by Chris Mays on 8/22/24. -// - -import Foundation -@_spi(STP) import StripeCore - -extension Locale { - /// iOS uses underscores for locale (`en_US`) but web uses hyphens (`en-US`) - var webIdentifier: String { - guard let region = stp_regionCode, - let language = stp_languageCode else { - return "" - } - return "\(language)-\(region)" - } -} diff --git a/StripeConnect/StripeConnect/Source/Internal/Webview/ConnectComponentWebView.swift b/StripeConnect/StripeConnect/Source/Internal/Webview/ConnectComponentWebView.swift index 8908cea5129..a881d306a7f 100644 --- a/StripeConnect/StripeConnect/Source/Internal/Webview/ConnectComponentWebView.swift +++ b/StripeConnect/StripeConnect/Source/Internal/Webview/ConnectComponentWebView.swift @@ -101,7 +101,7 @@ class ConnectComponentWebView: ConnectWebView { loadContent: loadContent) } func updateAppearance(appearance: Appearance) { - sendMessage(UpdateConnectInstanceSender.init(payload: .init(locale: webLocale.webIdentifier, appearance: .init(appearance: appearance, traitCollection: traitCollection)))) + sendMessage(UpdateConnectInstanceSender.init(payload: .init(locale: webLocale.toLanguageTag(), appearance: .init(appearance: appearance, traitCollection: traitCollection)))) updateColors(appearance: appearance) } @@ -162,7 +162,7 @@ private extension ConnectComponentWebView { // If self no longer exists give default values return .init(locale: "", appearance: .init(appearance: .default, traitCollection: .init())) } - return .init(locale: webLocale.webIdentifier, + return .init(locale: webLocale.toLanguageTag(), appearance: .init(appearance: componentManager.appearance, traitCollection: self.traitCollection), fonts: componentManager.fonts.map({ .init(customFontSource: $0) })) })) @@ -188,7 +188,7 @@ private extension ConnectComponentWebView { ) { [weak self] _ in // swiftlint:disable:previous unused_capture_list guard let self else { return } - sendMessage(UpdateConnectInstanceSender(payload: .init(locale: webLocale.webIdentifier, appearance: .init(appearance: componentManager.appearance, traitCollection: traitCollection)))) + sendMessage(UpdateConnectInstanceSender(payload: .init(locale: webLocale.toLanguageTag(), appearance: .init(appearance: componentManager.appearance, traitCollection: traitCollection)))) } } diff --git a/StripeCore/StripeCore.xcodeproj/project.pbxproj b/StripeCore/StripeCore.xcodeproj/project.pbxproj index c15c74a4036..54acec977d2 100644 --- a/StripeCore/StripeCore.xcodeproj/project.pbxproj +++ b/StripeCore/StripeCore.xcodeproj/project.pbxproj @@ -117,6 +117,7 @@ DFF3092E51B6C3ED81AB1448 /* String+Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FF688F66CD047D08B3AE0CB /* String+Localized.swift */; }; E2B25D45D457A76A782D9089 /* STPAnalyticEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B6C28853EF0316366FB8DC4 /* STPAnalyticEvent.swift */; }; E344C20A07D8B8F33B530974 /* TestConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B2A342F16484705840F1B5 /* TestConstants.swift */; }; + E6EF91C32CB9DC410082DD1B /* Locale+StripeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6EF91C22CB9DC3C0082DD1B /* Locale+StripeTests.swift */; }; EFE476BA387E91BE1D5D3E1D /* URLRequest+StripeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B75708617FB765AB211FD9A /* URLRequest+StripeTest.swift */; }; EFF90360C85642F7F2898186 /* URLEncoderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F2E1D80D0342CF09CB05415 /* URLEncoderTest.swift */; }; F5DB5D52E2668136FF6D70D6 /* NSMutableURLRequest+StripeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66CC52EF207F05E0EFAEACD8 /* NSMutableURLRequest+StripeTest.swift */; }; @@ -332,6 +333,7 @@ E1C72BA9C44FF60A0E7BEF76 /* STPMultipartFormDataPart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPMultipartFormDataPart.swift; sourceTree = ""; }; E4B2A342F16484705840F1B5 /* TestConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConstants.swift; sourceTree = ""; }; E60F4A38EEF5EA11568B3A64 /* StripeCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StripeCore.h; sourceTree = ""; }; + E6EF91C22CB9DC3C0082DD1B /* Locale+StripeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locale+StripeTests.swift"; sourceTree = ""; }; E919FBEB852CFEA9517FCBDC /* FraudDetectionData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FraudDetectionData.swift; sourceTree = ""; }; EA55726A0FE74A4D90A10C01 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; ECF3D265DCDD0D64F6D7E6B2 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = ""; }; @@ -427,6 +429,7 @@ 3A3F265C90373C3EF2F61523 /* Categories */ = { isa = PBXGroup; children = ( + E6EF91C22CB9DC3C0082DD1B /* Locale+StripeTests.swift */, 34895253739089BC125D2625 /* Dictionary+StripeTests.swift */, 8F2949BE5129DC4BBCD69B05 /* NSArray+StripeCoreTest.swift */, 66CC52EF207F05E0EFAEACD8 /* NSMutableURLRequest+StripeTest.swift */, @@ -950,6 +953,7 @@ 84487D8E9B08106C89753536 /* Error_SerializeForLoggingTest.swift in Sources */, 0A78AD04075C43A4059C344E /* STPAnalyticsClientTest.swift in Sources */, 934CCB00769674F13192A126 /* Dictionary+StripeTests.swift in Sources */, + E6EF91C32CB9DC410082DD1B /* Locale+StripeTests.swift in Sources */, 49ECDA412CA340E100F647F0 /* AsyncTests.swift in Sources */, A50CB2ACAC1DCF9539D76F25 /* NSArray+StripeCoreTest.swift in Sources */, F5DB5D52E2668136FF6D70D6 /* NSMutableURLRequest+StripeTest.swift in Sources */, diff --git a/StripeCore/StripeCore/Source/Categories/Locale+StripeCore.swift b/StripeCore/StripeCore/Source/Categories/Locale+StripeCore.swift index 5adf8f1f2be..3b064f2b29b 100644 --- a/StripeCore/StripeCore/Source/Categories/Locale+StripeCore.swift +++ b/StripeCore/StripeCore/Source/Categories/Locale+StripeCore.swift @@ -41,4 +41,54 @@ import Foundation return self.isoRegionCodes #endif } + + /// Returns the BCP 47(-ish) language tag representing the locale. + /// + /// The language tag is expected to be well-formed as long as the locale identifier contains a + /// valid language code. For example: + /// + /// ``` + /// let locale = Locale(identifier: "fr_CA") + /// locale.toLanguageTag() // -> "fr-CA" + /// ``` + /// + /// The following example returns `"-ES"`, even though `"und-ES"` will be the appropriate BCP 47 tag: + /// + /// ``` + /// let locale = Locale(identifier: "_ES") + /// locale.toLanguageTag() // -> "-ES" + /// ``` + /// All system iOS and macOS locales are expected to contain valid language codes. + /// + /// On iOS 16+, the device region may be different from the language region. When these are different, + /// the device region is encoded at the end. The example below corresponds to: + /// Language=English (UK) and Region=United States: + /// + /// ``` + /// let locale = Locale(identifier: "en_GB@rg=uszzzz") + /// locale.toLanguageTag() // -> "en-GB" + /// ``` + /// + func toLanguageTag() -> String { + var tag = Locale.canonicalLanguageIdentifier(from: self.identifier) + + // Drop sub-tags or extended variants like `en-US@calendar=gregorian` + // or `en-GB@rg=uszzzz` + if let unextended = tag.split(separator: "@").first { + tag = String(unextended) + } + + /* + iOS omits the language script when specifying: + language=Chinese, Traditional and region=Hong Kong (China) + + Stripe's web and backend localization will default to Simplified Chinese + (zh-Hans) if no script is specified, so insert the `Hant` script to + ensure Traditional Chinese is returned. + */ + if tag == "zh-HK" { + tag = "zh-Hant-HK" + } + return tag + } } diff --git a/StripeCore/StripeCoreTests/Categories/Locale+StripeTests.swift b/StripeCore/StripeCoreTests/Categories/Locale+StripeTests.swift new file mode 100644 index 00000000000..8e8d855df91 --- /dev/null +++ b/StripeCore/StripeCoreTests/Categories/Locale+StripeTests.swift @@ -0,0 +1,74 @@ +// +// Locale+StripeTests.swift +// StripeCore +// +// Created by Mel Ludowise on 10/11/24. +// + +@_spi(STP) import StripeCore +import XCTest + +class Locale_StripeTests: XCTestCase { + func testLanguageTag() { + // Language=English, region not specified + XCTAssertEqual(Locale(identifier: "en").toLanguageTag(), "en") + XCTAssertEqual(Locale(identifier: "en_GB").toLanguageTag(), "en-GB") + + // Language=English, region=US, calendar=Japanese + XCTAssertEqual(Locale(identifier: "en_US@calendar=japanese").toLanguageTag(), "en-US") + + // Chinese languages, region not specified + XCTAssertEqual(Locale(identifier: "zh").toLanguageTag(), "zh") + XCTAssertEqual(Locale(identifier: "zh-Hans").toLanguageTag(), "zh-Hans") + XCTAssertEqual(Locale(identifier: "zh-Hant").toLanguageTag(), "zh-Hant") + + // Language=Simplified Chinese, region=China mainland + XCTAssertEqual(Locale(identifier: "zh_CN").toLanguageTag(), "zh-Hans") + XCTAssertEqual(Locale(identifier: "zh-Hans_CN").toLanguageTag(), "zh-Hans") + + // Language=Simplified Chinese, region=Taiwan (China) + XCTAssertEqual(Locale(identifier: "zh-Hans_TW").toLanguageTag(), "zh-Hans-TW") + + // Language=Simplified Chinese, region=Hong Kong (China) + XCTAssertEqual(Locale(identifier: "zh-Hans_HK").toLanguageTag(), "zh-Hans-HK") + + // Language=Traditional Chinese, region=China Mainland + XCTAssertEqual(Locale(identifier: "zh-Hant_CN").toLanguageTag(), "zh-Hant-CN") + + // Language=Traditional Chinese, region=Taiwan (China) + XCTAssertEqual(Locale(identifier: "zh_TW").toLanguageTag(), "zh-Hant") + XCTAssertEqual(Locale(identifier: "zh-Hant_TW").toLanguageTag(), "zh-Hant") + + // Language=Traditional Chinese, region=Hong Kong (China) + XCTAssertEqual(Locale(identifier: "zh_HK").toLanguageTag(), "zh-Hant-HK") + XCTAssertEqual(Locale(identifier: "zh-Hant_HK").toLanguageTag(), "zh-Hant-HK") + } + + /// On iOS 16+, the device region may be different from the language region + /// The `@rg={region-code}zzzz` indicates the device region when it's different from the language region + func testLanguageTag_languageRegionDifferentFromDevice() { + // Language=English (UK), region=United States + XCTAssertEqual(Locale(identifier: "en_GB@rg=uszzzz").toLanguageTag(), "en-GB") + + // Language=Portuguese (Brazil), region=Portugal + XCTAssertEqual(Locale(identifier: "pt_BR@rg=ptzzzz").toLanguageTag(), "pt-BR") + + // Language=Simplified Chinese, region=Hong Kong (China) + XCTAssertEqual(Locale(identifier: "zh-Hans@rg=hkzzzz").toLanguageTag(), "zh-Hans") + + // Language=Simplified Chinese, region=Taiwan (China) + XCTAssertEqual(Locale(identifier: "zh-Hans@rg=twzzzz").toLanguageTag(), "zh-Hans") + + // Language=Traditional Chinese (Taiwan), region=China mainland + XCTAssertEqual(Locale(identifier: "zh-Hant_TW@rg=cnzzzz").toLanguageTag(), "zh-Hant") + + // Language=Traditional Chinese (Taiwan), region=Hong Kong (China) + XCTAssertEqual(Locale(identifier: "zh-Hant_TW@rg=hkzzzz").toLanguageTag(), "zh-Hant") + + // Language=Traditional Chinese (Hong Kong), region=Taiwan (China) + XCTAssertEqual(Locale(identifier: "zh-Hant_HK@rg=twzzzz").toLanguageTag(), "zh-Hant-HK") + + // Language=Traditional Chinese (Hong Kong), region=China mainland + XCTAssertEqual(Locale(identifier: "zh-Hant_HK@rg=cnzzzz").toLanguageTag(), "zh-Hant-HK") + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections.xcodeproj/project.pbxproj b/StripeFinancialConnections/StripeFinancialConnections.xcodeproj/project.pbxproj index 5aa2eeddfab..d4d00ff12a8 100644 --- a/StripeFinancialConnections/StripeFinancialConnections.xcodeproj/project.pbxproj +++ b/StripeFinancialConnections/StripeFinancialConnections.xcodeproj/project.pbxproj @@ -135,7 +135,6 @@ 77D3B375B9DBF80BA209BC99 /* FinancialConnectionsSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E05C2C5CDAA55CE700662040 /* FinancialConnectionsSessionTests.swift */; }; 7AE7474B7AFF416B6072721C /* StripeCore+Import.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CF67A1F497E6CC73029CF0 /* StripeCore+Import.swift */; }; 7DEC399FFE0BAAAB2026E684 /* PaymentAccount+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2A72263B4842E5E30FF91AD /* PaymentAccount+Extensions.swift */; }; - 825C2182D13D7AC2DF67BB5E /* Locale+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77AE2036F58EE5C098C1B25B /* Locale+Extensions.swift */; }; 82FD3CEE526DE8B6519F666E /* FinancialConnectionsSessionFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = A872C2B500306F775622F904 /* FinancialConnectionsSessionFetcher.swift */; }; 846D1D7429B9E414744DEC99 /* FinancialConnectionsSheetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20E7725EF317C3BD62ADF845 /* FinancialConnectionsSheetTests.swift */; }; 864C5159C62C562C655B53F7 /* StripeFinancialConnections.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E37D8CE9CD73443A9AAF2AE8 /* StripeFinancialConnections.framework */; }; @@ -422,7 +421,6 @@ 742D94AC4B2D17F8282A6788 /* fil */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fil; path = fil.lproj/Localizable.strings; sourceTree = ""; }; 75B23D2BD3CD4071A40D9AE9 /* FinancialConnectionsEvent+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FinancialConnectionsEvent+Extensions.swift"; sourceTree = ""; }; 77A71EBB1B98CD285DD17D5F /* AccountFetcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFetcherTests.swift; sourceTree = ""; }; - 77AE2036F58EE5C098C1B25B /* Locale+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locale+Extensions.swift"; sourceTree = ""; }; 780BC432329228B042DA97D8 /* NativeFlowDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeFlowDataManager.swift; sourceTree = ""; }; 782A419DCF59BE6AB6439D04 /* add@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "add@3x.png"; sourceTree = ""; }; 7AFC0D3ED86914DC4216CCCA /* AuthFlowHelpersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthFlowHelpersTests.swift; sourceTree = ""; }; @@ -1052,7 +1050,6 @@ 064D0E3A3AC71FAA60B54FC5 /* Helpers.swift */, 31AD3BE82B0C2F000080C800 /* ScreenNativeScale.swift */, 65BCC4356AE3295B4A2F4A28 /* Image.swift */, - 77AE2036F58EE5C098C1B25B /* Locale+Extensions.swift */, 267B3586136203186882F5CE /* NSAttributedString+Extensions.swift */, B2A72263B4842E5E30FF91AD /* PaymentAccount+Extensions.swift */, 6E2D765AC793D89D26B74FC4 /* STPLocalizedString.swift */, @@ -1348,7 +1345,6 @@ 34E12CB27B60F6A53D030765 /* FinancialConnectionsFont.swift in Sources */, C3338FA5019EC8E99E2BA62F /* Helpers.swift in Sources */, A573468B2800DABF384CAB43 /* Image.swift in Sources */, - 825C2182D13D7AC2DF67BB5E /* Locale+Extensions.swift in Sources */, 6A43202A2B7E8C5400A67A70 /* Constants.swift in Sources */, A79D6A26EE9FF96D24F4AC5C /* NSAttributedString+Extensions.swift in Sources */, 7DEC399FFE0BAAAB2026E684 /* PaymentAccount+Extensions.swift in Sources */, diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/Locale+Extensions.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/Locale+Extensions.swift deleted file mode 100644 index 2c8c7051e40..00000000000 --- a/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/Locale+Extensions.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// Locale+Extensions.swift -// StripeFinancialConnections -// -// Created by Krisjanis Gaidis on 6/20/23. -// - -import Foundation - -extension Locale { - - /// Returns the BCP 47(-ish) language tag representing the locale. - /// - /// The language tag is expected to be well-formed as log as the locale identifier contains a - /// valid language code. For example: - /// - /// ``` - /// let locale = Locale(identifier: "fr_CA") - /// locale.toLanguageTag() // -> "fr-CA" - /// ``` - /// - /// The following example returns `"-ES"`, even though `"und-ES"` will be the appropriate BCP 47 tag: - /// - /// ``` - /// let locale = Locale(identifier: "_ES") - /// locale.toLanguageTag() // -> "-ES" - /// ``` - /// - /// All system iOS and macOS locales are expected to contain valid language codes. - func toLanguageTag() -> String { - return Locale.canonicalLanguageIdentifier(from: self.identifier) - } -} diff --git a/StripePaymentSheet/StripePaymentSheet.xcodeproj/project.pbxproj b/StripePaymentSheet/StripePaymentSheet.xcodeproj/project.pbxproj index d997b6fd4e8..6c55f3a5f5c 100644 --- a/StripePaymentSheet/StripePaymentSheet.xcodeproj/project.pbxproj +++ b/StripePaymentSheet/StripePaymentSheet.xcodeproj/project.pbxproj @@ -51,7 +51,6 @@ 2CE83364A23B4E3BAFD447CA /* WalletHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E32B3AC4CC1C4F2DEEC5F292 /* WalletHeaderView.swift */; }; 2E4C37C73AD202C8A3DD2E4E /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FF291A25EA43D4D100983B /* LoadingViewController.swift */; }; 2EC9C94DD8D62E4F4EFC8AB8 /* IntentStatusPollerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 990304EF35A0EE37DCE20D5B /* IntentStatusPollerTest.swift */; }; - 2EDF4115FDC40A5B0672CCFD /* Locale+Link.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5DCF73BEC0CC35D4CE30361 /* Locale+Link.swift */; }; 311AC53D6C76953E9B70148A /* ConsumerSession+PublishableKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2D61B52BFA201D25E8F6428 /* ConsumerSession+PublishableKey.swift */; }; 313F5F832B0BE5FD00BD98A9 /* Docs.docc in Sources */ = {isa = PBXBuildFile; fileRef = 313F5F822B0BE5FD00BD98A9 /* Docs.docc */; }; 31699A812BE183B30048677F /* DownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31699A802BE183B30048677F /* DownloadManager.swift */; }; @@ -630,7 +629,6 @@ C3C1A5F36075EAEA5A413DC5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; C46CB5AB992F8EEFE4E5460A /* PaymentSheetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentSheetConfiguration.swift; sourceTree = ""; }; C47D00B9DE812D0801B4E33F /* PaymentSheetLPMConfirmFlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentSheetLPMConfirmFlowTests.swift; sourceTree = ""; }; - C5DCF73BEC0CC35D4CE30361 /* Locale+Link.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locale+Link.swift"; sourceTree = ""; }; C684CBDA487CC3E78676F52E /* LinkEmailElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkEmailElement.swift; sourceTree = ""; }; C784153FF2052C4CF240441C /* SeparatorLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorLabel.swift; sourceTree = ""; }; C830FEC205E7162FF4D414BE /* PaymentMethodMessagingViewFunctionalTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentMethodMessagingViewFunctionalTest.swift; sourceTree = ""; }; @@ -1037,7 +1035,6 @@ children = ( 790527588C37B2E2CE0DE3CF /* LinkPopupURLParser.swift */, F69E543E007B4361C7929FF5 /* LinkURLGenerator.swift */, - C5DCF73BEC0CC35D4CE30361 /* Locale+Link.swift */, 274491B25447ABB37EF29D93 /* OperationDebouncer.swift */, ); path = Utils; @@ -1840,7 +1837,6 @@ B626EE932BF2872200B05B05 /* PaymentMethodTypeImageView.swift in Sources */, FA32A3732505CC037CDE64D7 /* LinkURLGenerator.swift in Sources */, 61D8688E2C06553E001FAD84 /* RightAccessoryButton.swift in Sources */, - 2EDF4115FDC40A5B0672CCFD /* Locale+Link.swift in Sources */, 1ECC1086460E57AE75F18FBF /* OperationDebouncer.swift in Sources */, E236FE31A51D130F93F9299B /* LinkAccountContext.swift in Sources */, D0B9FBCB359A7D774B98D19E /* LinkCookieKey.swift in Sources */, diff --git a/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Utils/Locale+Link.swift b/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Utils/Locale+Link.swift deleted file mode 100644 index 34f995f4a53..00000000000 --- a/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Utils/Locale+Link.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// Locale+Link.swift -// StripePaymentSheet -// -// Created by Ramon Torres on 8/3/22. -// Copyright © 2022 Stripe, Inc. All rights reserved. -// - -import Foundation - -extension Locale { - - /// Returns the BCP 47(-ish) language tag representing the locale. - /// - /// The language tag is expected to be well-formed as log as the locale identifier contains a - /// valid language code. For example: - /// - /// ``` - /// let locale = Locale(identifier: "fr_CA") - /// locale.toLanguageTag() // -> "fr-CA" - /// ``` - /// - /// The following example returns `"-ES"`, even though `"und-ES"` will be the appropriate BCP 47 tag: - /// - /// ``` - /// let locale = Locale(identifier: "_ES") - /// locale.toLanguageTag() // -> "-ES" - /// ``` - /// - /// All system iOS and macOS locales are expected to contain valid language codes. - func toLanguageTag() -> String { - return Locale.canonicalLanguageIdentifier(from: self.identifier) - } - -} From 90f95a6dc4e2a08ff692d50ff6a77091d9254de9 Mon Sep 17 00:00:00 2001 From: Mat Schmid Date: Tue, 15 Oct 2024 10:11:42 -0400 Subject: [PATCH 2/2] Move FC SDK build failures to Customer UX bots slack channel (#4127) ## Summary This moves the Financial Connections build failures to the #connections-customer-ux-bots slack channel, as well as Kris's test channel. ## Motivation Better visibility on test failures. ## Testing N/a ## Changelog N/a --- bitrise.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/bitrise.yml b/bitrise.yml index 7259abaeeea..0875938bc05 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -202,6 +202,17 @@ workflows: inputs: - event_description: iOS E2E tests failing! $BITRISE_BUILD_URL. - integration_key: $AUX_PAGERDUTY_INTEGRATION_KEY + - slack@3: + is_always_run: true + run_if: .IsBuildFailed + inputs: + - webhook_url: $WEBHOOK_SLACK_CUX_BOTS + - webhook_url_on_error: $WEBHOOK_SLACK_CUX_BOTS + - slack@3: + is_always_run: true + inputs: + - webhook_url: $WEBHOOK_SLACK_CONNECTIONS_MOBILE + - webhook_url_on_error: $WEBHOOK_SLACK_CONNECTIONS_MOBILE - slack@3: is_always_run: true run_if: .IsBuildFailed