@oxyhq/core 9.2.3 → 10.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/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/i18n/index.js +22 -2
- package/dist/cjs/i18n/locales/en-US.json +4 -1
- package/dist/cjs/i18n/locales/es-ES.json +4 -1
- package/dist/cjs/i18n/locales/locales/en-US.json +4 -1
- package/dist/cjs/i18n/locales/locales/es-ES.json +4 -1
- package/dist/cjs/index.js +10 -5
- package/dist/cjs/mixins/OxyServices.assets.js +10 -6
- package/dist/cjs/mixins/OxyServices.language.js +11 -10
- package/dist/cjs/mixins/OxyServices.privacy.js +6 -0
- package/dist/cjs/mixins/OxyServices.user.js +40 -0
- package/dist/cjs/server/safeFetch.js +3 -3
- package/dist/cjs/session/SessionClient.js +1 -1
- package/dist/cjs/shared/utils/colorUtils.js +9 -9
- package/dist/cjs/utils/languageUtils.js +195 -158
- package/dist/cjs/utils/platform.js +9 -2
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/i18n/index.js +22 -2
- package/dist/esm/i18n/locales/en-US.json +4 -1
- package/dist/esm/i18n/locales/es-ES.json +4 -1
- package/dist/esm/i18n/locales/locales/en-US.json +4 -1
- package/dist/esm/i18n/locales/locales/es-ES.json +4 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/mixins/OxyServices.assets.js +10 -6
- package/dist/esm/mixins/OxyServices.language.js +12 -11
- package/dist/esm/mixins/OxyServices.privacy.js +6 -0
- package/dist/esm/mixins/OxyServices.user.js +40 -0
- package/dist/esm/server/safeFetch.js +3 -3
- package/dist/esm/session/SessionClient.js +1 -1
- package/dist/esm/shared/utils/colorUtils.js +9 -9
- package/dist/esm/utils/languageUtils.js +189 -156
- package/dist/esm/utils/platform.js +9 -2
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/mixins/OxyServices.language.d.ts +4 -4
- package/dist/types/mixins/OxyServices.user.d.ts +30 -0
- package/dist/types/models/interfaces.d.ts +8 -0
- package/dist/types/session/accountDialogController.d.ts +1 -1
- package/dist/types/utils/languageUtils.d.ts +86 -24
- package/package.json +3 -3
- package/src/HttpService.ts +2 -2
- package/src/crypto/keyManager.ts +3 -3
- package/src/crypto/recoveryPhrase.ts +1 -1
- package/src/i18n/index.ts +21 -2
- package/src/i18n/locales/en-US.json +4 -1
- package/src/i18n/locales/es-ES.json +4 -1
- package/src/index.ts +8 -2
- package/src/mixins/OxyServices.assets.ts +15 -11
- package/src/mixins/OxyServices.language.ts +31 -17
- package/src/mixins/OxyServices.privacy.ts +6 -0
- package/src/mixins/OxyServices.security.ts +1 -1
- package/src/mixins/OxyServices.user.ts +57 -0
- package/src/models/interfaces.ts +8 -0
- package/src/server/safeFetch.ts +3 -3
- package/src/session/SessionClient.ts +1 -1
- package/src/session/accountDialogController.ts +1 -1
- package/src/shared/utils/colorUtils.ts +11 -11
- package/src/shared/utils/errorUtils.ts +1 -1
- package/src/utils/__tests__/languageUtils.test.ts +219 -0
- package/src/utils/avatarUtils.ts +1 -1
- package/src/utils/languageUtils.ts +223 -162
- package/src/utils/platform.ts +21 -3
- package/src/utils/requestUtils.ts +3 -3
- package/src/utils/sessionUtils.ts +2 -2
|
@@ -1,187 +1,224 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* Locale utilities for OxyServices.
|
|
4
|
+
*
|
|
5
|
+
* The Oxy platform models account languages as full BCP-47 locales
|
|
6
|
+
* (`language-REGION`, e.g. `es-ES`, `es-MX`, `pt-BR`). Region is significant:
|
|
7
|
+
* "Spanish (Spain)" is a different locale than "Spanish (Mexico)". A user's
|
|
8
|
+
* account locales live on `User.languages` as an ordered list with the PRIMARY
|
|
9
|
+
* (UI) locale first — there is no singular `language` field.
|
|
10
|
+
*
|
|
11
|
+
* This module is the single source of truth for the supported-locale catalog
|
|
12
|
+
* and every locale operation the SDK exposes: parsing base subtags, canonical
|
|
13
|
+
* normalization, validation, metadata lookup, and resolving a user's locales.
|
|
14
|
+
* It is pure and side-effect free.
|
|
5
15
|
*/
|
|
6
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.SUPPORTED_LANGUAGES = void 0;
|
|
17
|
+
exports.FALLBACK_LOCALE = exports.SUPPORTED_LANGUAGES = void 0;
|
|
18
|
+
exports.getBaseLanguage = getBaseLanguage;
|
|
19
|
+
exports.normalizeLocale = normalizeLocale;
|
|
20
|
+
exports.isSupportedLocale = isSupportedLocale;
|
|
8
21
|
exports.getLanguageMetadata = getLanguageMetadata;
|
|
9
22
|
exports.getLanguageName = getLanguageName;
|
|
10
23
|
exports.getNativeLanguageName = getNativeLanguageName;
|
|
11
|
-
exports.normalizeLanguageCode = normalizeLanguageCode;
|
|
12
24
|
exports.isRTLLocale = isRTLLocale;
|
|
13
|
-
|
|
25
|
+
exports.getUserLanguages = getUserLanguages;
|
|
26
|
+
exports.getPrimaryLanguage = getPrimaryLanguage;
|
|
27
|
+
/**
|
|
28
|
+
* The supported-locale catalog. Ordered by prominence for display; the order
|
|
29
|
+
* is not otherwise significant. Every entry is a full `language-REGION` tag,
|
|
30
|
+
* with the base subtag lowercased and the region uppercased.
|
|
31
|
+
*/
|
|
14
32
|
exports.SUPPORTED_LANGUAGES = [
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
{
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
},
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
},
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
},
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
color: '#32D74B',
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
id: 'zh-CN',
|
|
89
|
-
name: 'Chinese',
|
|
90
|
-
nativeName: '中文',
|
|
91
|
-
flag: '🇨🇳',
|
|
92
|
-
icon: 'translate',
|
|
93
|
-
color: '#FF9F0A',
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
id: 'ar-SA',
|
|
97
|
-
name: 'Arabic',
|
|
98
|
-
nativeName: 'العربية',
|
|
99
|
-
flag: '🇸🇦',
|
|
100
|
-
icon: 'translate',
|
|
101
|
-
color: '#30B0C7',
|
|
102
|
-
},
|
|
33
|
+
// English
|
|
34
|
+
{ code: 'en-US', language: 'en', region: 'US', name: 'English (United States)', nativeName: 'English (United States)' },
|
|
35
|
+
{ code: 'en-GB', language: 'en', region: 'GB', name: 'English (United Kingdom)', nativeName: 'English (United Kingdom)' },
|
|
36
|
+
{ code: 'en-AU', language: 'en', region: 'AU', name: 'English (Australia)', nativeName: 'English (Australia)' },
|
|
37
|
+
{ code: 'en-CA', language: 'en', region: 'CA', name: 'English (Canada)', nativeName: 'English (Canada)' },
|
|
38
|
+
{ code: 'en-IN', language: 'en', region: 'IN', name: 'English (India)', nativeName: 'English (India)' },
|
|
39
|
+
// Spanish
|
|
40
|
+
{ code: 'es-ES', language: 'es', region: 'ES', name: 'Spanish (Spain)', nativeName: 'Español (España)' },
|
|
41
|
+
{ code: 'es-MX', language: 'es', region: 'MX', name: 'Spanish (Mexico)', nativeName: 'Español (México)' },
|
|
42
|
+
{ code: 'es-US', language: 'es', region: 'US', name: 'Spanish (United States)', nativeName: 'Español (Estados Unidos)' },
|
|
43
|
+
{ code: 'es-AR', language: 'es', region: 'AR', name: 'Spanish (Argentina)', nativeName: 'Español (Argentina)' },
|
|
44
|
+
{ code: 'es-CO', language: 'es', region: 'CO', name: 'Spanish (Colombia)', nativeName: 'Español (Colombia)' },
|
|
45
|
+
// Portuguese
|
|
46
|
+
{ code: 'pt-BR', language: 'pt', region: 'BR', name: 'Portuguese (Brazil)', nativeName: 'Português (Brasil)' },
|
|
47
|
+
{ code: 'pt-PT', language: 'pt', region: 'PT', name: 'Portuguese (Portugal)', nativeName: 'Português (Portugal)' },
|
|
48
|
+
// French
|
|
49
|
+
{ code: 'fr-FR', language: 'fr', region: 'FR', name: 'French (France)', nativeName: 'Français (France)' },
|
|
50
|
+
{ code: 'fr-CA', language: 'fr', region: 'CA', name: 'French (Canada)', nativeName: 'Français (Canada)' },
|
|
51
|
+
// German
|
|
52
|
+
{ code: 'de-DE', language: 'de', region: 'DE', name: 'German (Germany)', nativeName: 'Deutsch (Deutschland)' },
|
|
53
|
+
{ code: 'de-AT', language: 'de', region: 'AT', name: 'German (Austria)', nativeName: 'Deutsch (Österreich)' },
|
|
54
|
+
{ code: 'de-CH', language: 'de', region: 'CH', name: 'German (Switzerland)', nativeName: 'Deutsch (Schweiz)' },
|
|
55
|
+
// Italian
|
|
56
|
+
{ code: 'it-IT', language: 'it', region: 'IT', name: 'Italian (Italy)', nativeName: 'Italiano (Italia)' },
|
|
57
|
+
// Dutch
|
|
58
|
+
{ code: 'nl-NL', language: 'nl', region: 'NL', name: 'Dutch (Netherlands)', nativeName: 'Nederlands (Nederland)' },
|
|
59
|
+
{ code: 'nl-BE', language: 'nl', region: 'BE', name: 'Dutch (Belgium)', nativeName: 'Nederlands (België)' },
|
|
60
|
+
// Nordic
|
|
61
|
+
{ code: 'sv-SE', language: 'sv', region: 'SE', name: 'Swedish (Sweden)', nativeName: 'Svenska (Sverige)' },
|
|
62
|
+
{ code: 'nb-NO', language: 'nb', region: 'NO', name: 'Norwegian Bokmål (Norway)', nativeName: 'Norsk bokmål (Norge)' },
|
|
63
|
+
{ code: 'da-DK', language: 'da', region: 'DK', name: 'Danish (Denmark)', nativeName: 'Dansk (Danmark)' },
|
|
64
|
+
{ code: 'fi-FI', language: 'fi', region: 'FI', name: 'Finnish (Finland)', nativeName: 'Suomi (Suomi)' },
|
|
65
|
+
// Central & Eastern Europe
|
|
66
|
+
{ code: 'pl-PL', language: 'pl', region: 'PL', name: 'Polish (Poland)', nativeName: 'Polski (Polska)' },
|
|
67
|
+
{ code: 'cs-CZ', language: 'cs', region: 'CZ', name: 'Czech (Czechia)', nativeName: 'Čeština (Česko)' },
|
|
68
|
+
{ code: 'sk-SK', language: 'sk', region: 'SK', name: 'Slovak (Slovakia)', nativeName: 'Slovenčina (Slovensko)' },
|
|
69
|
+
{ code: 'hu-HU', language: 'hu', region: 'HU', name: 'Hungarian (Hungary)', nativeName: 'Magyar (Magyarország)' },
|
|
70
|
+
{ code: 'ro-RO', language: 'ro', region: 'RO', name: 'Romanian (Romania)', nativeName: 'Română (România)' },
|
|
71
|
+
{ code: 'bg-BG', language: 'bg', region: 'BG', name: 'Bulgarian (Bulgaria)', nativeName: 'Български (България)' },
|
|
72
|
+
{ code: 'hr-HR', language: 'hr', region: 'HR', name: 'Croatian (Croatia)', nativeName: 'Hrvatski (Hrvatska)' },
|
|
73
|
+
{ code: 'sr-RS', language: 'sr', region: 'RS', name: 'Serbian (Serbia)', nativeName: 'Српски (Србија)' },
|
|
74
|
+
{ code: 'sl-SI', language: 'sl', region: 'SI', name: 'Slovenian (Slovenia)', nativeName: 'Slovenščina (Slovenija)' },
|
|
75
|
+
{ code: 'lt-LT', language: 'lt', region: 'LT', name: 'Lithuanian (Lithuania)', nativeName: 'Lietuvių (Lietuva)' },
|
|
76
|
+
{ code: 'lv-LV', language: 'lv', region: 'LV', name: 'Latvian (Latvia)', nativeName: 'Latviešu (Latvija)' },
|
|
77
|
+
{ code: 'et-EE', language: 'et', region: 'EE', name: 'Estonian (Estonia)', nativeName: 'Eesti (Eesti)' },
|
|
78
|
+
{ code: 'uk-UA', language: 'uk', region: 'UA', name: 'Ukrainian (Ukraine)', nativeName: 'Українська (Україна)' },
|
|
79
|
+
{ code: 'ru-RU', language: 'ru', region: 'RU', name: 'Russian (Russia)', nativeName: 'Русский (Россия)' },
|
|
80
|
+
{ code: 'el-GR', language: 'el', region: 'GR', name: 'Greek (Greece)', nativeName: 'Ελληνικά (Ελλάδα)' },
|
|
81
|
+
// Turkish
|
|
82
|
+
{ code: 'tr-TR', language: 'tr', region: 'TR', name: 'Turkish (Türkiye)', nativeName: 'Türkçe (Türkiye)' },
|
|
83
|
+
// Catalan
|
|
84
|
+
{ code: 'ca-ES', language: 'ca', region: 'ES', name: 'Catalan (Spain)', nativeName: 'Català (Espanya)' },
|
|
85
|
+
// East, South & Southeast Asia
|
|
86
|
+
{ code: 'ja-JP', language: 'ja', region: 'JP', name: 'Japanese (Japan)', nativeName: '日本語 (日本)' },
|
|
87
|
+
{ code: 'ko-KR', language: 'ko', region: 'KR', name: 'Korean (South Korea)', nativeName: '한국어 (대한민국)' },
|
|
88
|
+
{ code: 'zh-CN', language: 'zh', region: 'CN', name: 'Chinese (Simplified, China)', nativeName: '中文 (中国)' },
|
|
89
|
+
{ code: 'zh-TW', language: 'zh', region: 'TW', name: 'Chinese (Traditional, Taiwan)', nativeName: '中文 (台灣)' },
|
|
90
|
+
{ code: 'zh-HK', language: 'zh', region: 'HK', name: 'Chinese (Traditional, Hong Kong)', nativeName: '中文 (香港)' },
|
|
91
|
+
{ code: 'hi-IN', language: 'hi', region: 'IN', name: 'Hindi (India)', nativeName: 'हिन्दी (भारत)' },
|
|
92
|
+
{ code: 'bn-BD', language: 'bn', region: 'BD', name: 'Bengali (Bangladesh)', nativeName: 'বাংলা (বাংলাদেশ)' },
|
|
93
|
+
{ code: 'id-ID', language: 'id', region: 'ID', name: 'Indonesian (Indonesia)', nativeName: 'Bahasa Indonesia (Indonesia)' },
|
|
94
|
+
{ code: 'ms-MY', language: 'ms', region: 'MY', name: 'Malay (Malaysia)', nativeName: 'Bahasa Melayu (Malaysia)' },
|
|
95
|
+
{ code: 'th-TH', language: 'th', region: 'TH', name: 'Thai (Thailand)', nativeName: 'ไทย (ประเทศไทย)' },
|
|
96
|
+
{ code: 'vi-VN', language: 'vi', region: 'VN', name: 'Vietnamese (Vietnam)', nativeName: 'Tiếng Việt (Việt Nam)' },
|
|
97
|
+
// Right-to-left
|
|
98
|
+
{ code: 'ar-SA', language: 'ar', region: 'SA', name: 'Arabic (Saudi Arabia)', nativeName: 'العربية (السعودية)', rtl: true },
|
|
99
|
+
{ code: 'ar-EG', language: 'ar', region: 'EG', name: 'Arabic (Egypt)', nativeName: 'العربية (مصر)', rtl: true },
|
|
100
|
+
{ code: 'he-IL', language: 'he', region: 'IL', name: 'Hebrew (Israel)', nativeName: 'עברית (ישראל)', rtl: true },
|
|
101
|
+
{ code: 'fa-IR', language: 'fa', region: 'IR', name: 'Persian (Iran)', nativeName: 'فارسی (ایران)', rtl: true },
|
|
102
|
+
{ code: 'ur-PK', language: 'ur', region: 'PK', name: 'Urdu (Pakistan)', nativeName: 'اردو (پاکستان)', rtl: true },
|
|
103
103
|
];
|
|
104
|
-
|
|
104
|
+
/** Canonical fallback locale used when a display value cannot be resolved. */
|
|
105
|
+
exports.FALLBACK_LOCALE = 'en-US';
|
|
106
|
+
/** O(1) lookup of a catalog entry by its canonical code. */
|
|
107
|
+
const LOCALE_BY_CODE = new Map(exports.SUPPORTED_LANGUAGES.map((entry) => [entry.code, entry]));
|
|
105
108
|
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
+
* Extract the base language subtag from a locale, lowercased.
|
|
110
|
+
*
|
|
111
|
+
* Tolerant of bare base subtags and of extra subtags (script/variant):
|
|
112
|
+
* `'es-ES'` → `'es'`, `'es'` → `'es'`, `'zh-Hant-TW'` → `'zh'`. Returns an
|
|
113
|
+
* empty string for empty input.
|
|
109
114
|
*/
|
|
110
|
-
function
|
|
111
|
-
|
|
112
|
-
return null;
|
|
113
|
-
// Direct match
|
|
114
|
-
const exactMatch = exports.SUPPORTED_LANGUAGES.find(lang => lang.id === languageCode);
|
|
115
|
-
if (exactMatch)
|
|
116
|
-
return exactMatch;
|
|
117
|
-
// Try to match base language code (e.g., 'en' matches 'en-US')
|
|
118
|
-
const baseCode = languageCode.split('-')[0];
|
|
119
|
-
const baseMatch = exports.SUPPORTED_LANGUAGES.find(lang => lang.id.startsWith(baseCode + '-'));
|
|
120
|
-
if (baseMatch)
|
|
121
|
-
return baseMatch;
|
|
122
|
-
return null;
|
|
115
|
+
function getBaseLanguage(locale) {
|
|
116
|
+
return locale.trim().toLowerCase().split('-')[0] ?? '';
|
|
123
117
|
}
|
|
124
118
|
/**
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
119
|
+
* Normalize a locale string to its canonical `language-REGION` form when it is
|
|
120
|
+
* a supported locale, otherwise `undefined`.
|
|
121
|
+
*
|
|
122
|
+
* Canonicalization lowercases the base subtag and uppercases the region
|
|
123
|
+
* subtag (`'es-es'` → `'es-ES'`, `'EN-us'` → `'en-US'`) and validates the
|
|
124
|
+
* result against {@link SUPPORTED_LANGUAGES}. A bare base subtag (`'es'`) has
|
|
125
|
+
* no region and is therefore not a locale — it returns `undefined`.
|
|
128
126
|
*/
|
|
129
|
-
function
|
|
130
|
-
const
|
|
131
|
-
|
|
127
|
+
function normalizeLocale(input) {
|
|
128
|
+
const trimmed = input.trim();
|
|
129
|
+
if (!trimmed)
|
|
130
|
+
return undefined;
|
|
131
|
+
const parts = trimmed.split('-');
|
|
132
|
+
if (parts.length < 2)
|
|
133
|
+
return undefined;
|
|
134
|
+
const base = parts[0]?.toLowerCase();
|
|
135
|
+
const region = parts[parts.length - 1]?.toUpperCase();
|
|
136
|
+
if (!base || !region)
|
|
137
|
+
return undefined;
|
|
138
|
+
const canonical = `${base}-${region}`;
|
|
139
|
+
return LOCALE_BY_CODE.has(canonical) ? canonical : undefined;
|
|
132
140
|
}
|
|
133
141
|
/**
|
|
134
|
-
*
|
|
135
|
-
* @param languageCode - BCP-47 language code (e.g., 'en-US', 'es-ES')
|
|
136
|
-
* @returns Native language name (e.g., 'Español') or the code if not found
|
|
142
|
+
* Whether `input` resolves to a supported BCP-47 locale.
|
|
137
143
|
*/
|
|
138
|
-
function
|
|
139
|
-
|
|
140
|
-
return metadata?.nativeName || languageCode || FALLBACK_LANGUAGE;
|
|
144
|
+
function isSupportedLocale(input) {
|
|
145
|
+
return normalizeLocale(input) !== undefined;
|
|
141
146
|
}
|
|
142
147
|
/**
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
* @
|
|
148
|
+
* Resolve catalog metadata for a locale.
|
|
149
|
+
*
|
|
150
|
+
* @param code - A locale tag (any case; e.g. `'es-ES'`, `'es-es'`).
|
|
151
|
+
* @returns The {@link SupportedLanguage} entry, or `null` when the tag is empty
|
|
152
|
+
* or not a supported locale.
|
|
146
153
|
*/
|
|
147
|
-
function
|
|
148
|
-
if (!
|
|
149
|
-
return
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
es: 'es-ES',
|
|
155
|
-
ca: 'ca-ES',
|
|
156
|
-
fr: 'fr-FR',
|
|
157
|
-
de: 'de-DE',
|
|
158
|
-
it: 'it-IT',
|
|
159
|
-
pt: 'pt-PT',
|
|
160
|
-
ja: 'ja-JP',
|
|
161
|
-
ko: 'ko-KR',
|
|
162
|
-
zh: 'zh-CN',
|
|
163
|
-
ar: 'ar-SA',
|
|
164
|
-
};
|
|
165
|
-
return map[lang] || lang;
|
|
154
|
+
function getLanguageMetadata(code) {
|
|
155
|
+
if (!code)
|
|
156
|
+
return null;
|
|
157
|
+
const canonical = normalizeLocale(code);
|
|
158
|
+
if (!canonical)
|
|
159
|
+
return null;
|
|
160
|
+
return LOCALE_BY_CODE.get(canonical) ?? null;
|
|
166
161
|
}
|
|
167
162
|
/**
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
|
|
163
|
+
* English display name for a locale (e.g. `'Spanish (Spain)'`).
|
|
164
|
+
* Falls back to the input tag, then {@link FALLBACK_LOCALE}.
|
|
165
|
+
*/
|
|
166
|
+
function getLanguageName(code) {
|
|
167
|
+
return getLanguageMetadata(code)?.name || code || exports.FALLBACK_LOCALE;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Native display name (endonym) for a locale (e.g. `'Español (España)'`).
|
|
171
|
+
* Falls back to the input tag, then {@link FALLBACK_LOCALE}.
|
|
172
|
+
*/
|
|
173
|
+
function getNativeLanguageName(code) {
|
|
174
|
+
return getLanguageMetadata(code)?.nativeName || code || exports.FALLBACK_LOCALE;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Base language subtags whose scripts are written right-to-left. Used to drive
|
|
178
|
+
* `I18nManager.forceRTL(...)` on React Native and `<html dir="rtl">` on web.
|
|
179
|
+
* Includes Arabic (`ar`), Hebrew (`he` / legacy `iw`), Persian (`fa`), and
|
|
180
|
+
* Urdu (`ur`).
|
|
178
181
|
*/
|
|
179
182
|
const RTL_LANGUAGE_BASES = new Set(['ar', 'he', 'iw', 'fa', 'ur']);
|
|
183
|
+
/**
|
|
184
|
+
* Whether a locale (or bare base subtag) is written right-to-left. Unknown
|
|
185
|
+
* tags are treated as left-to-right.
|
|
186
|
+
*/
|
|
180
187
|
function isRTLLocale(locale) {
|
|
181
188
|
if (!locale)
|
|
182
189
|
return false;
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
190
|
+
return RTL_LANGUAGE_BASES.has(getBaseLanguage(locale));
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Resolve the ordered list of account locales for a user, primary first.
|
|
194
|
+
*
|
|
195
|
+
* Reads `user.languages` (the only source — there is no singular `language`
|
|
196
|
+
* field), then normalizes each entry to its canonical `language-REGION` form,
|
|
197
|
+
* drops non-string and unsupported entries, and de-duplicates while preserving
|
|
198
|
+
* first-seen order. Pure and side-effect free — never throws on bad input.
|
|
199
|
+
*/
|
|
200
|
+
function getUserLanguages(user) {
|
|
201
|
+
const source = user?.languages;
|
|
202
|
+
if (!Array.isArray(source))
|
|
203
|
+
return [];
|
|
204
|
+
const result = [];
|
|
205
|
+
const seen = new Set();
|
|
206
|
+
for (const entry of source) {
|
|
207
|
+
if (typeof entry !== 'string')
|
|
208
|
+
continue;
|
|
209
|
+
const canonical = normalizeLocale(entry);
|
|
210
|
+
if (!canonical || seen.has(canonical))
|
|
211
|
+
continue;
|
|
212
|
+
seen.add(canonical);
|
|
213
|
+
result.push(canonical);
|
|
214
|
+
}
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Resolve the single PRIMARY locale for a user (the first entry from
|
|
219
|
+
* {@link getUserLanguages}), or `undefined` when the user has no supported
|
|
220
|
+
* locale.
|
|
221
|
+
*/
|
|
222
|
+
function getPrimaryLanguage(user) {
|
|
223
|
+
return getUserLanguages(user)[0];
|
|
187
224
|
}
|
|
@@ -14,6 +14,13 @@ exports.isIOS = isIOS;
|
|
|
14
14
|
exports.isAndroid = isAndroid;
|
|
15
15
|
exports.setPlatformOS = setPlatformOS;
|
|
16
16
|
exports.isWebBrowser = isWebBrowser;
|
|
17
|
+
/**
|
|
18
|
+
* Typed view over `globalThis` for the RN-platform marker, avoiding an `any`
|
|
19
|
+
* cast at each access site.
|
|
20
|
+
*/
|
|
21
|
+
function rnPlatformGlobal() {
|
|
22
|
+
return globalThis;
|
|
23
|
+
}
|
|
17
24
|
/**
|
|
18
25
|
* Detect the current platform without importing react-native
|
|
19
26
|
*
|
|
@@ -26,7 +33,7 @@ exports.isWebBrowser = isWebBrowser;
|
|
|
26
33
|
function detectPlatform() {
|
|
27
34
|
// Check if React Native Platform is available globally (set by RN runtime)
|
|
28
35
|
// This avoids static imports while still detecting RN environment
|
|
29
|
-
const rnPlatform =
|
|
36
|
+
const rnPlatform = rnPlatformGlobal().__REACT_NATIVE_PLATFORM__;
|
|
30
37
|
if (rnPlatform) {
|
|
31
38
|
return rnPlatform;
|
|
32
39
|
}
|
|
@@ -90,7 +97,7 @@ function isAndroid() {
|
|
|
90
97
|
*/
|
|
91
98
|
function setPlatformOS(os) {
|
|
92
99
|
cachedPlatform = os;
|
|
93
|
-
|
|
100
|
+
rnPlatformGlobal().__REACT_NATIVE_PLATFORM__ = os;
|
|
94
101
|
}
|
|
95
102
|
/**
|
|
96
103
|
* True only in a real web browser (a DOM is present), false on React Native
|