Skip to content

Commit 2e7f552

Browse files
committed
♻️(frontend) Refactor language-related code
- Refactors useTranslationsCustomizer to useCustomTranslations - Refactors useCustomTranslations to use useCustomTranslationsQuery - Refactors "useLanguageSynchronizer" to "useSynchronizedLanguage" - Removes "useChangeUserLanguage" - To change the user language, use "useAuthMutation" instead - Refactors "LanguagePicker" to better reflect its component role - Refactors "LanguagePicker" to use "useSynchronizedLangue" Signed-off-by: Robin Weber <[email protected]>
1 parent 55a7fd7 commit 2e7f552

File tree

13 files changed

+167
-231
lines changed

13 files changed

+167
-231
lines changed

src/frontend/apps/impress/src/features/auth/api/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ export interface User {
1111
email: string;
1212
full_name: string;
1313
short_name: string;
14-
language: string;
14+
language?: string;
1515
}

src/frontend/apps/impress/src/features/header/components/Header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import IconDocs from '@/assets/icons/icon-docs.svg';
55
import { Box, StyledLink } from '@/components/';
66
import { useCunninghamTheme } from '@/cunningham';
77
import { ButtonLogin } from '@/features/auth';
8-
import { LanguagePicker } from '@/features/language';
8+
import { LanguagePicker } from '@/features/language/components';
99
import { useResponsiveStore } from '@/stores';
1010

1111
import { HEADER_HEIGHT } from '../conf';

src/frontend/apps/impress/src/features/home/components/HomeHeader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Box } from '@/components';
66
import { useCunninghamTheme } from '@/cunningham';
77
import { ButtonTogglePanel, Title } from '@/features/header/';
88
import { LaGaufre } from '@/features/header/components/LaGaufre';
9-
import { LanguagePicker } from '@/features/language';
9+
import { LanguagePicker } from '@/features/language/components';
1010
import { useResponsiveStore } from '@/stores';
1111

1212
export const HEADER_HEIGHT = 91;

src/frontend/apps/impress/src/features/language/api/useChangeUserLanguage.tsx

Lines changed: 0 additions & 45 deletions
This file was deleted.

src/frontend/apps/impress/src/features/language/LanguagePicker.tsx renamed to src/frontend/apps/impress/src/features/language/components/LanguagePicker.tsx

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,31 @@
1-
import { Settings } from 'luxon';
21
import { useMemo } from 'react';
32
import { useTranslation } from 'react-i18next';
43
import { css } from 'styled-components';
54

65
import { DropdownMenu, Icon, Text } from '@/components/';
76
import { useConfig } from '@/core';
8-
import { useAuthQuery } from '@/features/auth';
9-
10-
import { useLanguageSynchronizer } from './hooks/useLanguageSynchronizer';
11-
import { getMatchingLocales } from './utils/locale';
7+
import {
8+
getMatchingLocales,
9+
useSynchronizedLanguage,
10+
} from '@/features/language';
1211

