@@ -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": "活動" | |||||
} |