@ccheever/exact-ibex-runtime 0.1.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/package.json +63 -0
- package/src/abort/AbortController.ts +23 -0
- package/src/abort/AbortSignal.ts +152 -0
- package/src/abort/index.ts +2 -0
- package/src/accessibility.ts +12 -0
- package/src/arraybuffer-detach.ts +109 -0
- package/src/base64/base64.ts +168 -0
- package/src/base64/index.ts +1 -0
- package/src/blob/Blob.ts +259 -0
- package/src/blob/File.ts +59 -0
- package/src/blob/FormData.ts +323 -0
- package/src/blob/index.ts +3 -0
- package/src/bootstrap.ts +1946 -0
- package/src/broadcast/BroadcastChannel.ts +280 -0
- package/src/broadcast/index.ts +5 -0
- package/src/cache/Cache.ts +349 -0
- package/src/cache/CacheStorage.ts +89 -0
- package/src/cache/index.ts +27 -0
- package/src/camera/index.ts +6202 -0
- package/src/camera/processor.worker.ts +194 -0
- package/src/camera/scene.ts +195 -0
- package/src/clipboard/Clipboard.ts +129 -0
- package/src/clipboard/ClipboardItem.ts +97 -0
- package/src/clipboard/index.ts +6 -0
- package/src/clone/index.ts +1 -0
- package/src/clone/structuredClone.ts +389 -0
- package/src/clone/transferableSymbols.ts +2 -0
- package/src/compression/CompressionStream.ts +146 -0
- package/src/compression/DecompressionStream.ts +342 -0
- package/src/compression/index.ts +4 -0
- package/src/console/Console.ts +341 -0
- package/src/console/index.ts +2 -0
- package/src/core/accessibility-state.ts +263 -0
- package/src/core/accessibility.ts +184 -0
- package/src/core/agent-state.ts +37 -0
- package/src/core/diagnostics-logs.ts +144 -0
- package/src/core/host-call-bridge.ts +16 -0
- package/src/core/i18n-helpers.ts +189 -0
- package/src/core/locale-state.ts +253 -0
- package/src/core/locale.ts +95 -0
- package/src/crypto/Crypto.ts +2743 -0
- package/src/crypto/index.ts +1 -0
- package/src/diagnostics/logs.ts +7 -0
- package/src/encoding/TextDecoder.ts +1181 -0
- package/src/encoding/TextDecoderStream.ts +58 -0
- package/src/encoding/TextEncoder.ts +180 -0
- package/src/encoding/TextEncoderStream.ts +39 -0
- package/src/encoding/index.ts +8 -0
- package/src/events/CloseEvent.ts +91 -0
- package/src/events/DOMException.ts +409 -0
- package/src/events/ErrorEvent.ts +39 -0
- package/src/events/Event.ts +151 -0
- package/src/events/EventTarget.ts +280 -0
- package/src/events/FocusEvent.ts +27 -0
- package/src/events/KeyboardEvent.ts +46 -0
- package/src/events/MessageEvent.ts +61 -0
- package/src/events/ProgressEvent.ts +33 -0
- package/src/events/PromiseRejectionEvent.ts +31 -0
- package/src/events/index.ts +52 -0
- package/src/eventsource/EventSource.ts +371 -0
- package/src/eventsource/index.ts +2 -0
- package/src/fetch/Headers.ts +642 -0
- package/src/fetch/Request.ts +760 -0
- package/src/fetch/Response.ts +543 -0
- package/src/fetch/body.ts +1256 -0
- package/src/fetch/cookie-jar.ts +566 -0
- package/src/fetch/demo.ts +207 -0
- package/src/fetch/errors.ts +101 -0
- package/src/fetch/fetch.ts +2610 -0
- package/src/fetch/index.ts +101 -0
- package/src/fetch/native-bridge.ts +65 -0
- package/src/fetch/types.ts +258 -0
- package/src/filereader/FileReader.ts +236 -0
- package/src/filereader/index.ts +1 -0
- package/src/fs/Dirent.ts +39 -0
- package/src/fs/ExactFile.ts +450 -0
- package/src/fs/Stats.ts +80 -0
- package/src/fs/index.ts +944 -0
- package/src/fs/promises.ts +386 -0
- package/src/fs/shared.ts +328 -0
- package/src/http-server/index.js +697 -0
- package/src/http-server/index.ts +27 -0
- package/src/identity.generated.ts +14 -0
- package/src/index.ts +283 -0
- package/src/indexeddb/IDBCursor.ts +188 -0
- package/src/indexeddb/IDBDatabase.ts +343 -0
- package/src/indexeddb/IDBFactory.ts +269 -0
- package/src/indexeddb/IDBIndex.ts +194 -0
- package/src/indexeddb/IDBKeyRange.ts +109 -0
- package/src/indexeddb/IDBObjectStore.ts +468 -0
- package/src/indexeddb/IDBRequest.ts +163 -0
- package/src/indexeddb/IDBTransaction.ts +207 -0
- package/src/indexeddb/index.ts +34 -0
- package/src/indexeddb/utils.ts +52 -0
- package/src/inspect/index.ts +1 -0
- package/src/inspect/inspect.ts +465 -0
- package/src/internal/detect.ts +104 -0
- package/src/locale.ts +10 -0
- package/src/location/index.ts +1059 -0
- package/src/locks/LockManager.ts +460 -0
- package/src/locks/index.ts +12 -0
- package/src/media/VideoFrame.ts +58 -0
- package/src/messaging/MessageChannel.ts +31 -0
- package/src/messaging/MessagePort.ts +180 -0
- package/src/messaging/index.ts +2 -0
- package/src/messaging.ts +247 -0
- package/src/native/NativeModules.ts +354 -0
- package/src/native/index.ts +1 -0
- package/src/navigator/Navigator.ts +351 -0
- package/src/navigator/index.ts +1 -0
- package/src/node/Buffer.ts +1786 -0
- package/src/node/index.ts +4 -0
- package/src/node/path.ts +495 -0
- package/src/node/process.ts +2528 -0
- package/src/performance/Performance.ts +532 -0
- package/src/performance/index.ts +21 -0
- package/src/polyfills/array.ts +236 -0
- package/src/polyfills/arraybuffer.ts +172 -0
- package/src/polyfills/groupby.ts +85 -0
- package/src/polyfills/index.ts +85 -0
- package/src/polyfills/intl.ts +1956 -0
- package/src/polyfills/iterator.ts +479 -0
- package/src/polyfills/promise.ts +37 -0
- package/src/polyfills/set.ts +245 -0
- package/src/polyfills/string.ts +85 -0
- package/src/polyfills/typedarray.ts +110 -0
- package/src/promise-rejection-tracking.ts +464 -0
- package/src/react-native/index.ts +388 -0
- package/src/runtime-entry.ts +55 -0
- package/src/scheduling/AnimationFrame.ts +105 -0
- package/src/scheduling/IdleCallback.ts +167 -0
- package/src/scheduling/index.ts +13 -0
- package/src/security/Capabilities.ts +1146 -0
- package/src/security/Permissions.ts +392 -0
- package/src/security/capability-bits.generated.ts +63 -0
- package/src/security/index.ts +16 -0
- package/src/sqlite/Database.ts +456 -0
- package/src/sqlite/Statement.ts +206 -0
- package/src/sqlite/constants.ts +79 -0
- package/src/sqlite/errors.ts +25 -0
- package/src/sqlite/index.ts +34 -0
- package/src/sqlite/module.js +438 -0
- package/src/storage/Storage.ts +291 -0
- package/src/storage/StorageManager.ts +91 -0
- package/src/storage/index.ts +3 -0
- package/src/stream-compat.ts +47 -0
- package/src/streams/ReadableStream.ts +4131 -0
- package/src/streams/TransformStream.ts +375 -0
- package/src/streams/WritableStream.ts +866 -0
- package/src/streams/index.ts +41 -0
- package/src/timers/Timers.ts +296 -0
- package/src/timers/index.ts +11 -0
- package/src/url/URL.ts +656 -0
- package/src/url/URLPattern.ts +850 -0
- package/src/url/URLSearchParams.ts +244 -0
- package/src/url/index.ts +9 -0
- package/src/websocket/WebSocket.ts +770 -0
- package/src/websocket/WebSocketError.ts +52 -0
- package/src/websocket/WebSocketStream.ts +628 -0
- package/src/websocket/index.ts +7 -0
- package/src/window/index.ts +872 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
export type LocaleDirection = 'ltr' | 'rtl';
|
|
2
|
+
|
|
3
|
+
export interface ParsedLocaleTag {
|
|
4
|
+
language: string;
|
|
5
|
+
script?: string;
|
|
6
|
+
region?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const RTL_SCRIPT_SUBTAGS = new Set([
|
|
10
|
+
'adlm',
|
|
11
|
+
'arab',
|
|
12
|
+
'hebr',
|
|
13
|
+
'mand',
|
|
14
|
+
'nkoo',
|
|
15
|
+
'rohg',
|
|
16
|
+
'samr',
|
|
17
|
+
'syrc',
|
|
18
|
+
'thaa',
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
const RTL_LANGUAGE_SUBTAGS = new Set([
|
|
22
|
+
'ae',
|
|
23
|
+
'aeb',
|
|
24
|
+
'ar',
|
|
25
|
+
'arc',
|
|
26
|
+
'arq',
|
|
27
|
+
'ars',
|
|
28
|
+
'ary',
|
|
29
|
+
'arz',
|
|
30
|
+
'azb',
|
|
31
|
+
'bal',
|
|
32
|
+
'bcc',
|
|
33
|
+
'bqi',
|
|
34
|
+
'ckb',
|
|
35
|
+
'dv',
|
|
36
|
+
'fa',
|
|
37
|
+
'glk',
|
|
38
|
+
'he',
|
|
39
|
+
'iw',
|
|
40
|
+
'ks',
|
|
41
|
+
'khw',
|
|
42
|
+
'lrc',
|
|
43
|
+
'mzn',
|
|
44
|
+
'nqo',
|
|
45
|
+
'pnb',
|
|
46
|
+
'ps',
|
|
47
|
+
'sd',
|
|
48
|
+
'syr',
|
|
49
|
+
'ug',
|
|
50
|
+
'ur',
|
|
51
|
+
'yi',
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
export function canonicalizeLocaleTag(tag: string): string {
|
|
55
|
+
const input = tag.trim();
|
|
56
|
+
if (input.length === 0) {
|
|
57
|
+
return input;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (typeof Intl === 'object' && Intl && typeof Intl.getCanonicalLocales === 'function') {
|
|
61
|
+
try {
|
|
62
|
+
const [canonical] = Intl.getCanonicalLocales(input);
|
|
63
|
+
if (typeof canonical === 'string' && canonical.length > 0) {
|
|
64
|
+
return canonical;
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// Fall through to the local canonicalizer.
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const parts = input.split('-');
|
|
72
|
+
if (parts.length > 0) {
|
|
73
|
+
parts[0] = parts[0]!.toLowerCase();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
for (let index = 1; index < parts.length; index += 1) {
|
|
77
|
+
const part = parts[index];
|
|
78
|
+
if (!part) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (part.length === 4) {
|
|
83
|
+
parts[index] = part[0]!.toUpperCase() + part.slice(1).toLowerCase();
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (part.length === 2 || (part.length === 3 && /^[0-9A-Za-z]{3}$/.test(part))) {
|
|
88
|
+
parts[index] = part.toUpperCase();
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
parts[index] = part.toLowerCase();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return parts.join('-');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function parseLocaleTag(tag: string): ParsedLocaleTag {
|
|
99
|
+
const canonical = canonicalizeLocaleTag(tag);
|
|
100
|
+
const parts = canonical.split('-').filter(Boolean);
|
|
101
|
+
const parsed: ParsedLocaleTag = {
|
|
102
|
+
language: parts[0]?.toLowerCase() || 'en',
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
for (const part of parts.slice(1)) {
|
|
106
|
+
if (part.length === 4 && parsed.script === undefined) {
|
|
107
|
+
parsed.script = part;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if ((part.length === 2 || part.length === 3) && parsed.region === undefined) {
|
|
112
|
+
parsed.region = part;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (part.length === 1) {
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return parsed;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function directionForLocaleTag(tag: string): LocaleDirection {
|
|
125
|
+
const parsed = parseLocaleTag(tag);
|
|
126
|
+
if (parsed.script && RTL_SCRIPT_SUBTAGS.has(parsed.script.toLowerCase())) {
|
|
127
|
+
return 'rtl';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return RTL_LANGUAGE_SUBTAGS.has(parsed.language) ? 'rtl' : 'ltr';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function isAsciiNeutral(codePoint: number): boolean {
|
|
134
|
+
return (
|
|
135
|
+
codePoint <= 0x20 ||
|
|
136
|
+
(codePoint >= 0x30 && codePoint <= 0x39) ||
|
|
137
|
+
(codePoint >= 0x21 && codePoint <= 0x2f) ||
|
|
138
|
+
(codePoint >= 0x3a && codePoint <= 0x40) ||
|
|
139
|
+
(codePoint >= 0x5b && codePoint <= 0x60) ||
|
|
140
|
+
(codePoint >= 0x7b && codePoint <= 0x7e)
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function isRtlCodePoint(codePoint: number): boolean {
|
|
145
|
+
return (
|
|
146
|
+
(codePoint >= 0x0590 && codePoint <= 0x08ff) ||
|
|
147
|
+
(codePoint >= 0xfb1d && codePoint <= 0xfdff) ||
|
|
148
|
+
(codePoint >= 0xfe70 && codePoint <= 0xfefc) ||
|
|
149
|
+
(codePoint >= 0x10800 && codePoint <= 0x10fff) ||
|
|
150
|
+
(codePoint >= 0x1e800 && codePoint <= 0x1eeff)
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function isNeutralCodePoint(codePoint: number): boolean {
|
|
155
|
+
return (
|
|
156
|
+
isAsciiNeutral(codePoint) ||
|
|
157
|
+
(codePoint >= 0x0660 && codePoint <= 0x0669) ||
|
|
158
|
+
(codePoint >= 0x06f0 && codePoint <= 0x06f9) ||
|
|
159
|
+
(codePoint >= 0x2000 && codePoint <= 0x206f) ||
|
|
160
|
+
(codePoint >= 0x20a0 && codePoint <= 0x20cf) ||
|
|
161
|
+
(codePoint >= 0x2190 && codePoint <= 0x21ff) ||
|
|
162
|
+
(codePoint >= 0x2460 && codePoint <= 0x27ff) ||
|
|
163
|
+
(codePoint >= 0x1f000 && codePoint <= 0x1faff)
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function detectTextDirection(
|
|
168
|
+
text: string | undefined,
|
|
169
|
+
fallback: LocaleDirection,
|
|
170
|
+
): LocaleDirection {
|
|
171
|
+
if (typeof text !== 'string' || text.length === 0) {
|
|
172
|
+
return fallback;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
for (const char of text) {
|
|
176
|
+
const codePoint = char.codePointAt(0);
|
|
177
|
+
if (codePoint === undefined) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (isNeutralCodePoint(codePoint)) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return isRtlCodePoint(codePoint) ? 'rtl' : 'ltr';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return fallback;
|
|
189
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
// @system @ref LLP 0011#state-modules — startup imports only locale state.
|
|
2
|
+
import {
|
|
3
|
+
canonicalizeLocaleTag,
|
|
4
|
+
directionForLocaleTag,
|
|
5
|
+
parseLocaleTag,
|
|
6
|
+
type LocaleDirection,
|
|
7
|
+
} from './i18n-helpers.js';
|
|
8
|
+
|
|
9
|
+
export interface ExactLocaleSnapshot {
|
|
10
|
+
readonly language: string;
|
|
11
|
+
readonly region: string;
|
|
12
|
+
readonly tag: string;
|
|
13
|
+
readonly tags: readonly string[];
|
|
14
|
+
readonly direction: LocaleDirection;
|
|
15
|
+
readonly uses24Hour: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface NativeLocaleSnapshot {
|
|
19
|
+
readonly tag?: string;
|
|
20
|
+
readonly tags?: readonly string[];
|
|
21
|
+
readonly uses24Hour?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ExactLocaleOverride {
|
|
25
|
+
readonly locale?: string;
|
|
26
|
+
readonly direction?: LocaleDirection;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type ExactLocaleListener = (snapshot: ExactLocaleSnapshot) => void;
|
|
30
|
+
|
|
31
|
+
interface ExactLocaleRuntimeState {
|
|
32
|
+
snapshot: ExactLocaleSnapshot;
|
|
33
|
+
override: ExactLocaleOverride | null;
|
|
34
|
+
listeners: Set<ExactLocaleListener>;
|
|
35
|
+
changeTimer: ReturnType<typeof setTimeout> | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
declare global {
|
|
39
|
+
// eslint-disable-next-line no-var
|
|
40
|
+
var __exactLocaleState: ExactLocaleRuntimeState | undefined;
|
|
41
|
+
// eslint-disable-next-line no-var
|
|
42
|
+
var __exactLocaleSnapshot: NativeLocaleSnapshot | undefined;
|
|
43
|
+
// eslint-disable-next-line no-var
|
|
44
|
+
var __exactLocaleChanged: ((snapshot?: NativeLocaleSnapshot | null) => void) | undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function freezeSnapshot(snapshot: ExactLocaleSnapshot): ExactLocaleSnapshot {
|
|
48
|
+
return Object.freeze({
|
|
49
|
+
...snapshot,
|
|
50
|
+
tags: Object.freeze([...snapshot.tags]),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function readNativeLocaleSnapshot(): NativeLocaleSnapshot | null {
|
|
55
|
+
const nativeSnapshot = globalThis.__exactLocaleSnapshot;
|
|
56
|
+
if (nativeSnapshot && typeof nativeSnapshot === 'object') {
|
|
57
|
+
return nativeSnapshot;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const g = globalThis as typeof globalThis & {
|
|
61
|
+
__exactLocale?: unknown;
|
|
62
|
+
__exactLanguage?: unknown;
|
|
63
|
+
__exactHostNavigator?: {
|
|
64
|
+
language?: unknown;
|
|
65
|
+
languages?: unknown;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const hostNavigator =
|
|
70
|
+
g.__exactHostNavigator && typeof g.__exactHostNavigator === 'object'
|
|
71
|
+
? g.__exactHostNavigator
|
|
72
|
+
: undefined;
|
|
73
|
+
const hostLanguages = Array.isArray(hostNavigator?.languages)
|
|
74
|
+
? hostNavigator.languages.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0)
|
|
75
|
+
: [];
|
|
76
|
+
const hostLanguage =
|
|
77
|
+
typeof hostNavigator?.language === 'string' && hostNavigator.language.length > 0
|
|
78
|
+
? hostNavigator.language
|
|
79
|
+
: undefined;
|
|
80
|
+
|
|
81
|
+
const fallbackTag =
|
|
82
|
+
typeof g.__exactLocale === 'string' && g.__exactLocale.length > 0
|
|
83
|
+
? g.__exactLocale
|
|
84
|
+
: typeof g.__exactLanguage === 'string' && g.__exactLanguage.length > 0
|
|
85
|
+
? g.__exactLanguage
|
|
86
|
+
: hostLanguage;
|
|
87
|
+
|
|
88
|
+
if (!fallbackTag && hostLanguages.length === 0) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
tag: fallbackTag,
|
|
94
|
+
tags: hostLanguages,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function detectUses24Hour(localeTag: string): boolean {
|
|
99
|
+
if (typeof Intl !== 'object' || !Intl || typeof Intl.DateTimeFormat !== 'function') {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const resolved = new Intl.DateTimeFormat(localeTag, { hour: 'numeric' }).resolvedOptions();
|
|
105
|
+
if (typeof resolved.hour12 === 'boolean') {
|
|
106
|
+
return resolved.hour12 === false;
|
|
107
|
+
}
|
|
108
|
+
if (typeof resolved.hourCycle === 'string') {
|
|
109
|
+
return resolved.hourCycle === 'h23' || resolved.hourCycle === 'h24';
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
// Ignore Intl failures and fall back to 12-hour.
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function buildLocaleSnapshot(
|
|
119
|
+
nativeSnapshot: NativeLocaleSnapshot | null,
|
|
120
|
+
override: ExactLocaleOverride | null,
|
|
121
|
+
): ExactLocaleSnapshot {
|
|
122
|
+
const rawTags = Array.isArray(nativeSnapshot?.tags)
|
|
123
|
+
? nativeSnapshot.tags.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0)
|
|
124
|
+
: [];
|
|
125
|
+
const primaryTag = canonicalizeLocaleTag(
|
|
126
|
+
override?.locale ??
|
|
127
|
+
nativeSnapshot?.tag ??
|
|
128
|
+
rawTags[0] ??
|
|
129
|
+
'en-US',
|
|
130
|
+
);
|
|
131
|
+
const tags =
|
|
132
|
+
override?.locale != null
|
|
133
|
+
? [primaryTag]
|
|
134
|
+
: rawTags.length > 0
|
|
135
|
+
? rawTags.map((tag) => canonicalizeLocaleTag(tag))
|
|
136
|
+
: [primaryTag];
|
|
137
|
+
const parsed = parseLocaleTag(primaryTag);
|
|
138
|
+
const region = parsed.region ?? '';
|
|
139
|
+
const uses24Hour =
|
|
140
|
+
typeof nativeSnapshot?.uses24Hour === 'boolean'
|
|
141
|
+
? nativeSnapshot.uses24Hour
|
|
142
|
+
: detectUses24Hour(primaryTag);
|
|
143
|
+
|
|
144
|
+
return freezeSnapshot({
|
|
145
|
+
language: parsed.language,
|
|
146
|
+
region,
|
|
147
|
+
tag: primaryTag,
|
|
148
|
+
tags: Object.freeze(tags),
|
|
149
|
+
direction: override?.direction ?? directionForLocaleTag(primaryTag),
|
|
150
|
+
uses24Hour,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function createDefaultState(): ExactLocaleRuntimeState {
|
|
155
|
+
return {
|
|
156
|
+
snapshot: buildLocaleSnapshot(readNativeLocaleSnapshot(), null),
|
|
157
|
+
override: null,
|
|
158
|
+
listeners: new Set(),
|
|
159
|
+
changeTimer: null,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function getLocaleState(): ExactLocaleRuntimeState {
|
|
164
|
+
if (!globalThis.__exactLocaleState) {
|
|
165
|
+
globalThis.__exactLocaleState = createDefaultState();
|
|
166
|
+
}
|
|
167
|
+
return globalThis.__exactLocaleState;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function dispatchLocaleChange(state: ExactLocaleRuntimeState): void {
|
|
171
|
+
const snapshot = state.snapshot;
|
|
172
|
+
for (const listener of state.listeners) {
|
|
173
|
+
try {
|
|
174
|
+
listener(snapshot);
|
|
175
|
+
} catch {
|
|
176
|
+
// Ignore listener failures.
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function scheduleLocaleChange(state: ExactLocaleRuntimeState): void {
|
|
182
|
+
if (state.changeTimer != null) {
|
|
183
|
+
clearTimeout(state.changeTimer);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
state.changeTimer = setTimeout(() => {
|
|
187
|
+
state.changeTimer = null;
|
|
188
|
+
dispatchLocaleChange(state);
|
|
189
|
+
}, 100);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function updateLocaleSnapshot(
|
|
193
|
+
nativeSnapshot: NativeLocaleSnapshot | null,
|
|
194
|
+
override: ExactLocaleOverride | null,
|
|
195
|
+
notify: boolean,
|
|
196
|
+
): ExactLocaleSnapshot {
|
|
197
|
+
const state = getLocaleState();
|
|
198
|
+
const nextSnapshot = buildLocaleSnapshot(nativeSnapshot, override);
|
|
199
|
+
const prevSnapshot = state.snapshot;
|
|
200
|
+
state.snapshot = nextSnapshot;
|
|
201
|
+
state.override = override;
|
|
202
|
+
|
|
203
|
+
const changed =
|
|
204
|
+
prevSnapshot.tag !== nextSnapshot.tag ||
|
|
205
|
+
prevSnapshot.direction !== nextSnapshot.direction ||
|
|
206
|
+
prevSnapshot.uses24Hour !== nextSnapshot.uses24Hour ||
|
|
207
|
+
prevSnapshot.tags.length !== nextSnapshot.tags.length ||
|
|
208
|
+
prevSnapshot.tags.some((tag, index) => tag !== nextSnapshot.tags[index]);
|
|
209
|
+
|
|
210
|
+
if (notify && changed) {
|
|
211
|
+
scheduleLocaleChange(state);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return nextSnapshot;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function subscribeExactLocaleChanges(listener: ExactLocaleListener): () => void {
|
|
218
|
+
const state = getLocaleState();
|
|
219
|
+
state.listeners.add(listener);
|
|
220
|
+
return () => {
|
|
221
|
+
state.listeners.delete(listener);
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function getExactLocaleSnapshot(): ExactLocaleSnapshot {
|
|
226
|
+
return getLocaleState().snapshot;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function getExactLocaleOverride(): ExactLocaleOverride | null {
|
|
230
|
+
return getLocaleState().override;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function refreshExactLocale(): ExactLocaleSnapshot {
|
|
234
|
+
return updateLocaleSnapshot(readNativeLocaleSnapshot(), getLocaleState().override, true);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function setExactLocaleOverride(override: ExactLocaleOverride | null): ExactLocaleSnapshot {
|
|
238
|
+
return updateLocaleSnapshot(readNativeLocaleSnapshot(), override, true);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function clearExactLocaleOverride(): ExactLocaleSnapshot {
|
|
242
|
+
return setExactLocaleOverride(null);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function _resetExactLocaleForTests(): void {
|
|
246
|
+
const state = globalThis.__exactLocaleState;
|
|
247
|
+
if (state?.changeTimer != null) {
|
|
248
|
+
clearTimeout(state.changeTimer);
|
|
249
|
+
}
|
|
250
|
+
delete globalThis.__exactLocaleState;
|
|
251
|
+
delete globalThis.__exactLocaleChanged;
|
|
252
|
+
delete globalThis.__exactLocaleSnapshot;
|
|
253
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getExactLocaleOverride,
|
|
3
|
+
getExactLocaleSnapshot,
|
|
4
|
+
readNativeLocaleSnapshot,
|
|
5
|
+
subscribeExactLocaleChanges,
|
|
6
|
+
updateLocaleSnapshot,
|
|
7
|
+
type ExactLocaleListener,
|
|
8
|
+
type ExactLocaleSnapshot,
|
|
9
|
+
type NativeLocaleSnapshot,
|
|
10
|
+
} from './locale-state.js';
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
_resetExactLocaleForTests,
|
|
14
|
+
clearExactLocaleOverride,
|
|
15
|
+
getExactLocaleSnapshot,
|
|
16
|
+
refreshExactLocale,
|
|
17
|
+
setExactLocaleOverride,
|
|
18
|
+
subscribeExactLocaleChanges,
|
|
19
|
+
type ExactLocaleSnapshot,
|
|
20
|
+
} from './locale-state.js';
|
|
21
|
+
|
|
22
|
+
interface ExactLocaleNamespace {
|
|
23
|
+
readonly language: string;
|
|
24
|
+
readonly region: string;
|
|
25
|
+
readonly tag: string;
|
|
26
|
+
readonly tags: readonly string[];
|
|
27
|
+
readonly direction: ExactLocaleSnapshot['direction'];
|
|
28
|
+
readonly uses24Hour: boolean;
|
|
29
|
+
addListener(event: 'change', listener: ExactLocaleListener): () => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function createLocaleNamespace(): ExactLocaleNamespace {
|
|
33
|
+
return {
|
|
34
|
+
get language() {
|
|
35
|
+
return getExactLocaleSnapshot().language;
|
|
36
|
+
},
|
|
37
|
+
get region() {
|
|
38
|
+
return getExactLocaleSnapshot().region;
|
|
39
|
+
},
|
|
40
|
+
get tag() {
|
|
41
|
+
return getExactLocaleSnapshot().tag;
|
|
42
|
+
},
|
|
43
|
+
get tags() {
|
|
44
|
+
return getExactLocaleSnapshot().tags;
|
|
45
|
+
},
|
|
46
|
+
get direction() {
|
|
47
|
+
return getExactLocaleSnapshot().direction;
|
|
48
|
+
},
|
|
49
|
+
get uses24Hour() {
|
|
50
|
+
return getExactLocaleSnapshot().uses24Hour;
|
|
51
|
+
},
|
|
52
|
+
addListener(_event: 'change', listener: ExactLocaleListener): () => void {
|
|
53
|
+
return subscribeExactLocaleChanges(listener);
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function installExactLocaleGlobal(): ExactLocaleNamespace {
|
|
59
|
+
const g = globalThis as typeof globalThis & {
|
|
60
|
+
Exact?: Record<string, unknown>;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
if (typeof g.Exact !== 'object' || g.Exact === null) {
|
|
64
|
+
g.Exact = {};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const exact = g.Exact as Record<string, unknown>;
|
|
68
|
+
const existing = exact.locale;
|
|
69
|
+
if (existing && typeof existing === 'object' && typeof (existing as ExactLocaleNamespace).addListener === 'function') {
|
|
70
|
+
return existing as ExactLocaleNamespace;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
updateLocaleSnapshot(readNativeLocaleSnapshot(), getExactLocaleOverride(), false);
|
|
74
|
+
|
|
75
|
+
const locale = createLocaleNamespace();
|
|
76
|
+
Object.defineProperty(exact, 'locale', {
|
|
77
|
+
value: locale,
|
|
78
|
+
writable: true,
|
|
79
|
+
configurable: true,
|
|
80
|
+
enumerable: true,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
globalThis.__exactLocaleChanged = (snapshot?: NativeLocaleSnapshot | null) => {
|
|
84
|
+
if (snapshot && typeof snapshot === 'object') {
|
|
85
|
+
globalThis.__exactLocaleSnapshot = snapshot;
|
|
86
|
+
}
|
|
87
|
+
updateLocaleSnapshot(
|
|
88
|
+
snapshot ?? readNativeLocaleSnapshot(),
|
|
89
|
+
getExactLocaleOverride(),
|
|
90
|
+
true,
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
return locale;
|
|
95
|
+
}
|