1*fe5cfb17STomasz Sapeta // Copyright 2021-present 650 Industries. All rights reserved. 2*fe5cfb17STomasz Sapeta 3*fe5cfb17STomasz Sapeta import Foundation 4*fe5cfb17STomasz Sapeta import ABI48_0_0ExpoModulesCore 5*fe5cfb17STomasz Sapeta 6*fe5cfb17STomasz Sapeta public class LocalizationModule: Module { definitionnull7*fe5cfb17STomasz Sapeta public func definition() -> ModuleDefinition { 8*fe5cfb17STomasz Sapeta Name("ExpoLocalization") 9*fe5cfb17STomasz Sapeta 10*fe5cfb17STomasz Sapeta Constants { 11*fe5cfb17STomasz Sapeta return Self.getCurrentLocalization() 12*fe5cfb17STomasz Sapeta } 13*fe5cfb17STomasz Sapeta AsyncFunction("getLocalizationAsync") { 14*fe5cfb17STomasz Sapeta return Self.getCurrentLocalization() 15*fe5cfb17STomasz Sapeta } 16*fe5cfb17STomasz Sapeta Function("getLocales") { 17*fe5cfb17STomasz Sapeta return Self.getLocales() 18*fe5cfb17STomasz Sapeta } 19*fe5cfb17STomasz Sapeta Function("getCalendars") { 20*fe5cfb17STomasz Sapeta return Self.getCalendars() 21*fe5cfb17STomasz Sapeta } 22*fe5cfb17STomasz Sapeta OnCreate { 23*fe5cfb17STomasz Sapeta if let enableRTL = Bundle.main.object(forInfoDictionaryKey: "ExpoLocalization_supportsRTL") as? Bool { 24*fe5cfb17STomasz Sapeta self.setSupportsRTL(enableRTL) 25*fe5cfb17STomasz Sapeta } 26*fe5cfb17STomasz Sapeta } 27*fe5cfb17STomasz Sapeta } 28*fe5cfb17STomasz Sapeta isRTLPreferredForCurrentLocalenull29*fe5cfb17STomasz Sapeta func isRTLPreferredForCurrentLocale() -> Bool { 30*fe5cfb17STomasz Sapeta return NSLocale.characterDirection(forLanguage: NSLocale.preferredLanguages.first ?? "en-US") == NSLocale.LanguageDirection.rightToLeft 31*fe5cfb17STomasz Sapeta } 32*fe5cfb17STomasz Sapeta setSupportsRTLnull33*fe5cfb17STomasz Sapeta func setSupportsRTL(_ supportsRTL: Bool) { 34*fe5cfb17STomasz Sapeta // These keys are used by ABI48_0_0React Native here: https://github.com/facebook/react-native/blob/main/ABI48_0_0React/Modules/ABI48_0_0RCTI18nUtil.m 35*fe5cfb17STomasz Sapeta // We set them before ABI48_0_0React loads to ensure it gets rendered correctly the first time the app is opened. 36*fe5cfb17STomasz Sapeta // On iOS we need to set both forceRTL and allowRTL so apps don't have to include localization strings. 37*fe5cfb17STomasz Sapeta UserDefaults.standard.set(supportsRTL, forKey: "ABI48_0_0RCTI18nUtil_allowRTL") 38*fe5cfb17STomasz Sapeta UserDefaults.standard.set(supportsRTL ? isRTLPreferredForCurrentLocale() : false, forKey: "ABI48_0_0RCTI18nUtil_forceRTL") 39*fe5cfb17STomasz Sapeta UserDefaults.standard.synchronize() 40*fe5cfb17STomasz Sapeta } 41*fe5cfb17STomasz Sapeta 42*fe5cfb17STomasz Sapeta // If the application isn't manually localized for the device language then the 43*fe5cfb17STomasz Sapeta // native `Locale.current` will fallback on using English US 44*fe5cfb17STomasz Sapeta // [cite](https://stackoverflow.com/questions/48136456/locale-current-reporting-wrong-language-on-device). 45*fe5cfb17STomasz Sapeta // This method will attempt to return the locale that the device is using regardless of the app, 46*fe5cfb17STomasz Sapeta // providing better parity across platforms. getLocalenull47*fe5cfb17STomasz Sapeta static func getLocale() -> Locale { 48*fe5cfb17STomasz Sapeta guard let preferredIdentifier = Locale.preferredLanguages.first else { 49*fe5cfb17STomasz Sapeta return Locale.current 50*fe5cfb17STomasz Sapeta } 51*fe5cfb17STomasz Sapeta return Locale(identifier: preferredIdentifier) 52*fe5cfb17STomasz Sapeta } 53*fe5cfb17STomasz Sapeta /** 54*fe5cfb17STomasz Sapeta Maps ios unique identifiers to [BCP 47 calendar types] 55*fe5cfb17STomasz Sapeta (https://github.com/unicode-org/cldr/blob/main/common/bcp47/calendar.xml) 56*fe5cfb17STomasz Sapeta */ getUnicodeCalendarIdentifiernull57*fe5cfb17STomasz Sapeta static func getUnicodeCalendarIdentifier(calendar: Calendar) -> String { 58*fe5cfb17STomasz Sapeta switch calendar.identifier { 59*fe5cfb17STomasz Sapeta case .buddhist: 60*fe5cfb17STomasz Sapeta return "buddhist" 61*fe5cfb17STomasz Sapeta case .chinese: 62*fe5cfb17STomasz Sapeta return "chinese" 63*fe5cfb17STomasz Sapeta case .coptic: 64*fe5cfb17STomasz Sapeta return "coptic" 65*fe5cfb17STomasz Sapeta case .ethiopicAmeteAlem: 66*fe5cfb17STomasz Sapeta return "ethioaa" 67*fe5cfb17STomasz Sapeta case .ethiopicAmeteMihret: 68*fe5cfb17STomasz Sapeta return "ethiopic" 69*fe5cfb17STomasz Sapeta case .gregorian: 70*fe5cfb17STomasz Sapeta return "gregory" 71*fe5cfb17STomasz Sapeta case .hebrew: 72*fe5cfb17STomasz Sapeta return "hebrew" 73*fe5cfb17STomasz Sapeta case .indian: 74*fe5cfb17STomasz Sapeta return "indian" 75*fe5cfb17STomasz Sapeta case .islamic: 76*fe5cfb17STomasz Sapeta return "islamic" 77*fe5cfb17STomasz Sapeta case .islamicCivil: 78*fe5cfb17STomasz Sapeta return "islamic-civil" 79*fe5cfb17STomasz Sapeta case .islamicTabular: 80*fe5cfb17STomasz Sapeta return "islamic-tbla" 81*fe5cfb17STomasz Sapeta case .islamicUmmAlQura: 82*fe5cfb17STomasz Sapeta return "islamic-umalqura" 83*fe5cfb17STomasz Sapeta case .japanese: 84*fe5cfb17STomasz Sapeta return "japanese" 85*fe5cfb17STomasz Sapeta case .persian: 86*fe5cfb17STomasz Sapeta return "persian" 87*fe5cfb17STomasz Sapeta case .republicOfChina: 88*fe5cfb17STomasz Sapeta return "roc" 89*fe5cfb17STomasz Sapeta case .iso8601: 90*fe5cfb17STomasz Sapeta return "iso8601" 91*fe5cfb17STomasz Sapeta } 92*fe5cfb17STomasz Sapeta } 93*fe5cfb17STomasz Sapeta getLocalesnull94*fe5cfb17STomasz Sapeta static func getLocales() -> [[String: Any?]] { 95*fe5cfb17STomasz Sapeta return (Locale.preferredLanguages.isEmpty ? [Locale.current.identifier] : Locale.preferredLanguages) 96*fe5cfb17STomasz Sapeta .map { languageTag -> [String: Any?] in 97*fe5cfb17STomasz Sapeta var locale = Locale.init(identifier: languageTag) 98*fe5cfb17STomasz Sapeta return [ 99*fe5cfb17STomasz Sapeta "languageTag": languageTag, 100*fe5cfb17STomasz Sapeta "languageCode": locale.languageCode, 101*fe5cfb17STomasz Sapeta "regionCode": locale.regionCode, 102*fe5cfb17STomasz Sapeta "textDirection": Locale.characterDirection(forLanguage: languageTag) == .rightToLeft ? "rtl" : "ltr", 103*fe5cfb17STomasz Sapeta "decimalSeparator": locale.decimalSeparator, 104*fe5cfb17STomasz Sapeta "digitGroupingSeparator": locale.groupingSeparator, 105*fe5cfb17STomasz Sapeta "measurementSystem": locale.usesMetricSystem ? "metric" : "us", 106*fe5cfb17STomasz Sapeta "currencyCode": locale.currencyCode, 107*fe5cfb17STomasz Sapeta "currencySymbol": locale.currencySymbol 108*fe5cfb17STomasz Sapeta ] 109*fe5cfb17STomasz Sapeta } 110*fe5cfb17STomasz Sapeta } 111*fe5cfb17STomasz Sapeta 112*fe5cfb17STomasz Sapeta // https://stackoverflow.com/a/28183182 uses24HourClocknull113*fe5cfb17STomasz Sapeta static func uses24HourClock() -> Bool { 114*fe5cfb17STomasz Sapeta let dateFormat = DateFormatter.dateFormat(fromTemplate: "j", options: 0, locale: Locale.current)! 115*fe5cfb17STomasz Sapeta 116*fe5cfb17STomasz Sapeta return dateFormat.firstIndex(of: "a") == nil 117*fe5cfb17STomasz Sapeta } 118*fe5cfb17STomasz Sapeta getCalendarsnull119*fe5cfb17STomasz Sapeta static func getCalendars() -> [[String: Any?]] { 120*fe5cfb17STomasz Sapeta var calendar = Locale.current.calendar 121*fe5cfb17STomasz Sapeta return [ 122*fe5cfb17STomasz Sapeta [ 123*fe5cfb17STomasz Sapeta "calendar": getUnicodeCalendarIdentifier(calendar: calendar), 124*fe5cfb17STomasz Sapeta "timeZone": "\(calendar.timeZone.identifier)", 125*fe5cfb17STomasz Sapeta "uses24hourClock": uses24HourClock(), 126*fe5cfb17STomasz Sapeta "firstWeekday": calendar.firstWeekday 127*fe5cfb17STomasz Sapeta ] 128*fe5cfb17STomasz Sapeta ] 129*fe5cfb17STomasz Sapeta } 130*fe5cfb17STomasz Sapeta getCurrentLocalizationnull131*fe5cfb17STomasz Sapeta static func getCurrentLocalization() -> [String: Any?] { 132*fe5cfb17STomasz Sapeta let locale = getLocale() 133*fe5cfb17STomasz Sapeta let languageCode = locale.languageCode ?? "en" 134*fe5cfb17STomasz Sapeta var languageIds = Locale.preferredLanguages 135*fe5cfb17STomasz Sapeta 136*fe5cfb17STomasz Sapeta if languageIds.isEmpty { 137*fe5cfb17STomasz Sapeta languageIds.append("en-US") 138*fe5cfb17STomasz Sapeta } 139*fe5cfb17STomasz Sapeta return [ 140*fe5cfb17STomasz Sapeta "currency": locale.currencyCode ?? "USD", 141*fe5cfb17STomasz Sapeta "decimalSeparator": locale.decimalSeparator ?? ".", 142*fe5cfb17STomasz Sapeta "digitGroupingSeparator": locale.groupingSeparator ?? ",", 143*fe5cfb17STomasz Sapeta "isoCurrencyCodes": Locale.isoCurrencyCodes, 144*fe5cfb17STomasz Sapeta "isMetric": locale.usesMetricSystem, 145*fe5cfb17STomasz Sapeta "isRTL": Locale.characterDirection(forLanguage: languageCode) == .rightToLeft, 146*fe5cfb17STomasz Sapeta "locale": languageIds.first, 147*fe5cfb17STomasz Sapeta "locales": languageIds, 148*fe5cfb17STomasz Sapeta "region": locale.regionCode ?? "US", 149*fe5cfb17STomasz Sapeta "timezone": TimeZone.current.identifier 150*fe5cfb17STomasz Sapeta ] 151*fe5cfb17STomasz Sapeta } 152*fe5cfb17STomasz Sapeta } 153