@amazon-devices/expo-localization 2.0.9000000000 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CMakeLists.txt CHANGED
@@ -12,24 +12,30 @@
12
12
  # FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
13
13
  #
14
14
  set(PACKAGE_NAME "expo-localization")
15
- set(AUTO_LINK_PACKAGE_NAME "${PACKAGE_NAME}-${KEPLER_REACT_NATIVE_MAJOR_VERSION}")
16
- set(SOURCES_KEPLER kepler/ExpoLocalization.cpp)
15
+ # The npm major versions the app-deployed artifact so it cannot collide with the frozen system lib
16
+ set(AUTO_LINK_PACKAGE_NAME "${PACKAGE_NAME}-3")
17
17
 
18
- find_package(ICU COMPONENTS i18n)
18
+ # Events-only module linking just the IDL packages it subscribes through so DT_NEEDED stays on the platform allowlist
19
+ set(SOURCES_KEPLER kepler/ExpoLocalizationEvents.cpp)
19
20
 
20
21
  add_library(${PACKAGE_NAME} STATIC ${SOURCES_KEPLER})
21
22
 
23
+ kepler_add_idl_import_library(
24
+ expo_localization_idl_pkg
25
+ PACKAGES
26
+ com.amazon.kepler.env
27
+ com.amazon.kepler.experimental_mr33.messaging
28
+ )
29
+
22
30
  target_link_libraries(
23
31
  ${PACKAGE_NAME}
24
32
  PUBLIC
25
- keplerui::keplerscript-${KEPLER_REACT_NATIVE_MAJOR_VERSION}
26
- ICU::i18n
33
+ turbomoduleAPI::turbomoduleAPI
34
+ expo_localization_idl_pkg
27
35
  )
28
36
 
29
37
  add_library(${AUTO_LINK_PACKAGE_NAME} SHARED kepler/KeplerInit.cpp)
30
38
 
