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