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