@amazon-devices/expo-localization 2.0.9000000001 → 3.0.2

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,11 +12,11 @@
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
- # ICU::uc is an explicit dependency — previously resolved transitively through expo-kepler-utils -> keplerscript-2
19
- find_package(ICU COMPONENTS i18n uc)
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)
20
20
 
21
21
  add_library(${PACKAGE_NAME} STATIC ${SOURCES_KEPLER})
22
22
 
@@ -24,7 +24,6 @@ kepler_add_idl_import_library(
24
24
  expo_localization_idl_pkg
25
25
  PACKAGES
26
26
  com.amazon.kepler.env
27
- com.amazon.kepler.device_config
28
27
  com.amazon.kepler.experimental_mr33.messaging
29
28
  )
30
29
 
@@ -32,15 +31,11 @@ target_link_libraries(
32
31
  ${PACKAGE_NAME}
33
32
  PUBLIC
34
33
  turbomoduleAPI::turbomoduleAPI
35
- ICU::i18n
36
- ICU::uc
37
34
  expo_localization_idl_pkg
38
35
  )
39
36
 
40
37
  add_library(${AUTO_LINK_PACKAGE_NAME} SHARED kepler/KeplerInit.cpp)
41
38
 
42
- set_target_properties(${AUTO_LINK_PACKAGE_NAME} PROPERTIES SOVERSION ${KEPLER_REACT_NATIVE_MAJOR_VERSION}.${KEPLER_REACT_NATIVE_MINOR_VERSION})
43
-
44
39
  target_link_libraries(
45
40
  ${AUTO_LINK_PACKAGE_NAME}
46
41
  PRIVATE
@@ -53,7 +48,6 @@ target_link_libraries(${AUTO_LINK_PACKAGE_NAME}_exe
53
48
  PRIVATE
54
49
  ${AUTO_LINK_PACKAGE_NAME}
55
50
  turbomoduleAPI::turbomoduleAPI
56
- ICU::i18n
57
51
  expo_localization_idl_pkg
58
52
  )
59
53
 
@@ -1,12 +1,12 @@
1
1
  import type { Subscription } from 'expo-modules-core';
2
2
  import { Calendar, Locale } from './Localization.types';
3
- export declare function addLocaleListener(listener: () => void): Subscription;
4
- export declare function addCalendarListener(listener: () => void): Subscription;
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: string;
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,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGtD,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAUxD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,YAAY,CAEpE;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,YAAY,CAStE;AAED,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,YAAY,QAE5D;AAED,QAAA,MAAM,MAAM;sBA1BM,MAAM,EAAE;wBACN,QAAQ,EAAE;;CA6B7B,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,13 +5,139 @@
5
5
  *
6
6
  * Use is subject to license terms.
7
7
  */
8
- import { TurboModuleRegistry } from '@amazon-devices/keplerscript-turbomodule-api';
9
- import { DeviceEventEmitter } from 'react-native';
10
- const nativeModule = TurboModuleRegistry.getEnforcing('ExpoLocalization');
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
137
  return DeviceEventEmitter.addListener('onLocaleSettingsChanged', listener);
13
138
  }
