fix: show the app in the chosen language right after a cold start

This commit is contained in:
Linus Rath
2026-10-15 02:35:11 +02:00
parent 29c172f2af
commit 512a28c494
2 changed files with 37 additions and 3 deletions
+27
View File
@@ -0,0 +1,27 @@
import { describe, it, expect, vi } from 'vitest';
vi.mock('react-native', () => ({
I18nManager: { isRTL: false, allowRTL: () => {}, forceRTL: () => {} },
}));
vi.mock('expo-localization', () => ({ getLocales: () => [{ languageTag: 'en-US', languageCode: 'en' }] }));
vi.mock('@react-native-async-storage/async-storage', () => ({
default: {
getItem: vi.fn(async () => JSON.stringify({ override: 'de' })),
setItem: vi.fn(async () => undefined),
},
}));
import { useLocaleStore } from '../locale-store';
describe('locale store', () => {
it('hands out a new t once the saved language loads, so screens re-render in it', async () => {
const before = useLocaleStore.getState().t;
expect(before('sidebar.settings', 'Settings')).toBe('Settings');
await useLocaleStore.getState().hydrate();
const after = useLocaleStore.getState().t;
expect(after).not.toBe(before);
expect(after('sidebar.settings', 'Settings')).toBe('Einstellungen');
});
});
+10 -3
View File
@@ -48,6 +48,13 @@ function applyLayoutDirection(locale: LocaleCode): boolean {
return manager.isRTL !== rtl;
}
// A new `t` per locale: most components select only `t`, and a function
// that never changed identity left them in the device language after the
// saved one loaded on a cold start (tab labels, the folder title).
function translatorFor(locale: LocaleCode): TranslateFn {
return (key, fallback, params) => translate(locale, key, fallback, params);
}
export const useLocaleStore = create<LocaleState>((set, get) => ({
locale: detectDeviceLocale(),
override: null,
@@ -62,7 +69,7 @@ export const useLocaleStore = create<LocaleState>((set, get) => ({
const override = stored && isSupportedLocale(stored) ? stored : null;
const locale = override ?? detectDeviceLocale();
const directionChangePending = applyLayoutDirection(locale);
set({ override, locale, hydrated: true, directionChangePending });
set({ override, locale, hydrated: true, directionChangePending, t: translatorFor(locale) });
} catch {
set({ hydrated: true });
}
@@ -71,11 +78,11 @@ export const useLocaleStore = create<LocaleState>((set, get) => ({
setOverride: (override) => {
const locale = override ?? detectDeviceLocale();
const directionChangePending = applyLayoutDirection(locale);
set({ override, locale, directionChangePending });
set({ override, locale, directionChangePending, t: translatorFor(locale) });
void AsyncStorage.setItem(STORAGE_KEY, JSON.stringify({ override })).catch(() => {});
},
t: (key, fallback, params) => translate(get().locale, key, fallback, params),
t: translatorFor(detectDeviceLocale()),
}));
// Non-hook accessor for code outside React (background tasks, stores).