1 |
|
2 | import { Platform, Subscription } from 'expo-modules-core';
|
3 | import * as rtlDetect from 'rtl-detect';
|
4 |
|
5 | import { Localization, Calendar, Locale, CalendarIdentifier } from './Localization.types';
|
6 |
|
7 | const getNavigatorLocales = () => {
|
8 | return Platform.isDOMAvailable ? navigator.languages || [navigator.language] : [];
|
9 | };
|
10 |
|
11 | type ExtendedLocale = Intl.Locale &
|
12 |
|
13 | Partial<{
|
14 | textInfo: { direction: 'ltr' | 'rtl' };
|
15 | timeZones: string[];
|
16 | weekInfo: { firstDay: number };
|
17 | hourCycles: string[];
|
18 | timeZone: string;
|
19 | calendars: string[];
|
20 | }>;
|
21 |
|
22 | const WEB_LANGUAGE_CHANGE_EVENT = 'languagechange';
|
23 |
|
24 | const USES_FAHRENHEIT = [
|
25 | 'AG',
|
26 | 'BZ',
|
27 | 'VG',
|
28 | 'FM',
|
29 | 'MH',
|
30 | 'MS',
|
31 | 'KN',
|
32 | 'BS',
|
33 | 'CY',
|
34 | 'TC',
|
35 | 'US',
|
36 | 'LR',
|
37 | 'PW',
|
38 | 'KY',
|
39 | ];
|
40 |
|
41 | export function addLocaleListener(listener: (event) => void): Subscription {
|
42 | addEventListener(WEB_LANGUAGE_CHANGE_EVENT, listener);
|
43 | return {
|
44 | remove: () => removeEventListener(WEB_LANGUAGE_CHANGE_EVENT, listener),
|
45 | };
|
46 | }
|
47 |
|
48 | export function addCalendarListener(listener: (event) => void): Subscription {
|
49 | addEventListener(WEB_LANGUAGE_CHANGE_EVENT, listener);
|
50 | return {
|
51 | remove: () => removeEventListener(WEB_LANGUAGE_CHANGE_EVENT, listener),
|
52 | };
|
53 | }
|
54 |
|
55 | export function removeSubscription(subscription: Subscription) {
|
56 | subscription.remove();
|
57 | }
|
58 |
|
59 | export default {
|
60 | get currency(): string | null {
|
61 |
|
62 | return null;
|
63 | },
|
64 | get decimalSeparator(): string {
|
65 | return (1.1).toLocaleString().substring(1, 2);
|
66 | },
|
67 | get digitGroupingSeparator(): string {
|
68 | const value = (1000).toLocaleString();
|
69 | return value.length === 5 ? value.substring(1, 2) : '';
|
70 | },
|
71 | get isRTL(): boolean {
|
72 | return rtlDetect.isRtlLang(this.locale) ?? false;
|
73 | },
|
74 | get isMetric(): boolean {
|
75 | const { region } = this;
|
76 | switch (region) {
|
77 | case 'US':
|
78 | case 'LR':
|
79 | case 'MM':
|
80 | return false;
|
81 | }
|
82 | return true;
|
83 | },
|
84 | get locale(): string {
|
85 | if (!Platform.isDOMAvailable) {
|
86 | return '';
|
87 | }
|
88 | const locale =
|
89 | navigator.language ||
|
90 | navigator['systemLanguage'] ||
|
91 | navigator['browserLanguage'] ||
|
92 | navigator['userLanguage'] ||
|
93 | this.locales[0];
|
94 | return locale;
|
95 | },
|
96 | get locales(): string[] {
|
97 | if (!Platform.isDOMAvailable) {
|
98 | return [];
|
99 | }
|
100 | const { languages = [] } = navigator;
|
101 | return Array.from(languages);
|
102 | },
|
103 | get timezone(): string {
|
104 | const defaultTimeZone = 'Etc/UTC';
|
105 | if (typeof Intl === 'undefined') {
|
106 | return defaultTimeZone;
|
107 | }
|
108 | return Intl.DateTimeFormat().resolvedOptions().timeZone || defaultTimeZone;
|
109 | },
|
110 | get isoCurrencyCodes(): string[] {
|
111 |
|
112 | return [];
|
113 | },
|
114 | get region(): string | null {
|
115 |
|
116 |
|
117 | const { locale } = this;
|
118 | const [, ...suffixes] = typeof locale === 'string' ? locale.split('-') : [];
|
119 | for (const suffix of suffixes) {
|
120 | if (suffix.length === 2) {
|
121 | return suffix.toUpperCase();
|
122 | }
|
123 | }
|
124 | return null;
|
125 | },
|
126 |
|
127 | getLocales(): Locale[] {
|
128 | const locales = getNavigatorLocales();
|
129 | return locales?.map((languageTag) => {
|
130 |
|
131 |
|
132 | const locale =
|
133 | typeof Intl !== 'undefined'
|
134 | ? (new Intl.Locale(languageTag) as unknown as ExtendedLocale)
|
135 | : { region: null, textInfo: null, language: null };
|
136 | const { region, textInfo, language } = locale;
|
137 |
|
138 |
|
139 | const digitGroupingSeparator =
|
140 | Array.from((10000).toLocaleString(languageTag)).filter((c) => c > '9' || c < '0')[0] ||
|
141 | null; // using 1e5 instead of 1e4 since for some locales (like pl-PL) 1e4 does not use digit grouping
|
142 | const decimalSeparator = (1.1).toLocaleString(languageTag).substring(1, 2);
|
143 | const temperatureUnit = region ? regionToTemperatureUnit(region) : null;
|
144 |
|
145 | return {
|
146 | languageTag,
|
147 | languageCode: language || languageTag.split('-')[0] || 'en',
|
148 | textDirection: (textInfo?.direction as 'ltr' | 'rtl') || null,
|
149 | digitGroupingSeparator,
|
150 | decimalSeparator,
|
151 | measurementSystem: null,
|
152 | currencyCode: null,
|
153 | currencySymbol: null,
|
154 | regionCode: region || null,
|
155 | temperatureUnit,
|
156 | };
|
157 | });
|
158 | },
|
159 | getCalendars(): Calendar[] {
|
160 | const locale = ((typeof Intl !== 'undefined'
|
161 | ? Intl.DateTimeFormat().resolvedOptions()
|
162 | : null) ?? null) as unknown as null | ExtendedLocale;
|
163 | return [
|
164 | {
|
165 | calendar: ((locale?.calendar || locale?.calendars?.[0]) as CalendarIdentifier) || null,
|
166 | timeZone: locale?.timeZone || locale?.timeZones?.[0] || null,
|
167 | uses24hourClock: (locale?.hourCycle || locale?.hourCycles?.[0])?.startsWith('h2') ?? null, //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/hourCycle
|
168 | firstWeekday: locale?.weekInfo?.firstDay || null,
|
169 | },
|
170 | ];
|
171 | },
|
172 |
|
173 | async getLocalizationAsync(): Promise<Omit<Localization, 'getCalendars' | 'getLocales'>> {
|
174 | const {
|
175 | currency,
|
176 | decimalSeparator,
|
177 | digitGroupingSeparator,
|
178 | isoCurrencyCodes,
|
179 | isMetric,
|
180 | isRTL,
|
181 | locale,
|
182 | locales,
|
183 | region,
|
184 | timezone,
|
185 | } = this;
|
186 | return {
|
187 | currency,
|
188 | decimalSeparator,
|
189 | digitGroupingSeparator,
|
190 | isoCurrencyCodes,
|
191 | isMetric,
|
192 | isRTL,
|
193 | locale,
|
194 | locales,
|
195 | region,
|
196 | timezone,
|
197 | };
|
198 | },
|
199 | };
|
200 |
|
201 | function regionToTemperatureUnit(region: string) {
|
202 | return USES_FAHRENHEIT.includes(region) ? 'fahrenheit' : 'celsius';
|
203 | }
|
204 |
|
\ | No newline at end of file |