mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-04 16:14:07 +00:00
fix: keep the desktop menus and tray in the language the app shows
This commit is contained in:
@@ -14,6 +14,14 @@ import { offerReturnedComposer, takeReturnedComposer } from '@/lib/popout/return
|
||||
const push = vi.fn();
|
||||
vi.mock('@/i18n/navigation', () => ({ useRouter: () => ({ push }) }));
|
||||
|
||||
// The catalog on screen: its language, and text that says which one it came from.
|
||||
const intl = vi.hoisted(() => ({ locale: 'en', prefix: '', messages: {} as object }));
|
||||
vi.mock('next-intl', () => ({
|
||||
useTranslations: () => (key: string) => `${intl.prefix}${key}`,
|
||||
useLocale: () => intl.locale,
|
||||
useMessages: () => intl.messages,
|
||||
}));
|
||||
|
||||
const canPrint = vi.fn(() => true);
|
||||
const canDownload = vi.fn(() => true);
|
||||
vi.mock('@/lib/device-policy', () => ({ canPrintAnywhere: () => canPrint(), canDownloadAnywhere: () => canDownload() }));
|
||||
@@ -68,6 +76,7 @@ function fakeBridge() {
|
||||
const original = useAuthStore.getState();
|
||||
|
||||
beforeEach(() => {
|
||||
Object.assign(intl, { locale: 'en', prefix: '', messages: {} });
|
||||
push.mockClear();
|
||||
resolveExternalLink.mockClear();
|
||||
resolveExternalLink.mockImplementation(async (ctx) => ctx.href);
|
||||
@@ -170,11 +179,26 @@ describe('DesktopHost', () => {
|
||||
it('says which languages are in use', async () => {
|
||||
const shell = fakeBridge();
|
||||
act(() => useLocaleStore.setState({ locale: 'de' }));
|
||||
intl.locale = 'de';
|
||||
render(<DesktopHost />);
|
||||
await waitFor(() => expect(shell.setLanguages).toHaveBeenCalled());
|
||||
expect(shell.setLanguages.mock.lastCall![0]).toMatchObject({ ui: 'de' });
|
||||
});
|
||||
|
||||
it('follows the text on screen, not only the stored choice', async () => {
|
||||
const shell = fakeBridge();
|
||||
// English chosen, but the first render still has the catalog the server picked from the OS.
|
||||
act(() => useLocaleStore.setState({ locale: 'en' }));
|
||||
Object.assign(intl, { locale: 'de', prefix: 'de:', messages: { de: true } });
|
||||
const view = render(<DesktopHost />);
|
||||
await waitFor(() => expect(shell.setLanguages).toHaveBeenCalled());
|
||||
// The chosen catalog lands a render later.
|
||||
Object.assign(intl, { locale: 'en', prefix: 'en:', messages: { en: true } });
|
||||
view.rerender(<DesktopHost />);
|
||||
await waitFor(() => expect(shell.setLanguages.mock.lastCall![0].labels.menuFile).toBe('en:desktopUi.menu.file'));
|
||||
expect(shell.setLanguages.mock.lastCall![0]).toMatchObject({ ui: 'en' });
|
||||
});
|
||||
|
||||
describe('Open Link in a native menu', () => {
|
||||
const openLink = (url: string) => `link.open:${JSON.stringify({ url, text: 'click' })}`;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useLocale, useMessages, useTranslations } from "next-intl";
|
||||
import { useStrictestDevices } from "@/hooks/use-device-policy";
|
||||
import { useRouter } from "@/i18n/navigation";
|
||||
import { openKeyboardShortcuts, parkAppCommand, requestAppCommand, type AppCommand } from "@/lib/app-commands";
|
||||
@@ -45,6 +45,8 @@ export function DesktopHost() {
|
||||
const singleKeys = useSettingsStore((s) => s.singleKeyShortcuts);
|
||||
const writingLanguage = useSettingsStore((s) => s.writingLanguage);
|
||||
const uiLocale = useLocaleStore((s) => s.locale);
|
||||
const activeLocale = useLocale();
|
||||
const messages = useMessages();
|
||||
const strictest = useStrictestDevices();
|
||||
const { restoreWindows } = useDesktopPrefs();
|
||||
const theme = useThemeStore((s) => s.theme);
|
||||
@@ -116,11 +118,14 @@ export function DesktopHost() {
|
||||
});
|
||||
}, [isAuthenticated, singleKeys, strictest]);
|
||||
|
||||
// Menus and spellcheck follow the interface.
|
||||
// Menus, the tray and spellcheck follow the interface: the language its text is in now,
|
||||
// with that text. The stored choice isn't it: on the first render the catalog can still
|
||||
// be the one the server picked from the OS language, and the chosen one lands a render
|
||||
// later. Going by the choice alone left the menus in the OS language for good.
|
||||
useEffect(() => {
|
||||
const ui = uiLocale || document.documentElement.lang;
|
||||
const ui = activeLocale || uiLocale || document.documentElement.lang;
|
||||
if (ui) setDesktopInterfaceLanguage(ui, shellLabels(tRef.current as unknown as Parameters<typeof shellLabels>[0]));
|
||||
}, [uiLocale]);
|
||||
}, [activeLocale, uiLocale, messages]);
|
||||
|
||||
// What the user writes in, whichever composer asks for it too.
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user