1*753557f6STomasz Sapeta // Copyright 2021-present 650 Industries. All rights reserved. 2*753557f6STomasz Sapeta 3*753557f6STomasz Sapeta import Foundation 4*753557f6STomasz Sapeta import ABI47_0_0ExpoModulesCore 5*753557f6STomasz Sapeta 6*753557f6STomasz Sapeta public class LocalizationModule: Module { definitionnull7*753557f6STomasz Sapeta public func definition() -> ModuleDefinition { 8*753557f6STomasz Sapeta Name("ExpoLocalization") 9*753557f6STomasz Sapeta 10*753557f6STomasz Sapeta Constants { 11*753557f6STomasz Sapeta return Self.getCurrentLocalization() 12*753557f6STomasz Sapeta } 13*753557f6STomasz Sapeta AsyncFunction("getLocalizationAsync") { 14*753557f6STomasz Sapeta return Self.getCurrentLocalization() 15*753557f6STomasz Sapeta } 16*753557f6STomasz Sapeta Function("getLocales") { 17*753557f6STomasz Sapeta return Self.getLocales() 18*753557f6STomasz Sapeta } 19*753557f6STomasz Sapeta Function("getCalendars") { 20*753557f6STomasz Sapeta return Self.getCalendars() 21*753557f6STomasz Sapeta } 22*753557f6STomasz Sapeta } 23*753557f6STomasz Sapeta 24*753557f6STomasz Sapeta // If the application isn't manually localized for the device language then the 25*753557f6STomasz Sapeta // native `Locale.current` will fallback on using English US 26*753557f6STomasz Sapeta // [cite](https://stackoverflow.com/questions/48136456/locale-current-reporting-wrong-language-on-device). 27*753557f6STomasz Sapeta // This method will attempt to return the locale that the device is using regardless of the app, 28*753557f6STomasz Sapeta // providing better parity across platforms. getLocalenull29*753557f6STomasz Sapeta static func getLocale() -> Locale { 30*753557f6STomasz Sapeta guard let preferredIdentifier = Locale.preferredLanguages.first else { 31*753557f6STomasz Sapeta return Locale.current 32*753557f6STomasz Sapeta } 33*753557f6STomasz Sapeta return Locale(identifier: preferredIdentifier) 34*753557f6STomasz Sapeta } 35*753557f6STomasz Sapeta /** 36*753557f6STomasz Sapeta Maps ios unique identifiers to [BCP 47 calendar types] 37*753557f6STomasz Sapeta (https://github.com/unicode-org/cldr/blob/main/common/bcp47/calendar.xml) 38*753557f6STomasz Sapeta */ getUnicodeCalendarIdentifiernull39*753557f6STomasz Sapeta static func getUnicodeCalendarIdentifier(calendar: Calendar) -> String { 40*753557f6STomasz Sapeta switch calendar.identifier { 41*753557f6STomasz Sapeta case .buddhist: 42*753557f6STomasz Sapeta return "buddhist" 43*753557f6STomasz Sapeta case .chinese: 44*753557f6STomasz Sapeta return "chinese" 45*753557f6STomasz Sapeta case .coptic: 46*753557f6STomasz Sapeta return "coptic" 47*753557f6STomasz Sapeta case .ethiopicAmeteAlem: 48*753557f6STomasz Sapeta return "ethioaa" 49*753557f6STomasz Sapeta case .ethiopicAmeteMihret: 50*753557f6STomasz Sapeta return "ethiopic" 51*753557f6STomasz Sapeta case .gregorian: 52*753557f6STomasz Sapeta return "gregory" 53*753557f6STomasz Sapeta case .hebrew: 54*753557f6STomasz Sapeta return "hebrew" 55*753557f6STomasz Sapeta case .indian: 56*753557f6STomasz Sapeta return "indian" 57*753557f6STomasz Sapeta case .islamic: 58*753557f6STomasz Sapeta return "islamic" 59*753557f6STomasz Sapeta case .islamicCivil: 60*753557f6STomasz Sapeta return "islamic-civil" 61*753557f6STomasz Sapeta case .islamicTabular: 62*753557f6STomasz Sapeta return "islamic-tbla" 63*753557f6STomasz Sapeta case .islamicUmmAlQura: 64*753557f6STomasz Sapeta return "islamic-umalqura" 65*753557f6STomasz Sapeta case .japanese: 66*753557f6STomasz Sapeta return "japanese" 67*753557f6STomasz Sapeta case .persian: 68*753557f6STomasz Sapeta return "persian" 69*753557f6STomasz Sapeta case .republicOfChina: 70*753557f6STomasz Sapeta return "roc" 71*753557f6STomasz Sapeta case .iso8601: 72*753557f6STomasz Sapeta return "iso8601" 73*753557f6STomasz Sapeta } 74*753557f6STomasz Sapeta } 75*753557f6STomasz Sapeta getLocalesnull76*753557f6STomasz Sapeta static func getLocales() -> [[String: Any?]] { 77*753557f6STomasz Sapeta return (Locale.preferredLanguages.isEmpty ? [Locale.current.identifier] : Locale.preferredLanguages) 78*753557f6STomasz Sapeta .map { languageTag -> [String: Any?] in 79*753557f6STomasz Sapeta var locale = Locale.init(identifier: languageTag) 80*753557f6STomasz Sapeta return [ 81*753557f6STomasz Sapeta "languageTag": languageTag, 82*753557f6STomasz Sapeta "languageCode": locale.languageCode, 83*753557f6STomasz Sapeta "regionCode": locale.regionCode, 84*753557f6STomasz Sapeta "textDirection": Locale.characterDirection(forLanguage: languageTag) == .rightToLeft ? "rtl" : "ltr", 85*753557f6STomasz Sapeta "decimalSeparator": locale.decimalSeparator, 86*753557f6STomasz Sapeta "digitGroupingSeparator": locale.groupingSeparator, 87*753557f6STomasz Sapeta "measurementSystem": locale.usesMetricSystem ? "metric" : "us", 88*753557f6STomasz Sapeta "currencyCode": locale.currencyCode, 89*753557f6STomasz Sapeta "currencySymbol": locale.currencySymbol 90*753557f6STomasz Sapeta ] 91*753557f6STomasz Sapeta } 92*753557f6STomasz Sapeta } 93*753557f6STomasz Sapeta 94*753557f6STomasz Sapeta // https://stackoverflow.com/a/28183182 uses24HourClocknull95*753557f6STomasz Sapeta static func uses24HourClock() -> Bool { 96*753557f6STomasz Sapeta let dateFormat = DateFormatter.dateFormat(fromTemplate: "j", options: 0, locale: Locale.current)! 97*753557f6STomasz Sapeta 98*753557f6STomasz Sapeta return dateFormat.firstIndex(of: "a") == nil 99*753557f6STomasz Sapeta } 100*753557f6STomasz Sapeta getCalendarsnull101*753557f6STomasz Sapeta static func getCalendars() -> [[String: Any?]] { 102*753557f6STomasz Sapeta var calendar = Locale.current.calendar 103*753557f6STomasz Sapeta return [ 104*753557f6STomasz Sapeta [ 105*753557f6STomasz Sapeta "calendar": getUnicodeCalendarIdentifier(calendar: calendar), 106*753557f6STomasz Sapeta "timeZone": "\(calendar.timeZone.identifier)", 107*753557f6STomasz Sapeta "uses24hourClock": uses24HourClock(), 108*753557f6STomasz Sapeta "firstWeekday": calendar.firstWeekday 109*753557f6STomasz Sapeta ] 110*753557f6STomasz Sapeta ] 111*753557f6STomasz Sapeta } 112*753557f6STomasz Sapeta getCurrentLocalizationnull113*753557f6STomasz Sapeta static func getCurrentLocalization() -> [String: Any?] { 114*753557f6STomasz Sapeta let locale = getLocale() 115*753557f6STomasz Sapeta let languageCode = locale.languageCode ?? "en" 116*753557f6STomasz Sapeta var languageIds = Locale.preferredLanguages 117*753557f6STomasz Sapeta 118*753557f6STomasz Sapeta if languageIds.isEmpty { 119*753557f6STomasz Sapeta languageIds.append("en-US") 120*753557f6STomasz Sapeta } 121*753557f6STomasz Sapeta return [ 122*753557f6STomasz Sapeta "currency": locale.currencyCode ?? "USD", 123*753557f6STomasz Sapeta "decimalSeparator": locale.decimalSeparator ?? ".", 124*753557f6STomasz Sapeta "digitGroupingSeparator": locale.groupingSeparator ?? ",", 125*753557f6STomasz Sapeta "isoCurrencyCodes": Locale.isoCurrencyCodes, 126*753557f6STomasz Sapeta "isMetric": locale.usesMetricSystem, 127*753557f6STomasz Sapeta "isRTL": Locale.characterDirection(forLanguage: languageCode) == .rightToLeft, 128*753557f6STomasz Sapeta "locale": languageIds.first, 129*753557f6STomasz Sapeta "locales": languageIds, 130*753557f6STomasz Sapeta "region": locale.regionCode ?? "US", 131*753557f6STomasz Sapeta "timezone": TimeZone.current.identifier 132*753557f6STomasz Sapeta ] 133*753557f6STomasz Sapeta } 134*753557f6STomasz Sapeta } 135