Add localization selection

This commit is contained in:
Roman Drozd 2020-06-09 17:53:03 +02:00
parent 0ad219d98f
commit 93b9f7f818
10 changed files with 250 additions and 96 deletions

View file

@ -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",

View file

@ -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}`;

View 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 }
});

View file

@ -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
);
}
}

View file

@ -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)));

View file

@ -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
View 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;

View file

@ -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
});

View file

@ -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
);

View 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;
};