✨(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
|
||||
- ✨(frontend) Custom block quote with export #646
|
||||
- ✨(frontend) add open source section homepage #666
|
||||
- ✨(frontend) synchronize language-choice #401
|
||||
|
||||
## Changed
|
||||
|
||||
|
||||
@@ -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/';
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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