diff options
Diffstat (limited to 'gui/src/renderer')
| -rw-r--r-- | gui/src/renderer/app.tsx | 118 | ||||
| -rw-r--r-- | gui/src/renderer/components/AdvancedSettings.tsx | 68 | ||||
| -rw-r--r-- | gui/src/renderer/components/SelectLanguage.tsx | 119 | ||||
| -rw-r--r-- | gui/src/renderer/components/Selector.tsx | 89 | ||||
| -rw-r--r-- | gui/src/renderer/components/Settings.tsx | 6 | ||||
| -rw-r--r-- | gui/src/renderer/containers/AccountPage.tsx | 4 | ||||
| -rw-r--r-- | gui/src/renderer/containers/ConnectPage.tsx | 4 | ||||
| -rw-r--r-- | gui/src/renderer/containers/SelectLanguagePage.tsx | 31 | ||||
| -rw-r--r-- | gui/src/renderer/containers/SettingsPage.tsx | 5 | ||||
| -rw-r--r-- | gui/src/renderer/containers/WireguardKeysPage.tsx | 4 | ||||
| -rw-r--r-- | gui/src/renderer/redux/settings/reducers.ts | 1 | ||||
| -rw-r--r-- | gui/src/renderer/redux/userinterface/actions.ts | 15 | ||||
| -rw-r--r-- | gui/src/renderer/redux/userinterface/reducers.ts | 5 | ||||
| -rw-r--r-- | gui/src/renderer/routes.tsx | 3 | ||||
| -rw-r--r-- | gui/src/renderer/transitions.ts | 1 |
15 files changed, 366 insertions, 107 deletions
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx index 39e6f56fda..c816701731 100644 --- a/gui/src/renderer/app.tsx +++ b/gui/src/renderer/app.tsx @@ -43,6 +43,28 @@ import { TunnelState, } from '../shared/daemon-rpc-types'; +interface IPreferredLocaleDescriptor { + name: string; + code: string; +} + +const supportedLocaleList = [ + { name: 'Deutsche', code: 'de' }, + { name: 'English', code: 'en' }, + { name: 'Español', code: 'es' }, + { name: 'Français', code: 'fr' }, + { name: 'Italiano', code: 'it' }, + { name: '日本語', code: 'ja' }, + { name: 'Nederlands', code: 'nl' }, + { name: 'Norsk', code: 'no' }, + { name: 'Português', code: 'pt' }, + { name: 'Русский', code: 'ru' }, + { name: 'Svenska', code: 'sv' }, + { name: 'Türkçe', code: 'tr' }, + { name: '简体中文', code: 'zh-CN' }, + { name: '繁體中文', code: 'zh-TW' }, +]; + export default class AppRenderer { private memoryHistory = createMemoryHistory(); private reduxStore = configureStore(this.memoryHistory); @@ -61,10 +83,12 @@ export default class AppRenderer { ), }; - private locale: string; - private tunnelState: TunnelState; - private settings: ISettings; - private guiSettings: IGuiSettingsState; + private locale = 'en'; + private location?: ILocation; + private relayListPair!: IRelayListPair; + private tunnelState!: TunnelState; + private settings!: ISettings; + private guiSettings!: IGuiSettingsState; private connectedToDaemon = false; private autoConnected = false; private doingLogin = false; @@ -73,6 +97,20 @@ export default class AppRenderer { constructor() { setupLogging(getRendererLogFile()); + IpcRendererEventChannel.locale.listen((locale) => { + // load translations for the new locale + this.loadTranslations(locale); + + // set current locale + this.setLocale(locale); + + // refresh the relay list pair with the new translations + this.propagateRelayListPairToRedux(); + + // refresh the location with the new translations + this.propagateLocationToRedux(); + }); + IpcRendererEventChannel.windowShape.listen((windowShapeParams) => { if (typeof windowShapeParams.arrowPosition === 'number') { this.reduxActions.userInterface.updateWindowArrowPosition(windowShapeParams.arrowPosition); @@ -113,8 +151,7 @@ export default class AppRenderer { }); IpcRendererEventChannel.relays.listen((relayListPair: IRelayListPair) => { - this.setRelays(relayListPair.relays); - this.setBridges(relayListPair.bridges); + this.setRelayListPair(relayListPair); }); IpcRendererEventChannel.currentVersion.listen((currentVersion: ICurrentAppVersionInfo) => { @@ -144,15 +181,9 @@ export default class AppRenderer { // Request the initial state from the main process const initialState = IpcRendererEventChannel.state.get(); - this.locale = initialState.locale; - this.tunnelState = initialState.tunnelState; - this.settings = initialState.settings; - this.guiSettings = initialState.guiSettings; - // Load translations - for (const catalogue of [messages, countries, cities, relayLocations]) { - loadTranslations(this.locale, catalogue); - } + this.loadTranslations(initialState.locale); + this.setLocale(initialState.locale); this.setAccountExpiry(initialState.accountData && initialState.accountData.expiry); this.setAccountHistory(initialState.accountHistory); @@ -164,8 +195,7 @@ export default class AppRenderer { this.setLocation(initialState.location); } - this.setRelays(initialState.relays); - this.setBridges(initialState.bridges); + this.setRelayListPair(initialState.relayListPair); this.setCurrentVersion(initialState.currentVersion); this.setUpgradeVersion(initialState.upgradeVersion); this.setGuiSettings(initialState.guiSettings); @@ -185,12 +215,7 @@ export default class AppRenderer { <Provider store={this.reduxStore}> <ConnectedRouter history={this.memoryHistory}> <ErrorBoundary> - <AppRoutes - sharedProps={{ - app: this, - locale: this.locale, - }} - /> + <AppRoutes sharedProps={{ app: this }} /> </ErrorBoundary> </ConnectedRouter> </Provider> @@ -338,6 +363,31 @@ export default class AppRenderer { actions.settings.setWireguardKeygenEvent(keygenEvent); } + public getPreferredLocaleList(): IPreferredLocaleDescriptor[] { + return [ + { + name: messages.pgettext('application-languages', 'System default'), + code: 'system', + }, + ...supportedLocaleList, + ]; + } + + public setPreferredLocale(preferredLocale: string) { + IpcRendererEventChannel.guiSettings.setPreferredLocale(preferredLocale); + } + + private loadTranslations(locale: string) { + for (const catalogue of [messages, countries, cities, relayLocations]) { + loadTranslations(locale, catalogue); + } + } + + private setLocale(locale: string) { + this.locale = locale; + this.reduxActions.userInterface.updateLocale(locale); + } + private setRelaySettings(relaySettings: RelaySettings) { const actions = this.reduxActions; @@ -546,7 +596,14 @@ export default class AppRenderer { } private setLocation(location: ILocation) { - this.reduxActions.connection.newLocation(this.translateLocation(location)); + this.location = location; + this.propagateLocationToRedux(); + } + + private propagateLocationToRedux() { + if (this.location) { + this.reduxActions.connection.newLocation(this.translateLocation(this.location)); + } } private translateLocation(inputLocation: ILocation): ILocation { @@ -589,16 +646,17 @@ export default class AppRenderer { .sort((countryA, countryB) => countryA.name.localeCompare(countryB.name, this.locale)); } - private setRelays(relayList: IRelayList) { - const locations = this.convertRelayListToLocationList(relayList); - - this.reduxActions.settings.updateRelayLocations(locations); + private setRelayListPair(relayListPair: IRelayListPair) { + this.relayListPair = relayListPair; + this.propagateRelayListPairToRedux(); } - private setBridges(relayList: IRelayList) { - const locations = this.convertRelayListToLocationList(relayList); + private propagateRelayListPairToRedux() { + const relays = this.convertRelayListToLocationList(this.relayListPair.relays); + const bridges = this.convertRelayListToLocationList(this.relayListPair.relays); - this.reduxActions.settings.updateBridgeLocations(locations); + this.reduxActions.settings.updateRelayLocations(relays); + this.reduxActions.settings.updateBridgeLocations(bridges); } private setCurrentVersion(versionInfo: ICurrentAppVersionInfo) { diff --git a/gui/src/renderer/components/AdvancedSettings.tsx b/gui/src/renderer/components/AdvancedSettings.tsx index c9a985789b..53c0feb4bd 100644 --- a/gui/src/renderer/components/AdvancedSettings.tsx +++ b/gui/src/renderer/components/AdvancedSettings.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import { Component, View } from 'reactxp'; import { sprintf } from 'sprintf-js'; -import { colors } from '../../config.json'; import { BridgeState, RelayProtocol, TunnelProtocol } from '../../shared/daemon-rpc-types'; import { messages } from '../../shared/gettext'; import styles from './AdvancedSettingsStyles'; @@ -15,6 +14,7 @@ import { NavigationScrollbars, TitleBarItem, } from './NavigationBar'; +import Selector, { ISelectorItem } from './Selector'; import SettingsHeader, { HeaderTitle } from './SettingsHeader'; const MIN_MSSFIX_VALUE = 1000; @@ -404,69 +404,3 @@ export default class AdvancedSettings extends Component<IProps, IState> { return mssfix === undefined || (mssfix >= MIN_MSSFIX_VALUE && mssfix <= MAX_MSSFIX_VALUE); } } - -interface ISelectorItem<T> { - label: string; - value: T; -} - -interface ISelectorProps<T> { - title: string; - values: Array<ISelectorItem<T>>; - value: T; - onSelect: (value: T) => void; -} - -class Selector<T> extends Component<ISelectorProps<T>> { - public render() { - return ( - <Cell.Section style={styles.advanced_settings__selector_section}> - <Cell.SectionTitle>{this.props.title}</Cell.SectionTitle> - {this.props.values.map((item, i) => ( - <SelectorCell - key={i} - value={item.value} - selected={item.value === this.props.value} - onSelect={this.props.onSelect}> - {item.label} - </SelectorCell> - ))} - </Cell.Section> - ); - } -} - -interface ISelectorCell<T> { - value: T; - selected: boolean; - onSelect: (value: T) => void; - children?: React.ReactText; -} - -class SelectorCell<T> extends Component<ISelectorCell<T>> { - public render() { - return ( - <Cell.CellButton - style={this.props.selected ? styles.advanced_settings__cell_selected_hover : undefined} - cellHoverStyle={ - this.props.selected ? styles.advanced_settings__cell_selected_hover : undefined - } - onPress={this.onPress}> - <Cell.Icon - style={this.props.selected ? undefined : styles.advanced_settings__cell_icon_invisible} - source="icon-tick" - width={24} - height={24} - tintColor={colors.white} - /> - <Cell.Label>{this.props.children}</Cell.Label> - </Cell.CellButton> - ); - } - - private onPress = () => { - if (!this.props.selected) { - this.props.onSelect(this.props.value); - } - }; -} diff --git a/gui/src/renderer/components/SelectLanguage.tsx b/gui/src/renderer/components/SelectLanguage.tsx new file mode 100644 index 0000000000..c5c2228d00 --- /dev/null +++ b/gui/src/renderer/components/SelectLanguage.tsx @@ -0,0 +1,119 @@ +import * as React from 'react'; +import ReactDOM from 'react-dom'; +import { Component, Styles, View } from 'reactxp'; +import { colors } from '../../config.json'; +import { messages } from '../../shared/gettext'; +import CustomScrollbars from './CustomScrollbars'; +import { Container, Layout } from './Layout'; +import { + BackBarItem, + NavigationBar, + NavigationContainer, + NavigationItems, + NavigationScrollbars, + TitleBarItem, +} from './NavigationBar'; +import Selector, { ISelectorItem, SelectorCell } from './Selector'; +import SettingsHeader, { HeaderTitle } from './SettingsHeader'; + +interface IProps { + preferredLocale: string; + preferredLocalesList: Array<{ name: string; code: string }>; + setPreferredLocale: (locale: string) => void; + onClose: () => void; +} + +interface IState { + source: Array<ISelectorItem<string>>; +} + +const styles = { + page: Styles.createViewStyle({ + backgroundColor: colors.darkBlue, + flex: 1, + }), + container: Styles.createViewStyle({ + flex: 1, + }), + selector: Styles.createViewStyle({ + marginBottom: 0, + }), + // plain CSS style + scrollview: { + flex: 1, + }, +}; + +export default class SelectLanguage extends Component<IProps, IState> { + private scrollView = React.createRef<CustomScrollbars>(); + private selectedCellRef = React.createRef<SelectorCell<string>>(); + + constructor(props: IProps) { + super(props); + + this.state = { + source: [ + ...this.props.preferredLocalesList.map((item) => ({ label: item.name, value: item.code })), + ], + }; + } + + public componentDidMount() { + this.scrollToSelectedCell(); + } + + public render() { + return ( + <Layout> + <Container> + <View style={styles.page}> + <NavigationContainer> + <NavigationBar> + <NavigationItems> + <BackBarItem action={this.props.onClose}> + {// TRANSLATORS: Back button in navigation bar + messages.pgettext('select-language-nav', 'Settings')} + </BackBarItem> + <TitleBarItem> + {// TRANSLATORS: Title label in navigation bar + messages.pgettext('select-language-nav', 'Select language')} + </TitleBarItem> + </NavigationItems> + </NavigationBar> + + <View style={styles.container}> + <NavigationScrollbars style={styles.scrollview}> + <SettingsHeader> + <HeaderTitle> + {messages.pgettext('select-language-nav', 'Select language')} + </HeaderTitle> + </SettingsHeader> + <Selector + style={styles.selector} + title="" + values={this.state.source} + value={this.props.preferredLocale} + onSelect={this.props.setPreferredLocale} + selectedCellRef={this.selectedCellRef} + /> + </NavigationScrollbars> + </View> + </NavigationContainer> + </View> + </Container> + </Layout> + ); + } + + private scrollToSelectedCell() { + const ref = this.selectedCellRef.current; + const scrollView = this.scrollView.current; + + if (scrollView && ref) { + const cellDOMNode = ReactDOM.findDOMNode(ref); + if (cellDOMNode instanceof HTMLElement) { + scrollView.scrollToElement(cellDOMNode, 'middle'); + } + } + } +} diff --git a/gui/src/renderer/components/Selector.tsx b/gui/src/renderer/components/Selector.tsx new file mode 100644 index 0000000000..b57f49aa85 --- /dev/null +++ b/gui/src/renderer/components/Selector.tsx @@ -0,0 +1,89 @@ +import * as React from 'react'; +import { Component, Styles, Types } from 'reactxp'; +import { colors } from '../../config.json'; +import * as Cell from './Cell'; + +export interface ISelectorItem<T> { + label: string; + value: T; +} + +interface ISelectorProps<T> { + style?: Types.ViewStyleRuleSet; + title?: string; + values: Array<ISelectorItem<T>>; + value: T; + onSelect: (value: T) => void; + selectedCellRef?: React.Ref<SelectorCell<T>>; +} + +const styles = { + section: Styles.createViewStyle({ + marginBottom: 24, + }), + cell: { + selectedHover: Styles.createButtonStyle({ + backgroundColor: colors.green, + }), + }, + invisibleIcon: Styles.createViewStyle({ + opacity: 0, + }), +}; + +export default class Selector<T> extends Component<ISelectorProps<T>> { + public render() { + return ( + <Cell.Section style={[styles.section, this.props.style]}> + {this.props.title && <Cell.SectionTitle>{this.props.title}</Cell.SectionTitle>} + {this.props.values.map((item, i) => { + const selected = item.value === this.props.value; + + return ( + <SelectorCell + key={i} + value={item.value} + selected={selected} + ref={selected ? this.props.selectedCellRef : undefined} + onSelect={this.props.onSelect}> + {item.label} + </SelectorCell> + ); + })} + </Cell.Section> + ); + } +} + +interface ISelectorCellProps<T> { + value: T; + selected: boolean; + onSelect: (value: T) => void; + children?: React.ReactText; +} + +export class SelectorCell<T> extends Component<ISelectorCellProps<T>> { + public render() { + return ( + <Cell.CellButton + style={this.props.selected ? styles.cell.selectedHover : undefined} + cellHoverStyle={this.props.selected ? styles.cell.selectedHover : undefined} + onPress={this.onPress}> + <Cell.Icon + style={this.props.selected ? undefined : styles.invisibleIcon} + source="icon-tick" + width={24} + height={24} + tintColor={colors.white} + /> + <Cell.Label>{this.props.children}</Cell.Label> + </Cell.CellButton> + ); + } + + private onPress = () => { + if (!this.props.selected) { + this.props.onSelect(this.props.value); + } + }; +} diff --git a/gui/src/renderer/components/Settings.tsx b/gui/src/renderer/components/Settings.tsx index 6c04d9a8e5..c48d57641a 100644 --- a/gui/src/renderer/components/Settings.tsx +++ b/gui/src/renderer/components/Settings.tsx @@ -30,6 +30,7 @@ export interface IProps { isOffline: boolean; onQuit: () => void; onClose: () => void; + onViewSelectLanguage: () => void; onViewAccount: () => void; onViewSupport: () => void; onViewPreferences: () => void; @@ -61,6 +62,11 @@ export default class Settings extends Component<IProps> { <HeaderTitle>{messages.pgettext('settings-view', 'Settings')}</HeaderTitle> </SettingsHeader> <View> + <Cell.CellButton onPress={this.props.onViewSelectLanguage}> + <Cell.Label>{messages.pgettext('settings-view', 'Language')}</Cell.Label> + <Cell.Icon height={12} width={7} source="icon-chevron" /> + </Cell.CellButton> + {this.renderTopButtons()} {this.renderMiddleButtons()} {this.renderBottomButtons()} diff --git a/gui/src/renderer/containers/AccountPage.tsx b/gui/src/renderer/containers/AccountPage.tsx index 9edd159e1c..ee8588a7c3 100644 --- a/gui/src/renderer/containers/AccountPage.tsx +++ b/gui/src/renderer/containers/AccountPage.tsx @@ -8,10 +8,10 @@ import Account from '../components/Account'; import { IReduxState, ReduxDispatch } from '../redux/store'; import { ISharedRouteProps } from '../routes'; -const mapStateToProps = (state: IReduxState, props: ISharedRouteProps) => ({ +const mapStateToProps = (state: IReduxState, _props: ISharedRouteProps) => ({ accountToken: state.account.accountToken, accountExpiry: state.account.expiry, - expiryLocale: props.locale, + expiryLocale: state.userInterface.locale, isOffline: state.connection.isBlocked, }); const mapDispatchToProps = (dispatch: ReduxDispatch, props: ISharedRouteProps) => { diff --git a/gui/src/renderer/containers/ConnectPage.tsx b/gui/src/renderer/containers/ConnectPage.tsx index 66974ffed8..ffc7b76299 100644 --- a/gui/src/renderer/containers/ConnectPage.tsx +++ b/gui/src/renderer/containers/ConnectPage.tsx @@ -64,10 +64,10 @@ function getRelayName( } } -const mapStateToProps = (state: IReduxState, props: ISharedRouteProps) => { +const mapStateToProps = (state: IReduxState, _props: ISharedRouteProps) => { return { accountExpiry: state.account.expiry - ? new AccountExpiry(state.account.expiry, props.locale) + ? new AccountExpiry(state.account.expiry, state.userInterface.locale) : undefined, selectedRelayName: getRelayName(state.settings.relaySettings, state.settings.relayLocations), connection: state.connection, diff --git a/gui/src/renderer/containers/SelectLanguagePage.tsx b/gui/src/renderer/containers/SelectLanguagePage.tsx new file mode 100644 index 0000000000..248e63e367 --- /dev/null +++ b/gui/src/renderer/containers/SelectLanguagePage.tsx @@ -0,0 +1,31 @@ +import { goBack } from 'connected-react-router'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import SelectLanguage from '../components/SelectLanguage'; + +import { IReduxState, ReduxDispatch } from '../redux/store'; +import { ISharedRouteProps } from '../routes'; + +const mapStateToProps = (state: IReduxState) => ({ + preferredLocale: state.settings.guiSettings.preferredLocale, +}); + +const mapDispatchToProps = (dispatch: ReduxDispatch, props: ISharedRouteProps) => { + const history = bindActionCreators({ goBack }, dispatch); + + return { + preferredLocalesList: props.app.getPreferredLocaleList(), + setPreferredLocale(locale: string) { + props.app.setPreferredLocale(locale); + history.goBack(); + }, + onClose() { + history.goBack(); + }, + }; +}; + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(SelectLanguage); diff --git a/gui/src/renderer/containers/SettingsPage.tsx b/gui/src/renderer/containers/SettingsPage.tsx index eb036920c1..ca83b996c1 100644 --- a/gui/src/renderer/containers/SettingsPage.tsx +++ b/gui/src/renderer/containers/SettingsPage.tsx @@ -7,10 +7,10 @@ import Settings from '../components/Settings'; import { IReduxState, ReduxDispatch } from '../redux/store'; import { ISharedRouteProps } from '../routes'; -const mapStateToProps = (state: IReduxState, props: ISharedRouteProps) => ({ +const mapStateToProps = (state: IReduxState, _props: ISharedRouteProps) => ({ loginState: state.account.status, accountExpiry: state.account.expiry, - expiryLocale: props.locale, + expiryLocale: state.userInterface.locale, appVersion: state.version.current, consistentVersion: state.version.consistent, upToDateVersion: !state.version.currentIsOutdated, @@ -21,6 +21,7 @@ const mapDispatchToProps = (dispatch: ReduxDispatch, _props: ISharedRouteProps) return { onQuit: () => remote.app.quit(), onClose: () => history.goBack(), + onViewSelectLanguage: () => history.push('/settings/language'), onViewAccount: () => history.push('/settings/account'), onViewSupport: () => history.push('/settings/support'), onViewPreferences: () => history.push('/settings/preferences'), diff --git a/gui/src/renderer/containers/WireguardKeysPage.tsx b/gui/src/renderer/containers/WireguardKeysPage.tsx index 813526ff29..8c88a042cc 100644 --- a/gui/src/renderer/containers/WireguardKeysPage.tsx +++ b/gui/src/renderer/containers/WireguardKeysPage.tsx @@ -8,10 +8,10 @@ import { IWgKey } from '../redux/settings/reducers'; import { IReduxState, ReduxDispatch } from '../redux/store'; import { ISharedRouteProps } from '../routes'; -const mapStateToProps = (state: IReduxState, props: ISharedRouteProps) => ({ +const mapStateToProps = (state: IReduxState, _props: ISharedRouteProps) => ({ keyState: state.settings.wireguardKeyState, isOffline: state.connection.isBlocked, - locale: props.locale, + locale: state.userInterface.locale, }); const mapDispatchToProps = (dispatch: ReduxDispatch, props: ISharedRouteProps) => { const history = bindActionCreators({ push, goBack }, dispatch); diff --git a/gui/src/renderer/redux/settings/reducers.ts b/gui/src/renderer/redux/settings/reducers.ts index fed04b38e4..585eefbbb2 100644 --- a/gui/src/renderer/redux/settings/reducers.ts +++ b/gui/src/renderer/redux/settings/reducers.ts @@ -135,6 +135,7 @@ export interface ISettingsReduxState { const initialState: ISettingsReduxState = { autoStart: false, guiSettings: { + preferredLocale: 'system', enableSystemNotifications: true, autoConnect: true, monochromaticIcon: false, diff --git a/gui/src/renderer/redux/userinterface/actions.ts b/gui/src/renderer/redux/userinterface/actions.ts index f9e2fff444..c83ef47bff 100644 --- a/gui/src/renderer/redux/userinterface/actions.ts +++ b/gui/src/renderer/redux/userinterface/actions.ts @@ -1,5 +1,10 @@ import { LocationScope } from './reducers'; +export interface IUpdateLocaleAction { + type: 'UPDATE_LOCALE'; + locale: string; +} + export interface IUpdateWindowArrowPositionAction { type: 'UPDATE_WINDOW_ARROW_POSITION'; arrowPosition: number; @@ -15,10 +20,18 @@ export interface ISetLocationScopeAction { } export type UserInterfaceAction = + | IUpdateLocaleAction | IUpdateWindowArrowPositionAction | IUpdateConnectionInfoOpenAction | ISetLocationScopeAction; +function updateLocale(locale: string): IUpdateLocaleAction { + return { + type: 'UPDATE_LOCALE', + locale, + }; +} + function updateWindowArrowPosition(arrowPosition: number): IUpdateWindowArrowPositionAction { return { type: 'UPDATE_WINDOW_ARROW_POSITION', @@ -39,4 +52,4 @@ function setLocationScope(scope: LocationScope): ISetLocationScopeAction { }; } -export default { updateWindowArrowPosition, toggleConnectionPanel, setLocationScope }; +export default { updateLocale, updateWindowArrowPosition, toggleConnectionPanel, setLocationScope }; diff --git a/gui/src/renderer/redux/userinterface/reducers.ts b/gui/src/renderer/redux/userinterface/reducers.ts index 729d5194b6..7941d6001b 100644 --- a/gui/src/renderer/redux/userinterface/reducers.ts +++ b/gui/src/renderer/redux/userinterface/reducers.ts @@ -6,12 +6,14 @@ export enum LocationScope { } export interface IUserInterfaceReduxState { + locale: string; arrowPosition?: number; connectionPanelVisible: boolean; locationScope: LocationScope; } const initialState: IUserInterfaceReduxState = { + locale: 'en', connectionPanelVisible: false, locationScope: LocationScope.relay, }; @@ -21,6 +23,9 @@ export default function( action: ReduxAction, ): IUserInterfaceReduxState { switch (action.type) { + case 'UPDATE_LOCALE': + return { ...state, locale: action.locale }; + case 'UPDATE_WINDOW_ARROW_POSITION': return { ...state, arrowPosition: action.arrowPosition }; diff --git a/gui/src/renderer/routes.tsx b/gui/src/renderer/routes.tsx index 814eeae31e..2f715c472a 100644 --- a/gui/src/renderer/routes.tsx +++ b/gui/src/renderer/routes.tsx @@ -9,6 +9,7 @@ import LaunchPage from './containers/LaunchPage'; import LoginPage from './containers/LoginPage'; import PlatformWindowContainer from './containers/PlatformWindowContainer'; import PreferencesPage from './containers/PreferencesPage'; +import SelectLanguagePage from './containers/SelectLanguagePage'; import SelectLocationPage from './containers/SelectLocationPage'; import SettingsPage from './containers/SettingsPage'; import SupportPage from './containers/SupportPage'; @@ -17,7 +18,6 @@ import { getTransitionProps } from './transitions'; export interface ISharedRouteProps { app: App; - locale: string; } type CustomRouteProps = { @@ -84,6 +84,7 @@ class AppRoutes extends React.Component<IAppRoutesProps, IAppRoutesState> { <CustomRoute exact={true} path="/login" component={LoginPage} /> <CustomRoute exact={true} path="/connect" component={ConnectPage} /> <CustomRoute exact={true} path="/settings" component={SettingsPage} /> + <CustomRoute exact={true} path="/settings/language" component={SelectLanguagePage} /> <CustomRoute exact={true} path="/settings/account" component={AccountPage} /> <CustomRoute exact={true} path="/settings/preferences" component={PreferencesPage} /> <CustomRoute diff --git a/gui/src/renderer/transitions.ts b/gui/src/renderer/transitions.ts index 21c9884fb0..23ada748aa 100644 --- a/gui/src/renderer/transitions.ts +++ b/gui/src/renderer/transitions.ts @@ -40,6 +40,7 @@ const transitions: ITransitionMap = { * (null) is used to indicate any route. */ const transitionRules = [ + r('/settings', '/settings/language', transitions.push), r('/settings', '/settings/account', transitions.push), r('/settings', '/settings/preferences', transitions.push), r('/settings', '/settings/advanced', transitions.push), |
