all repos — caroster @ a24828bb07e615f04a4c38d7bf5a6b21b18e31de

[Octree] Group carpool to your event https://caroster.io

fix: 🌐 Improve languages management
Tim Izzo tim@octree.ch
Mon, 05 Sep 2022 09:19:13 +0000
commit

a24828bb07e615f04a4c38d7bf5a6b21b18e31de

parent

0ef5db83c821a22b6f546115c9adf20636563c79

M frontend/containers/GenericMenu/Action.tsxfrontend/containers/GenericMenu/Action.tsx

@@ -8,7 +8,7 @@ export type ActionType = {

divider?: boolean; label: JSX.Element | string; id: string; - onClick: () => void; + onClick?: () => void; }; interface Props {
M frontend/containers/GenericMenu/index.tsxfrontend/containers/GenericMenu/index.tsx

@@ -4,9 +4,16 @@ import useAuthStore from '../../stores/useAuthStore';

import useProfile from '../../hooks/useProfile'; import useSettings from '../../hooks/useSettings'; import Languages from '../Languages/MenuItem'; -import Action from './Action'; +import Action, {ActionType} from './Action'; + +interface Props { + anchorEl: Element; + setAnchorEl: (el: Element) => void; + actions: ActionType[]; +} -const GenericMenu = ({anchorEl, setAnchorEl, actions = []}) => { +const GenericMenu = (props: Props) => { + const {anchorEl, setAnchorEl, actions = []} = props; const {t} = useTranslation(); const settings = useSettings(); const logout = useAuthStore(s => s.logout);
M frontend/containers/Languages/Icon.tsxfrontend/containers/Languages/Icon.tsx

@@ -5,15 +5,14 @@ import Icon from '@material-ui/core/Icon';

import Menu from '@material-ui/core/Menu'; import MenuItem from '@material-ui/core/MenuItem'; import {useTranslation} from 'react-i18next'; -import {Enum_Userspermissionsuser_Lang} from '../../generated/graphql'; +import {Enum_Userspermissionsuser_Lang as Lang} from '../../generated/graphql'; import withLanguagesSelection, { LanguageSelectionComponentProps, } from './withLanguagesSelection'; const IconLanguageSelection = ({ language, - setLanguage, - onConfirmCallback, + onChangeLang, displayMenu, }: LanguageSelectionComponentProps & {displayMenu?: boolean}) => { const {t} = useTranslation();

@@ -23,11 +22,9 @@ const handleClick = event => {

setAnchorEl(event.currentTarget); }; - const onConfirm = (lang: Enum_Userspermissionsuser_Lang) => { - setLanguage(lang); + const onConfirm = (lang: Lang) => { setAnchorEl(null); - - onConfirmCallback(lang); + onChangeLang(lang); }; return (

@@ -55,12 +52,12 @@ open={Boolean(anchorEl)}

onClose={() => setAnchorEl(null)} > <MenuItem - disabled={language === Enum_Userspermissionsuser_Lang.Fr} - onClick={() => onConfirm(Enum_Userspermissionsuser_Lang.Fr)} + disabled={language === Lang.Fr} + onClick={() => onConfirm(Lang.Fr)} >{t`languages.fr`}</MenuItem> <MenuItem - disabled={language === Enum_Userspermissionsuser_Lang.En} - onClick={() => onConfirm(Enum_Userspermissionsuser_Lang.En)} + disabled={language === Lang.En} + onClick={() => onConfirm(Lang.En)} >{t`languages.en`}</MenuItem> </Menu> </>
M frontend/containers/Languages/MenuItem.tsxfrontend/containers/Languages/MenuItem.tsx

@@ -3,17 +3,14 @@ import MenuList from '@material-ui/core/MenuList';

import MenuItem from '@material-ui/core/MenuItem'; import {makeStyles} from '@material-ui/core/styles'; import {useTranslation} from 'react-i18next'; -import { - Enum_Userspermissionsuser_Lang, -} from '../../generated/graphql'; +import {Enum_Userspermissionsuser_Lang as Lang} from '../../generated/graphql'; import withLanguagesSelection, { LanguageSelectionComponentProps, } from './withLanguagesSelection'; const Languages = ({ language, - setLanguage, - onConfirmCallback, + onChangeLang, }: LanguageSelectionComponentProps) => { const {t} = useTranslation(); const [isSelecting, setSelecting] = useState(false);

@@ -23,11 +20,9 @@ const handleClick = event => {

setSelecting(!isSelecting); }; - const onConfirm = (lang: Enum_Userspermissionsuser_Lang) => { - setLanguage(lang); + const onConfirm = (lang: Lang) => { setSelecting(false); - - onConfirmCallback(lang); + onChangeLang(lang); }; return (

@@ -35,12 +30,12 @@ <>

<MenuItem onClick={handleClick}>{t('menu.language')}</MenuItem> <MenuList className={languagesList} dense> <MenuItem - disabled={language === Enum_Userspermissionsuser_Lang.Fr} - onClick={() => onConfirm(Enum_Userspermissionsuser_Lang.Fr)} + disabled={language === Lang.Fr} + onClick={() => onConfirm(Lang.Fr)} >{t`languages.fr`}</MenuItem> <MenuItem - disabled={language === Enum_Userspermissionsuser_Lang.En} - onClick={() => onConfirm(Enum_Userspermissionsuser_Lang.En)} + disabled={language === Lang.En} + onClick={() => onConfirm(Lang.En)} >{t`languages.en`}</MenuItem> </MenuList> </>
M frontend/containers/Languages/withLanguagesSelection.tsxfrontend/containers/Languages/withLanguagesSelection.tsx

@@ -1,17 +1,15 @@

-import {useEffect} from 'react'; -import useLangStore from '../../stores/useLangStore'; import useProfile from '../../hooks/useProfile'; import { useUpdateMeMutation, - Enum_Userspermissionsuser_Lang, + Enum_Userspermissionsuser_Lang as Lang, } from '../../generated/graphql'; - -type LangFunction = (lang: Enum_Userspermissionsuser_Lang) => void; +import {useTranslation} from 'react-i18next'; +import {changeLang} from '../../lib/i18n'; export interface LanguageSelectionComponentProps { - language: Enum_Userspermissionsuser_Lang; - setLanguage: LangFunction; - onConfirmCallback: LangFunction; + language: Lang; + Lang; + onChangeLang: (lang: Lang) => void; } const withLanguagesSelection =

@@ -19,18 +17,15 @@ (

LanguageSelectionComponent: ( args: LanguageSelectionComponentProps ) => JSX.Element - ) => - (props) => { - const language = useLangStore(s => s.language); - const setLanguage = useLangStore(s => s.setLanguage); - const {profile, connected} = useProfile(); + ) => + props => { + const {connected} = useProfile(); const [updateProfile] = useUpdateMeMutation(); - - useEffect(() => { - if (profile?.lang) setLanguage(profile.lang); - }, [profile]); + const {i18n} = useTranslation(); + const language = i18n.language.toUpperCase(); - const onConfirmCallback = (lang: Enum_Userspermissionsuser_Lang) => { + const onChangeLang = (lang: Lang) => { + changeLang(lang); if (connected) { updateProfile({ variables: {

@@ -45,8 +40,7 @@

return ( <LanguageSelectionComponent language={language} - setLanguage={setLanguage} - onConfirmCallback={onConfirmCallback} + onChangeLang={onChangeLang} {...props} /> );
M frontend/containers/SignUpForm/index.tsxfrontend/containers/SignUpForm/index.tsx

@@ -9,17 +9,15 @@ import Typography from '@material-ui/core/Typography';

import CircularProgress from '@material-ui/core/CircularProgress'; import {makeStyles} from '@material-ui/core/styles'; import useToastsStore from '../../stores/useToastStore'; -import useLangStore from '../../stores/useLangStore'; import {useRegisterMutation} from '../../generated/graphql'; const SignUp = () => { - const {t} = useTranslation(); + const {t, i18n} = useTranslation(); const classes = useStyles(); const addToast = useToastsStore(s => s.addToast); const router = useRouter(); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(''); - const lang = useLangStore(s => s.language); const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const [email, setEmail] = useState('');

@@ -38,6 +36,7 @@ e.preventDefault?.();

if (isLoading) return; setIsLoading(true); try { + const lang = i18n.language.toUpperCase(); await register({ variables: { user: {
M frontend/hooks/useSettings.tsfrontend/hooks/useSettings.ts

@@ -1,10 +1,11 @@

+import {useTranslation} from 'react-i18next'; import {useSettingQuery, SettingQuery} from '../generated/graphql'; -import useLangStore from '../stores/useLangStore'; + +const defaulData: SettingQuery = {}; const useSettings = () => { - const language = useLangStore(s => s.language); - const locale = {FR: 'fr', EN: 'en'}[language]; - const defaulData: SettingQuery = {}; + const {i18n} = useTranslation(); + const locale = i18n.language; const {data = defaulData} = useSettingQuery({variables: {locale}}); return data?.setting?.data?.attributes; };
D frontend/i18n.ts

@@ -1,39 +0,0 @@

-import i18n from 'i18next'; -import {initReactI18next} from 'react-i18next'; -import translationFr from './locales/fr.json'; -import translationEn from './locales/en.json'; -import {Enum_Userspermissionsuser_Lang} from './generated/graphql'; - -const resources = { - fr: { - translation: translationFr, - }, - en: { - translation: translationEn, - }, -}; - -export const getUserLng = () => { - if ( - typeof window !== 'undefined' && - typeof window.navigator !== 'undefined' - ) { - if (navigator.language === 'fr' || navigator.language.includes('fr-')) { - return Enum_Userspermissionsuser_Lang.Fr - } - } - return Enum_Userspermissionsuser_Lang.En -}; - -i18n - .use(initReactI18next) // passes i18n down to react-i18next - .init({ - resources, - lng: getUserLng(), - supportedLngs: ['fr', 'en'], - interpolation: { - escapeValue: false, // react already safes from xss - }, - }); - -export default i18n;
A frontend/lib/i18n.ts

@@ -0,0 +1,62 @@

+import i18n from 'i18next'; +import {initReactI18next} from 'react-i18next'; +import 'moment/locale/fr-ch'; +import moment from 'moment'; +import translationFr from '../locales/fr.json'; +import translationEn from '../locales/en.json'; +import {Enum_Userspermissionsuser_Lang as Lang} from '../generated/graphql'; + +const STORAGE_KEY = 'i18n-lang'; + +type AvailableLang = 'en' | 'fr'; + +const resources = { + fr: { + translation: translationFr, + }, + en: { + translation: translationEn, + }, +}; + +export const getNavigatorLang = (): AvailableLang => { + if (typeof localStorage !== 'undefined') { + const storedLang = localStorage.getItem(STORAGE_KEY); + if (storedLang) return storedLang as AvailableLang; + } + if (typeof window !== 'undefined' && typeof window.navigator !== 'undefined') + if (navigator.language === 'fr' || navigator.language.includes('fr-')) + return 'fr'; + return 'en'; +}; + +i18n + .use(initReactI18next) // passes i18n down to react-i18next + .init({ + resources, + lng: getNavigatorLang(), + supportedLngs: ['fr', 'en'], + interpolation: { + escapeValue: false, // react already safes from xss + }, + }); + +// Moment - On lang change +const changeMomentLang = (i18nLang: string) => { + const momentLang = i18nLang === 'fr' ? 'fr-ch' : 'en'; + moment.locale(momentLang); +}; + +// Moment - On page load +const i18nLang = getNavigatorLang(); +changeMomentLang(i18nLang); + +export const changeLang = (lang: Lang) => { + const i18nLang = lang.toLowerCase(); + i18n.changeLanguage(i18nLang); + changeMomentLang(i18nLang); + if (typeof localStorage !== 'undefined') + localStorage.setItem(STORAGE_KEY, i18nLang); +}; + +export default i18n;
M frontend/pages/_app.tsxfrontend/pages/_app.tsx

@@ -7,36 +7,22 @@ import {MuiPickersUtilsProvider} from '@material-ui/pickers';

import moment from 'moment'; import MomentUtils from '@date-io/moment'; import {useApollo} from '../lib/apolloClient'; -import {Enum_Userspermissionsuser_Lang} from '../generated/graphql'; -import useLangStore from '../stores/useLangStore'; import Metas from '../containers/Metas'; import Toasts from '../components/Toasts'; import theme from '../theme'; -import 'moment/locale/fr-ch'; import {useTranslation} from 'react-i18next'; -import {getUserLng} from '../i18n'; import useProfile from '../hooks/useProfile'; - -moment.locale('fr-ch'); +import {changeLang} from '../lib/i18n'; const App = function (props: AppProps) { const {Component, pageProps} = props; const apolloClient = useApollo(pageProps); const {profile} = useProfile(); const {i18n} = useTranslation(); - const language = useLangStore(s => s.language); - const setLanguage = useLangStore(s => s.setLanguage); useEffect(() => { - setLanguage(getUserLng()); - }, []); - - useEffect(() => { - const languageProfile = profile?.lang ?? language; - const momentLang = languageProfile === 'FR' ? 'fr-ch' : 'en'; - moment.locale(momentLang); - i18n.changeLanguage(languageProfile?.toLowerCase()); - }, [language, profile?.lang]); + if (profile?.lang) changeLang(profile.lang); + }, [profile]); useEffect(() => { // Remove the server-side injected CSS.

@@ -53,9 +39,7 @@ <ThemeProvider theme={theme}>

<MuiPickersUtilsProvider libInstance={moment} utils={MomentUtils} - locale={ - language === Enum_Userspermissionsuser_Lang.Fr ? 'fr-ch' : 'en' - } + locale={i18n.language === 'fr' ? 'fr-ch' : 'en'} > <CssBaseline /> <Component {...pageProps} />
D frontend/stores/useLangStore.ts

@@ -1,24 +0,0 @@

-import create from 'zustand'; -import {persist} from 'zustand/middleware'; -import {Enum_Userspermissionsuser_Lang} from '../generated/graphql'; - -const STORAGE_KEY = 'caroster-lang'; - -type State = { - language: Enum_Userspermissionsuser_Lang; - setLanguage: (language?: Enum_Userspermissionsuser_Lang) => void; -}; - -const useLangStore = create<State>( - persist( - set => ({ - language: Enum_Userspermissionsuser_Lang.Fr, - setLanguage: language => set({language}), - }), - { - name: STORAGE_KEY, - } - ) -); - -export default useLangStore;