14
139
  export function addCalendarListener(listener) {
140
+ // Calendar values include the timezone so both signals invalidate them
15
141
  const localeSubscription = DeviceEventEmitter.addListener('onLocaleSettingsChanged', listener);
16
142
  const timezoneSubscription = DeviceEventEmitter.addListener('onCalendarSettingsChanged', listener);
17
143
  return {
@@ -24,10 +150,115 @@ export function addCalendarListener(listener) {
24
150
  export function removeSubscription(subscription) {
25
151
  subscription.remove();
26
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);
186
+ }
27
187
  const module = {
28
- getLocales: nativeModule.getLocales,
29
- getCalendars: nativeModule.getCalendars,
30
- locale: nativeModule.getLocale(),
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
+ },
31
262
  };
32
263
  export default module;
33
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,EAAqB,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAE5F,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAUlD,MAAM,YAAY,GAAG,mBAAmB,CAAC,YAAY,CAAO,kBAAkB,CAAC,CAAC;AAEhF,MAAM,UAAU,iBAAiB,CAAC,QAAoB;IAClD,OAAO,kBAAkB,CAAC,WAAW,CAAC,yBAAyB,EAAE,QAAQ,CAAC,CAAC;AAC/E,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,QAAoB;IACpD,MAAM,kBAAkB,GAAG,kBAAkB,CAAC,WAAW,CAAC,yBAAyB,EAAE,QAAQ,CAAC,CAAC;IAC/F,MAAM,oBAAoB,GAAG,kBAAkB,CAAC,WAAW,CAAC,2BAA2B,EAAE,QAAQ,CAAC,CAAC;IACnG,OAAO;QACH,MAAM,EAAE,GAAG,EAAE;YACT,kBAAkB,CAAC,MAAM,EAAE,CAAC;YAC5B,oBAAoB,CAAC,MAAM,EAAE,CAAC;QAClC,CAAC;KACY,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,YAA0B;IACzD,YAAY,CAAC,MAAM,EAAE,CAAC;AAC1B,CAAC;AAED,MAAM,MAAM,GAAG;IACX,UAAU,EAAE,YAAY,CAAC,UAAU;IACnC,YAAY,EAAE,YAAY,CAAC,YAAY;IACvC,MAAM,EAAE,YAAY,CAAC,SAAS,EAAE;CACnC,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 type { Subscription } from 'expo-modules-core';\nimport { DeviceEventEmitter } from 'react-native';\n\nimport { Calendar, Locale } from './Localization.types';\n\ninterface Spec extends KeplerTurboModule {\n getLocales(): Locale[];\n getCalendars(): Calendar[];\n getLocale(): string;\n}\n\nconst nativeModule = TurboModuleRegistry.getEnforcing<Spec>('ExpoLocalization');\n\nexport function addLocaleListener(listener: () => void): Subscription {\n return DeviceEventEmitter.addListener('onLocaleSettingsChanged', listener);\n}\n\nexport function addCalendarListener(listener: () => void): Subscription {\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\nconst module = {\n getLocales: nativeModule.getLocales,\n getCalendars: nativeModule.getCalendars,\n locale: nativeModule.getLocale(),\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
+ }
@@ -12,20 +12,29 @@
12
12
  #include <apmf/iface/com/amazon/kepler/experimental_mr33/messaging/IMessageChannel.h>
13
13
  #include <apmf/ptr.h>
14
14
 
15
+ #include <atomic>
16
+ #include <memory>
17
+ #include <string>
18
+
15
19
  #include "expo-export.h"
16
20
 
17
21
  namespace tbm = com::amazon::kepler::turbomodule;
18
22
 
19
- class EXPO_EXPORT ExpoLocalization : public tbm::KeplerTurboModule {
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 {
20
25
  public:
21
- ExpoLocalization();
22
- std::string getLocale();
23
- tbm::JSArray getLocales();
24
- tbm::JSArray getCalendars();
26
+ ExpoLocalizationEvents();
27
+ ~ExpoLocalizationEvents() override;
25
28
 
26
29
  void aggregateMethods(tbm::MethodAggregator<tbm::KeplerTurboModule>& methodAggregator) const noexcept override;
27
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
+
28
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)};
29
38
  apmf::detail::Ptr<apmf::iface::com::amazon::kepler::experimental_mr33::messaging::IMessageChannel>
30
39
  m_timezoneChannel;
31
40
  };
@@ -9,12 +9,12 @@
9
9
  #include <Kepler/turbomodule/KeplerTurboModuleRegistration.h>
10
10
  #include <Kepler/turbomodule/TMLog.h>
11
11
 
12
- #include "ExpoLocalization.h"
12
+ #include "ExpoLocalizationEvents.h"
13
13
 
14
14
  extern "C" {
15
15
  __attribute__((visibility("default"))) void autoLinkKeplerTurboModulesV1() noexcept {
16
16
  TMDEBUG("autoLinkKeplerTurboModulesV1 for expo-localization");
17
- KEPLER_REGISTER_TURBO_MODULE_WITH_NAME("ExpoLocalization", ExpoLocalization);
17
+ KEPLER_REGISTER_TURBO_MODULE_WITH_NAME("ExpoLocalizationAppTM", ExpoLocalizationEvents);
18
18
  }
19
19
  }
20
20
 
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name": "@amazon-devices/expo-localization", "version": "2.0.9000000001+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": "^50.0.0-alpha.1"}}
1
+ {"name": "@amazon-devices/expo-localization", "version": "3.0.2+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": "*"}}
@@ -0,0 +1 @@
1
+ {"root":["./src/withExpoLocalization.ts"],"version":"5.9.3"}
@@ -10,10 +10,11 @@ module.exports = {
10
10
  platforms: {
11
11
  kepler: {
12
12
  autolink: {
13
- ExpoLocalizationLib: {
14
- provider: 'system',
15
- libraryName: 'libexpo-localization-2.so.2',
16
- turbomodules: ['ExpoLocalization'],
13
+ ExpoLocalizationAppLib: {
14
+ provider: 'application',
15
+ linkDynamic: true,
16
+ libraryName: 'libexpo-localization-3.so',
17
+ turbomodules: ['ExpoLocalizationAppTM'],
17
18
  },
18
19
  },
19
20
  },
@@ -6,43 +6,287 @@
6
6
  * Use is subject to license terms.
7
7
  */
8
8
 
9
- import { KeplerTurboModule, TurboModuleRegistry } from '@amazon-devices/keplerscript-turbomodule-api';
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.
10
11
  import type { Subscription } from 'expo-modules-core';
11
- import { DeviceEventEmitter } from 'react-native';
12
+ import * as rtlDetect from 'rtl-detect';
12
13
 
13
- import { Calendar, Locale } from './Localization.types';
14
+ import { Calendar, CalendarIdentifier, Locale } from './Localization.types';
14
15
 
15
- interface Spec extends KeplerTurboModule {
16
- getLocales(): Locale[];
17
- getCalendars(): Calendar[];
18
- getLocale(): string;
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 = {};
19
22
  }
20
23
 
21
- const nativeModule = TurboModuleRegistry.getEnforcing<Spec>('ExpoLocalization');
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
+ }>;
22
30
 
23
- export function addLocaleListener(listener: () => void): Subscription {
24
- return DeviceEventEmitter.addListener('onLocaleSettingsChanged', 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;
25
157
  }
26
158
 
27
- export function addCalendarListener(listener: () => void): Subscription {
28
- const localeSubscription = DeviceEventEmitter.addListener('onLocaleSettingsChanged', listener);
29
- const timezoneSubscription = DeviceEventEmitter.addListener('onCalendarSettingsChanged', listener);
30
- return {
31
- remove: () => {
32
- localeSubscription.remove();
33
- timezoneSubscription.remove();
34
- },
35
- } as Subscription;
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;
36
169
  }
37
170
 
38
171
  export function removeSubscription(subscription: Subscription) {
39
- subscription.remove();
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);
40
209
  }
41
210
 
42
211
  const module = {
43
- getLocales: nativeModule.getLocales,
44
- getCalendars: nativeModule.getCalendars,
45
- locale: nativeModule.getLocale(),
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
+ },
46
290
  };
47
291
 
48
292
  export default module;
@@ -1,214 +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 <Kepler/turbomodule/NativeObject.h>
12
- #include <Kepler/turbomodule/TMLog.h>
13
- #include <apmf/fn.h>
14
- #include <apmf/iface/com/amazon/kepler/device_config/DeviceConfigNotFoundError.h>
15
- #include <apmf/iface/com/amazon/kepler/device_config/DeviceConfigResult.h>
16
- #include <apmf/iface/com/amazon/kepler/device_config/IDeviceConfig.h>
17
- #include <apmf/iface/com/amazon/kepler/device_config/IDeviceConfigModule.h>
18
- #include <apmf/iface/com/amazon/kepler/env/IEnvModule.h>
19
- #include <apmf/iface/com/amazon/kepler/env/ILocaleConfig.h>
20
- #include <apmf/iface/com/amazon/kepler/env/IResolvedLocalesListener.h>
21
- #include <apmf/iface/com/amazon/kepler/experimental_mr33/messaging/IKeplerMessagingModule.h>
22
- #include <apmf/iface/com/amazon/kepler/experimental_mr33/messaging/IMessageFilterBuilder.h>
23
- #include <apmf/iface/com/amazon/kepler/experimental_mr33/messaging/IMessageListener.h>
24
- #include <apmf/apmf_base.h>
25
- #include <unicode/currunit.h>
26
- #include <unicode/dcfmtsym.h>
27
- #include <unicode/dtptngen.h>
28
- #include <unicode/numfmt.h>
29
- #include <unicode/ulocdata.h>
30
-
31
- #include <memory>
32
- #include <string>
33
- #include <fmt/core.h>
34
-
35
- namespace env = apmf::iface::com::amazon::kepler::env;
36
- namespace devConf = apmf::iface::com::amazon::kepler::device_config;
37
- namespace messaging = apmf::iface::com::amazon::kepler::experimental_mr33::messaging;
38
-
39
- static const std::string g_timeZoneConfig{"com.amazon.devconf/system/configuration/time-zone"};
40
-
41
- apmf::Ptr<devConf::IDeviceConfig> getDeviceConfig() {
42
- return apmf::detail::GetProcessObject()
43
- ->getComponent("/com.amazon.kepler.device_config")
44
- .TryQueryInterface<devConf::IDeviceConfigModule>()
45
- ->getDeviceConfig();
46
- }
47
-
48
- std::string toUTF8String(const icu::UnicodeString& from) {
49
- std::string result;
50
- from.toUTF8String(result);
51
- return result;
52
- }
53
-
54
- apmf::Ptr<env::IEnvModule> getEnvModule() {
55
- return apmf::detail::GetProcessObject()
56
- ->getComponent("/com.amazon.kepler.env")
57
- .TryQueryInterface<env::IEnvModule>();
58
- }
59
-
60
- ExpoLocalization::ExpoLocalization() : KeplerTurboModule("ExpoLocalization") {
61
- TMDEBUG(fmt::format("ExpoLocalization constructor {}", name()));
62
-
63
- getEnvModule()->getLocaleConfig()->addResolvedLocalesListener(nullptr, apmf::Fn1([this](auto locales) {
64
- this->emit("onLocaleSettingsChanged", nullptr);
65
- }));
66
-
67
- // Subscribe to timezone config changes via MR33 messaging
68
- auto proc = apmf::detail::GetProcessObject();
69
- auto messagingModule = proc->getComponent("/com.amazon.kepler.experimental_mr33.messaging")
70
- .TryQueryInterface<messaging::IKeplerMessagingModule>();
71
- if (messagingModule) {
72
- struct TZListener : apmf::ApmfBase<TZListener, messaging::IMessageListener> {
73
- ExpoLocalization* owner;
74
- explicit TZListener(ExpoLocalization* o) : owner(o) {}
75
- void onMessage(apmf::View<messaging::IMessage> /*message*/) {
76
- owner->emit("onCalendarSettingsChanged", nullptr);
77
- }
78
- void onError(apmf::View<apmf::IError> /*error*/) {}
79
- };
80
-
81
- auto channel = messagingModule->makeMessageChannel(
82
- "ExpoLocalization_TZ",
83
- apmf::detail::Ptr<TZListener>::Make(this));
84
-
85
- apmf::detail::Array<apmf::detail::Ptr<messaging::IMessageFilter>> filters;
86
- filters.push_back(
87
- messagingModule->makeMessageFilterBuilder()
88
- ->baseUri("broadcast://*/com.amazon.devconf/system/configuration/time-zone")
89
- ->build());
90
- channel->registerMessageFilter(filters);
91
- m_timezoneChannel = std::move(channel);
92
- }
93
- };
94
-
95
- std::string ExpoLocalization::getLocale() {
96
- auto locales = getEnvModule()->getLocaleConfig()->getResolvedLocales(nullptr);
97
- if (locales.empty()) {
98
- return "";
99
- }
100
- return locales[0].identifier;
101
- }
102
-
103
- tbm::NativeJSArray ExpoLocalization::getLocales() {
104
- auto locales = getEnvModule()->getLocaleConfig()->getResolvedLocales(nullptr);
105
- tbm::NativeJSArray resultArray(locales.size());
106
-
107
- for (std::size_t i = 0; i < locales.size(); i++) {
108
- tbm::NativeJSObject result;
109
- const std::string& identifier = locales[i].identifier;
110
-
111
- const icu::Locale loc(identifier.c_str());
112
- result["languageCode"] = std::string(loc.getLanguage());
113
- result["languageTag"] = identifier;
114
-
115
- UErrorCode status = U_ZERO_ERROR;
116
- std::unique_ptr<icu::NumberFormat> nf(icu::NumberFormat::createInstance(loc, status));
117
- if (U_SUCCESS(status) != 0) {
118
- result["currencyCode"] = toUTF8String(nf->getCurrency());
119
- } else {
120
- result["currencyCode"] = nullptr;
121
- }
122
-
123
- status = U_ZERO_ERROR;
124
- const icu::DecimalFormatSymbols dfs(loc, status);
125
- if (U_SUCCESS(status) != 0) {
126
- result["currencySymbol"] = toUTF8String(dfs.getSymbol(icu::DecimalFormatSymbols::kCurrencySymbol));
127
- result["decimalSeparator"] =
128
- toUTF8String(dfs.getSymbol(icu::DecimalFormatSymbols::kDecimalSeparatorSymbol));
129
- result["digitGroupingSeparator"] =
130
- toUTF8String(dfs.getSymbol(icu::DecimalFormatSymbols::kGroupingSeparatorSymbol));
131
- } else {
132
- result["currencySymbol"] = nullptr;
133
- result["decimalSeparator"] = nullptr;
134
- result["digitGroupingSeparator"] = nullptr;
135
- }
136
-
137
- status = U_ZERO_ERROR;
138
- auto measurementSystem = ulocdata_getMeasurementSystem(loc.getLanguage(), &status);
139
- if (measurementSystem == UMS_SI) {
140
- result["measurementSystem"] = "metric";
141
- } else if (measurementSystem == UMS_US) {
142
- result["measurementSystem"] = "imperial";
143
- } else if (measurementSystem == UMS_UK) {
144
- result["measurementSystem"] = "uk";
145
- } else {
146
- result["measurementSystem"] = nullptr;
147
- }
148
-
149
- result["textDirection"] = (loc.isRightToLeft() != 0) ? "rtl" : "ltr";
150
-
151
- result["regionCode"] = nullptr;
152
-
153
- resultArray[i] = result;
154
- }
155
-
156
- return resultArray;
157
- }
158
-
159
- tbm::NativeJSArray ExpoLocalization::getCalendars() {
160
- const auto& locales = getEnvModule()->getLocaleConfig()->getResolvedLocales(nullptr);
161
- tbm::NativeJSArray calendarArray(locales.size());
162
-
163
- for (std::size_t i = 0; i < locales.size(); i++) {
164
- tbm::NativeJSObject calendar;
165
- const std::string& identifier = locales[i].identifier;
166
-
167
- const icu::Locale loc(identifier.c_str());
168
-
169
- UErrorCode status = U_ZERO_ERROR;
170
- std::unique_ptr<icu::Calendar> c(icu::Calendar::createInstance(loc, status));
171
- if (U_SUCCESS(status) != 0) {
172
- calendar["calendar"] = std::string(c->getType());
173
- } else {
174
- calendar["calendar"] = nullptr;
175
- }
176
-
177
- status = U_ZERO_ERROR;
178
- const int firstWeekday = static_cast<int>(c->getFirstDayOfWeek(status));
179
- if (U_SUCCESS(status) != 0) {
180
- calendar["firstWeekday"] = firstWeekday;
181
- } else {
182
- calendar["firstWeekday"] = nullptr;
183
- }
184
-
185
- calendar["uses24hourClock"] = nullptr;
186
- status = U_ZERO_ERROR;
187
- std::unique_ptr<icu::DateTimePatternGenerator> dtp(icu::DateTimePatternGenerator::createInstance(loc, status));
188
- if (U_SUCCESS(status) != 0) {
189
- status = U_ZERO_ERROR;
190
- auto cycle = dtp->getDefaultHourCycle(status);
191
- if (U_SUCCESS(status) != 0) {
192
- calendar["uses24hourClock"] = (cycle == UDAT_HOUR_CYCLE_23 || cycle == UDAT_HOUR_CYCLE_24);
193
- }
194
- }
195
-
196
- apmf::Ptr<devConf::IDeviceConfig> component = getDeviceConfig();
197
- try {
198
- calendar["timezone"] = component->get(g_timeZoneConfig);
199
- } catch (const apmf::BaseError& e) {
200
- TMERROR(fmt::format("Error while processing time zone data: {}", e.what()));
201
- calendar["timezone"] = nullptr;
202
- }
203
-
204
- calendarArray[i] = calendar;
205
- }
206
-
207
- return calendarArray;
208
- }
209
-
210
- void ExpoLocalization::aggregateMethods(tbm::MethodAggregator<KeplerTurboModule>& methodAggregator) const noexcept {
211
- methodAggregator.addMethod("getLocale", 0, &ExpoLocalization::getLocale);
212
- methodAggregator.addMethod("getLocales", 0, &ExpoLocalization::getLocales);
213
- methodAggregator.addMethod("getCalendars", 0, &ExpoLocalization::getCalendars);
214
- }
@@ -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
@@ -1,7 +0,0 @@
1
- {
2
- "versions": {
3
- "2.0.0": "/com.amazon.kepler.expo_localization_2@IExpoLocalization_0",
4
- "2.0.9000000000": "/com.amazon.kepler.expo_localization_2@IExpoLocalization_0",
5
- "2.0.9000000001": "/com.amazon.kepler.expo_localization_2@IExpoLocalization_0"
6
- }
7
- }