Skip to content
Source
Sign inSign up

I18nProvider.tsx

76 lines2.8 KB

1import "./intl-polyfills";
2import { resolveLocale } from "@t3tools/client-runtime/locales";
3import { useAtomValue } from "@effect/atom-react";
4import { AsyncResult } from "effect/reactivity";
5import { GTProvider, initializeGT } from "gt-react-native";
6import { useDeferredValue, type ReactNode } from "react";
7import { Platform, TurboModuleRegistry, View, type TurboModule } from "react-native";
8​
9import { mobilePreferencesAtom } from "../state/preferences";
10import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from "./locales";
11import de from "./translations/de.json";
12import es from "./translations/es.json";
13import fr from "./translations/fr.json";
14import ja from "./translations/ja.json";
15import ko from "./translations/ko.json";
16import ptBR from "./translations/pt-BR.json";
17import ru from "./translations/ru.json";
18import zh from "./translations/zh.json";
19​
20// Screens import GT from here so the polyfills above always load first.
21export { useGT } from "gt-react-native";
22​
23// Committed catalogs, so forks and offline installs never reach GT's CDN. English is the source.
24const TRANSLATIONS: Readonly<Record<string, Record<string, string>>> = {
25 es,
26 zh,
27 "pt-BR": ptBR,
28 fr,
29 de,
30 ja,
31 ko,
32 ru,
33};
34​
35initializeGT({
36 defaultLocale: DEFAULT_LOCALE,
37 locales: [...SUPPORTED_LOCALES],
38 loadTranslations: async (locale: string) => TRANSLATIONS[locale] ?? {},
39});
40​
41// Read the device languages from gt-react-native's own native module: GT's getLocale() prefers
42// the last locale passed to GTProvider, which it persists, so it can't express "system".
43function readSystemLocales(): ReadonlyArray<string> {
44 try {
45 const locales =
46 TurboModuleRegistry.get<{ getNativeLocales(): string[] } & TurboModule>(
47 "GtReactNative",
48 )?.getNativeLocales() ?? [];
49 // On iOS the first entry is NSLocale.currentLocale, whose language is the app's bundle
50 // localization (English only), not the user's; preferredLanguages follow it.
51 return Platform.OS === "ios" ? locales.slice(1) : locales;
52 } catch {
53 return [];
54 }
56​
57export function useLanguagePreference(): string {
58 const preferences = useAtomValue(mobilePreferencesAtom);
59 return AsyncResult.isSuccess(preferences)
60 ? (preferences.value.language ?? DEFAULT_LOCALE)
61 : DEFAULT_LOCALE;
63​
64/** Translates the screens inside it into the saved language; "system" follows the device. */
65export function I18nProvider(props: { readonly children: ReactNode }) {
66 // GT suspends while a locale's catalog loads. Deferring the switch keeps the current language
67 // on screen until then, instead of GT's full-screen spinner.
68 const locale = useDeferredValue(
69 resolveLocale(useLanguagePreference(), readSystemLocales(), SUPPORTED_LOCALES, DEFAULT_LOCALE),
70 );
71 return (
72 <GTProvider locale={locale} fallback={<View className="flex-1 bg-screen" />}>
73 {props.children}
74 </GTProvider>
75 );