(app) get language from backend; set browser-detected language if null

- adds useLanguageSynchronizer hook to update the:
  1. frontend-language to the user-preference - if there is one.
  2. user-preference to the (browser-detected) frontend-language - otherwise.
This commit is contained in:
rvveber
2025-02-25 15:41:03 +01:00
committed by Samuel Paccoud
parent 7941fc91d5
commit a7944cce80
4 changed files with 89 additions and 1 deletions

View File

@@ -20,6 +20,7 @@ and this project adheres to
- 🔒️ Manage unsafe attachments #663
- ✨(frontend) Custom block quote with export #646
- ✨(frontend) add open source section homepage #666
- ✨(frontend) synchronize language-choice #401
## Changed

View File

@@ -4,7 +4,6 @@ import { useEffect } from 'react';
import { useCunninghamTheme } from '@/cunningham';
import { Auth } from '@/features/auth';
import '@/i18n/initI18n';
import { useResponsiveStore } from '@/stores/';
import { ConfigProvider } from './config/';

View File

@@ -3,6 +3,7 @@ import { PropsWithChildren, useEffect } from 'react';
import { Box } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { useLanguageSynchronizer } from '@/features/language/hooks/useLanguageSynchronizer';
import { CrispProvider, PostHogProvider } from '@/services';
import { useSentryStore } from '@/stores/useSentryStore';
@@ -12,6 +13,7 @@ export const ConfigProvider = ({ children }: PropsWithChildren) => {
const { data: conf } = useConfig();
const { setSentry } = useSentryStore();
const { setTheme } = useCunninghamTheme();
const { synchronizeLanguage } = useLanguageSynchronizer();
useEffect(() => {
if (!conf?.SENTRY_DSN) {
@@ -29,6 +31,10 @@ export const ConfigProvider = ({ children }: PropsWithChildren) => {
setTheme(conf.FRONTEND_THEME);
}, [conf?.FRONTEND_THEME, setTheme]);
useEffect(() => {
void synchronizeLanguage();
}, [synchronizeLanguage]);
if (!conf) {
return (
<Box $height="100vh" $width="100vw" $align="center" $justify="center">

View File

@@ -0,0 +1,82 @@
import { useCallback, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useConfig } from '@/core';
import { useAuthQuery } from '@/features/auth/api';
import { useChangeUserLanguage } from '@/features/language/api/useChangeUserLanguage';
import { getMatchingLocales } from '@/features/language/utils/locale';
import { availableFrontendLanguages } from '@/i18n/initI18n';
export const useLanguageSynchronizer = () => {
const { data: conf, isSuccess: confInitialized } = useConfig();
const { data: user, isSuccess: userInitialized } = useAuthQuery();
const { i18n } = useTranslation();
const { mutateAsync: changeUserLanguage } = useChangeUserLanguage();
const languageSynchronizing = useRef(false);
const availableBackendLanguages = useMemo(() => {
return conf?.LANGUAGES.map(([locale]) => locale);
}, [conf]);
const synchronizeLanguage = useCallback(
async (direction?: 'toBackend' | 'toFrontend') => {
if (
languageSynchronizing.current ||
!userInitialized ||
!confInitialized ||
!availableBackendLanguages ||
!availableFrontendLanguages
) {
return;
}
languageSynchronizing.current = true;
try {
const userPreferredLanguages = user.language ? [user.language] : [];
const setOrDetectedLanguages = i18n.languages;
// Default direction depends on whether a user already has a language preference
direction =
direction ??
(userPreferredLanguages.length ? 'toFrontend' : 'toBackend');
if (direction === 'toBackend') {
// Update user's preference from frontends's language
const closestBackendLanguage =
getMatchingLocales(
availableBackendLanguages,
setOrDetectedLanguages,
)[0] || availableBackendLanguages[0];
await changeUserLanguage({
userId: user.id,
language: closestBackendLanguage,
});
} else {
// Update frontends's language from user's preference
const closestFrontendLanguage =
getMatchingLocales(
availableFrontendLanguages,
userPreferredLanguages,
)[0] || availableFrontendLanguages[0];
if (i18n.resolvedLanguage !== closestFrontendLanguage) {
await i18n.changeLanguage(closestFrontendLanguage);
}
}
} catch (error) {
console.error('Error synchronizing language', error);
} finally {
languageSynchronizing.current = false;
}
},
[
i18n,
user,
userInitialized,
confInitialized,
availableBackendLanguages,
changeUserLanguage,
],
);
return { synchronizeLanguage };
};