Skip to content

Commit e158dc3

Browse files
authored
Fix translation updates and improve LocaleSwitcher usability (#686)
### Summary & Motivation Fix an issue where not all translations using `t(..)` were updated when changing the language, causing some text to remain in the previous language until a full page reload. This ensures the correct language is applied immediately across the entire UI. Additionally, improve the **LocaleSwitcher** by enabling language changes via keyboard navigation and updating its styling to align with the rest of the UI. Spacing adjustments have also been made to the top menu buttons to prevent border cropping. ### Checklist - [x] I have added tests, or done manual regression tests - [x] I have updated the documentation, if necessary
2 parents 8b90366 + 13b2403 commit e158dc3

File tree

4 files changed

+38
-42
lines changed

4 files changed

+38
-42
lines changed

application/account-management/WebApp/shared/components/topMenu/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export function TopMenu({ children }: Readonly<TopMenuProps>) {
2222
{children}
2323
</Breadcrumbs>
2424
<div className="flex flex-row gap-6 items-center">
25-
<span className="hidden sm:flex">
25+
<span className="hidden sm:flex gap-2">
2626
<ThemeModeSelector />
2727
<Button variant="icon" aria-label={t`Help`}>
2828
<LifeBuoyIcon size={20} />

application/back-office/WebApp/shared/components/topMenu/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export function TopMenu({ children }: Readonly<TopMenuProps>) {
2424
{children}
2525
</Breadcrumbs>
2626
<div className="flex flex-row gap-6 items-center">
27-
<span className="hidden sm:flex">
27+
<span className="hidden sm:flex gap-2">
2828
<ThemeModeSelector />
2929
<Button variant="icon" aria-label={t`Help`}>
3030
<LifeBuoyIcon size={20} />
Lines changed: 28 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
1-
import { LanguagesIcon } from "lucide-react";
1+
import { LanguagesIcon, CheckIcon } from "lucide-react";
22
import { type Locale, translationContext } from "./TranslationContext";
3-
import { use, useContext, useMemo, useState } from "react";
3+
import { use, useContext, useMemo } from "react";
44
import { useLingui } from "@lingui/react";
55
import { Button } from "@repo/ui/components/Button";
6-
import { ListBox, ListBoxItem } from "@repo/ui/components/ListBox";
7-
import { Popover } from "@repo/ui/components/Popover";
8-
import { DialogTrigger } from "@repo/ui/components/Dialog";
9-
import type { Selection } from "react-aria-components";
6+
import { Menu, MenuItem, MenuTrigger } from "@repo/ui/components/Menu";
107
import { AuthenticationContext } from "@repo/infrastructure/auth/AuthenticationProvider";
118
import { preferredLocaleKey } from "./constants";
9+
import type { Key } from "@react-types/shared";
1210

1311
export function LocaleSwitcher() {
14-
const [isOpen, setIsOpen] = useState(false);
1512
const { setLocale, getLocaleInfo, locales } = use(translationContext);
1613
const { i18n } = useLingui();
1714
const { userInfo } = useContext(AuthenticationContext);
@@ -25,52 +22,45 @@ export function LocaleSwitcher() {
2522
[locales, getLocaleInfo]
2623
);
2724

28-
const handleLocaleChange = async (selection: Selection) => {
29-
const newLocale = [...selection][0] as Locale;
30-
if (newLocale != null) {
25+
const handleLocaleChange = (key: Key) => {
26+
const locale = key.toString() as Locale;
27+
if (locale !== currentLocale) {
3128
if (userInfo?.isAuthenticated) {
32-
await fetch("/api/account-management/users/change-locale", {
29+
void fetch("/api/account-management/users/me/change-locale", {
3330
method: "PUT",
3431
headers: { "Content-Type": "application/json" },
35-
body: JSON.stringify({ locale: newLocale })
32+
body: JSON.stringify({ locale })
3633
})
3734
.then(async (_) => {
38-
await setLocale(newLocale);
39-
localStorage.setItem(preferredLocaleKey, newLocale);
35+
await setLocale(locale);
36+
localStorage.setItem(preferredLocaleKey, locale);
4037
})
4138
.catch((error) => console.error("Failed to update locale:", error));
4239
} else {
43-
await setLocale(newLocale);
44-
localStorage.setItem(preferredLocaleKey, newLocale);
40+
void setLocale(locale).then(() => {
41+
localStorage.setItem(preferredLocaleKey, locale);
42+
});
4543
}
46-
47-
setIsOpen(false);
4844
}
4945
};
5046

5147
const currentLocale = i18n.locale as Locale;
5248

5349
return (
54-
<DialogTrigger onOpenChange={setIsOpen} isOpen={isOpen}>
55-
<Button variant="icon">
56-
<LanguagesIcon />
50+
<MenuTrigger>
51+
<Button variant="icon" aria-label="Select language">
52+
<LanguagesIcon className="h-5 w-5" />
5753
</Button>
58-
<Popover>
59-
<ListBox
60-
selectionMode="single"
61-
selectionBehavior="replace"
62-
onSelectionChange={handleLocaleChange}
63-
selectedKeys={[currentLocale]}
64-
className="border-none px-4 py-2"
65-
aria-label="Select a language"
66-
>
67-
{items.map((item) => (
68-
<ListBoxItem key={item.id} id={item.id}>
69-
{item.label}
70-
</ListBoxItem>
71-
))}
72-
</ListBox>
73-
</Popover>
74-
</DialogTrigger>
54+
<Menu onAction={handleLocaleChange} aria-label="Select language">
55+
{items.map((item) => (
56+
<MenuItem key={item.id} id={item.id} textValue={item.label}>
57+
<div className="flex items-center gap-2">
58+
<span>{item.label}</span>
59+
{item.id === currentLocale && <CheckIcon className="h-4 w-4 ml-auto" />}
60+
</div>
61+
</MenuItem>
62+
))}
63+
</Menu>
64+
</MenuTrigger>
7565
);
7666
}

application/shared-webapp/infrastructure/translations/Translation.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useMemo } from "react";
1+
import { useMemo, useState } from "react";
22
import { I18nProvider } from "@lingui/react";
33
import { i18n, type Messages } from "@lingui/core";
44
import localeMap from "./i18n.config.json";
@@ -118,19 +118,25 @@ type TranslationProviderProps = {
118118
};
119119

120120
function TranslationProvider({ children, translation }: Readonly<TranslationProviderProps>) {
121+
const [currentLocale, setCurrentLocale] = useState(i18n.locale);
122+
121123
const value: TranslationContext = useMemo(
122124
() => ({
123125
setLocale: async (locale: string) => {
124126
await translation.dynamicActivate(locale);
127+
setCurrentLocale(locale); // Update state to force re-render
125128
},
126129
locales: translation.locales,
127130
getLocaleInfo: translation.getLocaleInfo
128131
}),
129132
[translation]
130133
);
134+
131135
return (
132136
<TranslationContextProvider value={value}>
133-
<I18nProvider i18n={i18n}>{children}</I18nProvider>
137+
<I18nProvider key={currentLocale} i18n={i18n}>
138+
{children}
139+
</I18nProvider>
134140
</TranslationContextProvider>
135141
);
136142
}

0 commit comments

Comments
 (0)