@symbiote-native/localization 0.0.1
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/LICENSE +21 -0
- package/README.md +153 -0
- package/build/angular/index.d.ts +3 -0
- package/build/angular/index.js +7 -0
- package/build/angular/services/calendars.service/index.d.ts +6 -0
- package/build/angular/services/calendars.service/index.js +68 -0
- package/build/angular/services/locales.service/index.d.ts +6 -0
- package/build/angular/services/locales.service/index.js +69 -0
- package/build/core/index.d.ts +4 -0
- package/build/core/index.js +3 -0
- package/build/core/localization.d.ts +39 -0
- package/build/core/localization.js +42 -0
- package/build/core/native-module.d.ts +12 -0
- package/build/core/native-module.js +21 -0
- package/build/core/types.d.ts +174 -0
- package/build/core/types.js +59 -0
- package/build/react/hooks/use-calendars/index.d.ts +2 -0
- package/build/react/hooks/use-calendars/index.js +13 -0
- package/build/react/hooks/use-locales/index.d.ts +2 -0
- package/build/react/hooks/use-locales/index.js +15 -0
- package/build/react/index.d.ts +3 -0
- package/build/react/index.js +7 -0
- package/build/vue/composables/use-calendars/index.d.ts +3 -0
- package/build/vue/composables/use-calendars/index.js +16 -0
- package/build/vue/composables/use-locales/index.d.ts +3 -0
- package/build/vue/composables/use-locales/index.js +20 -0
- package/build/vue/index.d.ts +3 -0
- package/build/vue/index.js +7 -0
- package/build-ngc/angular/index.d.ts +3 -0
- package/build-ngc/angular/index.js +8 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/angular/services/calendars.service/index.d.ts +9 -0
- package/build-ngc/angular/services/calendars.service/index.js +27 -0
- package/build-ngc/angular/services/calendars.service/index.js.map +1 -0
- package/build-ngc/angular/services/locales.service/index.d.ts +9 -0
- package/build-ngc/angular/services/locales.service/index.js +28 -0
- package/build-ngc/angular/services/locales.service/index.js.map +1 -0
- package/build-ngc/core/index.d.ts +4 -0
- package/build-ngc/core/index.js +4 -0
- package/build-ngc/core/index.js.map +1 -0
- package/build-ngc/core/localization.d.ts +39 -0
- package/build-ngc/core/localization.js +43 -0
- package/build-ngc/core/localization.js.map +1 -0
- package/build-ngc/core/native-module.d.ts +12 -0
- package/build-ngc/core/native-module.js +22 -0
- package/build-ngc/core/native-module.js.map +1 -0
- package/build-ngc/core/types.d.ts +174 -0
- package/build-ngc/core/types.js +60 -0
- package/build-ngc/core/types.js.map +1 -0
- package/native-link.json +12 -0
- package/package.json +107 -0
- package/src/angular/index.ts +17 -0
- package/src/angular/services/calendars.service/calendars.service.test.ts +102 -0
- package/src/angular/services/calendars.service/index.ts +26 -0
- package/src/angular/services/locales.service/index.ts +27 -0
- package/src/angular/services/locales.service/locales.service.test.ts +122 -0
- package/src/core/index.ts +4 -0
- package/src/core/localization.test.ts +64 -0
- package/src/core/localization.ts +45 -0
- package/src/core/native-module.ts +43 -0
- package/src/core/types.ts +181 -0
- package/src/react/hooks/use-calendars/index.ts +16 -0
- package/src/react/hooks/use-calendars/use-calendars.test.tsx +87 -0
- package/src/react/hooks/use-locales/index.ts +18 -0
- package/src/react/hooks/use-locales/use-locales.test.tsx +107 -0
- package/src/react/index.ts +18 -0
- package/src/vue/composables/use-calendars/index.ts +25 -0
- package/src/vue/composables/use-calendars/use-calendars.test.ts +96 -0
- package/src/vue/composables/use-locales/index.ts +24 -0
- package/src/vue/composables/use-locales/use-locales.test.ts +116 -0
- package/src/vue/index.ts +18 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 A. Prokopenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# @symbiote-native/localization
|
|
2
|
+
|
|
3
|
+
Port of [`expo-localization`](https://docs.expo.dev/versions/latest/sdk/localization/) for
|
|
4
|
+
[SymbioteNative](../../README.md) — the device's locale list and preferred calendar settings,
|
|
5
|
+
reachable from every adapter (React, Vue, Angular), not just React.
|
|
6
|
+
|
|
7
|
+
Built the same way as [`@symbiote-native/battery`](../battery) and
|
|
8
|
+
[`@symbiote-native/device`](../device), an `expo-modules-core`-based wrapper (see the
|
|
9
|
+
`symbiote-expo-native-module` project skill for the full mechanism: why `expo-modules-core` is
|
|
10
|
+
depended on directly and never the `expo` meta-package, why the upstream JS is hand-ported into
|
|
11
|
+
`core/` rather than imported, and how autolinking picks up the native module).
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @symbiote-native/localization
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Depends on `expo-localization` and `expo-modules-core` directly (regular dependencies, pinned to
|
|
20
|
+
exact versions — never a caret range, since this package's `core/` is hand-ported against one
|
|
21
|
+
specific native API shape and a newer resolve could silently drift the two apart). Never install
|
|
22
|
+
`expo-localization` yourself, and never add the `expo` package to this project — it bundles its
|
|
23
|
+
own Metro/Babel pipeline that conflicts with this project's own.
|
|
24
|
+
|
|
25
|
+
### Required one-time step: native autolinking wiring
|
|
26
|
+
|
|
27
|
+
Unlike a plain RN native module, `expo-localization`'s native code is discovered by
|
|
28
|
+
`expo-modules-autolinking`, not RN's own `react-native.config.cjs` mechanism — this needs wiring
|
|
29
|
+
into the native host app **once**, covering this package and every other `expo-modules-core`
|
|
30
|
+
package (`@symbiote-native/battery`, `@symbiote-native/device`, ...) with zero further changes:
|
|
31
|
+
|
|
32
|
+
| Platform | Touches |
|
|
33
|
+
|---|---|
|
|
34
|
+
| iOS | `ios/Podfile` — add `use_expo_modules!` |
|
|
35
|
+
| iOS | `AppDelegate.swift` — Expo's runtime-bootstrap hook |
|
|
36
|
+
| Android | `settings.gradle` / `app/build.gradle` — resolve and include the Expo Gradle projects |
|
|
37
|
+
| Android | `MainApplication.kt` — Expo's bootstrap hook, plus a hand-written native-module name map (there's no `expo` meta-package here to auto-generate one) |
|
|
38
|
+
|
|
39
|
+
Full mechanics — the Podfile pieces that normally ship inside the `expo` package, the `expo`
|
|
40
|
+
peer-dependency exclusion list — live in the `symbiote-expo-native-module` skill. Reference
|
|
41
|
+
implementation: `.examples/expo-react/ios/Podfile` and
|
|
42
|
+
`.examples/expo-react/android/app/src/main/java/com/canary/MainApplication.kt`.
|
|
43
|
+
|
|
44
|
+
No platform permission string is needed — locale and calendar settings are read-only system
|
|
45
|
+
state with no runtime permission prompt on either platform.
|
|
46
|
+
|
|
47
|
+
## Shape
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
src/core/ types.ts — Locale, Weekday, CalendarIdentifier, Calendar, hand-ported
|
|
51
|
+
verbatim from Localization.types.ts. native-module.ts resolves the
|
|
52
|
+
native module through expo-modules-core's requireNativeModule and
|
|
53
|
+
exposes addLocaleListener/addCalendarListener. localization.ts —
|
|
54
|
+
getLocales/getCalendars, synchronous getters delegating straight to the
|
|
55
|
+
native module.
|
|
56
|
+
src/react/hooks/ @symbiote-native/localization/react — useLocales, useCalendars
|
|
57
|
+
src/vue/composables/ @symbiote-native/localization/vue — same two names, Vue lifecycle
|
|
58
|
+
src/angular/services/ @symbiote-native/localization/angular — LocalesService, CalendarsService
|
|
59
|
+
(`.connect()` returns a Signal)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Two independent getters, each with its own native change listener and its own reactive hook per
|
|
63
|
+
adapter — mirroring `@symbiote-native/battery`'s shape of shipping several distinct hooks in one
|
|
64
|
+
package, not one combined hook. Each hook/composable/service seeds its return value from the
|
|
65
|
+
matching synchronous `get*()` call (no initial "loading" state needed — the native call is
|
|
66
|
+
sync, not async) and recomputes it whenever the matching listener fires.
|
|
67
|
+
|
|
68
|
+
## Use it
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
// React — examples/expo-react/screens/LocalizationScreen.tsx
|
|
72
|
+
import { useLocales, useCalendars } from '@symbiote-native/localization/react';
|
|
73
|
+
|
|
74
|
+
function LocalizationScreen() {
|
|
75
|
+
const locales = useLocales(); // Locale[], guaranteed at least 1 element
|
|
76
|
+
const calendars = useCalendars(); // Calendar[], guaranteed at least 1 element
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<>
|
|
80
|
+
<Text>{locales[0].languageTag}</Text>
|
|
81
|
+
<Text>{calendars[0].timeZone}</Text>
|
|
82
|
+
</>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```vue
|
|
88
|
+
<!-- Vue — examples/expo-vue-sfc/screens/LocalizationScreen.vue -->
|
|
89
|
+
<script setup lang="ts">
|
|
90
|
+
import { useLocales, useCalendars } from '@symbiote-native/localization/vue';
|
|
91
|
+
|
|
92
|
+
const locales = useLocales(); // Ref<Locale[]>
|
|
93
|
+
const calendars = useCalendars(); // Ref<Calendar[]>
|
|
94
|
+
</script>
|
|
95
|
+
<template>
|
|
96
|
+
<Text>{{ locales[0].languageTag }}</Text>
|
|
97
|
+
<Text>{{ calendars[0].timeZone }}</Text>
|
|
98
|
+
</template>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
// Angular — examples/expo-angular/src/screens/LocalizationScreen.ts
|
|
103
|
+
import { Component, inject } from '@angular/core';
|
|
104
|
+
import { LocalesService, CalendarsService } from '@symbiote-native/localization/angular';
|
|
105
|
+
|
|
106
|
+
@Component({ /* ... */ })
|
|
107
|
+
export class LocalizationScreen {
|
|
108
|
+
readonly locales = inject(LocalesService).connect(); // Signal<Locale[]>
|
|
109
|
+
readonly calendars = inject(CalendarsService).connect(); // Signal<Calendar[]>
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## API
|
|
114
|
+
|
|
115
|
+
Two independent synchronous getters (`getLocales`, `getCalendars`), each with its own
|
|
116
|
+
listener-based subscription (`addLocaleListener`/`addCalendarListener`) and its own adapter-level
|
|
117
|
+
lifecycle hook — one hook/composable/service per getter, matching upstream's own `useLocales`/
|
|
118
|
+
`useCalendars` being two separate hooks, not one combined hook.
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
getLocales(): Locale[] // user's locales, in device-settings order
|
|
122
|
+
getCalendars(): Calendar[] // user's preferred calendars
|
|
123
|
+
addLocaleListener(listener): EventSubscription // fires when locale settings change
|
|
124
|
+
addCalendarListener(listener): EventSubscription // fires when calendar settings change
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Plus `Locale`, `Weekday` (enum), `CalendarIdentifier` (enum), `Calendar` — ported from upstream's
|
|
128
|
+
`Localization.types.ts`.
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import { getLocales, getCalendars } from '@symbiote-native/localization';
|
|
132
|
+
|
|
133
|
+
// framework-scoped entry points re-export the same free functions, plus a lifecycle
|
|
134
|
+
// hook/composable/service per getter:
|
|
135
|
+
import { useLocales, useCalendars } from '@symbiote-native/localization/react';
|
|
136
|
+
import { useLocales, useCalendars } from '@symbiote-native/localization/vue';
|
|
137
|
+
import { LocalesService, CalendarsService } from '@symbiote-native/localization/angular';
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Each hook/composable/service seeds its initial value from the matching synchronous `get*()`
|
|
141
|
+
call, then subscribes to the matching listener for updates, and unsubscribes on unmount —
|
|
142
|
+
mirroring upstream's own `useLocales`/`useCalendars`.
|
|
143
|
+
|
|
144
|
+
## Test it
|
|
145
|
+
|
|
146
|
+
No Fabric/Descriptor angle at all — localization is a pure synchronous-function + `EventEmitter`
|
|
147
|
+
listener surface, never a view. Tests inject a fake native-module object in place of the real
|
|
148
|
+
`requireNativeModule` resolution (`src/core/localization.test.ts`,
|
|
149
|
+
`src/{react,vue,angular}/**/*.test.{ts,tsx}`, `vitest`), the same pattern
|
|
150
|
+
`@symbiote-native/battery` and `@symbiote-native/device` use — no `installFabric()`, no
|
|
151
|
+
ViewConfig for the core test; the adapter hook/composable/service tests do use `installFabric()`
|
|
152
|
+
purely to mount a host component, same as every other sibling package. Native rendering itself is
|
|
153
|
+
verified on-device — see the parent [README](../../README.md).
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { LocalesService } from './services/locales.service';
|
|
2
|
+
export { CalendarsService } from './services/calendars.service';
|
|
3
|
+
export { getLocales, getCalendars, addLocaleListener, addCalendarListener, Weekday, CalendarIdentifier, type Locale, type Calendar, type EventSubscription, } from '../core';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// LocalesService/CalendarsService are the Angular-only lifecycle half; the free functions and
|
|
2
|
+
// event subscription plumbing all live in core, shared with React/Vue. Two separate services
|
|
3
|
+
// (not one combined service), matching upstream's own useLocales/useCalendars being two separate
|
|
4
|
+
// hooks.
|
|
5
|
+
export { LocalesService } from './services/locales.service';
|
|
6
|
+
export { CalendarsService } from './services/calendars.service';
|
|
7
|
+
export { getLocales, getCalendars, addLocaleListener, addCalendarListener, Weekday, CalendarIdentifier, } from '../core';
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
2
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
3
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
4
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
5
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
6
|
+
var _, done = false;
|
|
7
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
8
|
+
var context = {};
|
|
9
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
10
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
11
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
12
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
13
|
+
if (kind === "accessor") {
|
|
14
|
+
if (result === void 0) continue;
|
|
15
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
16
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
17
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
18
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
19
|
+
}
|
|
20
|
+
else if (_ = accept(result)) {
|
|
21
|
+
if (kind === "field") initializers.unshift(_);
|
|
22
|
+
else descriptor[key] = _;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
26
|
+
done = true;
|
|
27
|
+
};
|
|
28
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
29
|
+
var useValue = arguments.length > 2;
|
|
30
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
31
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
32
|
+
}
|
|
33
|
+
return useValue ? value : void 0;
|
|
34
|
+
};
|
|
35
|
+
import { effect, inject, Injectable, Injector, signal } from '@angular/core';
|
|
36
|
+
import { addCalendarListener, getCalendars } from '../../../core';
|
|
37
|
+
// Angular twin of React's `useCalendars` hook and Vue's `useCalendars` composable — mirrors
|
|
38
|
+
// LocalesService's shape exactly.
|
|
39
|
+
//
|
|
40
|
+
// readonly calendars = inject(CalendarsService).connect();
|
|
41
|
+
// // template: {{ calendars() }}
|
|
42
|
+
let CalendarsService = (() => {
|
|
43
|
+
let _classDecorators = [Injectable({ providedIn: 'root' })];
|
|
44
|
+
let _classDescriptor;
|
|
45
|
+
let _classExtraInitializers = [];
|
|
46
|
+
let _classThis;
|
|
47
|
+
var CalendarsService = class {
|
|
48
|
+
static { _classThis = this; }
|
|
49
|
+
static {
|
|
50
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
51
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
52
|
+
CalendarsService = _classThis = _classDescriptor.value;
|
|
53
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
54
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
55
|
+
}
|
|
56
|
+
injector = inject(Injector);
|
|
57
|
+
connect() {
|
|
58
|
+
const calendars = signal(getCalendars());
|
|
59
|
+
effect(onCleanup => {
|
|
60
|
+
const subscription = addCalendarListener(() => calendars.set(getCalendars()));
|
|
61
|
+
onCleanup(() => subscription.remove());
|
|
62
|
+
}, { injector: this.injector });
|
|
63
|
+
return calendars.asReadonly();
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
return CalendarsService = _classThis;
|
|
67
|
+
})();
|
|
68
|
+
export { CalendarsService };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
2
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
3
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
4
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
5
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
6
|
+
var _, done = false;
|
|
7
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
8
|
+
var context = {};
|
|
9
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
10
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
11
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
12
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
13
|
+
if (kind === "accessor") {
|
|
14
|
+
if (result === void 0) continue;
|
|
15
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
16
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
17
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
18
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
19
|
+
}
|
|
20
|
+
else if (_ = accept(result)) {
|
|
21
|
+
if (kind === "field") initializers.unshift(_);
|
|
22
|
+
else descriptor[key] = _;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
26
|
+
done = true;
|
|
27
|
+
};
|
|
28
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
29
|
+
var useValue = arguments.length > 2;
|
|
30
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
31
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
32
|
+
}
|
|
33
|
+
return useValue ? value : void 0;
|
|
34
|
+
};
|
|
35
|
+
import { effect, inject, Injectable, Injector, signal } from '@angular/core';
|
|
36
|
+
import { addLocaleListener, getLocales } from '../../../core';
|
|
37
|
+
// Angular twin of React's `useLocales` hook and Vue's `useLocales` composable. See
|
|
38
|
+
// packages/battery's BatteryStateService for the `connect()` pattern rationale. getLocales() is
|
|
39
|
+
// synchronous, so the signal seeds from it directly rather than an async `.then()` fetch.
|
|
40
|
+
//
|
|
41
|
+
// readonly locales = inject(LocalesService).connect();
|
|
42
|
+
// // template: {{ locales() }}
|
|
43
|
+
let LocalesService = (() => {
|
|
44
|
+
let _classDecorators = [Injectable({ providedIn: 'root' })];
|
|
45
|
+
let _classDescriptor;
|
|
46
|
+
let _classExtraInitializers = [];
|
|
47
|
+
let _classThis;
|
|
48
|
+
var LocalesService = class {
|
|
49
|
+
static { _classThis = this; }
|
|
50
|
+
static {
|
|
51
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
52
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
53
|
+
LocalesService = _classThis = _classDescriptor.value;
|
|
54
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
55
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
56
|
+
}
|
|
57
|
+
injector = inject(Injector);
|
|
58
|
+
connect() {
|
|
59
|
+
const locales = signal(getLocales());
|
|
60
|
+
effect(onCleanup => {
|
|
61
|
+
const subscription = addLocaleListener(() => locales.set(getLocales()));
|
|
62
|
+
onCleanup(() => subscription.remove());
|
|
63
|
+
}, { injector: this.injector });
|
|
64
|
+
return locales.asReadonly();
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
return LocalesService = _classThis;
|
|
68
|
+
})();
|
|
69
|
+
export { LocalesService };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { getLocales, getCalendars } from './localization';
|
|
2
|
+
export { addLocaleListener, addCalendarListener } from './native-module';
|
|
3
|
+
export { Weekday, CalendarIdentifier, type Locale, type Calendar } from './types';
|
|
4
|
+
export type { EventSubscription } from 'expo-modules-core';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Calendar, Locale } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* List of user's locales, returned as an array of objects of type `Locale`.
|
|
4
|
+
* Guaranteed to contain at least 1 element.
|
|
5
|
+
* These are returned in the order the user defines in their device settings.
|
|
6
|
+
* On the web currency and measurements systems are not provided, instead returned as null.
|
|
7
|
+
* If needed, you can infer them from the current region using a lookup table.
|
|
8
|
+
* @example
|
|
9
|
+
* ```js
|
|
10
|
+
* [{
|
|
11
|
+
* "languageTag": "pl-PL",
|
|
12
|
+
* "languageCode": "pl",
|
|
13
|
+
* "textDirection": "ltr",
|
|
14
|
+
* "digitGroupingSeparator": " ",
|
|
15
|
+
* "decimalSeparator": ",",
|
|
16
|
+
* "measurementSystem": "metric",
|
|
17
|
+
* "currencyCode": "PLN",
|
|
18
|
+
* "currencySymbol": "zł",
|
|
19
|
+
* "regionCode": "PL",
|
|
20
|
+
* "temperatureUnit": "celsius"
|
|
21
|
+
* }]
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare const getLocales: () => Locale[];
|
|
25
|
+
/**
|
|
26
|
+
* List of user's preferred calendars, returned as an array of objects of type `Calendar`.
|
|
27
|
+
* Guaranteed to contain at least 1 element.
|
|
28
|
+
* For now always returns a single element, but it's likely to return a user preference list on some platforms in the future.
|
|
29
|
+
* @example
|
|
30
|
+
* ```js
|
|
31
|
+
* [{
|
|
32
|
+
* "calendar": "gregory",
|
|
33
|
+
* "timeZone": "Europe/Warsaw",
|
|
34
|
+
* "uses24hourClock": true,
|
|
35
|
+
* "firstWeekday": 1
|
|
36
|
+
* }]
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare const getCalendars: () => Calendar[];
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Hand-ported from .vendors/expo/packages/expo-localization/src/Localization.ts (sdk-57) —
|
|
2
|
+
// both functions delegate straight to the native module, no extra wrapping needed beyond typing
|
|
3
|
+
// them as synchronous getters.
|
|
4
|
+
import ExpoLocalizationModule from './native-module';
|
|
5
|
+
/**
|
|
6
|
+
* List of user's locales, returned as an array of objects of type `Locale`.
|
|
7
|
+
* Guaranteed to contain at least 1 element.
|
|
8
|
+
* These are returned in the order the user defines in their device settings.
|
|
9
|
+
* On the web currency and measurements systems are not provided, instead returned as null.
|
|
10
|
+
* If needed, you can infer them from the current region using a lookup table.
|
|
11
|
+
* @example
|
|
12
|
+
* ```js
|
|
13
|
+
* [{
|
|
14
|
+
* "languageTag": "pl-PL",
|
|
15
|
+
* "languageCode": "pl",
|
|
16
|
+
* "textDirection": "ltr",
|
|
17
|
+
* "digitGroupingSeparator": " ",
|
|
18
|
+
* "decimalSeparator": ",",
|
|
19
|
+
* "measurementSystem": "metric",
|
|
20
|
+
* "currencyCode": "PLN",
|
|
21
|
+
* "currencySymbol": "zł",
|
|
22
|
+
* "regionCode": "PL",
|
|
23
|
+
* "temperatureUnit": "celsius"
|
|
24
|
+
* }]
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export const getLocales = ExpoLocalizationModule.getLocales;
|
|
28
|
+
/**
|
|
29
|
+
* List of user's preferred calendars, returned as an array of objects of type `Calendar`.
|
|
30
|
+
* Guaranteed to contain at least 1 element.
|
|
31
|
+
* For now always returns a single element, but it's likely to return a user preference list on some platforms in the future.
|
|
32
|
+
* @example
|
|
33
|
+
* ```js
|
|
34
|
+
* [{
|
|
35
|
+
* "calendar": "gregory",
|
|
36
|
+
* "timeZone": "Europe/Warsaw",
|
|
37
|
+
* "uses24hourClock": true,
|
|
38
|
+
* "firstWeekday": 1
|
|
39
|
+
* }]
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export const getCalendars = ExpoLocalizationModule.getCalendars;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type EventSubscription } from 'expo-modules-core';
|
|
2
|
+
import type { Calendar, Locale } from './types';
|
|
3
|
+
export type INativeLocalizationModule = {
|
|
4
|
+
getLocales(): Locale[];
|
|
5
|
+
getCalendars(): Calendar[];
|
|
6
|
+
addListener(eventName: 'onLocaleSettingsChanged', listener: (event?: unknown) => void): EventSubscription;
|
|
7
|
+
addListener(eventName: 'onCalendarSettingsChanged', listener: (event?: unknown) => void): EventSubscription;
|
|
8
|
+
};
|
|
9
|
+
declare const ExpoLocalizationModule: INativeLocalizationModule;
|
|
10
|
+
export declare function addLocaleListener(listener: (event?: unknown) => void): EventSubscription;
|
|
11
|
+
export declare function addCalendarListener(listener: (event?: unknown) => void): EventSubscription;
|
|
12
|
+
export default ExpoLocalizationModule;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Hand-ported from .vendors/expo/packages/expo-localization/src/ExpoLocalization.native.ts
|
|
2
|
+
// (sdk-57). getLocales()/getCalendars() are synchronous, JSI-bridged native calls (no await) —
|
|
3
|
+
// the native module itself implements them as sync-bridged functions, the same constants-style
|
|
4
|
+
// read pattern this repo already uses for expo-device's eagerly-resolved fields
|
|
5
|
+
// (packages/device/src/core/native-module.ts). One native module fans out two independent event
|
|
6
|
+
// streams (locale settings vs calendar settings) through the same addListener, keyed by event
|
|
7
|
+
// name — mirrors the three-event-names-on-one-addListener overload trick in
|
|
8
|
+
// packages/battery/src/core/native-module.ts.
|
|
9
|
+
import { requireNativeModule } from 'expo-modules-core';
|
|
10
|
+
const EXPO_LOCALIZATION_MODULE_NAME = 'ExpoLocalization';
|
|
11
|
+
const ExpoLocalizationModule = requireNativeModule(EXPO_LOCALIZATION_MODULE_NAME);
|
|
12
|
+
export function addLocaleListener(
|
|
13
|
+
// NOTE: upstream never uses the event's data — the listener is invoked purely as an
|
|
14
|
+
// invalidate signal for useLocales/the Vue composable/the Angular service below.
|
|
15
|
+
listener) {
|
|
16
|
+
return ExpoLocalizationModule.addListener('onLocaleSettingsChanged', listener);
|
|
17
|
+
}
|
|
18
|
+
export function addCalendarListener(listener) {
|
|
19
|
+
return ExpoLocalizationModule.addListener('onCalendarSettingsChanged', listener);
|
|
20
|
+
}
|
|
21
|
+
export default ExpoLocalizationModule;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
export type Locale = {
|
|
2
|
+
/**
|
|
3
|
+
* An [IETF BCP 47 language tag](https://en.wikipedia.org/wiki/IETF_language_tag) with a region code.
|
|
4
|
+
* @example
|
|
5
|
+
* `'en-US'`, `'es-419'`, `'pl-PL'`.
|
|
6
|
+
*/
|
|
7
|
+
languageTag: string;
|
|
8
|
+
/**
|
|
9
|
+
* An [IETF BCP 47 language tag](https://en.wikipedia.org/wiki/IETF_language_tag) without the region code.
|
|
10
|
+
* @example
|
|
11
|
+
* `'en'`, `'es'`, `'pl'`.
|
|
12
|
+
*/
|
|
13
|
+
languageCode: string | null;
|
|
14
|
+
/**
|
|
15
|
+
* An [ISO 15924](https://en.wikipedia.org/wiki/ISO_15924) 4-letter script code. On Android and Web, it may be `null` if none is defined.
|
|
16
|
+
* @example
|
|
17
|
+
* `'Latn'`, `'Hans'`, `'Hebr'`.
|
|
18
|
+
*/
|
|
19
|
+
languageScriptCode: string | null;
|
|
20
|
+
/**
|
|
21
|
+
* The region code for your device that comes from the Region setting under Language & Region on iOS, Region settings on Android and is parsed from locale on Web (can be `null` on Web).
|
|
22
|
+
* @example
|
|
23
|
+
* `'US'`.
|
|
24
|
+
*/
|
|
25
|
+
regionCode: string | null;
|
|
26
|
+
/**
|
|
27
|
+
* The region code for the preferred language. When the language is not region-specific, it returns the same value as `regionCode`. When the language is region-specific, it returns the region code for the language (`en-CA` -> `CA`).
|
|
28
|
+
* Prefer using `regionCode` for any internalization purposes.
|
|
29
|
+
* @example
|
|
30
|
+
* `'US'`.
|
|
31
|
+
*/
|
|
32
|
+
languageRegionCode: string | null;
|
|
33
|
+
/**
|
|
34
|
+
* Currency code for the locale.
|
|
35
|
+
* On iOS, it's the currency code from the `Region` setting under Language & Region, not for the current locale.
|
|
36
|
+
* On Android, it's the currency specifc to the locale in the list, as there are no separate settings for selecting a region.
|
|
37
|
+
* Is `null` on Web, use a table lookup based on region instead.
|
|
38
|
+
* @example
|
|
39
|
+
* `'USD'`, `'EUR'`, `'PLN'`.
|
|
40
|
+
*/
|
|
41
|
+
currencyCode: string | null;
|
|
42
|
+
/**
|
|
43
|
+
* Currency symbol for the currency specified by `currencyCode`.
|
|
44
|
+
* @example
|
|
45
|
+
* `'$'`, `'€'`, `'zł'`.
|
|
46
|
+
*/
|
|
47
|
+
currencySymbol: string | null;
|
|
48
|
+
/**
|
|
49
|
+
* Currency code for the locale.
|
|
50
|
+
* On iOS, it's the currency code for the current locale in the list, not the device region.
|
|
51
|
+
* On Android, it's equal to `currencyCode`.
|
|
52
|
+
* Is `null` on Web.
|
|
53
|
+
* Prefer using `currencyCode` for any internalization purposes.
|
|
54
|
+
* @example
|
|
55
|
+
* `'USD'`, `'EUR'`, `'PLN'`.
|
|
56
|
+
*/
|
|
57
|
+
languageCurrencyCode: string | null;
|
|
58
|
+
/**
|
|
59
|
+
* Currency symbol for the currency specified by `languageCurrencyCode`.
|
|
60
|
+
* Prefer using `currencySymbol` for any internalization purposes.
|
|
61
|
+
* @example
|
|
62
|
+
* `'$'`, `'€'`, `'zł'`.
|
|
63
|
+
*/
|
|
64
|
+
languageCurrencySymbol: string | null;
|
|
65
|
+
/**
|
|
66
|
+
* Decimal separator used for formatting numbers with fractional parts.
|
|
67
|
+
* @example
|
|
68
|
+
* `'.'`, `','`.
|
|
69
|
+
*/
|
|
70
|
+
decimalSeparator: string | null;
|
|
71
|
+
/**
|
|
72
|
+
* Digit grouping separator used for formatting large numbers.
|
|
73
|
+
* @example
|
|
74
|
+
* `'.'`, `','`.
|
|
75
|
+
*/
|
|
76
|
+
digitGroupingSeparator: string | null;
|
|
77
|
+
/**
|
|
78
|
+
* Text direction for the locale. One of: `'ltr'`, `'rtl'`.
|
|
79
|
+
*/
|
|
80
|
+
textDirection: 'ltr' | 'rtl';
|
|
81
|
+
/**
|
|
82
|
+
* The measurement system used in the locale.
|
|
83
|
+
* Is `null` on Web, as user chosen measurement system is not exposed on the web and using locale to determine measurement systems is unreliable.
|
|
84
|
+
* Ask for user preferences if possible.
|
|
85
|
+
*/
|
|
86
|
+
measurementSystem: `metric` | `us` | `uk` | null;
|
|
87
|
+
/**
|
|
88
|
+
* The temperature unit used in the locale.
|
|
89
|
+
* Returns `null` if the region code is unknown.
|
|
90
|
+
*/
|
|
91
|
+
temperatureUnit: 'celsius' | 'fahrenheit' | null;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* An enum mapping days of the week in Gregorian calendar to their index as returned by the `firstWeekday` property.
|
|
95
|
+
*/
|
|
96
|
+
export declare enum Weekday {
|
|
97
|
+
SUNDAY = 1,
|
|
98
|
+
MONDAY = 2,
|
|
99
|
+
TUESDAY = 3,
|
|
100
|
+
WEDNESDAY = 4,
|
|
101
|
+
THURSDAY = 5,
|
|
102
|
+
FRIDAY = 6,
|
|
103
|
+
SATURDAY = 7
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The calendar identifier, one of [Unicode calendar types](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar).
|
|
107
|
+
*/
|
|
108
|
+
export declare enum CalendarIdentifier {
|
|
109
|
+
/** Thai Buddhist calendar */
|
|
110
|
+
BUDDHIST = "buddhist",
|
|
111
|
+
/** Traditional Chinese calendar */
|
|
112
|
+
CHINESE = "chinese",
|
|
113
|
+
/** Coptic calendar */
|
|
114
|
+
COPTIC = "coptic",
|
|
115
|
+
/** Traditional Korean calendar */
|
|
116
|
+
DANGI = "dangi",
|
|
117
|
+
/** Ethiopic calendar, Amete Alem (epoch approx. 5493 B.C.E) */
|
|
118
|
+
ETHIOAA = "ethioaa",
|
|
119
|
+
/** Ethiopic calendar, Amete Mihret (epoch approx, 8 C.E.) */
|
|
120
|
+
ETHIOPIC = "ethiopic",
|
|
121
|
+
/** Gregorian calendar (also referred to upstream as "gregorian" — dropped as a separate
|
|
122
|
+
* member here since this repo's lint bans duplicate enum values). */
|
|
123
|
+
GREGORY = "gregory",
|
|
124
|
+
/** Traditional Hebrew calendar */
|
|
125
|
+
HEBREW = "hebrew",
|
|
126
|
+
/** Indian calendar */
|
|
127
|
+
INDIAN = "indian",
|
|
128
|
+
/** Islamic calendar */
|
|
129
|
+
ISLAMIC = "islamic",
|
|
130
|
+
/** Islamic calendar, tabular (intercalary years [2,5,7,10,13,16,18,21,24,26,29] - civil epoch) */
|
|
131
|
+
ISLAMIC_CIVIL = "islamic-civil",
|
|
132
|
+
/** Islamic calendar, Saudi Arabia sighting */
|
|
133
|
+
ISLAMIC_RGSA = "islamic-rgsa",
|
|
134
|
+
/**Islamic calendar, tabular (intercalary years [2,5,7,10,13,16,18,21,24,26,29] - astronomical epoch) */
|
|
135
|
+
ISLAMIC_TBLA = "islamic-tbla",
|
|
136
|
+
/** Islamic calendar, Umm al-Qura */
|
|
137
|
+
ISLAMIC_UMALQURA = "islamic-umalqura",
|
|
138
|
+
/** ISO calendar (Gregorian calendar using the ISO 8601 calendar week rules) */
|
|
139
|
+
ISO8601 = "iso8601",
|
|
140
|
+
/** Japanese imperial calendar */
|
|
141
|
+
JAPANESE = "japanese",
|
|
142
|
+
/** Persian calendar */
|
|
143
|
+
PERSIAN = "persian",
|
|
144
|
+
/** Civil (algorithmic) Arabic calendar */
|
|
145
|
+
ROC = "roc"
|
|
146
|
+
}
|
|
147
|
+
export type Calendar = {
|
|
148
|
+
/**
|
|
149
|
+
* The calendar identifier, one of [Unicode calendar types](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar).
|
|
150
|
+
*
|
|
151
|
+
* On Android is limited to one of device's [available calendar types](https://developer.android.com/reference/java/util/Calendar#getAvailableCalendarTypes()).
|
|
152
|
+
*
|
|
153
|
+
* On iOS uses [calendar identifiers](https://developer.apple.com/documentation/foundation/calendar/identifier), but maps them to the corresponding Unicode types, will also never contain `'dangi'` or `'islamic-rgsa'` due to it not being implemented on iOS.
|
|
154
|
+
*/
|
|
155
|
+
calendar: CalendarIdentifier | null;
|
|
156
|
+
/**
|
|
157
|
+
* True when current device settings use 24-hour time format.
|
|
158
|
+
* Can be null on some browsers that don't support the [hourCycle](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/hourCycle) property in [Intl](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) API.
|
|
159
|
+
*/
|
|
160
|
+
uses24hourClock: boolean | null;
|
|
161
|
+
/**
|
|
162
|
+
* The first day of the week. For most calendars Sunday is numbered `1`, with Saturday being number `7`.
|
|
163
|
+
* Can be null on some browsers that don't support the [weekInfo](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/weekInfo) property in [Intl](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) API.
|
|
164
|
+
* @example
|
|
165
|
+
* `1`, `7`.
|
|
166
|
+
*/
|
|
167
|
+
firstWeekday: Weekday | null;
|
|
168
|
+
/**
|
|
169
|
+
* Time zone for the calendar. Can be `null` on Web.
|
|
170
|
+
* @example
|
|
171
|
+
* `'America/Los_Angeles'`, `'Europe/Warsaw'`, `'GMT+1'`.
|
|
172
|
+
*/
|
|
173
|
+
timeZone: string | null;
|
|
174
|
+
};
|