Skip to content

Commit

Permalink
Fix translation updates and improve LocaleSwitcher usability (#686)
Browse files Browse the repository at this point in the history
### 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
  • Loading branch information
tjementum authored Jan 27, 2025
2 parents 8b90366 + 13b2403 commit e158dc3
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function TopMenu({ children }: Readonly<TopMenuProps>) {
{children}
</Breadcrumbs>
<div className="flex flex-row gap-6 items-center">
<span className="hidden sm:flex">
<span className="hidden sm:flex gap-2">
<ThemeModeSelector />
<Button variant="icon" aria-label={t`Help`}>
<LifeBuoyIcon size={20} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function TopMenu({ children }: Readonly<TopMenuProps>) {
{children}
</Breadcrumbs>
<div className="flex flex-row gap-6 items-center">
<span className="hidden sm:flex">
<span className="hidden sm:flex gap-2">
<ThemeModeSelector />
<Button variant="icon" aria-label={t`Help`}>
<LifeBuoyIcon size={20} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import { LanguagesIcon } from "lucide-react";
import { LanguagesIcon, CheckIcon } from "lucide-react";
import { type Locale, translationContext } from "./TranslationContext";
import { use, useContext, useMemo, useState } from "react";
import { use, useContext, useMemo } from "react";
import { useLingui } from "@lingui/react";
import { Button } from "@repo/ui/components/Button";
import { ListBox, ListBoxItem } from "@repo/ui/components/ListBox";
import { Popover } from "@repo/ui/components/Popover";
import { DialogTrigger } from "@repo/ui/components/Dialog";
import type { Selection } from "react-aria-components";
import { Menu, MenuItem, MenuTrigger } from "@repo/ui/components/Menu";
import { AuthenticationContext } from "@repo/infrastructure/auth/AuthenticationProvider";
import { preferredLocaleKey } from "./constants";
import type { Key } from "@react-types/shared";

export function LocaleSwitcher() {
const [isOpen, setIsOpen] = useState(false);
const { setLocale, getLocaleInfo, locales } = use(translationContext);
const { i18n } = useLingui();
const { userInfo } = useContext(AuthenticationContext);
Expand All @@ -25,52 +22,45 @@ export function LocaleSwitcher() {
[locales, getLocaleInfo]
);

const handleLocaleChange = async (selection: Selection) => {
const newLocale = [...selection][0] as Locale;
if (newLocale != null) {
const handleLocaleChange = (key: Key) => {
const locale = key.toString() as Locale;
if (locale !== currentLocale) {
if (userInfo?.isAuthenticated) {
await fetch("/api/account-management/users/change-locale", {
void fetch("/api/account-management/users/me/change-locale", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ locale: newLocale })
body: JSON.stringify({ locale })
})
.then(async (_) => {
await setLocale(newLocale);
localStorage.setItem(preferredLocaleKey, newLocale);
await setLocale(locale);
localStorage.setItem(preferredLocaleKey, locale);
})
.catch((error) => console.error("Failed to update locale:", error));
} else {
await setLocale(newLocale);
localStorage.setItem(preferredLocaleKey, newLocale);
void setLocale(locale).then(() => {
localStorage.setItem(preferredLocaleKey, locale);
});
}

setIsOpen(false);
}
};

const currentLocale = i18n.locale as Locale;

return (
<DialogTrigger onOpenChange={setIsOpen} isOpen={isOpen}>
<Button variant="icon">
<LanguagesIcon />
<MenuTrigger>
<Button variant="icon" aria-label="Select language">
<LanguagesIcon className="h-5 w-5" />
</Button>
<Popover>
<ListBox
selectionMode="single"
selectionBehavior="replace"
onSelectionChange={handleLocaleChange}
selectedKeys={[currentLocale]}
className="border-none px-4 py-2"
aria-label="Select a language"
>
{items.map((item) => (
<ListBoxItem key={item.id} id={item.id}>
{item.label}
</ListBoxItem>
))}
</ListBox>
</Popover>
</DialogTrigger>
<Menu onAction={handleLocaleChange} aria-label="Select language">
{items.map((item) => (
<MenuItem key={item.id} id={item.id} textValue={item.label}>
<div className="flex items-center gap-2">
<span>{item.label}</span>
{item.id === currentLocale && <CheckIcon className="h-4 w-4 ml-auto" />}
</div>
</MenuItem>
))}
</Menu>
</MenuTrigger>
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMemo } from "react";
import { useMemo, useState } from "react";
import { I18nProvider } from "@lingui/react";
import { i18n, type Messages } from "@lingui/core";
import localeMap from "./i18n.config.json";
Expand Down Expand Up @@ -118,19 +118,25 @@ type TranslationProviderProps = {
};

function TranslationProvider({ children, translation }: Readonly<TranslationProviderProps>) {
const [currentLocale, setCurrentLocale] = useState(i18n.locale);

const value: TranslationContext = useMemo(
() => ({
setLocale: async (locale: string) => {
await translation.dynamicActivate(locale);
setCurrentLocale(locale); // Update state to force re-render
},
locales: translation.locales,
getLocaleInfo: translation.getLocaleInfo
}),
[translation]
);

return (
<TranslationContextProvider value={value}>
<I18nProvider i18n={i18n}>{children}</I18nProvider>
<I18nProvider key={currentLocale} i18n={i18n}>
{children}
</I18nProvider>
</TranslationContextProvider>
);
}

0 comments on commit e158dc3

Please sign in to comment.