31
- set_target_properties(${AUTO_LINK_PACKAGE_NAME} PROPERTIES SOVERSION ${KEPLER_REACT_NATIVE_MAJOR_VERSION}.${KEPLER_REACT_NATIVE_MINOR_VERSION})
32
-
33
39
  target_link_libraries(
34
40
  ${AUTO_LINK_PACKAGE_NAME}
35
41
  PRIVATE
@@ -41,8 +47,8 @@ add_executable(${AUTO_LINK_PACKAGE_NAME}_exe kepler/KeplerInit.cpp)
41
47
  target_link_libraries(${AUTO_LINK_PACKAGE_NAME}_exe
42
48
  PRIVATE
43
49
  ${AUTO_LINK_PACKAGE_NAME}
44
- keplerui::keplerscript-${KEPLER_REACT_NATIVE_MAJOR_VERSION}
45
- ICU::i18n
50
+ turbomoduleAPI::turbomoduleAPI
51
+ expo_localization_idl_pkg
46
52
  )
47
53
 
48
54
  install(TARGETS ${AUTO_LINK_PACKAGE_NAME}
package/README.kepler.md CHANGED
@@ -107,3 +107,4 @@ Check out the dedicated documentation page for info about this library, API refe
107
107
  | Package version | Based on | @amazon-devices/react-native-kepler version |
108
108
  | ---------------- | -------- | --------------------------------- |
109
109
  | 2.0.x | expo-localization v14.6.0 | 2.0.x |
110
+ | 2.0.x | expo-localization v14.6.0 | 4.0.x+rn0.83.0 |
@@ -1,12 +1,12 @@
1
- import { Subscription } from 'expo-modules-core';
1
+ import type { Subscription } from 'expo-modules-core';
2
2
  import { Calendar, Locale } from './Localization.types';
3
- export declare function addLocaleListener(listener: (event: any) => void): void;
4
- export declare function addCalendarListener(listener: (event: any) => void): void;
3
+ export declare function addLocaleListener(listener: (event: any) => void): Subscription;
4
+ export declare function addCalendarListener(listener: (event: any) => void): Subscription;
5
5
  export declare function removeSubscription(subscription: Subscription): void;
6
6
  declare const module: {
7
- getLocales: () => Locale[];
8
- getCalendars: () => Calendar[];
9
- locale: any;
7
+ readonly locale: string;
8
+ getLocales(): Locale[];
9
+ getCalendars(): Calendar[];
10
10
  };
11
11
  export default module;
12
12
  //# sourceMappingURL=ExpoLocalization.kepler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoLocalization.kepler.d.ts","sourceRoot":"","sources":["../src/ExpoLocalization.kepler.ts"],"names":[],"mappings":"AASA,OAAO,EAAC,YAAY,EAAC,MAAM,mBAAmB,CAAC;AAE/C,OAAO,EAAC,QAAQ,EAAE,MAAM,EAAC,MAAM,sBAAsB,CAAC;AAUtD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,CAAC,KAAK,KAAA,KAAK,IAAI,QAE1D;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,CAAC,KAAK,KAAA,KAAK,IAAI,QAE5D;AAED,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,YAAY,QAE5D;AAED,QAAA,MAAM,MAAM;sBAnBM,MAAM,EAAE;wBACN,QAAQ,EAAE;;CAsB7B,CAAC;AAEF,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"ExpoLocalization.kepler.d.ts","sourceRoot":"","sources":["../src/ExpoLocalization.kepler.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGtD,OAAO,EAAE,QAAQ,EAAsB,MAAM,EAAE,MAAM,sBAAsB,CAAC;AA6I5E,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,CAAC,KAAK,KAAA,KAAK,IAAI,GAAG,YAAY,CAEzE;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,CAAC,KAAK,KAAA,KAAK,IAAI,GAAG,YAAY,CAU3E;AAED,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,YAAY,QAE5D;AAsCD,QAAA,MAAM,MAAM;qBACI,MAAM;kBAIN,MAAM,EAAE;oBAuCN,QAAQ,EAAE;CAmC3B,CAAC;AAEF,eAAe,MAAM,CAAC"}
@@ -5,22 +5,260 @@
5
5
  *
6
6
  * Use is subject to license terms.
7
7
  */
8
- import { TurboModuleRegistry } from '@amazon-devices/keplerscript-turbomodule-api';
9
- const nativeModule = TurboModuleRegistry.getEnforcing('ExpoLocalization');
10
- const i18nManager = TurboModuleRegistry.getEnforcing('I18nManager');
8
+ import * as rtlDetect from 'rtl-detect';
9
+ // RNLocalize looks up its TM at module load so guard the require and fall back to Intl where the library is absent
10
+ let RNLocalize;
11
+ try {
12
+ RNLocalize = require('@amazon-devices/react-native-localize');
13
+ }
14
+ catch {
15
+ RNLocalize = {};
16
+ }
17
+ // CLDR territory currency data so each entry carries its own region's tender with the user currency as fallback
18
+ const REGION_CURRENCIES = {
19
+ // euro area and territories
20
+ AD: 'EUR', AT: 'EUR', AX: 'EUR', BE: 'EUR', BL: 'EUR', CY: 'EUR', DE: 'EUR', EE: 'EUR',
21
+ ES: 'EUR', FI: 'EUR', FR: 'EUR', GF: 'EUR', GP: 'EUR', GR: 'EUR', HR: 'EUR', IE: 'EUR',
22
+ IT: 'EUR', LT: 'EUR', LU: 'EUR', LV: 'EUR', MC: 'EUR', ME: 'EUR', MF: 'EUR', MQ: 'EUR',
23
+ MT: 'EUR', NL: 'EUR', PM: 'EUR', PT: 'EUR', RE: 'EUR', SI: 'EUR', SK: 'EUR', SM: 'EUR',
24
+ TF: 'EUR', VA: 'EUR', XK: 'EUR', YT: 'EUR',
25
+ // US dollar territories
26
+ US: 'USD', AS: 'USD', BQ: 'USD', EC: 'USD', FM: 'USD', GU: 'USD', IO: 'USD', MH: 'USD',
27
+ MP: 'USD', PR: 'USD', PW: 'USD', SV: 'USD', TC: 'USD', TL: 'USD', UM: 'USD', VG: 'USD', VI: 'USD',
28
+ // pound and crown dependencies
29
+ GB: 'GBP', GG: 'GBP', GS: 'GBP', IM: 'GBP', JE: 'GBP',
30
+ // Australian and New Zealand dollar territories
31
+ AU: 'AUD', CC: 'AUD', CX: 'AUD', HM: 'AUD', KI: 'AUD', NF: 'AUD', NR: 'AUD', TV: 'AUD',
32
+ NZ: 'NZD', CK: 'NZD', NU: 'NZD', PN: 'NZD', TK: 'NZD',
33
+ // CFA and CFP francs and East Caribbean dollar
34
+ BF: 'XOF', BJ: 'XOF', CI: 'XOF', GW: 'XOF', ML: 'XOF', NE: 'XOF', SN: 'XOF', TG: 'XOF',
35
+ CF: 'XAF', CG: 'XAF', CM: 'XAF', GA: 'XAF', GQ: 'XAF', TD: 'XAF',
36
+ NC: 'XPF', PF: 'XPF', WF: 'XPF',
37
+ AG: 'XCD', AI: 'XCD', DM: 'XCD', GD: 'XCD', KN: 'XCD', LC: 'XCD', MS: 'XCD', VC: 'XCD',
38
+ // nordics and remaining single tenders
39
+ DK: 'DKK', FO: 'DKK', GL: 'DKK', NO: 'NOK', BV: 'NOK', SJ: 'NOK', SE: 'SEK', IS: 'ISK',
40
+ CH: 'CHF', LI: 'CHF',
41
+ AE: 'AED', AF: 'AFN', AL: 'ALL', AM: 'AMD', AO: 'AOA', AR: 'ARS', AW: 'AWG', AZ: 'AZN',
42
+ BA: 'BAM', BB: 'BBD', BD: 'BDT', BG: 'BGN', BH: 'BHD', BI: 'BIF', BM: 'BMD', BN: 'BND',
43
+ BO: 'BOB', BR: 'BRL', BS: 'BSD', BT: 'BTN', BW: 'BWP', BY: 'BYN', BZ: 'BZD', CA: 'CAD',
44
+ CD: 'CDF', CL: 'CLP', CN: 'CNY', CO: 'COP', CR: 'CRC', CU: 'CUP', CV: 'CVE', CW: 'ANG',
45
+ CZ: 'CZK', DJ: 'DJF', DO: 'DOP', DZ: 'DZD', EG: 'EGP', EH: 'MAD', ER: 'ERN', ET: 'ETB',
46
+ FJ: 'FJD', FK: 'FKP', GE: 'GEL', GH: 'GHS', GI: 'GIP', GM: 'GMD', GN: 'GNF', GT: 'GTQ',
47
+ GY: 'GYD', HK: 'HKD', HN: 'HNL', HT: 'HTG', HU: 'HUF', ID: 'IDR', IL: 'ILS', IN: 'INR',
48
+ IQ: 'IQD', IR: 'IRR', JM: 'JMD', JO: 'JOD', JP: 'JPY', KE: 'KES', KG: 'KGS', KH: 'KHR',
49
+ KM: 'KMF', KP: 'KPW', KR: 'KRW', KW: 'KWD', KY: 'KYD', KZ: 'KZT', LA: 'LAK', LB: 'LBP',
50
+ LK: 'LKR', LR: 'LRD', LS: 'LSL', LY: 'LYD', MA: 'MAD', MD: 'MDL', MG: 'MGA', MK: 'MKD',
51
+ MM: 'MMK', MN: 'MNT', MO: 'MOP', MR: 'MRU', MU: 'MUR', MV: 'MVR', MW: 'MWK', MX: 'MXN',
52
+ MY: 'MYR', MZ: 'MZN', NA: 'NAD', NG: 'NGN', NI: 'NIO', NP: 'NPR', OM: 'OMR', PA: 'PAB',
53
+ PE: 'PEN', PG: 'PGK', PH: 'PHP', PK: 'PKR', PL: 'PLN', PS: 'ILS', PY: 'PYG', QA: 'QAR',
54
+ RO: 'RON', RS: 'RSD', RU: 'RUB', RW: 'RWF', SA: 'SAR', SB: 'SBD', SC: 'SCR', SD: 'SDG',
55
+ SG: 'SGD', SH: 'SHP', SL: 'SLE', SO: 'SOS', SR: 'SRD', SS: 'SSP', ST: 'STN', SY: 'SYP',
56
+ SZ: 'SZL', TH: 'THB', TJ: 'TJS', TM: 'TMT', TN: 'TND', TO: 'TOP', TR: 'TRY', TT: 'TTD',
57
+ TW: 'TWD', TZ: 'TZS', UA: 'UAH', UG: 'UGX', UY: 'UYU', UZ: 'UZS', VE: 'VES', VN: 'VND',
58
+ VU: 'VUV', WS: 'WST', YE: 'YER', ZA: 'ZAR', ZM: 'ZMW', ZW: 'ZWG',
59
+ };
60
+ // https://wisevoter.com/country-rankings/countries-that-use-fahrenheit/
61
+ const USES_FAHRENHEIT = [
62
+ 'AG', 'BZ', 'VG', 'FM', 'MH', 'MS', 'KN', 'BS', 'TC', 'US', 'LR', 'PW', 'KY',
63
+ ];
64
+ // CLDR weekData firstDay per region with Sunday as 1 and Saturday as 7 and
65
+ // Monday as the default for regions in neither list
66
+ const WEEK_STARTS_SUNDAY = [
67
+ 'AG', 'AS', 'BD', 'BR', 'BS', 'BT', 'BW', 'BZ', 'CA', 'CO', 'DM', 'DO', 'ET',
68
+ 'GT', 'GU', 'HK', 'HN', 'ID', 'IL', 'IN', 'JM', 'JP', 'KE', 'KH', 'KR', 'LA',
69
+ 'MH', 'MM', 'MO', 'MT', 'MX', 'MZ', 'NI', 'NP', 'PA', 'PE', 'PH', 'PK', 'PR',
70
+ 'PY', 'SA', 'SG', 'SV', 'TH', 'TT', 'TW', 'UM', 'US', 'VE', 'VI', 'WS', 'YE',
71
+ 'ZA', 'ZW',
72
+ ];
73
+ const WEEK_STARTS_SATURDAY = ['AE', 'AF', 'BH', 'DJ', 'DZ', 'EG', 'IQ', 'IR', 'JO', 'KW', 'LY', 'OM', 'QA', 'SD', 'SY'];
74
+ function firstWeekdayForRegion(region) {
75
+ if (region == null) {
76
+ return 2;
77
+ }
78
+ if (WEEK_STARTS_SUNDAY.includes(region)) {
79
+ return 1;
80
+ }
81
+ if (WEEK_STARTS_SATURDAY.includes(region)) {
82
+ return 7;
83
+ }
84
+ return 2;
85
+ }
86
+ function currentLanguageTags() {
87
+ try {
88
+ const locales = RNLocalize.getLocales();
89
+ if (Array.isArray(locales) && locales.length > 0) {
90
+ return locales.map((l) => l.languageTag);
91
+ }
92
+ }
93
+ catch { }
94
+ if (typeof Intl !== 'undefined') {
95
+ const tag = Intl.DateTimeFormat().resolvedOptions().locale;
96
+ if (tag) {
97
+ return [tag];
98
+ }
99
+ }
100
+ return ['en-US'];
101
+ }
102
+ function currencyForCurrentLocale() {
103
+ try {
104
+ const currencies = RNLocalize.getCurrencies();
105
+ if (Array.isArray(currencies) && currencies.length > 0) {
106
+ return currencies[0];
107
+ }
108
+ }
109
+ catch { }
110
+ return null;
111
+ }
112
+ function currencySymbolFor(languageTag, currencyCode) {
113
+ if (!currencyCode || typeof Intl === 'undefined') {
114
+ return null;
115
+ }
116
+ try {
117
+ const parts = new Intl.NumberFormat(languageTag, {
118
+ style: 'currency',
119
+ currency: currencyCode,
120
+ }).formatToParts(0);
121
+ return parts.find((p) => p.type === 'currency')?.value ?? null;
122
+ }
123
+ catch {
124
+ return null;
125
+ }
126
+ }
127
+ // Best-effort start of the events turbomodule which forwards OS locale and timezone changes to DeviceEventEmitter
128
+ try {
129
+ const { TurboModuleRegistry } = require('@amazon-devices/keplerscript-turbomodule-api');
130
+ TurboModuleRegistry.getEnforcing('ExpoLocalizationAppTM');
131
+ }
132
+ catch {
133
+ // without it the listeners never fire which was the previous behavior
134
+ }
135
+ const DeviceEventEmitter = require('react-native').DeviceEventEmitter;
11
136
  export function addLocaleListener(listener) {
12
- i18nManager.addListener('Locale', listener);
137
+ return DeviceEventEmitter.addListener('onLocaleSettingsChanged', listener);
13
138
  }
14
139
  export function addCalendarListener(listener) {
15
- i18nManager.addListener('Timezone', listener);
140
+ // Calendar values include the timezone so both signals invalidate them
141
+ const localeSubscription = DeviceEventEmitter.addListener('onLocaleSettingsChanged', listener);
142
+ const timezoneSubscription = DeviceEventEmitter.addListener('onCalendarSettingsChanged', listener);
143
+ return {
144
+ remove: () => {
145
+ localeSubscription.remove();
146
+ timezoneSubscription.remove();
147
+ },
148
+ };
16
149
  }
17
150
  export function removeSubscription(subscription) {
18
- i18nManager.removeListeners();
151
+ subscription.remove();
152
+ }
153
+ // Wire exactly the surface the 2.x Vega module exposed which is locale and getLocales and getCalendars
154
+ function currentLocaleTag() {
155
+ return currentLanguageTags()[0] || 'en-US';
156
+ }
157
+ function regionOfTag(tag) {
158
+ const [, ...suffixes] = tag.split('-');
159
+ for (const suffix of suffixes) {
160
+ if (suffix.length === 2) {
161
+ return suffix.toUpperCase();
162
+ }
163
+ }
164
+ return null;
165
+ }
166
+ function currentTimezone() {
167
+ try {
168
+ const tz = RNLocalize.getTimeZone();
169
+ if (tz) {
170
+ return tz;
171
+ }
172
+ }
173
+ catch { }
174
+ if (typeof Intl !== 'undefined') {
175
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || 'Etc/UTC';
176
+ }
177
+ return 'Etc/UTC';
178
+ }
179
+ function usesMetric() {
180
+ try {
181
+ return RNLocalize.usesMetricSystem();
182
+ }
183
+ catch { }
184
+ const region = regionOfTag(currentLocaleTag());
185
+ return region == null || !['US', 'LR', 'MM'].includes(region);
19
186
  }
20
187
  const module = {
21
- getLocales: nativeModule.getLocales,
22
- getCalendars: nativeModule.getCalendars,
23
- locale: i18nManager.getAppLocale(),
188
+ get locale() {
189
+ return currentLocaleTag();
190
+ },
191
+ getLocales() {
192
+ const userCurrency = currencyForCurrentLocale();
193
+ return currentLanguageTags().map((languageTag) => {
194
+ const locale = typeof Intl !== 'undefined'
195
+ ? new Intl.Locale(languageTag)
196
+ : { region: undefined, textInfo: undefined, language: undefined };
197
+ const region = locale.region ?? null;
198
+ const languageCode = locale.language || languageTag.split('-')[0] || 'en';
199
+ const textDirection = locale.textInfo?.direction ?? (rtlDetect.isRtlLang(languageTag) ? 'rtl' : 'ltr');
200
+ const digitGroupingSeparator = Array.from((10000).toLocaleString(languageTag)).filter((c) => c > '9' || c < '0')[0] || null;
201
+ const decimalSeparator = (1.1).toLocaleString(languageTag).substring(1, 2);
202
+ const currencyCode = (region && REGION_CURRENCIES[region]) || userCurrency;
203
+ return {
204
+ languageTag,
205
+ languageCode,
206
+ textDirection,
207
+ digitGroupingSeparator,
208
+ decimalSeparator,
209
+ measurementSystem: (region === 'GB'
210
+ ? 'uk'
211
+ : region
212
+ ? (['US', 'LR', 'MM'].includes(region) ? 'us' : 'metric')
213
+ : usesMetric()
214
+ ? 'metric'
215
+ : 'us'),
216
+ currencyCode,
217
+ currencySymbol: currencySymbolFor(languageTag, currencyCode),
218
+ regionCode: region,
219
+ temperatureUnit: region
220
+ ? (USES_FAHRENHEIT.includes(region) ? 'fahrenheit' : 'celsius')
221
+ : null,
222
+ };
223
+ });
224
+ },
225
+ getCalendars() {
226
+ const resolved = typeof Intl !== 'undefined' ? Intl.DateTimeFormat().resolvedOptions() : undefined;
227
+ let calendar = resolved?.calendar ?? null;
228
+ try {
229
+ calendar = RNLocalize.getCalendar() ?? calendar;
230
+ }
231
+ catch { }
232
+ let uses24hourClock = null;
233
+ try {
234
+ uses24hourClock = RNLocalize.uses24HourClock();
235
+ }
236
+ catch {
237
+ const hourCycle = resolved?.hourCycle;
238
+ uses24hourClock = hourCycle ? hourCycle.startsWith('h2') : null;
239
+ }
240
+ // weekInfo converted from ISO numbering when the engine has it and the CLDR region table otherwise
241
+ let firstWeekday = null;
242
+ if (typeof Intl !== 'undefined') {
243
+ try {
244
+ const locale = new Intl.Locale(currentLocaleTag());
245
+ const isoFirstDay = locale.weekInfo?.firstDay;
246
+ firstWeekday = isoFirstDay != null ? (isoFirstDay % 7) + 1 : null;
247
+ }
248
+ catch { }
249
+ }
250
+ if (firstWeekday == null) {
251
+ firstWeekday = firstWeekdayForRegion(regionOfTag(currentLocaleTag()));
252
+ }
253
+ return [
254
+ {
255
+ calendar,
256
+ timeZone: currentTimezone(),
257
+ uses24hourClock,
258
+ firstWeekday,
259
+ },
260
+ ];
261
+ },
24
262
  };
25
263
  export default module;
26
264
  //# sourceMappingURL=ExpoLocalization.kepler.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoLocalization.kepler.js","sourceRoot":"","sources":["../src/ExpoLocalization.kepler.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAoB,mBAAmB,EAAC,MAAM,oCAAoC,CAAC;AAU1F,MAAM,YAAY,GAAG,mBAAmB,CAAC,YAAY,CAAO,kBAAkB,CAAS,CAAC;AACxF,MAAM,WAAW,GAAG,mBAAmB,CAAC,YAAY,CAAC,aAAa,CAAQ,CAAC;AAE3E,MAAM,UAAU,iBAAiB,CAAC,QAAyB;IACvD,WAAW,CAAC,WAAW,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,QAAyB;IACzD,WAAW,CAAC,WAAW,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,YAA0B;IACzD,WAAW,CAAC,eAAe,EAAE,CAAC;AAClC,CAAC;AAED,MAAM,MAAM,GAAG;IACX,UAAU,EAAE,YAAY,CAAC,UAAU;IACnC,YAAY,EAAE,YAAY,CAAC,YAAY;IACvC,MAAM,EAAE,WAAW,CAAC,YAAY,EAAE;CACrC,CAAC;AAEF,eAAe,MAAM,CAAC","sourcesContent":["/*\n * Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * PROPRIETARY/CONFIDENTIAL\n *\n * Use is subject to license terms.\n */\n\nimport {KeplerTurboModule, TurboModuleRegistry} from '@amazon-devices/keplerscript-turbomodule-api';\nimport {Subscription} from 'expo-modules-core';\n\nimport {Calendar, Locale} from './Localization.types';\n\ninterface Spec extends KeplerTurboModule {\n getLocales(): Locale[];\n getCalendars(): Calendar[];\n}\n\nconst nativeModule = TurboModuleRegistry.getEnforcing<Spec>('ExpoLocalization') as Spec;\nconst i18nManager = TurboModuleRegistry.getEnforcing('I18nManager') as any;\n\nexport function addLocaleListener(listener: (event) => void) {\n i18nManager.addListener('Locale', listener);\n}\n\nexport function addCalendarListener(listener: (event) => void) {\n i18nManager.addListener('Timezone', listener);\n}\n\nexport function removeSubscription(subscription: Subscription) {\n i18nManager.removeListeners();\n}\n\nconst module = {\n getLocales: nativeModule.getLocales,\n getCalendars: nativeModule.getCalendars,\n locale: i18nManager.getAppLocale(),\n};\n\nexport default module;\n"]}
1
+ {"version":3,"file":"ExpoLocalization.kepler.js","sourceRoot":"","sources":["../src/ExpoLocalization.kepler.ts"],"names":[],"mappings":"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AE,EAAE,KAAK;IACtF,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK;IACpB,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK;IACtF,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK;IACtF,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK;IACtF,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK;IACtF,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK;IACtF,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK;IACtF,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK;IACtF,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK;IACtF,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK;IACtF,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK;IACtF,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK;IACtF,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK;IACtF,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK;IACtF,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK;IACtF,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,E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","sourcesContent":["/*\n * Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * PROPRIETARY/CONFIDENTIAL\n *\n * Use is subject to license terms.\n */\n\n// Pure JS on Hermes Intl plus the react-native-localize turbomodule so the\n// package ships app-deployed with no native library and no embedded ICU.\nimport type { Subscription } from 'expo-modules-core';\nimport * as rtlDetect from 'rtl-detect';\n\nimport { Calendar, CalendarIdentifier, Locale } from './Localization.types';\n\n// RNLocalize looks up its TM at module load so guard the require and fall back to Intl where the library is absent\nlet RNLocalize: any;\ntry {\n RNLocalize = require('@amazon-devices/react-native-localize');\n} catch {\n RNLocalize = {};\n}\n\n// textInfo and weekInfo are recent Intl additions so both are feature detected\ntype ExtendedIntlLocale = Intl.Locale &\n Partial<{\n textInfo: { direction: 'ltr' | 'rtl' };\n weekInfo: { firstDay: number };\n }>;\n\n// CLDR territory currency data so each entry carries its own region's tender with the user currency as fallback\nconst REGION_CURRENCIES: Record<string, string> = {\n // euro area and territories\n AD: 'EUR', AT: 'EUR', AX: 'EUR', BE: 'EUR', BL: 'EUR', CY: 'EUR', DE: 'EUR', EE: 'EUR',\n ES: 'EUR', FI: 'EUR', FR: 'EUR', GF: 'EUR', GP: 'EUR', GR: 'EUR', HR: 'EUR', IE: 'EUR',\n IT: 'EUR', LT: 'EUR', LU: 'EUR', LV: 'EUR', MC: 'EUR', ME: 'EUR', MF: 'EUR', MQ: 'EUR',\n MT: 'EUR', NL: 'EUR', PM: 'EUR', PT: 'EUR', RE: 'EUR', SI: 'EUR', SK: 'EUR', SM: 'EUR',\n TF: 'EUR', VA: 'EUR', XK: 'EUR', YT: 'EUR',\n // US dollar territories\n US: 'USD', AS: 'USD', BQ: 'USD', EC: 'USD', FM: 'USD', GU: 'USD', IO: 'USD', MH: 'USD',\n MP: 'USD', PR: 'USD', PW: 'USD', SV: 'USD', TC: 'USD', TL: 'USD', UM: 'USD', VG: 'USD', VI: 'USD',\n // pound and crown dependencies\n GB: 'GBP', GG: 'GBP', GS: 'GBP', IM: 'GBP', JE: 'GBP',\n // Australian and New Zealand dollar territories\n AU: 'AUD', CC: 'AUD', CX: 'AUD', HM: 'AUD', KI: 'AUD', NF: 'AUD', NR: 'AUD', TV: 'AUD',\n NZ: 'NZD', CK: 'NZD', NU: 'NZD', PN: 'NZD', TK: 'NZD',\n // CFA and CFP francs and East Caribbean dollar\n BF: 'XOF', BJ: 'XOF', CI: 'XOF', GW: 'XOF', ML: 'XOF', NE: 'XOF', SN: 'XOF', TG: 'XOF',\n CF: 'XAF', CG: 'XAF', CM: 'XAF', GA: 'XAF', GQ: 'XAF', TD: 'XAF',\n NC: 'XPF', PF: 'XPF', WF: 'XPF',\n AG: 'XCD', AI: 'XCD', DM: 'XCD', GD: 'XCD', KN: 'XCD', LC: 'XCD', MS: 'XCD', VC: 'XCD',\n // nordics and remaining single tenders\n DK: 'DKK', FO: 'DKK', GL: 'DKK', NO: 'NOK', BV: 'NOK', SJ: 'NOK', SE: 'SEK', IS: 'ISK',\n CH: 'CHF', LI: 'CHF',\n AE: 'AED', AF: 'AFN', AL: 'ALL', AM: 'AMD', AO: 'AOA', AR: 'ARS', AW: 'AWG', AZ: 'AZN',\n BA: 'BAM', BB: 'BBD', BD: 'BDT', BG: 'BGN', BH: 'BHD', BI: 'BIF', BM: 'BMD', BN: 'BND',\n BO: 'BOB', BR: 'BRL', BS: 'BSD', BT: 'BTN', BW: 'BWP', BY: 'BYN', BZ: 'BZD', CA: 'CAD',\n CD: 'CDF', CL: 'CLP', CN: 'CNY', CO: 'COP', CR: 'CRC', CU: 'CUP', CV: 'CVE', CW: 'ANG',\n CZ: 'CZK', DJ: 'DJF', DO: 'DOP', DZ: 'DZD', EG: 'EGP', EH: 'MAD', ER: 'ERN', ET: 'ETB',\n FJ: 'FJD', FK: 'FKP', GE: 'GEL', GH: 'GHS', GI: 'GIP', GM: 'GMD', GN: 'GNF', GT: 'GTQ',\n GY: 'GYD', HK: 'HKD', HN: 'HNL', HT: 'HTG', HU: 'HUF', ID: 'IDR', IL: 'ILS', IN: 'INR',\n IQ: 'IQD', IR: 'IRR', JM: 'JMD', JO: 'JOD', JP: 'JPY', KE: 'KES', KG: 'KGS', KH: 'KHR',\n KM: 'KMF', KP: 'KPW', KR: 'KRW', KW: 'KWD', KY: 'KYD', KZ: 'KZT', LA: 'LAK', LB: 'LBP',\n LK: 'LKR', LR: 'LRD', LS: 'LSL', LY: 'LYD', MA: 'MAD', MD: 'MDL', MG: 'MGA', MK: 'MKD',\n MM: 'MMK', MN: 'MNT', MO: 'MOP', MR: 'MRU', MU: 'MUR', MV: 'MVR', MW: 'MWK', MX: 'MXN',\n MY: 'MYR', MZ: 'MZN', NA: 'NAD', NG: 'NGN', NI: 'NIO', NP: 'NPR', OM: 'OMR', PA: 'PAB',\n PE: 'PEN', PG: 'PGK', PH: 'PHP', PK: 'PKR', PL: 'PLN', PS: 'ILS', PY: 'PYG', QA: 'QAR',\n RO: 'RON', RS: 'RSD', RU: 'RUB', RW: 'RWF', SA: 'SAR', SB: 'SBD', SC: 'SCR', SD: 'SDG',\n SG: 'SGD', SH: 'SHP', SL: 'SLE', SO: 'SOS', SR: 'SRD', SS: 'SSP', ST: 'STN', SY: 'SYP',\n SZ: 'SZL', TH: 'THB', TJ: 'TJS', TM: 'TMT', TN: 'TND', TO: 'TOP', TR: 'TRY', TT: 'TTD',\n TW: 'TWD', TZ: 'TZS', UA: 'UAH', UG: 'UGX', UY: 'UYU', UZ: 'UZS', VE: 'VES', VN: 'VND',\n VU: 'VUV', WS: 'WST', YE: 'YER', ZA: 'ZAR', ZM: 'ZMW', ZW: 'ZWG',\n};\n\n// https://wisevoter.com/country-rankings/countries-that-use-fahrenheit/\nconst USES_FAHRENHEIT = [\n 'AG', 'BZ', 'VG', 'FM', 'MH', 'MS', 'KN', 'BS', 'TC', 'US', 'LR', 'PW', 'KY',\n];\n\n// CLDR weekData firstDay per region with Sunday as 1 and Saturday as 7 and\n// Monday as the default for regions in neither list\nconst WEEK_STARTS_SUNDAY = [\n 'AG', 'AS', 'BD', 'BR', 'BS', 'BT', 'BW', 'BZ', 'CA', 'CO', 'DM', 'DO', 'ET',\n 'GT', 'GU', 'HK', 'HN', 'ID', 'IL', 'IN', 'JM', 'JP', 'KE', 'KH', 'KR', 'LA',\n 'MH', 'MM', 'MO', 'MT', 'MX', 'MZ', 'NI', 'NP', 'PA', 'PE', 'PH', 'PK', 'PR',\n 'PY', 'SA', 'SG', 'SV', 'TH', 'TT', 'TW', 'UM', 'US', 'VE', 'VI', 'WS', 'YE',\n 'ZA', 'ZW',\n];\nconst WEEK_STARTS_SATURDAY = ['AE', 'AF', 'BH', 'DJ', 'DZ', 'EG', 'IQ', 'IR', 'JO', 'KW', 'LY', 'OM', 'QA', 'SD', 'SY'];\n\nfunction firstWeekdayForRegion(region: string | null): number {\n if (region == null) {\n return 2;\n }\n if (WEEK_STARTS_SUNDAY.includes(region)) {\n return 1;\n }\n if (WEEK_STARTS_SATURDAY.includes(region)) {\n return 7;\n }\n return 2;\n}\n\nfunction currentLanguageTags(): string[] {\n try {\n const locales = RNLocalize.getLocales();\n if (Array.isArray(locales) && locales.length > 0) {\n return locales.map((l: { languageTag: string }) => l.languageTag);\n }\n } catch {}\n if (typeof Intl !== 'undefined') {\n const tag = Intl.DateTimeFormat().resolvedOptions().locale;\n if (tag) {\n return [tag];\n }\n }\n return ['en-US'];\n}\n\nfunction currencyForCurrentLocale(): string | null {\n try {\n const currencies = RNLocalize.getCurrencies();\n if (Array.isArray(currencies) && currencies.length > 0) {\n return currencies[0];\n }\n } catch {}\n return null;\n}\n\nfunction currencySymbolFor(languageTag: string, currencyCode: string | null): string | null {\n if (!currencyCode || typeof Intl === 'undefined') {\n return null;\n }\n try {\n const parts = new Intl.NumberFormat(languageTag, {\n style: 'currency',\n currency: currencyCode,\n }).formatToParts(0);\n return parts.find((p) => p.type === 'currency')?.value ?? null;\n } catch {\n return null;\n }\n}\n\n// Best-effort start of the events turbomodule which forwards OS locale and timezone changes to DeviceEventEmitter\ntry {\n const { TurboModuleRegistry } = require('@amazon-devices/keplerscript-turbomodule-api');\n TurboModuleRegistry.getEnforcing('ExpoLocalizationAppTM');\n} catch {\n // without it the listeners never fire which was the previous behavior\n}\n\nconst DeviceEventEmitter = require('react-native').DeviceEventEmitter;\n\nexport function addLocaleListener(listener: (event) => void): Subscription {\n return DeviceEventEmitter.addListener('onLocaleSettingsChanged', listener) as Subscription;\n}\n\nexport function addCalendarListener(listener: (event) => void): Subscription {\n // Calendar values include the timezone so both signals invalidate them\n const localeSubscription = DeviceEventEmitter.addListener('onLocaleSettingsChanged', listener);\n const timezoneSubscription = DeviceEventEmitter.addListener('onCalendarSettingsChanged', listener);\n return {\n remove: () => {\n localeSubscription.remove();\n timezoneSubscription.remove();\n },\n } as Subscription;\n}\n\nexport function removeSubscription(subscription: Subscription) {\n subscription.remove();\n}\n\n// Wire exactly the surface the 2.x Vega module exposed which is locale and getLocales and getCalendars\nfunction currentLocaleTag(): string {\n return currentLanguageTags()[0] || 'en-US';\n}\n\nfunction regionOfTag(tag: string): string | null {\n const [, ...suffixes] = tag.split('-');\n for (const suffix of suffixes) {\n if (suffix.length === 2) {\n return suffix.toUpperCase();\n }\n }\n return null;\n}\n\nfunction currentTimezone(): string {\n try {\n const tz = RNLocalize.getTimeZone();\n if (tz) {\n return tz;\n }\n } catch {}\n if (typeof Intl !== 'undefined') {\n return Intl.DateTimeFormat().resolvedOptions().timeZone || 'Etc/UTC';\n }\n return 'Etc/UTC';\n}\n\nfunction usesMetric(): boolean {\n try {\n return RNLocalize.usesMetricSystem();\n } catch {}\n const region = regionOfTag(currentLocaleTag());\n return region == null || !['US', 'LR', 'MM'].includes(region);\n}\n\nconst module = {\n get locale(): string {\n return currentLocaleTag();\n },\n\n getLocales(): Locale[] {\n const userCurrency = currencyForCurrentLocale();\n return currentLanguageTags().map((languageTag) => {\n const locale =\n typeof Intl !== 'undefined'\n ? (new Intl.Locale(languageTag) as unknown as ExtendedIntlLocale)\n : ({ region: undefined, textInfo: undefined, language: undefined } as unknown as ExtendedIntlLocale);\n const region = locale.region ?? null;\n const languageCode = locale.language || languageTag.split('-')[0] || 'en';\n const textDirection: 'ltr' | 'rtl' =\n locale.textInfo?.direction ?? (rtlDetect.isRtlLang(languageTag) ? 'rtl' : 'ltr');\n const digitGroupingSeparator =\n Array.from((10000).toLocaleString(languageTag)).filter((c) => c > '9' || c < '0')[0] || null;\n const decimalSeparator = (1.1).toLocaleString(languageTag).substring(1, 2);\n const currencyCode = (region && REGION_CURRENCIES[region]) || userCurrency;\n\n return {\n languageTag,\n languageCode,\n textDirection,\n digitGroupingSeparator,\n decimalSeparator,\n measurementSystem: (region === 'GB'\n ? 'uk'\n : region\n ? (['US', 'LR', 'MM'].includes(region) ? 'us' : 'metric')\n : usesMetric()\n ? 'metric'\n : 'us') as Locale['measurementSystem'],\n currencyCode,\n currencySymbol: currencySymbolFor(languageTag, currencyCode),\n regionCode: region,\n temperatureUnit: region\n ? ((USES_FAHRENHEIT.includes(region) ? 'fahrenheit' : 'celsius') as Locale['temperatureUnit'])\n : null,\n };\n });\n },\n\n getCalendars(): Calendar[] {\n const resolved =\n typeof Intl !== 'undefined' ? Intl.DateTimeFormat().resolvedOptions() : undefined;\n let calendar: CalendarIdentifier | null = (resolved?.calendar as CalendarIdentifier) ?? null;\n try {\n calendar = (RNLocalize.getCalendar() as CalendarIdentifier) ?? calendar;\n } catch {}\n let uses24hourClock: boolean | null = null;\n try {\n uses24hourClock = RNLocalize.uses24HourClock();\n } catch {\n const hourCycle = (resolved as { hourCycle?: string } | undefined)?.hourCycle;\n uses24hourClock = hourCycle ? hourCycle.startsWith('h2') : null;\n }\n // weekInfo converted from ISO numbering when the engine has it and the CLDR region table otherwise\n let firstWeekday: number | null = null;\n if (typeof Intl !== 'undefined') {\n try {\n const locale = new Intl.Locale(currentLocaleTag()) as ExtendedIntlLocale;\n const isoFirstDay = locale.weekInfo?.firstDay;\n firstWeekday = isoFirstDay != null ? (isoFirstDay % 7) + 1 : null;\n } catch {}\n }\n if (firstWeekday == null) {\n firstWeekday = firstWeekdayForRegion(regionOfTag(currentLocaleTag()));\n }\n return [\n {\n calendar,\n timeZone: currentTimezone(),\n uses24hourClock,\n firstWeekday,\n },\n ];\n },\n};\n\nexport default module;\n"]}
@@ -0,0 +1,108 @@
1
+ /*
2
+ * Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ *
4
+ * PROPRIETARY/CONFIDENTIAL
5
+ *
6
+ * Use is subject to license terms.
7
+ */
8
+
9
+ #include "ExpoLocalizationEvents.h"
10
+
11
+ #include <Kepler/turbomodule/TMLog.h>
12
+ #include <apmf/apmf_base.h>
13
+ #include <apmf/fn.h>
14
+ #include <apmf/iface/com/amazon/kepler/env/IEnvModule.h>
15
+ #include <apmf/iface/com/amazon/kepler/env/ILocaleConfig.h>
16
+ #include <apmf/iface/com/amazon/kepler/env/IResolvedLocalesListener.h>
17
+ #include <apmf/iface/com/amazon/kepler/experimental_mr33/messaging/IKeplerMessagingModule.h>
18
+ #include <apmf/iface/com/amazon/kepler/experimental_mr33/messaging/IMessageFilterBuilder.h>
19
+ #include <apmf/iface/com/amazon/kepler/experimental_mr33/messaging/IMessageListener.h>
20
+
21
+ #include <memory>
22
+ #include <string>
23
+
24
+ namespace env = apmf::iface::com::amazon::kepler::env;
25
+ namespace messaging = apmf::iface::com::amazon::kepler::experimental_mr33::messaging;
26
+
27
+ namespace {
28
+
29
+ apmf::Ptr<env::IEnvModule> getEnvModule() {
30
+ return apmf::detail::GetProcessObject()
31
+ ->getComponent("/com.amazon.kepler.env")
32
+ .TryQueryInterface<env::IEnvModule>();
33
+ }
34
+
35
+ } // namespace
36
+
37
+ ExpoLocalizationEvents::ExpoLocalizationEvents() : KeplerTurboModule("ExpoLocalizationEvents") {
38
+ TMDEBUG(std::string("ExpoLocalizationEvents constructor ") + name());
39
+
40
+ // Guarded because this runs in a noexcept autolink entry and the env lookup throws on hosts without the module
41
+ try {
42
+ getEnvModule()->getLocaleConfig()->addResolvedLocalesListener(
43
+ nullptr, apmf::Fn1([this, alive = m_alive](auto locales) {
44
+ if (alive->load()) {
45
+ this->emit("onLocaleSettingsChanged", nullptr);
46
+ }
47
+ }));
48
+ } catch (const std::exception& e) {
49
+ TMWARN(std::string("env locale config unavailable so locale change events are disabled: ") + e.what());
50
+ } catch (...) {
51
+ TMWARN("env locale config unavailable so locale change events are disabled");
52
+ }
53
+
54
+ // Timezone changes via MR33 messaging with the same degradation where it is absent
55
+ apmf::detail::Ptr<messaging::IKeplerMessagingModule> messagingModule;
56
+ try {
57
+ auto proc = apmf::detail::GetProcessObject();
58
+ messagingModule = proc->getComponent("/com.amazon.kepler.experimental_mr33.messaging")
59
+ .TryQueryInterface<messaging::IKeplerMessagingModule>();
60
+ } catch (const std::exception& e) {
61
+ TMWARN(std::string("MR33 messaging unavailable so timezone change events are disabled: ") + e.what());
62
+ } catch (...) {
63
+ TMWARN("MR33 messaging unavailable so timezone change events are disabled");
64
+ }
65
+ if (messagingModule) {
66
+ struct TZListener : apmf::ApmfBase<TZListener, messaging::IMessageListener> {
67
+ ExpoLocalizationEvents* owner;
68
+ std::shared_ptr<std::atomic<bool>> alive;
69
+ TZListener(ExpoLocalizationEvents* o, std::shared_ptr<std::atomic<bool>> a)
70
+ : owner(o), alive(std::move(a)) {}
71
+ void onMessage(apmf::View<messaging::IMessage> /*message*/) {
72
+ if (alive->load()) {
73
+ owner->emit("onCalendarSettingsChanged", nullptr);
74
+ }
75
+ }
76
+ void onError(apmf::View<apmf::IError> /*error*/) {}
77
+ };
78
+
79
+ auto channel = messagingModule->makeMessageChannel(
80
+ "ExpoLocalization_TZ",
81
+ apmf::detail::Ptr<TZListener>::Make(this, m_alive));
82
+
83
+ apmf::detail::Array<apmf::detail::Ptr<messaging::IMessageFilter>> filters;
84
+ filters.push_back(
85
+ messagingModule->makeMessageFilterBuilder()
86
+ ->baseUri("broadcast://*/com.amazon.devconf/system/configuration/time-zone")
87
+ ->build());
88
+ channel->registerMessageFilter(filters);
89
+ m_timezoneChannel = std::move(channel);
90
+ }
91
+ }
92
+
93
+ ExpoLocalizationEvents::~ExpoLocalizationEvents() {
94
+ m_alive->store(false);
95
+ }
96
+
97
+ // No-ops because subscriptions live in JS via DeviceEventEmitter and emits do not track counts
98
+ void ExpoLocalizationEvents::addListener(std::string /*eventName*/) {}
99
+
100
+ void ExpoLocalizationEvents::removeListeners(double /*count*/) {}
101
+
102
+ void ExpoLocalizationEvents::aggregateMethods(
103
+ tbm::MethodAggregator<KeplerTurboModule>& methodAggregator) const noexcept {
104
+ // The standard RN event module methods keep the napi method table non empty
105
+ // because NapiTurboModule::defineProperties rejects a zero method count
106
+ methodAggregator.addMethod("addListener", 0, &ExpoLocalizationEvents::addListener);
107
+ methodAggregator.addMethod("removeListeners", 0, &ExpoLocalizationEvents::removeListeners);
108
+ }
@@ -0,0 +1,40 @@
1
+ /*
2
+ * Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ *
4
+ * PROPRIETARY/CONFIDENTIAL
5
+ *
6
+ * Use is subject to license terms.
7
+ */
8
+
9
+ #pragma once
10
+
11
+ #include <Kepler/turbomodule/KeplerTurboModule.h>
12
+ #include <apmf/iface/com/amazon/kepler/experimental_mr33/messaging/IMessageChannel.h>
13
+ #include <apmf/ptr.h>
14
+
15
+ #include <atomic>
16
+ #include <memory>
17
+ #include <string>
18
+
19
+ #include "expo-export.h"
20
+
21
+ namespace tbm = com::amazon::kepler::turbomodule;
22
+
23
+ // Events-only turbomodule that forwards the two OS change signals to JS since the read path is pure JS
24
+ class EXPO_EXPORT ExpoLocalizationEvents : public tbm::KeplerTurboModule {
25
+ public:
26
+ ExpoLocalizationEvents();
27
+ ~ExpoLocalizationEvents() override;
28
+
29
+ void aggregateMethods(tbm::MethodAggregator<tbm::KeplerTurboModule>& methodAggregator) const noexcept override;
30
+
31
+ // Standard RN event module surface so the napi method table is never empty
32
+ void addListener(std::string eventName);
33
+ void removeListeners(double count);
34
+
35
+ private:
36
+ // Liveness flag so a callback delivered after destruction cannot touch the freed instance
37
+ std::shared_ptr<std::atomic<bool>> m_alive{std::make_shared<std::atomic<bool>>(true)};
38
+ apmf::detail::Ptr<apmf::iface::com::amazon::kepler::experimental_mr33::messaging::IMessageChannel>
39
+ m_timezoneChannel;
40
+ };
@@ -6,18 +6,15 @@
6
6
  * Use is subject to license terms.
7
7
  */
8
8
 
9
- #include <KeplerScript/logger/KPLog.h>
10
- #include <KeplerScript/utils/autolink/KeplerAutoLinkInfo.h>
11
- #include <keplerui-072/keplerscript-export.h>
9
+ #include <Kepler/turbomodule/KeplerTurboModuleRegistration.h>
10
+ #include <Kepler/turbomodule/TMLog.h>
12
11
 
13
- #include "ExpoLocalization.h"
14
- #include "RegisterModule.h"
12
+ #include "ExpoLocalizationEvents.h"
15
13
 
16
14
  extern "C" {
17
- KEPLERSCRIPT_EXPORT void keplerAutoLinkInitV1(const kepler::autolink::v1::KeplerAutoLinkInfo* autoLinkInfo) noexcept {
18
- volta::react::KPLog.debug("keplerAutoLinkInitV1 for ExpoLocalization");
19
-
20
- REGISTER_MODULE(autoLinkInfo, ExpoLocalization, "ExpoLocalization");
15
+ __attribute__((visibility("default"))) void autoLinkKeplerTurboModulesV1() noexcept {
16
+ TMDEBUG("autoLinkKeplerTurboModulesV1 for expo-localization");
17
+ KEPLER_REGISTER_TURBO_MODULE_WITH_NAME("ExpoLocalizationAppTM", ExpoLocalizationEvents);
21
18
  }
22
19
  }
23
20
 
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name": "@amazon-devices/expo-localization", "version": "2.0.9000000000+14.6.0", "description": "Provides an interface for native user localization information.", "main": "build/Localization.js", "types": "build/Localization.d.ts", "sideEffects": false, "scripts": {"build": "expo-module build", "clean": "expo-module clean", "lint": "expo-module lint", "test": "expo-module test", "prepublishOnly": "expo-module prepublishOnly", "expo-module": "expo-module"}, "kepler": {"temporary-alias-for-package": {"name": "expo-localization", "version": "14.6.0"}}, "keywords": ["react-native", "expo", "localization", "locales", "l10n"], "repository": {"type": "git", "url": "https://github.com/expo/expo.git", "directory": "packages/expo-localization"}, "bugs": {"url": "https://github.com/expo/expo/issues"}, "contributors": ["Evan Bacon <bacon@expo.io> (https://github.com/evanbacon)"], "author": "650 Industries, Inc.", "license": "See license in LICENSE", "homepage": "https://docs.expo.dev/versions/latest/sdk/localization/", "jest": {"preset": "expo-module-scripts"}, "dependencies": {"rtl-detect": "^1.0.2"}, "devDependencies": {"expo-module-scripts": "^3.0.0", "i18n-js": "^3.1.0", "@types/minimatch": "^3.0.5"}, "peerDependencies": {"expo": "*"}}
1
+ {"name": "@amazon-devices/expo-localization", "version": "3.0.0+14.6.0", "description": "Provides an interface for native user localization information.", "main": "build/Localization.js", "types": "build/Localization.d.ts", "sideEffects": false, "scripts": {"build": "expo-module build", "clean": "expo-module clean", "lint": "expo-module lint", "test": "expo-module test", "prepublishOnly": "expo-module prepublishOnly", "expo-module": "expo-module"}, "kepler": {"temporary-alias-for-package": {"name": "expo-localization", "version": "14.6.0"}}, "keywords": ["react-native", "expo", "localization", "locales", "l10n"], "repository": {"type": "git", "url": "https://github.com/expo/expo.git", "directory": "packages/expo-localization"}, "bugs": {"url": "https://github.com/expo/expo/issues"}, "contributors": ["Evan Bacon <bacon@expo.io> (https://github.com/evanbacon)"], "author": "650 Industries, Inc.", "license": "See license in LICENSE", "homepage": "https://docs.expo.dev/versions/latest/sdk/localization/", "jest": {"preset": "expo-module-scripts"}, "dependencies": {"rtl-detect": "^1.0.2"}, "devDependencies": {"expo-module-scripts": "^3.0.0", "i18n-js": "^3.1.0", "@types/minimatch": "^3.0.5", "@amazon-devices/react-native-localize": "^2.0.0"}, "peerDependencies": {"expo": "^50.0.0-alpha.1", "@amazon-devices/react-native-localize": "*"}}
@@ -10,9 +10,11 @@ module.exports = {
10
10
  platforms: {
11
11
  kepler: {
12
12
  autolink: {
13
- ExpoLocalizationLib: {
14
- provider: 'system',
15
- turbomodules: ['ExpoLocalization'],
13
+ ExpoLocalizationAppLib: {
14
+ provider: 'application',
15
+ linkDynamic: true,
16
+ libraryName: 'libexpo-localization-3.so',
17
+ turbomodules: ['ExpoLocalizationAppTM'],
16
18
  },
17
19
  },
18
20
  },
@@ -6,35 +6,287 @@
6
6
  * Use is subject to license terms.
7
7
  */
8
8
 
9
- import {KeplerTurboModule, TurboModuleRegistry} from '@amazon-devices/keplerscript-turbomodule-api';
10
- import {Subscription} from 'expo-modules-core';
9
+ // Pure JS on Hermes Intl plus the react-native-localize turbomodule so the
10
+ // package ships app-deployed with no native library and no embedded ICU.
11
+ import type { Subscription } from 'expo-modules-core';
12
+ import * as rtlDetect from 'rtl-detect';
11
13
 
12
- import {Calendar, Locale} from './Localization.types';
14
+ import { Calendar, CalendarIdentifier, Locale } from './Localization.types';
13
15
 
14
- interface Spec extends KeplerTurboModule {
15
- getLocales(): Locale[];
16
- getCalendars(): Calendar[];
16
+ // RNLocalize looks up its TM at module load so guard the require and fall back to Intl where the library is absent
17
+ let RNLocalize: any;
18
+ try {
19
+ RNLocalize = require('@amazon-devices/react-native-localize');
20
+ } catch {
21
+ RNLocalize = {};
17
22
  }
18
23
 
19
- const nativeModule = TurboModuleRegistry.getEnforcing<Spec>('ExpoLocalization') as Spec;
20
- const i18nManager = TurboModuleRegistry.getEnforcing('I18nManager') as any;
24
+ // textInfo and weekInfo are recent Intl additions so both are feature detected
25
+ type ExtendedIntlLocale = Intl.Locale &
26
+ Partial<{
27
+ textInfo: { direction: 'ltr' | 'rtl' };
28
+ weekInfo: { firstDay: number };
29
+ }>;
21
30
 
22
- export function addLocaleListener(listener: (event) => void) {
23
- i18nManager.addListener('Locale', listener);
31
+ // CLDR territory currency data so each entry carries its own region's tender with the user currency as fallback
32
+ const REGION_CURRENCIES: Record<string, string> = {
33
+ // euro area and territories
34
+ AD: 'EUR', AT: 'EUR', AX: 'EUR', BE: 'EUR', BL: 'EUR', CY: 'EUR', DE: 'EUR', EE: 'EUR',
35
+ ES: 'EUR', FI: 'EUR', FR: 'EUR', GF: 'EUR', GP: 'EUR', GR: 'EUR', HR: 'EUR', IE: 'EUR',
36
+ IT: 'EUR', LT: 'EUR', LU: 'EUR', LV: 'EUR', MC: 'EUR', ME: 'EUR', MF: 'EUR', MQ: 'EUR',
37
+ MT: 'EUR', NL: 'EUR', PM: 'EUR', PT: 'EUR', RE: 'EUR', SI: 'EUR', SK: 'EUR', SM: 'EUR',
38
+ TF: 'EUR', VA: 'EUR', XK: 'EUR', YT: 'EUR',
39
+ // US dollar territories
40
+ US: 'USD', AS: 'USD', BQ: 'USD', EC: 'USD', FM: 'USD', GU: 'USD', IO: 'USD', MH: 'USD',
41
+ MP: 'USD', PR: 'USD', PW: 'USD', SV: 'USD', TC: 'USD', TL: 'USD', UM: 'USD', VG: 'USD', VI: 'USD',
42
+ // pound and crown dependencies
43
+ GB: 'GBP', GG: 'GBP', GS: 'GBP', IM: 'GBP', JE: 'GBP',
44
+ // Australian and New Zealand dollar territories
45
+ AU: 'AUD', CC: 'AUD', CX: 'AUD', HM: 'AUD', KI: 'AUD', NF: 'AUD', NR: 'AUD', TV: 'AUD',
46
+ NZ: 'NZD', CK: 'NZD', NU: 'NZD', PN: 'NZD', TK: 'NZD',
47
+ // CFA and CFP francs and East Caribbean dollar
48
+ BF: 'XOF', BJ: 'XOF', CI: 'XOF', GW: 'XOF', ML: 'XOF', NE: 'XOF', SN: 'XOF', TG: 'XOF',
49
+ CF: 'XAF', CG: 'XAF', CM: 'XAF', GA: 'XAF', GQ: 'XAF', TD: 'XAF',
50
+ NC: 'XPF', PF: 'XPF', WF: 'XPF',
51
+ AG: 'XCD', AI: 'XCD', DM: 'XCD', GD: 'XCD', KN: 'XCD', LC: 'XCD', MS: 'XCD', VC: 'XCD',
52
+ // nordics and remaining single tenders
53
+ DK: 'DKK', FO: 'DKK', GL: 'DKK', NO: 'NOK', BV: 'NOK', SJ: 'NOK', SE: 'SEK', IS: 'ISK',
54
+ CH: 'CHF', LI: 'CHF',
55
+ AE: 'AED', AF: 'AFN', AL: 'ALL', AM: 'AMD', AO: 'AOA', AR: 'ARS', AW: 'AWG', AZ: 'AZN',
56
+ BA: 'BAM', BB: 'BBD', BD: 'BDT', BG: 'BGN', BH: 'BHD', BI: 'BIF', BM: 'BMD', BN: 'BND',
57
+ BO: 'BOB', BR: 'BRL', BS: 'BSD', BT: 'BTN', BW: 'BWP', BY: 'BYN', BZ: 'BZD', CA: 'CAD',
58
+ CD: 'CDF', CL: 'CLP', CN: 'CNY', CO: 'COP', CR: 'CRC', CU: 'CUP', CV: 'CVE', CW: 'ANG',
59
+ CZ: 'CZK', DJ: 'DJF', DO: 'DOP', DZ: 'DZD', EG: 'EGP', EH: 'MAD', ER: 'ERN', ET: 'ETB',
60
+ FJ: 'FJD', FK: 'FKP', GE: 'GEL', GH: 'GHS', GI: 'GIP', GM: 'GMD', GN: 'GNF', GT: 'GTQ',
61
+ GY: 'GYD', HK: 'HKD', HN: 'HNL', HT: 'HTG', HU: 'HUF', ID: 'IDR', IL: 'ILS', IN: 'INR',
62
+ IQ: 'IQD', IR: 'IRR', JM: 'JMD', JO: 'JOD', JP: 'JPY', KE: 'KES', KG: 'KGS', KH: 'KHR',
63
+ KM: 'KMF', KP: 'KPW', KR: 'KRW', KW: 'KWD', KY: 'KYD', KZ: 'KZT', LA: 'LAK', LB: 'LBP',
64
+ LK: 'LKR', LR: 'LRD', LS: 'LSL', LY: 'LYD', MA: 'MAD', MD: 'MDL', MG: 'MGA', MK: 'MKD',
65
+ MM: 'MMK', MN: 'MNT', MO: 'MOP', MR: 'MRU', MU: 'MUR', MV: 'MVR', MW: 'MWK', MX: 'MXN',
66
+ MY: 'MYR', MZ: 'MZN', NA: 'NAD', NG: 'NGN', NI: 'NIO', NP: 'NPR', OM: 'OMR', PA: 'PAB',
67
+ PE: 'PEN', PG: 'PGK', PH: 'PHP', PK: 'PKR', PL: 'PLN', PS: 'ILS', PY: 'PYG', QA: 'QAR',
68
+ RO: 'RON', RS: 'RSD', RU: 'RUB', RW: 'RWF', SA: 'SAR', SB: 'SBD', SC: 'SCR', SD: 'SDG',
69
+ SG: 'SGD', SH: 'SHP', SL: 'SLE', SO: 'SOS', SR: 'SRD', SS: 'SSP', ST: 'STN', SY: 'SYP',
70
+ SZ: 'SZL', TH: 'THB', TJ: 'TJS', TM: 'TMT', TN: 'TND', TO: 'TOP', TR: 'TRY', TT: 'TTD',
71
+ TW: 'TWD', TZ: 'TZS', UA: 'UAH', UG: 'UGX', UY: 'UYU', UZ: 'UZS', VE: 'VES', VN: 'VND',
72
+ VU: 'VUV', WS: 'WST', YE: 'YER', ZA: 'ZAR', ZM: 'ZMW', ZW: 'ZWG',
73
+ };
74
+
75
+ // https://wisevoter.com/country-rankings/countries-that-use-fahrenheit/
76
+ const USES_FAHRENHEIT = [
77
+ 'AG', 'BZ', 'VG', 'FM', 'MH', 'MS', 'KN', 'BS', 'TC', 'US', 'LR', 'PW', 'KY',
78
+ ];
79
+
80
+ // CLDR weekData firstDay per region with Sunday as 1 and Saturday as 7 and
81
+ // Monday as the default for regions in neither list
82
+ const WEEK_STARTS_SUNDAY = [
83
+ 'AG', 'AS', 'BD', 'BR', 'BS', 'BT', 'BW', 'BZ', 'CA', 'CO', 'DM', 'DO', 'ET',
84
+ 'GT', 'GU', 'HK', 'HN', 'ID', 'IL', 'IN', 'JM', 'JP', 'KE', 'KH', 'KR', 'LA',
85
+ 'MH', 'MM', 'MO', 'MT', 'MX', 'MZ', 'NI', 'NP', 'PA', 'PE', 'PH', 'PK', 'PR',
86
+ 'PY', 'SA', 'SG', 'SV', 'TH', 'TT', 'TW', 'UM', 'US', 'VE', 'VI', 'WS', 'YE',
87
+ 'ZA', 'ZW',
88
+ ];
89
+ const WEEK_STARTS_SATURDAY = ['AE', 'AF', 'BH', 'DJ', 'DZ', 'EG', 'IQ', 'IR', 'JO', 'KW', 'LY', 'OM', 'QA', 'SD', 'SY'];
90
+
91
+ function firstWeekdayForRegion(region: string | null): number {
92
+ if (region == null) {
93
+ return 2;
94
+ }
95
+ if (WEEK_STARTS_SUNDAY.includes(region)) {
96
+ return 1;
97
+ }
98
+ if (WEEK_STARTS_SATURDAY.includes(region)) {
99
+ return 7;
100
+ }
101
+ return 2;
102
+ }
103
+
104
+ function currentLanguageTags(): string[] {
105
+ try {
106
+ const locales = RNLocalize.getLocales();
107
+ if (Array.isArray(locales) && locales.length > 0) {
108
+ return locales.map((l: { languageTag: string }) => l.languageTag);
109
+ }
110
+ } catch {}
111
+ if (typeof Intl !== 'undefined') {
112
+ const tag = Intl.DateTimeFormat().resolvedOptions().locale;
113
+ if (tag) {
114
+ return [tag];
115
+ }
116
+ }
117
+ return ['en-US'];
118
+ }
119
+
120
+ function currencyForCurrentLocale(): string | null {
121
+ try {
122
+ const currencies = RNLocalize.getCurrencies();
123
+ if (Array.isArray(currencies) && currencies.length > 0) {
124
+ return currencies[0];
125
+ }
126
+ } catch {}
127
+ return null;
128
+ }
129
+
130
+ function currencySymbolFor(languageTag: string, currencyCode: string | null): string | null {
131
+ if (!currencyCode || typeof Intl === 'undefined') {
132
+ return null;
133
+ }
134
+ try {
135
+ const parts = new Intl.NumberFormat(languageTag, {
136
+ style: 'currency',
137
+ currency: currencyCode,
138
+ }).formatToParts(0);
139
+ return parts.find((p) => p.type === 'currency')?.value ?? null;
140
+ } catch {
141
+ return null;
142
+ }
143
+ }
144
+
145
+ // Best-effort start of the events turbomodule which forwards OS locale and timezone changes to DeviceEventEmitter
146
+ try {
147
+ const { TurboModuleRegistry } = require('@amazon-devices/keplerscript-turbomodule-api');
148
+ TurboModuleRegistry.getEnforcing('ExpoLocalizationAppTM');
149
+ } catch {
150
+ // without it the listeners never fire which was the previous behavior
151
+ }
152
+
153
+ const DeviceEventEmitter = require('react-native').DeviceEventEmitter;
154
+
155
+ export function addLocaleListener(listener: (event) => void): Subscription {
156
+ return DeviceEventEmitter.addListener('onLocaleSettingsChanged', listener) as Subscription;
24
157
  }
25
158
 
26
- export function addCalendarListener(listener: (event) => void) {
27
- i18nManager.addListener('Timezone', listener);
159
+ export function addCalendarListener(listener: (event) => void): Subscription {
160
+ // Calendar values include the timezone so both signals invalidate them
161
+ const localeSubscription = DeviceEventEmitter.addListener('onLocaleSettingsChanged', listener);
162
+ const timezoneSubscription = DeviceEventEmitter.addListener('onCalendarSettingsChanged', listener);
163
+ return {
164
+ remove: () => {
165
+ localeSubscription.remove();
166
+ timezoneSubscription.remove();
167
+ },
168
+ } as Subscription;
28
169
  }
29
170
 
30
171
  export function removeSubscription(subscription: Subscription) {
31
- i18nManager.removeListeners();
172
+ subscription.remove();
173
+ }
174
+
175
+ // Wire exactly the surface the 2.x Vega module exposed which is locale and getLocales and getCalendars
176
+ function currentLocaleTag(): string {
177
+ return currentLanguageTags()[0] || 'en-US';
178
+ }
179
+
180
+ function regionOfTag(tag: string): string | null {
181
+ const [, ...suffixes] = tag.split('-');
182
+ for (const suffix of suffixes) {
183
+ if (suffix.length === 2) {
184
+ return suffix.toUpperCase();
185
+ }
186
+ }
187
+ return null;
188
+ }
189
+
190
+ function currentTimezone(): string {
191
+ try {
192
+ const tz = RNLocalize.getTimeZone();
193
+ if (tz) {
194
+ return tz;
195
+ }
196
+ } catch {}
197
+ if (typeof Intl !== 'undefined') {
198
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || 'Etc/UTC';
199
+ }
200
+ return 'Etc/UTC';
201
+ }
202
+
203
+ function usesMetric(): boolean {
204
+ try {
205
+ return RNLocalize.usesMetricSystem();
206
+ } catch {}
207
+ const region = regionOfTag(currentLocaleTag());
208
+ return region == null || !['US', 'LR', 'MM'].includes(region);
32
209
  }
33
210
 
34
211
  const module = {
35
- getLocales: nativeModule.getLocales,
36
- getCalendars: nativeModule.getCalendars,
37
- locale: i18nManager.getAppLocale(),
212
+ get locale(): string {
213
+ return currentLocaleTag();
214
+ },
215
+
216
+ getLocales(): Locale[] {
217
+ const userCurrency = currencyForCurrentLocale();
218
+ return currentLanguageTags().map((languageTag) => {
219
+ const locale =
220
+ typeof Intl !== 'undefined'
221
+ ? (new Intl.Locale(languageTag) as unknown as ExtendedIntlLocale)
222
+ : ({ region: undefined, textInfo: undefined, language: undefined } as unknown as ExtendedIntlLocale);
223
+ const region = locale.region ?? null;
224
+ const languageCode = locale.language || languageTag.split('-')[0] || 'en';
225
+ const textDirection: 'ltr' | 'rtl' =
226
+ locale.textInfo?.direction ?? (rtlDetect.isRtlLang(languageTag) ? 'rtl' : 'ltr');
227
+ const digitGroupingSeparator =
228
+ Array.from((10000).toLocaleString(languageTag)).filter((c) => c > '9' || c < '0')[0] || null;
229
+ const decimalSeparator = (1.1).toLocaleString(languageTag).substring(1, 2);
230
+ const currencyCode = (region && REGION_CURRENCIES[region]) || userCurrency;
231
+
232
+ return {
233
+ languageTag,
234
+ languageCode,
235
+ textDirection,
236
+ digitGroupingSeparator,
237
+ decimalSeparator,
238
+ measurementSystem: (region === 'GB'
239
+ ? 'uk'
240
+ : region
241
+ ? (['US', 'LR', 'MM'].includes(region) ? 'us' : 'metric')
242
+ : usesMetric()
243
+ ? 'metric'
244
+ : 'us') as Locale['measurementSystem'],
245
+ currencyCode,
246
+ currencySymbol: currencySymbolFor(languageTag, currencyCode),
247
+ regionCode: region,
248
+ temperatureUnit: region
249
+ ? ((USES_FAHRENHEIT.includes(region) ? 'fahrenheit' : 'celsius') as Locale['temperatureUnit'])
250
+ : null,
251
+ };
252
+ });
253
+ },
254
+
255
+ getCalendars(): Calendar[] {
256
+ const resolved =
257
+ typeof Intl !== 'undefined' ? Intl.DateTimeFormat().resolvedOptions() : undefined;
258
+ let calendar: CalendarIdentifier | null = (resolved?.calendar as CalendarIdentifier) ?? null;
259
+ try {
260
+ calendar = (RNLocalize.getCalendar() as CalendarIdentifier) ?? calendar;
261
+ } catch {}
262
+ let uses24hourClock: boolean | null = null;
263
+ try {
264
+ uses24hourClock = RNLocalize.uses24HourClock();
265
+ } catch {
266
+ const hourCycle = (resolved as { hourCycle?: string } | undefined)?.hourCycle;
267
+ uses24hourClock = hourCycle ? hourCycle.startsWith('h2') : null;
268
+ }
269
+ // weekInfo converted from ISO numbering when the engine has it and the CLDR region table otherwise
270
+ let firstWeekday: number | null = null;
271
+ if (typeof Intl !== 'undefined') {
272
+ try {
273
+ const locale = new Intl.Locale(currentLocaleTag()) as ExtendedIntlLocale;
274
+ const isoFirstDay = locale.weekInfo?.firstDay;
275
+ firstWeekday = isoFirstDay != null ? (isoFirstDay % 7) + 1 : null;
276
+ } catch {}
277
+ }
278
+ if (firstWeekday == null) {
279
+ firstWeekday = firstWeekdayForRegion(regionOfTag(currentLocaleTag()));
280
+ }
281
+ return [
282
+ {
283
+ calendar,
284
+ timeZone: currentTimezone(),
285
+ uses24hourClock,
286
+ firstWeekday,
287
+ },
288
+ ];
289
+ },
38
290
  };
39
291
 
40
292
  export default module;
@@ -1,128 +0,0 @@
1
- /*
2
- * Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
- *
4
- * PROPRIETARY/CONFIDENTIAL
5
- *
6
- * Use is subject to license terms.
7
- */
8
-
9
- #include "ExpoLocalization.h"
10
-
11
- #include <Volta/i18n/I18nManager.h>
12
- #include <uchar.h>
13
- #include <unicode/currunit.h>
14
- #include <unicode/dcfmtsym.h>
15
- #include <unicode/dtptngen.h>
16
- #include <unicode/numfmt.h>
17
- #include <unicode/ulocdata.h>
18
-
19
- #include <memory>
20
-
21
- std::string toUTF8String(icu::UnicodeString from) {
22
- std::string result;
23
- from.toUTF8String(result);
24
- return result;
25
- }
26
-
27
- ExpoLocalization::ExpoLocalization() : volta::react::KeplerTurboModule("ExpoLocalization") {}
28
-
29
- volta::react::NativeJSArray ExpoLocalization::getLocales() {
30
- volta::react::NativeJSObject result;
31
- UErrorCode status = U_ZERO_ERROR;
32
-
33
- auto appLocale = volta::i18n::I18nManager::getInstance().getAppLocale();
34
- icu::Locale loc(appLocale.c_str());
35
-
36
- result["languageCode"] = std::string(loc.getLanguage());
37
- result["languageTag"] = appLocale;
38
-
39
- std::unique_ptr<icu::NumberFormat> nf(icu::NumberFormat::createInstance(loc, status));
40
- if (U_SUCCESS(status)) {
41
- result["currencyCode"] = toUTF8String(nf->getCurrency());
42
- } else {
43
- result["currencyCode"] = nullptr;
44
- }
45
-
46
- status = U_ZERO_ERROR;
47
- icu::DecimalFormatSymbols dfs(loc, status);
48
- if (U_SUCCESS(status)) {
49
- result["currencySymbol"] = toUTF8String(dfs.getSymbol(icu::DecimalFormatSymbols::kCurrencySymbol));
50
- result["decimalSeparator"] = toUTF8String(dfs.getSymbol(icu::DecimalFormatSymbols::kDecimalSeparatorSymbol));
51
- result["digitGroupingSeparator"] =
52
- toUTF8String(dfs.getSymbol(icu::DecimalFormatSymbols::kGroupingSeparatorSymbol));
53
- } else {
54
- result["currencySymbol"] = nullptr;
55
- result["decimalSeparator"] = nullptr;
56
- result["digitGroupingSeparator"] = nullptr;
57
- }
58
-
59
- status = U_ZERO_ERROR;
60
- auto measurementSystem = ulocdata_getMeasurementSystem(loc.getLanguage(), &status);
61
- if (measurementSystem == UMS_SI) {
62
- result["measurementSystem"] = "metric";
63
- } else if (measurementSystem == UMS_US) {
64
- result["measurementSystem"] = "imperial";
65
- } else if (measurementSystem == UMS_UK) {
66
- result["measurementSystem"] = "uk";
67
- } else {
68
- result["measurementSystem"] = nullptr;
69
- }
70
-
71
- result["textDirection"] = loc.isRightToLeft() ? "rtl" : "ltr";
72
-
73
- result["regionCode"] = nullptr;
74
-
75
- return {result};
76
- }
77
-
78
- volta::react::NativeJSArray ExpoLocalization::getCalendars() {
79
- volta::react::NativeJSObject calendar;
80
-
81
- auto appLocale = volta::i18n::I18nManager::getInstance().getAppLocale();
82
- icu::Locale loc(appLocale.c_str());
83
-
84
- std::string timezone = volta::i18n::I18nManager::getInstance().getTimezone();
85
- icu::UnicodeString timezoneUc(timezone.c_str());
86
- std::unique_ptr<icu::TimeZone> tz(icu::TimeZone::createTimeZone(timezoneUc));
87
-
88
- UErrorCode status = U_ZERO_ERROR;
89
- std::unique_ptr<icu::Calendar> c(icu::Calendar::createInstance(loc, status));
90
- if (U_SUCCESS(status)) {
91
- calendar["calendar"] = std::string(c->getType());
92
- } else {
93
- calendar["calendar"] = nullptr;
94
- }
95
-
96
- status = U_ZERO_ERROR;
97
- int firstWeekday = (int) c->getFirstDayOfWeek(status);
98
- if (U_SUCCESS(status)) {
99
- calendar["firstWeekday"] = firstWeekday;
100
- } else {
101
- calendar["firstWeekday"] = nullptr;
102
- }
103
-
104
- calendar["uses24hourClock"] = nullptr;
105
- status = U_ZERO_ERROR;
106
- std::unique_ptr<icu::DateTimePatternGenerator> dtp(icu::DateTimePatternGenerator::createInstance(loc, status));
107
- if (U_SUCCESS(status)) {
108
- status = U_ZERO_ERROR;
109
- auto cycle = dtp->getDefaultHourCycle(status);
110
- if (U_SUCCESS(status)) {
111
- calendar["uses24hourClock"] = (cycle == UDAT_HOUR_CYCLE_23 || cycle == UDAT_HOUR_CYCLE_24);
112
- }
113
- }
114
-
115
- if (timezone.length() == 0) {
116
- calendar["timezone"] = nullptr;
117
- } else {
118
- calendar["timezone"] = timezone;
119
- }
120
-
121
- return {calendar};
122
- }
123
-
124
- void ExpoLocalization::aggregateMethods(
125
- volta::react::MethodAggregator<volta::react::KeplerTurboModule>& methodAggregator) const noexcept {
126
- methodAggregator.addMethod("getLocales", 0, &ExpoLocalization::getLocales);
127
- methodAggregator.addMethod("getCalendars", 0, &ExpoLocalization::getCalendars);
128
- }
@@ -1,22 +0,0 @@
1
- /*
2
- * Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
- *
4
- * PROPRIETARY/CONFIDENTIAL
5
- *
6
- * Use is subject to license terms.
7
- */
8
-
9
- #pragma once
10
-
11
- #include <KeplerScript/modules/KeplerTurboModule.h>
12
-
13
- class KEPLERSCRIPT_EXPORT ExpoLocalization : public volta::react::KeplerTurboModule {
14
- public:
15
- ExpoLocalization();
16
- volta::react::NativeJSArray getLocales();
17
- volta::react::NativeJSArray getCalendars();
18
-
19
- protected:
20
- void aggregateMethods(
21
- volta::react::MethodAggregator<volta::react::KeplerTurboModule>& methodAggregator) const noexcept override;
22
- };
@@ -1,78 +0,0 @@
1
- /*
2
- * Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
- *
4
- * PROPRIETARY/CONFIDENTIAL
5
- *
6
- * Use is subject to license terms.
7
- */
8
-
9
- #include "../ExpoLocalization.h"
10
-
11
- #include <gmock/gmock-actions.h>
12
- #include <gmock/gmock.h>
13
- #include <gtest/gtest.h>
14
-
15
- #include "catch.hpp"
16
-
17
- namespace {
18
- template<typename T>
19
- void assertKey(const volta::react::NativeJSObject& object, const std::string& key, const T& expectedValue) {
20
- EXPECT_TRUE(object.find(key) != std::end(object)) << "Expected key: " << key;
21
- EXPECT_EQ(std::get<T>(object.at(key)), expectedValue);
22
- }
23
-
24
- void assertKey(const volta::react::NativeJSObject& object, const std::string& key) {
25
- EXPECT_TRUE(object.find(key) != std::end(object)) << "Expected key: " << key;
26
- }
27
- } // namespace
28
-
29
- namespace volta::react {
30
-
31
- TEST_CASE("ExpoLocalization") {
32
- SECTION("getLocales") {
33
- ExpoLocalization module;
34
-
35
- auto locales = module.getLocales();
36
- EXPECT_EQ(locales.size(), 1);
37
-
38
- auto locale = std::get<NativeJSObject>(locales[0]);
39
-
40
- assertKey(locale, "languageCode");
41
- assertKey(locale, "languageTag");
42
-
43
- assertKey(locale, "currencyCode");
44
- assertKey(locale, "currencySymbol");
45
- assertKey(locale, "digitGroupingSeparator");
46
- assertKey(locale, "measurementSystem");
47
-
48
- assertKey<std::string>(locale, "textDirection", "ltr");
49
- assertKey<std::nullptr_t>(locale, "regionCode", nullptr);
50
- }
51
-
52
- SECTION("getCalendars") {
53
- ExpoLocalization module;
54
-
55
- auto calendars = module.getCalendars();
56
- EXPECT_EQ(calendars.size(), 1);
57
-
58
- auto calendar = std::get<NativeJSObject>(calendars[0]);
59
-
60
- assertKey<std::string>(calendar, "calendar", "gregorian");
61
- assertKey(calendar, "firstWeekday");
62
- assertKey(calendar, "uses24hourClock");
63
- assertKey<std::nullptr_t>(calendar, "timezone", nullptr);
64
- }
65
-
66
- SECTION("aggregateMethods") {
67
- ExpoLocalization module;
68
-
69
- std::weak_ptr<facebook::react::Instance> instance;
70
-
71
- module.triggerMethodAggregation(*module.jsQueue_, instance);
72
-
73
- auto end = module.methodContainerMap_.end();
74
- REQUIRE(module.methodContainerMap_.find("getLocales") != end);
75
- REQUIRE(module.methodContainerMap_.find("getCalendars") != end);
76
- }
77
- }
78
- } // namespace volta::react