| @@ -50,6 +50,7 @@ | |||
| "react-to-print": "^2.14.13", | |||
| "react-toastify": "^9.1.3", | |||
| "react-window": "^1.8.7", | |||
| "react-intl": "^6.4.7", | |||
| "redux": "^4.2.0", | |||
| "simplebar": "^5.3.8", | |||
| "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 { store } from 'store'; | |||
| import reportWebVitals from './reportWebVitals'; | |||
| import {I18nProvider} from "./components/I18nProvider"; | |||
| // ==============================|| MAIN - REACT DOM RENDER ||============================== // | |||
| @@ -26,9 +27,11 @@ const root = createRoot(container); // createRoot(container!) if you use TypeScr | |||
| root.render( | |||
| <StrictMode> | |||
| <ReduxProvider store={store}> | |||
| <BrowserRouter basename="/"> | |||
| <I18nProvider> | |||
| <BrowserRouter basename="/"> | |||
| <App /> | |||
| </BrowserRouter> | |||
| </BrowserRouter> | |||
| </I18nProvider> | |||
| </ReduxProvider> | |||
| </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 | |||
| // import Search from './Search'; | |||
| import Profile from './Profile'; | |||
| import LocaleSelector from "./LocaleSelector"; | |||
| // import Notification from './Notification'; | |||
| // import MobileSection from './MobileSection'; | |||
| @@ -34,7 +35,7 @@ const HeaderContent = () => { | |||
| > | |||
| <GithubOutlined /> | |||
| </IconButton> */} | |||
| <LocaleSelector/> | |||
| {/* <Notification /> */} | |||
| <Profile /> | |||
| {/* <MobileSection /> */} | |||
| @@ -50,6 +50,7 @@ import * as UrlUtils from "utils/ApiPathConst" | |||
| // import { MenuFoldOutlined,MenuOutlined } from '@ant-design/icons'; | |||
| // import { AppBar } from '../../../../node_modules/@mui/material/index'; | |||
| import { Link } from "react-router-dom"; | |||
| import LocaleSelector from "./HeaderContent/LocaleSelector"; | |||
| const drawerWidth = 240; | |||
| @@ -383,6 +384,8 @@ function Header(props) { | |||
| <ul id="navbar" width="100%" > | |||
| {logoutContent} | |||
| </ul> | |||
| <LocaleSelector/> | |||
| {/* <Profile /> */} | |||
| </Stack> | |||
| </Box> | |||
| @@ -43,6 +43,7 @@ import { useDispatch } from "react-redux"; | |||
| import { handleLogin } from "auth/index"; | |||
| import useJwt from "../../../auth/jwt/useJwt"; | |||
| import { handleLogoutFunction } from 'auth/index'; | |||
| import {FormattedMessage} from "react-intl"; | |||
| // ============================|| FIREBASE - LOGIN ||============================ // | |||
| const AuthLoginCustom = () => { | |||
| @@ -214,7 +215,11 @@ const AuthLoginCustom = () => { | |||
| <Grid container spacing={3}> | |||
| <Grid item xs={12}> | |||
| <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 | |||
| id="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": "活動" | |||
| } | |||