✨(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:
@@ -20,6 +20,7 @@ and this project adheres to
|
|||||||
- 🔒️ Manage unsafe attachments #663
|
- 🔒️ Manage unsafe attachments #663
|
||||||
- ✨(frontend) Custom block quote with export #646
|
- ✨(frontend) Custom block quote with export #646
|
||||||
- ✨(frontend) add open source section homepage #666
|
- ✨(frontend) add open source section homepage #666
|
||||||
|
- ✨(frontend) synchronize language-choice #401
|
||||||
|
|
||||||
## Changed
|
## Changed
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useEffect } from 'react';
|
|||||||
|
|
||||||
import { useCunninghamTheme } from '@/cunningham';
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
import { Auth } from '@/features/auth';
|
import { Auth } from '@/features/auth';
|
||||||
import '@/i18n/initI18n';
|
|
||||||
import { useResponsiveStore } from '@/stores/';
|
import { useResponsiveStore } from '@/stores/';
|
||||||
|
|
||||||
import { ConfigProvider } from './config/';
|
import { ConfigProvider } from './config/';
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { PropsWithChildren, useEffect } from 'react';
|
|||||||
|
|
||||||
import { Box } from '@/components';
|
import { Box } from '@/components';
|
||||||
import { useCunninghamTheme } from '@/cunningham';
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
|
import { useLanguageSynchronizer } from '@/features/language/hooks/useLanguageSynchronizer';
|
||||||
import { CrispProvider, PostHogProvider } from '@/services';
|
import { CrispProvider, PostHogProvider } from '@/services';
|
||||||
import { useSentryStore } from '@/stores/useSentryStore';
|
import { useSentryStore } from '@/stores/useSentryStore';
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ export const ConfigProvider = ({ children }: PropsWithChildren) => {
|
|||||||
const { data: conf } = useConfig();
|
const { data: conf } = useConfig();
|
||||||
const { setSentry } = useSentryStore();
|
const { setSentry } = useSentryStore();
|
||||||
const { setTheme } = useCunninghamTheme();
|
const { setTheme } = useCunninghamTheme();
|
||||||
|
const { synchronizeLanguage } = useLanguageSynchronizer();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!conf?.SENTRY_DSN) {
|
if (!conf?.SENTRY_DSN) {
|
||||||
@@ -29,6 +31,10 @@ export const ConfigProvider = ({ children }: PropsWithChildren) => {
|
|||||||
setTheme(conf.FRONTEND_THEME);
|
setTheme(conf.FRONTEND_THEME);
|
||||||
}, [conf?.FRONTEND_THEME, setTheme]);
|
}, [conf?.FRONTEND_THEME, setTheme]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void synchronizeLanguage();
|
||||||
|
}, [synchronizeLanguage]);
|
||||||
|
|
||||||
if (!conf) {
|
if (!conf) {
|
||||||
return (
|
return (
|
||||||
<Box $height="100vh" $width="100vw" $align="center" $justify="center">
|
<Box $height="100vh" $width="100vw" $align="center" $justify="center">
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user