1312
export const LanguagePicker = () => {
1413
const { t, i18n } = useTranslation();
1514
const { data: conf } = useConfig();
16-
const { data: user } = useAuthQuery();
17-
const { synchronizeLanguage } = useLanguageSynchronizer();
18-
const language = i18n.languages[0];
19-
Settings.defaultLocale = language;
15+
const { changeLanguageSynchronized } = useSynchronizedLanguage();
16+
const language = i18n.language;
2017

2118
// Compute options for dropdown
2219
const optionsPicker = useMemo(() => {
2320
const backendOptions = conf?.LANGUAGES ?? [[language, language]];
24-
return backendOptions.map(([backendLocale, label]) => {
25-
// Determine if the option is selected
26-
const isSelected =
27-
getMatchingLocales([backendLocale], [language]).length > 0;
28-
// Define callback for updating both frontend and backend languages
29-
const callback = () => {
30-
i18n
31-
.changeLanguage(backendLocale)
32-
.then(() => {
33-
if (conf?.LANGUAGES && user) {
34-
synchronizeLanguage(conf.LANGUAGES, user, 'toBackend');
35-
}
36-
})
37-
.catch((err) => {
38-
console.error('Error changing language', err);
39-
});
21+
return backendOptions.map(([backendLocale, backendLabel]) => {
22+
return {
23+
label: backendLabel,
24+
isSelected: getMatchingLocales([backendLocale], [language]).length > 0,
25+
callback: () => changeLanguageSynchronized(backendLocale),
4026
};
41-
return { label, isSelected, callback };
4227
});
43-
}, [conf?.LANGUAGES, i18n, language, synchronizeLanguage, user]);
28+
}, [changeLanguageSynchronized, conf?.LANGUAGES, language]);
4429

4530
// Extract current language label for display
4631
const currentLanguageLabel =
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './LanguagePicker';
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './useSynchronizedLanguage';
2+
export * from './useCustomTranslations';
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Resource } from 'i18next';
2+
import { useCallback, useEffect, useMemo } from 'react';
3+
import { useTranslation } from 'react-i18next';
4+
5+
import { useConfig } from '@/core';
6+
7+
export const useCustomTranslations = () => {
8+
const { i18n } = useTranslation();
9+
const { data: currentConfig } = useConfig();
10+
11+
const currentCustomTranslations: Resource = useMemo(
12+
() => currentConfig?.theme_customization?.translations || {},
13+
[currentConfig],
14+
);
15+
16+
// Overwrite translations with a resource
17+
const customizeTranslations = useCallback(
18+
(currentCustomTranslations: Resource) => {
19+
Object.entries(currentCustomTranslations).forEach(([lng, namespaces]) => {
20+
Object.entries(namespaces).forEach(([ns, value]) => {
21+
i18n.addResourceBundle(lng, ns, value, true, true);
22+
});
23+
});
24+
// trigger re-render
25+
if (Object.entries(currentCustomTranslations).length > 0) {
26+
void i18n.changeLanguage(i18n.language);
27+
}
28+
},
29+
[i18n],
30+
);
31+
32+
useEffect(() => {
33+
if (currentCustomTranslations) {
34+
customizeTranslations(currentCustomTranslations);
35+
}
36+
}, [currentCustomTranslations, customizeTranslations]);
37+
38+
return {
39+
currentCustomTranslations,
40+
customizeTranslations,
41+
};
42+
};

src/frontend/apps/impress/src/features/language/hooks/useLanguageSynchronizer.ts

Lines changed: 0 additions & 70 deletions
This file was deleted.
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import debug from 'debug';
2+
import { useCallback, useEffect, useMemo } from 'react';
3+
import { useTranslation } from 'react-i18next';
4+
5+
import { useConfig } from '@/core/config/api/useConfig';
6+
import { User, useAuthMutation, useAuthQuery } from '@/features/auth';
7+
import { getMatchingLocales } from '@/features/language/utils/locale';
8+
9+
const log = debug('features:language');
10+
11+
export const useSynchronizedLanguage = () => {
12+
const { i18n } = useTranslation();
13+
const { data: currentUser } = useAuthQuery();
14+
const { update: updateUser } = useAuthMutation();
15+
const { data: config } = useConfig();
16+
17+
const availableFrontendLanguages = useMemo(
18+
() => Object.keys(i18n?.options?.resources || { en: '<- fallback' }),
19+
[i18n],
20+
);
21+
const availableBackendLanguages = useMemo(
22+
() => config?.LANGUAGES?.map(([locale]) => locale) || [],
23+
[config?.LANGUAGES],
24+
);
25+
const currentFrontendLanguage = useMemo(
26+
() => i18n.resolvedLanguage || i18n.language,
27+
[i18n],
28+
);
29+
const currentBackendLanguage = useMemo(
30+
() => currentUser?.language,
31+
[currentUser],
32+
);
33+
34+
const changeBackendLanguage = useCallback(
35+
(language: string, user?: User) => {
36+
const closestBackendLanguage = getMatchingLocales(
37+
availableBackendLanguages,
38+
[language],
39+
)[0];
40+
41+
if (!user || user.language === closestBackendLanguage) {
42+
return;
43+
}
44+
45+
log('Updating backend language (%O)', {
46+
requested: language,
47+
from: user.language,
48+
to: closestBackendLanguage,
49+
});
50+
void updateUser({ ...user, language: closestBackendLanguage });
51+
},
52+
[availableBackendLanguages, updateUser],
53+
);
54+
55+
const changeFrontendLanguage = useCallback(
56+
(language: string) => {
57+
const closestFrontendLanguage = getMatchingLocales(
58+
availableFrontendLanguages,
59+
[language],
60+
)[0];
61+
if (i18n.resolvedLanguage === closestFrontendLanguage) {
62+
return;
63+
}
64+
65+
log('Updating frontend language (%O)', {
66+
requested: language,
67+
from: i18n.resolvedLanguage,
68+
to: closestFrontendLanguage,
69+
});
70+
void i18n.changeLanguage(closestFrontendLanguage);
71+
},
72+
[availableFrontendLanguages, i18n],
73+
);
74+
75+
const changeLanguageSynchronized = useCallback(
76+
(language: string, user?: User) => {
77+
changeFrontendLanguage(language);
78+
changeBackendLanguage(language, user ?? currentUser);
79+
},
80+
[changeBackendLanguage, changeFrontendLanguage, currentUser],
81+
);
82+
83+
useEffect(() => {
84+
if (currentBackendLanguage) {
85+
changeLanguageSynchronized(currentBackendLanguage);
86+
} else if (currentFrontendLanguage) {
87+
changeLanguageSynchronized(currentFrontendLanguage);
88+
}
89+
}, [
90+
currentBackendLanguage,
91+
currentFrontendLanguage,
92+
changeLanguageSynchronized,
93+
currentUser,
94+
]);
95+
96+
return {
97+
changeLanguageSynchronized,
98+
changeFrontendLanguage,
99+
changeBackendLanguage,
100+
availableFrontendLanguages,
101+
availableBackendLanguages,
102+
currentFrontendLanguage,
103+
currentBackendLanguage,
104+
};
105+
};

0 commit comments

Comments
 (0)