| @@ -50,6 +50,7 @@ | |||||
| "react-to-print": "^2.14.13", | "react-to-print": "^2.14.13", | ||||
| "react-toastify": "^9.1.3", | "react-toastify": "^9.1.3", | ||||
| "react-window": "^1.8.7", | "react-window": "^1.8.7", | ||||
| "react-intl": "^6.4.7", | |||||
| "redux": "^4.2.0", | "redux": "^4.2.0", | ||||
| "simplebar": "^5.3.8", | "simplebar": "^5.3.8", | ||||
| "simplebar-react": "^2.4.1", | "simplebar-react": "^2.4.1", | ||||
| @@ -0,0 +1,47 @@ | |||||
| import React, {useState, useEffect, createContext} from 'react'; | |||||
| import { IntlProvider } from 'react-intl'; | |||||
| import enMessages from '../translations/en.json'; | |||||
| import cnMessages from '../translations/zh-CN.json'; | |||||
| import hkMessages from '../translations/zh-HK.json'; | |||||
| const LocaleContext = createContext(); | |||||
| export const I18nProvider = ({ children }) => { | |||||
| const systemMessages = { | |||||
| "en": enMessages, | |||||
| "zh": hkMessages, | |||||
| "zh-HK": hkMessages, | |||||
| "zh-CN": cnMessages | |||||
| }; | |||||
| const [locale, setLocale] = useState('en'); // Default locale, you can change this as per your requirement | |||||
| const [messages, setMessages] = useState(systemMessages[locale]); | |||||
| useEffect(() => { | |||||
| if(localStorage.getItem('locale') === null){ | |||||
| //no locale case | |||||
| localStorage.setItem('locale','en'); | |||||
| } | |||||
| else{ | |||||
| setLocale(localStorage.getItem('locale')); | |||||
| } | |||||
| }, []); | |||||
| useEffect(() => { | |||||
| // Load the messages for the selected locale | |||||
| const fetchMessages = async () => { | |||||
| setMessages(systemMessages[locale]); | |||||
| }; | |||||
| fetchMessages(); | |||||
| }, [locale]); | |||||
| return ( | |||||
| <LocaleContext.Provider value={{ locale, setLocale }} > | |||||
| <IntlProvider locale={locale} messages={messages}> | |||||
| {children} | |||||
| </IntlProvider> | |||||
| </LocaleContext.Provider> | |||||
| ); | |||||
| } | |||||
| export default LocaleContext; | |||||
| @@ -16,6 +16,7 @@ import 'assets/third-party/apex-chart.css'; | |||||
| import App from './App'; | import App from './App'; | ||||
| import { store } from 'store'; | import { store } from 'store'; | ||||
| import reportWebVitals from './reportWebVitals'; | import reportWebVitals from './reportWebVitals'; | ||||
| import {I18nProvider} from "./components/I18nProvider"; | |||||
| // ==============================|| MAIN - REACT DOM RENDER ||============================== // | // ==============================|| MAIN - REACT DOM RENDER ||============================== // | ||||
| @@ -26,9 +27,11 @@ const root = createRoot(container); // createRoot(container!) if you use TypeScr | |||||
| root.render( | root.render( | ||||
| <StrictMode> | <StrictMode> | ||||
| <ReduxProvider store={store}> | <ReduxProvider store={store}> | ||||
| <BrowserRouter basename="/"> | |||||
| <I18nProvider> | |||||
| <BrowserRouter basename="/"> | |||||
| <App /> | <App /> | ||||
| </BrowserRouter> | |||||
| </BrowserRouter> | |||||
| </I18nProvider> | |||||
| </ReduxProvider> | </ReduxProvider> | ||||
| </StrictMode> | </StrictMode> | ||||
| ); | ); | ||||
| @@ -0,0 +1,142 @@ | |||||
| import {useContext, useRef, useState} from 'react'; | |||||
| import ListItem from '@mui/material/ListItem'; | |||||
| // material-ui | |||||
| import { useTheme } from '@mui/material/styles'; | |||||
| import { | |||||
| Box, | |||||
| ClickAwayListener, | |||||
| IconButton, | |||||
| List, | |||||
| ListItemButton, | |||||
| ListItemText, | |||||
| Paper, | |||||
| Popper, | |||||
| useMediaQuery | |||||
| } from '@mui/material'; | |||||
| import Transitions from 'components/@extended/Transitions'; | |||||
| import LanguageIcon from '@mui/icons-material/Language'; | |||||
| import {FormattedMessage} from "react-intl"; | |||||
| import * as React from "react"; | |||||
| import LocaleContext from "../../../../components/I18nProvider"; | |||||
| // ==============================|| HEADER CONTENT - NOTIFICATION ||============================== // | |||||
| const LocaleSelector = () => { | |||||
| const theme = useTheme(); | |||||
| const matchesXs = useMediaQuery(theme.breakpoints.down('md')); | |||||
| const { setLocale } = useContext(LocaleContext); | |||||
| const anchorRef = useRef(null); | |||||
| const [open, setOpen] = useState(false); | |||||
| const handleToggle = () => { | |||||
| setOpen((prevOpen) => !prevOpen); | |||||
| }; | |||||
| const handleClose = (event) => { | |||||
| if (anchorRef.current && anchorRef.current.contains(event.target)) { | |||||
| return; | |||||
| } | |||||
| setOpen(false); | |||||
| }; | |||||
| const iconBackColorOpen = 'grey.300'; | |||||
| const iconBackColor = 'grey.100'; | |||||
| return ( | |||||
| <Box sx={{ flexShrink: 0, ml: 0.75 }}> | |||||
| <IconButton | |||||
| disableRipple | |||||
| color="secondary" | |||||
| sx={{ color: 'text.primary', bgcolor: open ? iconBackColorOpen : iconBackColor }} | |||||
| aria-label="open profile" | |||||
| ref={anchorRef} | |||||
| aria-controls={open ? 'profile-grow' : undefined} | |||||
| aria-haspopup="true" | |||||
| onClick={handleToggle} | |||||
| > | |||||
| <LanguageIcon /> | |||||
| </IconButton> | |||||
| <Popper | |||||
| placement={matchesXs ? 'bottom' : 'bottom-end'} | |||||
| open={open} | |||||
| anchorEl={anchorRef.current} | |||||
| role={undefined} | |||||
| transition | |||||
| disablePortal | |||||
| popperOptions={{ | |||||
| modifiers: [ | |||||
| { | |||||
| name: 'offset', | |||||
| options: { | |||||
| offset: [matchesXs ? -5 : 0, 9] | |||||
| } | |||||
| } | |||||
| ] | |||||
| }} | |||||
| > | |||||
| {({ TransitionProps }) => ( | |||||
| <Transitions type="fade" in={open} {...TransitionProps}> | |||||
| <Paper | |||||
| sx={{ | |||||
| boxShadow: theme.customShadows.z1, | |||||
| width: '100%', | |||||
| minWidth: 285, | |||||
| maxWidth: 420, | |||||
| [theme.breakpoints.down('md')]: { | |||||
| maxWidth: 285 | |||||
| } | |||||
| }} | |||||
| > | |||||
| <ClickAwayListener onClickAway={handleClose}> | |||||
| <List | |||||
| component="nav" | |||||
| > | |||||
| <ListItem disablePadding> | |||||
| <ListItemButton | |||||
| onClick={() => { | |||||
| setLocale("en") | |||||
| localStorage.setItem('locale','en'); | |||||
| }} | |||||
| > | |||||
| <ListItemText | |||||
| primary= <FormattedMessage id="en"/> | |||||
| /> | |||||
| </ListItemButton> | |||||
| </ListItem> | |||||
| <ListItem disablePadding> | |||||
| <ListItemButton | |||||
| onClick={() => { | |||||
| setLocale("zh-HK") | |||||
| localStorage.setItem('locale','zh-HK'); | |||||
| }} | |||||
| > | |||||
| <ListItemText | |||||
| primary= <FormattedMessage id="zh-HK"/> | |||||
| /> | |||||
| </ListItemButton> | |||||
| </ListItem> | |||||
| <ListItem disablePadding> | |||||
| <ListItemButton | |||||
| onClick={() => { | |||||
| setLocale("zh-CN") | |||||
| localStorage.setItem('locale','zh-CN'); | |||||
| }} | |||||
| > | |||||
| <ListItemText | |||||
| primary= <FormattedMessage id="zh-CN"/> | |||||
| /> | |||||
| </ListItemButton> | |||||
| </ListItem> | |||||
| </List> | |||||
| </ClickAwayListener> | |||||
| </Paper> | |||||
| </Transitions> | |||||
| )} | |||||
| </Popper> | |||||
| </Box> | |||||
| ); | |||||
| }; | |||||
| export default LocaleSelector; | |||||
| @@ -7,6 +7,7 @@ import { Button ,Box } from '@mui/material'; | |||||
| // project import | // project import | ||||
| // import Search from './Search'; | // import Search from './Search'; | ||||
| import Profile from './Profile'; | import Profile from './Profile'; | ||||
| import LocaleSelector from "./LocaleSelector"; | |||||
| // import Notification from './Notification'; | // import Notification from './Notification'; | ||||
| // import MobileSection from './MobileSection'; | // import MobileSection from './MobileSection'; | ||||
| @@ -34,7 +35,7 @@ const HeaderContent = () => { | |||||
| > | > | ||||
| <GithubOutlined /> | <GithubOutlined /> | ||||
| </IconButton> */} | </IconButton> */} | ||||
| <LocaleSelector/> | |||||
| {/* <Notification /> */} | {/* <Notification /> */} | ||||
| <Profile /> | <Profile /> | ||||
| {/* <MobileSection /> */} | {/* <MobileSection /> */} | ||||
| @@ -50,6 +50,7 @@ import * as UrlUtils from "utils/ApiPathConst" | |||||
| // import { MenuFoldOutlined,MenuOutlined } from '@ant-design/icons'; | // import { MenuFoldOutlined,MenuOutlined } from '@ant-design/icons'; | ||||
| // import { AppBar } from '../../../../node_modules/@mui/material/index'; | // import { AppBar } from '../../../../node_modules/@mui/material/index'; | ||||
| import { Link } from "react-router-dom"; | import { Link } from "react-router-dom"; | ||||
| import LocaleSelector from "./HeaderContent/LocaleSelector"; | |||||
| const drawerWidth = 240; | const drawerWidth = 240; | ||||
| @@ -383,6 +384,8 @@ function Header(props) { | |||||
| <ul id="navbar" width="100%" > | <ul id="navbar" width="100%" > | ||||
| {logoutContent} | {logoutContent} | ||||
| </ul> | </ul> | ||||
| <LocaleSelector/> | |||||
| {/* <Profile /> */} | {/* <Profile /> */} | ||||
| </Stack> | </Stack> | ||||
| </Box> | </Box> | ||||
| @@ -43,6 +43,7 @@ import { useDispatch } from "react-redux"; | |||||
| import { handleLogin } from "auth/index"; | import { handleLogin } from "auth/index"; | ||||
| import useJwt from "../../../auth/jwt/useJwt"; | import useJwt from "../../../auth/jwt/useJwt"; | ||||
| import { handleLogoutFunction } from 'auth/index'; | import { handleLogoutFunction } from 'auth/index'; | ||||
| import {FormattedMessage} from "react-intl"; | |||||
| // ============================|| FIREBASE - LOGIN ||============================ // | // ============================|| FIREBASE - LOGIN ||============================ // | ||||
| const AuthLoginCustom = () => { | const AuthLoginCustom = () => { | ||||
| @@ -214,7 +215,11 @@ const AuthLoginCustom = () => { | |||||
| <Grid container spacing={3}> | <Grid container spacing={3}> | ||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <Stack spacing={1}> | <Stack spacing={1}> | ||||
| <InputLabel htmlFor="email-login"><Typography variant="h5">用戶登入名稱</Typography></InputLabel> | |||||
| <InputLabel htmlFor="email-login"> | |||||
| <Typography variant="h5"> | |||||
| <FormattedMessage id="userLoginName"/> | |||||
| </Typography> | |||||
| </InputLabel> | |||||
| <OutlinedInput | <OutlinedInput | ||||
| id="username" | id="username" | ||||
| name="username" | name="username" | ||||
| @@ -0,0 +1,10 @@ | |||||
| { | |||||
| "en": "English", | |||||
| "zh-HK": "Traditional Chinese", | |||||
| "zh-CN": "Simplified Chinese", | |||||
| "userLoginName": "User login name", | |||||
| "Dashboard": "Dashboard", | |||||
| "event": "Event" | |||||
| } | |||||
| @@ -0,0 +1,10 @@ | |||||
| { | |||||
| "en": "英文", | |||||
| "zh-HK": "繁体中文", | |||||
| "zh-CN": "简体中文", | |||||
| "userLoginName": "用戶登入名稱", | |||||
| "Dashboard": "仪表板", | |||||
| "event": "活动" | |||||
| } | |||||
| @@ -0,0 +1,10 @@ | |||||
| { | |||||
| "en": "英文", | |||||
| "zh-HK": "繁體中文", | |||||
| "zh-CN": "簡體中文", | |||||
| "userLoginName": "用戶登入名稱", | |||||
| "Dashboard": "儀表板", | |||||
| "event": "活動" | |||||
| } | |||||