summaryrefslogtreecommitdiffhomepage
path: root/gui/src/renderer
diff options
context:
space:
mode:
Diffstat (limited to 'gui/src/renderer')
-rw-r--r--gui/src/renderer/app.tsx118
-rw-r--r--gui/src/renderer/components/AdvancedSettings.tsx68
-rw-r--r--gui/src/renderer/components/SelectLanguage.tsx119
-rw-r--r--gui/src/renderer/components/Selector.tsx89
-rw-r--r--gui/src/renderer/components/Settings.tsx6
-rw-r--r--gui/src/renderer/containers/AccountPage.tsx4
-rw-r--r--gui/src/renderer/containers/ConnectPage.tsx4
-rw-r--r--gui/src/renderer/containers/SelectLanguagePage.tsx31
-rw-r--r--gui/src/renderer/containers/SettingsPage.tsx5
-rw-r--r--gui/src/renderer/containers/WireguardKeysPage.tsx4
-rw-r--r--gui/src/renderer/redux/settings/reducers.ts1
-rw-r--r--gui/src/renderer/redux/userinterface/actions.ts15
-rw-r--r--gui/src/renderer/redux/userinterface/reducers.ts5
-rw-r--r--gui/src/renderer/routes.tsx3
-rw-r--r--gui/src/renderer/transitions.ts1
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),