mirror of
https://github.com/edumeet/edumeet.git
synced 2026-08-02 07:42:09 +00:00
Add localization selection
This commit is contained in:
parent
0ad219d98f
commit
93b9f7f818
10 changed files with 250 additions and 96 deletions
|
|
@ -29,6 +29,7 @@
|
|||
"react-dom": "^16.10.2",
|
||||
"react-flip-toolkit": "^7.0.9",
|
||||
"react-intl": "^3.4.0",
|
||||
"react-intl-redux": "^2.2.0",
|
||||
"react-redux": "^7.1.1",
|
||||
"react-router-dom": "^5.1.2",
|
||||
"react-scripts": "3.4.1",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { getSignalingUrl } from './urlFactory';
|
|||
import { SocketTimeoutError } from './utils';
|
||||
import * as requestActions from './actions/requestActions';
|
||||
import * as meActions from './actions/meActions';
|
||||
import * as intlActions from './actions/intlActions';
|
||||
import * as roomActions from './actions/roomActions';
|
||||
import * as peerActions from './actions/peerActions';
|
||||
import * as peerVolumeActions from './actions/peerVolumeActions';
|
||||
|
|
@ -16,6 +17,8 @@ import * as producerActions from './actions/producerActions';
|
|||
import * as notificationActions from './actions/notificationActions';
|
||||
import * as transportActions from './actions/transportActions';
|
||||
import { permissions } from './permissions';
|
||||
// import { updateIntl } from 'react-intl-redux';
|
||||
import * as locales from './translations/locales';
|
||||
|
||||
let createTorrent;
|
||||
|
||||
|
|
@ -115,6 +118,7 @@ export default class RoomClient
|
|||
{
|
||||
store = data.store;
|
||||
intl = data.intl;
|
||||
|
||||
}
|
||||
|
||||
constructor(
|
||||
|
|
@ -255,6 +259,9 @@ export default class RoomClient
|
|||
this._startKeyListener();
|
||||
|
||||
this._startDevicesListener();
|
||||
|
||||
this.setLocale(store.getState().intl.locale);
|
||||
|
||||
}
|
||||
|
||||
close()
|
||||
|
|
@ -298,7 +305,7 @@ export default class RoomClient
|
|||
|
||||
switch (key)
|
||||
{
|
||||
|
||||
|
||||
/*
|
||||
case String.fromCharCode(37):
|
||||
{
|
||||
|
|
@ -496,6 +503,21 @@ export default class RoomClient
|
|||
});
|
||||
}
|
||||
|
||||
setLocale(locale)
|
||||
{
|
||||
|
||||
if (locale === null) locale = locales.detect();
|
||||
const one = locales.loadOne(locale);
|
||||
|
||||
store.dispatch(intlActions.updateIntl({
|
||||
locale : one.locale[0],
|
||||
messages : one.messages,
|
||||
list : locales.getList()
|
||||
}));
|
||||
|
||||
document.documentElement.lang = one.locale[0].toUpperCase();
|
||||
}
|
||||
|
||||
login(roomId = this._roomId)
|
||||
{
|
||||
const url = `/auth/login?peerId=${this._peerId}&roomId=${roomId}`;
|
||||
|
|
|
|||
7
app/src/actions/intlActions.js
Normal file
7
app/src/actions/intlActions.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { UPDATE } from 'react-intl-redux';
|
||||
|
||||
export const updateIntl = ({ locale, formats, messages, list }) =>
|
||||
({
|
||||
type : UPDATE,
|
||||
payload : { locale, formats, messages, list }
|
||||
});
|
||||
|
|
@ -174,7 +174,6 @@ const PulsingBadge = withStyles((theme) =>
|
|||
const TopBar = (props) =>
|
||||
{
|
||||
const intl = useIntl();
|
||||
|
||||
const [ mobileMoreAnchorEl, setMobileMoreAnchorEl ] = useState(null);
|
||||
const [ anchorEl, setAnchorEl ] = useState(null);
|
||||
const [ currentMenu, setCurrentMenu ] = useState(null);
|
||||
|
|
@ -233,7 +232,9 @@ const TopBar = (props) =>
|
|||
canProduceExtraVideo,
|
||||
canLock,
|
||||
canPromote,
|
||||
classes
|
||||
classes,
|
||||
locale,
|
||||
localesList
|
||||
} = props;
|
||||
|
||||
const isMenuOpen = Boolean(anchorEl);
|
||||
|
|
@ -508,6 +509,17 @@ const TopBar = (props) =>
|
|||
</IconButton>
|
||||
</div>
|
||||
<div className={classes.divider} />
|
||||
|
||||
<Button
|
||||
aria-label={locale.split(/[-_]/)[0]}
|
||||
className={classes.actionButton}
|
||||
color='secondary'
|
||||
disableRipple='true'
|
||||
onClick={(event) => handleMenuOpen(event, 'localeMenu')}
|
||||
>
|
||||
{locale.split(/[-_]/)[0]}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'label.leave',
|
||||
|
|
@ -599,6 +611,25 @@ const TopBar = (props) =>
|
|||
</MenuItem>
|
||||
</Paper>
|
||||
}
|
||||
|
||||
{ currentMenu === 'localeMenu' &&
|
||||
<Paper>
|
||||
{localesList.map((item, index) => (
|
||||
<MenuItem
|
||||
selected={item.locale.includes(locale)}
|
||||
key={index}
|
||||
onClick={() =>
|
||||
{
|
||||
roomClient.setLocale(item.locale[0]);
|
||||
handleMenuClose();
|
||||
}}
|
||||
>
|
||||
{item.name}
|
||||
</MenuItem>)
|
||||
)}
|
||||
</Paper>
|
||||
}
|
||||
|
||||
</Popover>
|
||||
<Menu
|
||||
anchorEl={mobileMoreAnchorEl}
|
||||
|
|
@ -840,7 +871,10 @@ TopBar.propTypes =
|
|||
canLock : PropTypes.bool.isRequired,
|
||||
canPromote : PropTypes.bool.isRequired,
|
||||
classes : PropTypes.object.isRequired,
|
||||
theme : PropTypes.object.isRequired
|
||||
theme : PropTypes.object.isRequired,
|
||||
intl : PropTypes.object.isRequired,
|
||||
locale : PropTypes.object.isRequired,
|
||||
localesList : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const makeMapStateToProps = () =>
|
||||
|
|
@ -870,7 +904,9 @@ const makeMapStateToProps = () =>
|
|||
state.toolarea.unreadFiles + raisedHandsSelector(state),
|
||||
canProduceExtraVideo : hasExtraVideoPermission(state),
|
||||
canLock : hasLockPermission(state),
|
||||
canPromote : hasPromotionPermission(state)
|
||||
canPromote : hasPromotionPermission(state),
|
||||
locale : state.intl.locale,
|
||||
localesList : state.intl.list
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
|
|
@ -933,7 +969,9 @@ export default withRoomContext(connect(
|
|||
prev.me.roles === next.me.roles &&
|
||||
prev.toolarea.unreadMessages === next.toolarea.unreadMessages &&
|
||||
prev.toolarea.unreadFiles === next.toolarea.unreadFiles &&
|
||||
prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen
|
||||
prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen &&
|
||||
prev.locale === next.locale &&
|
||||
prev.localesList === next.localesList
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import FormControl from '@material-ui/core/FormControl';
|
|||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
import Select from '@material-ui/core/Select';
|
||||
import Switch from '@material-ui/core/Switch';
|
||||
import { withRoomContext } from '../../RoomContext';
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
|
|
@ -33,20 +34,26 @@ const styles = (theme) =>
|
|||
}
|
||||
});
|
||||
|
||||
const AppearenceSettings = ({
|
||||
isMobile,
|
||||
room,
|
||||
settings,
|
||||
onTogglePermanentTopBar,
|
||||
onToggleHiddenControls,
|
||||
onToggleButtonControlBar,
|
||||
onToggleShowNotifications,
|
||||
onToggleDrawerOverlayed,
|
||||
handleChangeMode,
|
||||
handleChangeAspectRatio,
|
||||
classes
|
||||
}) =>
|
||||
const AppearenceSettings = (props) =>
|
||||
{
|
||||
const {
|
||||
roomClient,
|
||||
isMobile,
|
||||
room,
|
||||
locale,
|
||||
settings,
|
||||
onTogglePermanentTopBar,
|
||||
onToggleHiddenControls,
|
||||
onToggleButtonControlBar,
|
||||
onToggleShowNotifications,
|
||||
onToggleDrawerOverlayed,
|
||||
handleChangeMode,
|
||||
handleChangeAspectRatio,
|
||||
classes,
|
||||
localesList
|
||||
|
||||
} = props;
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
const modes = [ {
|
||||
|
|
@ -73,6 +80,42 @@ const AppearenceSettings = ({
|
|||
|
||||
return (
|
||||
<React.Fragment>
|
||||
|
||||
{/* MOJE */}
|
||||
<FormControl className={classes.setting}>
|
||||
<Select
|
||||
value={locale || ''}
|
||||
onChange={(event) =>
|
||||
{
|
||||
if (event.target.value)
|
||||
roomClient.setLocale(event.target.value);
|
||||
}
|
||||
}
|
||||
name={intl.formatMessage({
|
||||
id : 'settings.language',
|
||||
defaultMessage : 'Language'
|
||||
})}
|
||||
autoWidth
|
||||
className={classes.selectEmpty}
|
||||
>
|
||||
{ localesList.map((item, index) =>
|
||||
{
|
||||
return (
|
||||
<MenuItem key={index} value={item.locale[0]}>
|
||||
{item.name}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
<FormattedMessage
|
||||
id='settings.language'
|
||||
defaultMessage='Select language'
|
||||
/>
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
{/* MOJE */}
|
||||
|
||||
<FormControl className={classes.setting}>
|
||||
<Select
|
||||
value={room.mode || ''}
|
||||
|
|
@ -189,6 +232,7 @@ const AppearenceSettings = ({
|
|||
|
||||
AppearenceSettings.propTypes =
|
||||
{
|
||||
roomClient : PropTypes.any.isRequired,
|
||||
isMobile : PropTypes.bool.isRequired,
|
||||
room : appPropTypes.Room.isRequired,
|
||||
settings : PropTypes.object.isRequired,
|
||||
|
|
@ -199,14 +243,19 @@ AppearenceSettings.propTypes =
|
|||
onToggleDrawerOverlayed : PropTypes.func.isRequired,
|
||||
handleChangeMode : PropTypes.func.isRequired,
|
||||
handleChangeAspectRatio : PropTypes.func.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
classes : PropTypes.object.isRequired,
|
||||
intl : PropTypes.object.isRequired,
|
||||
locale : PropTypes.object.isRequired,
|
||||
localesList : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
({
|
||||
isMobile : state.me.browser.platform === 'mobile',
|
||||
room : state.room,
|
||||
settings : state.settings
|
||||
isMobile : state.me.browser.platform === 'mobile',
|
||||
room : state.room,
|
||||
settings : state.settings,
|
||||
locale : state.intl.locale,
|
||||
localesList : state.intl.list
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
|
|
@ -219,7 +268,7 @@ const mapDispatchToProps = {
|
|||
handleChangeAspectRatio : settingsActions.setAspectRatio
|
||||
};
|
||||
|
||||
export default connect(
|
||||
export default withRoomContext(connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
null,
|
||||
|
|
@ -229,8 +278,10 @@ export default connect(
|
|||
return (
|
||||
prev.me.browser === next.me.browser &&
|
||||
prev.room === next.room &&
|
||||
prev.settings === next.settings
|
||||
prev.settings === next.settings &&
|
||||
prev.locale === next.locale &&
|
||||
prev.localesList === next.localesList
|
||||
);
|
||||
}
|
||||
}
|
||||
)(withStyles(styles)(AppearenceSettings));
|
||||
)(withStyles(styles)(AppearenceSettings)));
|
||||
|
|
@ -3,7 +3,11 @@ import React, { Suspense } from 'react';
|
|||
import { render } from 'react-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import isElectron from 'is-electron';
|
||||
import { createIntl, createIntlCache, RawIntlProvider } from 'react-intl';
|
||||
|
||||
import { createIntl } from 'react-intl';
|
||||
// import { createIntl, createIntlCache } from 'react-intl';
|
||||
import { IntlProvider } from 'react-intl-redux';
|
||||
|
||||
import { Route, HashRouter, BrowserRouter } from 'react-router-dom';
|
||||
import randomString from 'random-string';
|
||||
import Logger from './Logger';
|
||||
|
|
@ -22,56 +26,12 @@ import { SnackbarProvider } from 'notistack';
|
|||
import * as serviceWorker from './serviceWorker';
|
||||
import { ReactLazyPreload } from './components/ReactLazyPreload';
|
||||
import { detectDevice } from 'mediasoup-client';
|
||||
// import messagesEnglish from './translations/en';
|
||||
import messagesNorwegian from './translations/nb';
|
||||
import messagesGerman from './translations/de';
|
||||
import messagesHungarian from './translations/hu';
|
||||
import messagesPolish from './translations/pl';
|
||||
import messagesDanish from './translations/dk';
|
||||
import messagesFrench from './translations/fr';
|
||||
import messagesGreek from './translations/el';
|
||||
import messagesRomanian from './translations/ro';
|
||||
import messagesPortuguese from './translations/pt';
|
||||
import messagesChineseSimplified from './translations/cn';
|
||||
import messagesChineseTraditional from './translations/tw';
|
||||
import messagesSpanish from './translations/es';
|
||||
import messagesCroatian from './translations/hr';
|
||||
import messagesCzech from './translations/cs';
|
||||
import messagesItalian from './translations/it';
|
||||
import messagesUkrainian from './translations/uk';
|
||||
import messagesTurkish from './translations/tr';
|
||||
import messagesLatvian from './translations/lv';
|
||||
import messagesHindi from './translations/hi';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const App = ReactLazyPreload(() => import(/* webpackChunkName: "app" */ './components/App'));
|
||||
|
||||
const cache = createIntlCache();
|
||||
|
||||
const messages =
|
||||
{
|
||||
// 'en' : messagesEnglish,
|
||||
'nb' : messagesNorwegian,
|
||||
'de' : messagesGerman,
|
||||
'hu' : messagesHungarian,
|
||||
'pl' : messagesPolish,
|
||||
'dk' : messagesDanish,
|
||||
'fr' : messagesFrench,
|
||||
'el' : messagesGreek,
|
||||
'ro' : messagesRomanian,
|
||||
'pt' : messagesPortuguese,
|
||||
'zh-hans' : messagesChineseSimplified,
|
||||
'zh-hant' : messagesChineseTraditional,
|
||||
'es' : messagesSpanish,
|
||||
'hr' : messagesCroatian,
|
||||
'cs' : messagesCzech,
|
||||
'it' : messagesItalian,
|
||||
'uk' : messagesUkrainian,
|
||||
'tr' : messagesTurkish,
|
||||
'lv' : messagesLatvian,
|
||||
'hi' : messagesHindi
|
||||
};
|
||||
// const cache = createIntlCache();
|
||||
|
||||
const supportedBrowsers={
|
||||
'windows' : {
|
||||
|
|
@ -86,24 +46,7 @@ const supportedBrowsers={
|
|||
'samsung internet for android' : '>=11.1.1.52'
|
||||
};
|
||||
|
||||
const browserLanguage = (navigator.language || navigator.browserLanguage).toLowerCase();
|
||||
|
||||
let locale = browserLanguage.split(/[-_]/)[0]; // language without region code
|
||||
|
||||
if (locale === 'zh')
|
||||
{
|
||||
if (browserLanguage === 'zh-cn')
|
||||
locale = 'zh-hans';
|
||||
else
|
||||
locale = 'zh-hant';
|
||||
}
|
||||
|
||||
const intl = createIntl({
|
||||
locale,
|
||||
messages : messages[locale]
|
||||
}, cache);
|
||||
|
||||
document.documentElement.lang = locale;
|
||||
const intl = createIntl();
|
||||
|
||||
if (process.env.REACT_APP_DEBUG === '*' || process.env.NODE_ENV !== 'production')
|
||||
{
|
||||
|
|
@ -198,12 +141,12 @@ function run()
|
|||
{
|
||||
render(
|
||||
<MuiThemeProvider theme={theme}>
|
||||
<RawIntlProvider value={intl}>
|
||||
<IntlProvider value={intl}>
|
||||
<UnsupportedBrowser
|
||||
webrtcUnavailable={webrtcUnavailable}
|
||||
platform={device.platform}
|
||||
/>
|
||||
</RawIntlProvider>
|
||||
</IntlProvider>
|
||||
</MuiThemeProvider>,
|
||||
document.getElementById('multiparty-meeting')
|
||||
);
|
||||
|
|
@ -235,7 +178,7 @@ function run()
|
|||
render(
|
||||
<Provider store={store}>
|
||||
<MuiThemeProvider theme={theme}>
|
||||
<RawIntlProvider value={intl}>
|
||||
<IntlProvider value={intl}>
|
||||
<PersistGate loading={<LoadingView />} persistor={persistor}>
|
||||
<RoomContext.Provider value={roomClient}>
|
||||
<SnackbarProvider>
|
||||
|
|
@ -250,7 +193,7 @@ function run()
|
|||
</SnackbarProvider>
|
||||
</RoomContext.Provider>
|
||||
</PersistGate>
|
||||
</RawIntlProvider>
|
||||
</IntlProvider>
|
||||
</MuiThemeProvider>
|
||||
</Provider>,
|
||||
document.getElementById('multiparty-meeting')
|
||||
|
|
|
|||
18
app/src/reducers/intl.js
Normal file
18
app/src/reducers/intl.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { UPDATE } from 'react-intl-redux';
|
||||
|
||||
const initialState = {
|
||||
locale : null,
|
||||
messages : null
|
||||
};
|
||||
|
||||
const intlReducer = (state = initialState, action) =>
|
||||
{
|
||||
if (action.type !== UPDATE)
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
return { ...state, ...action.payload };
|
||||
};
|
||||
|
||||
export default intlReducer;
|
||||
|
|
@ -12,6 +12,8 @@ import toolarea from './toolarea';
|
|||
import files from './files';
|
||||
import settings from './settings';
|
||||
import transports from './transports';
|
||||
import intl from './intl';
|
||||
// import { intlReducer } from 'react-intl-redux';
|
||||
|
||||
export default combineReducers({
|
||||
room,
|
||||
|
|
@ -26,5 +28,7 @@ export default combineReducers({
|
|||
chat,
|
||||
toolarea,
|
||||
files,
|
||||
settings
|
||||
settings,
|
||||
// intl : intlReducer
|
||||
intl
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ const persistConfig =
|
|||
key : 'root',
|
||||
storage : storage,
|
||||
stateReconciler : autoMergeLevel2,
|
||||
whitelist : [ 'settings' ]
|
||||
// whitelist : [ 'settings']
|
||||
whitelist : [ 'settings', 'intl' ]
|
||||
};
|
||||
|
||||
const reduxMiddlewares =
|
||||
|
|
@ -49,8 +50,17 @@ const enhancer = composeEnhancers(
|
|||
|
||||
const pReducer = persistReducer(persistConfig, rootReducer);
|
||||
|
||||
const initialState = {
|
||||
intl : {
|
||||
locale : null,
|
||||
messages : null
|
||||
}
|
||||
// ...other initialState
|
||||
};
|
||||
|
||||
export const store = createStore(
|
||||
pReducer,
|
||||
initialState,
|
||||
enhancer
|
||||
);
|
||||
|
||||
|
|
|
|||
60
app/src/translations/locales.js
Normal file
60
app/src/translations/locales.js
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
const list = [
|
||||
{ name: 'English', file: 'en', locale: [ 'en' ] },
|
||||
{ name: 'Chech', file: 'cs', locale: [ 'cs' ] },
|
||||
{ name: 'Chinese (Simplified)', file: 'cn', locale: [ 'zn', 'zn-cn' ] }, // hans
|
||||
{ name: 'Chinese (Traditional)', file: 'tw', locale: [ 'zn-tw', 'zn-hk', 'zn-sg' ] }, // hant
|
||||
{ name: 'Croatian', file: 'hr', locale: [ 'hr' ] },
|
||||
{ name: 'Danish', file: 'dk', locale: [ 'dk' ] },
|
||||
{ name: 'French', file: 'fr', locale: [ 'fr' ] },
|
||||
{ name: 'German', file: 'de', locale: [ 'de' ] },
|
||||
{ name: 'Greek', file: 'el', locale: [ 'el' ] },
|
||||
{ name: 'Hindi', file: 'hi', locale: [ 'hi' ] },
|
||||
{ name: 'Hungarian', file: 'hu', locale: [ 'hu' ] },
|
||||
{ name: 'Italian', file: 'it', locale: [ 'it' ] },
|
||||
{ name: 'Latvian', file: 'lv', locale: [ 'lv' ] },
|
||||
{ name: 'Norwegian', file: 'nb', locale: [ 'nb' ] },
|
||||
{ name: 'Polish', file: 'pl', locale: [ 'pl' ] },
|
||||
{ name: 'Portuguese', file: 'pt', locale: [ 'pt' ] },
|
||||
{ name: 'Romanian', file: 'ro', locale: [ 'ro' ] },
|
||||
{ name: 'Spanish', file: 'es', locale: [ 'es' ] },
|
||||
{ name: 'Turkish', file: 'tr', locale: [ 'tr' ] },
|
||||
{ name: 'Ukrainian', file: 'uk', locale: [ 'uk' ] }
|
||||
];
|
||||
|
||||
export const detect = () =>
|
||||
{
|
||||
const localeFull = (navigator.language || navigator.browserLanguage).toLowerCase();
|
||||
|
||||
// const localeCountry = localeFull.split(/[-_]/)[0];
|
||||
|
||||
// const localeRegion = localeFull.split(/[-_]/)[1] || null;
|
||||
|
||||
return localeFull;
|
||||
};
|
||||
|
||||
export const getList = () => list;
|
||||
|
||||
export const loadOne = (locale) =>
|
||||
{
|
||||
let res = {};
|
||||
|
||||
try
|
||||
{
|
||||
res = list.filter((item) =>
|
||||
item.locale.includes(locale) || item.locale.includes(locale.split(/[-_]/)[0])
|
||||
)[0];
|
||||
|
||||
res.messages = require(`./${res.file}`);
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
|
||||
res = list.filter((item) => item.locale.includes('en'))[0];
|
||||
|
||||
res.messages = require(`./${res.file}`);
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue