aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/api/server/ServerApi.js13
-rw-r--r--src/app.js4
-rw-r--r--src/components/auth/AuthLayout.js27
-rw-r--r--src/components/auth/Invite.js32
-rw-r--r--src/components/auth/Login.js9
-rw-r--r--src/components/auth/Pricing.js23
-rw-r--r--src/components/auth/Signup.js3
-rw-r--r--src/components/layout/AppLayout.js136
-rw-r--r--src/components/layout/Sidebar.js3
-rw-r--r--src/components/services/content/ErrorHandlers/WebviewErrorHandler.js84
-rw-r--r--src/components/services/content/ErrorHandlers/styles.js25
-rw-r--r--src/components/services/content/ServiceDisabled.js1
-rw-r--r--src/components/services/content/ServiceWebview.js65
-rw-r--r--src/components/services/content/Services.js6
-rw-r--r--src/components/services/content/WebviewCrashHandler.js24
-rw-r--r--src/components/settings/SettingsLayout.js23
-rw-r--r--src/components/settings/account/AccountDashboard.js48
-rw-r--r--src/components/settings/navigation/SettingsNavigation.js5
-rw-r--r--src/components/settings/recipes/RecipeItem.js1
-rw-r--r--src/components/settings/recipes/RecipesDashboard.js10
-rw-r--r--src/components/settings/services/EditServiceForm.js53
-rw-r--r--src/components/settings/services/ServiceItem.js1
-rw-r--r--src/components/settings/services/ServicesDashboard.js20
-rw-r--r--src/components/settings/settings/EditSettingsForm.js14
-rw-r--r--src/components/settings/user/EditUserForm.js4
-rw-r--r--src/components/subscription/SubscriptionForm.js49
-rw-r--r--src/components/subscription/SubscriptionPopup.js4
-rw-r--r--src/components/ui/AppLoader.js15
-rw-r--r--src/components/ui/AppLoader/index.js70
-rw-r--r--src/components/ui/AppLoader/styles.js16
-rw-r--r--src/components/ui/Button.js3
-rw-r--r--src/components/ui/FullscreenLoader/index.js56
-rw-r--r--src/components/ui/FullscreenLoader/styles.js23
-rw-r--r--src/components/ui/ImageUpload.js10
-rw-r--r--src/components/ui/InfoBar.js4
-rw-r--r--src/components/ui/Infobox.js2
-rw-r--r--src/components/ui/Input.js6
-rw-r--r--src/components/ui/Link.js1
-rw-r--r--src/components/ui/PremiumFeatureContainer/index.js1
-rw-r--r--src/components/ui/PremiumFeatureContainer/styles.js2
-rw-r--r--src/components/ui/Radio.js4
-rw-r--r--src/components/ui/SearchInput.js27
-rw-r--r--src/components/ui/Select.js8
-rw-r--r--src/components/ui/StatusBarTargetUrl.js3
-rw-r--r--src/components/ui/Tabs/TabItem.js4
-rw-r--r--src/components/ui/WebviewLoader/index.js25
-rw-r--r--src/components/ui/WebviewLoader/styles.js9
-rw-r--r--src/components/util/ErrorBoundary/index.js60
-rw-r--r--src/components/util/ErrorBoundary/styles.js13
-rw-r--r--src/config.js3
-rw-r--r--src/containers/auth/AuthLayoutContainer.js4
-rw-r--r--src/containers/layout/AppLayoutContainer.js5
-rw-r--r--src/containers/settings/AccountScreen.js35
-rw-r--r--src/containers/settings/EditServiceScreen.js65
-rw-r--r--src/containers/settings/EditSettingsScreen.js49
-rw-r--r--src/containers/settings/EditUserScreen.js19
-rw-r--r--src/containers/settings/InviteScreen.js16
-rw-r--r--src/containers/settings/RecipesScreen.js40
-rw-r--r--src/containers/settings/ServicesScreen.js31
-rw-r--r--src/containers/settings/SettingsWindow.js15
-rw-r--r--src/containers/subscription/SubscriptionFormScreen.js2
-rw-r--r--src/electron/Settings.js1
-rw-r--r--src/electron/ipc-api/download.js2
-rw-r--r--src/features/delayApp/Component.js23
-rw-r--r--src/features/delayApp/index.js1
-rw-r--r--src/features/serviceProxy/index.js8
-rw-r--r--src/helpers/array-helpers.js4
-rw-r--r--src/helpers/i18n-helpers.js30
-rw-r--r--src/i18n/locales/ca.json45
-rw-r--r--src/i18n/locales/cs.json13
-rw-r--r--src/i18n/locales/de.json25
-rw-r--r--src/i18n/locales/el.json13
-rw-r--r--src/i18n/locales/en-US.json15
-rw-r--r--src/i18n/locales/es.json41
-rw-r--r--src/i18n/locales/fr.json45
-rw-r--r--src/i18n/locales/ga.json13
-rw-r--r--src/i18n/locales/hr.json13
-rw-r--r--src/i18n/locales/hu.json13
-rw-r--r--src/i18n/locales/id.json49
-rw-r--r--src/i18n/locales/it.json23
-rw-r--r--src/i18n/locales/ja.json45
-rw-r--r--src/i18n/locales/ka.json13
-rw-r--r--src/i18n/locales/nl-BE.json13
-rw-r--r--src/i18n/locales/nl.json41
-rw-r--r--src/i18n/locales/pl.json37
-rw-r--r--src/i18n/locales/pt-BR.json39
-rw-r--r--src/i18n/locales/pt.json13
-rw-r--r--src/i18n/locales/ru.json13
-rw-r--r--src/i18n/locales/sk.json13
-rw-r--r--src/i18n/locales/sr.json13
-rw-r--r--src/i18n/locales/tr.json21
-rw-r--r--src/i18n/locales/uk.json13
-rw-r--r--src/i18n/locales/zh-TW.json33
-rw-r--r--src/index.js8
-rw-r--r--src/lib/Tray.js10
-rw-r--r--src/models/News.js3
-rw-r--r--src/models/Order.js5
-rw-r--r--src/models/Plan.js1
-rw-r--r--src/models/Recipe.js12
-rw-r--r--src/models/RecipePreview.js6
-rw-r--r--src/models/Service.js51
-rw-r--r--src/models/User.js17
-rw-r--r--src/stores/AppStore.js16
-rw-r--r--src/stores/DictionaryStore.js45
-rw-r--r--src/stores/FeaturesStore.js1
-rw-r--r--src/stores/GlobalErrorStore.js1
-rw-r--r--src/stores/NewsStore.js1
-rw-r--r--src/stores/PaymentStore.js3
-rw-r--r--src/stores/RecipePreviewsStore.js2
-rw-r--r--src/stores/RecipesStore.js2
-rw-r--r--src/stores/RequestStore.js3
-rw-r--r--src/stores/ServicesStore.js23
-rw-r--r--src/stores/SettingsStore.js25
-rw-r--r--src/stores/UserStore.js26
-rw-r--r--src/stores/index.js2
-rw-r--r--src/stores/lib/CachedRequest.js1
-rw-r--r--src/stores/lib/Reaction.js2
-rw-r--r--src/stores/lib/Request.js8
-rw-r--r--src/stores/lib/Store.js4
-rw-r--r--src/styles/auth.scss14
-rw-r--r--src/styles/content-tabs.scss16
-rw-r--r--src/styles/searchInput.scss4
-rw-r--r--src/styles/select.scss4
-rw-r--r--src/styles/settings.scss4
-rw-r--r--src/styles/welcome.scss5
-rw-r--r--src/theme/dark/index.js7
-rw-r--r--src/theme/default/index.js8
-rw-r--r--src/webview/contextMenu.js67
-rw-r--r--src/webview/darkmode.js12
-rw-r--r--src/webview/notifications.js7
-rw-r--r--src/webview/plugin.js90
-rw-r--r--src/webview/recipe.js131
-rw-r--r--src/webview/spellchecker.js34
133 files changed, 1903 insertions, 795 deletions
diff --git a/src/api/server/ServerApi.js b/src/api/server/ServerApi.js
index 164419951..2871769a9 100644
--- a/src/api/server/ServerApi.js
+++ b/src/api/server/ServerApi.js
@@ -42,6 +42,7 @@ const API_VERSION = 'v1';
42 42
43export default class ServerApi { 43export default class ServerApi {
44 recipePreviews = []; 44 recipePreviews = [];
45
45 recipes = []; 46 recipes = [];
46 47
47 // User 48 // User
@@ -522,8 +523,7 @@ export default class ServerApi {
522 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/recipes/${s.service}`, 523 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/recipes/${s.service}`,
523 this._prepareAuthRequest({ 524 this._prepareAuthRequest({
524 method: 'GET', 525 method: 'GET',
525 }), 526 }));
526 );
527 527
528 if (request.status === 200) { 528 if (request.status === 200) {
529 const data = await request.json(); 529 const data = await request.json();
@@ -549,9 +549,9 @@ export default class ServerApi {
549 549
550 await this._bulkRecipeCheck(recipes); 550 await this._bulkRecipeCheck(recipes);
551 551
552 return Promise.all(services 552 /* eslint-disable no-return-await */
553 .map(async service => await this._prepareServiceModel(service)) // eslint-disable-line 553 return Promise.all(services.map(async service => await this._prepareServiceModel(service)));
554 ); 554 /* eslint-enable no-return-await */
555 } 555 }
556 556
557 async _prepareServiceModel(service) { 557 async _prepareServiceModel(service) {
@@ -596,8 +596,7 @@ export default class ServerApi {
596 } 596 }
597 597
598 return recipe; 598 return recipe;
599 }), 599 })).catch(err => console.error('Can\'t load recipe', err));
600 ).catch(err => console.error('Can\'t load recipe', err));
601 } 600 }
602 601
603 _mapRecipePreviewModel(recipes) { 602 _mapRecipePreviewModel(recipes) {
diff --git a/src/app.js b/src/app.js
index 43d0cf018..831dd93ce 100644
--- a/src/app.js
+++ b/src/app.js
@@ -4,7 +4,9 @@ import React from 'react';
4import { render } from 'react-dom'; 4import { render } from 'react-dom';
5import { Provider } from 'mobx-react'; 5import { Provider } from 'mobx-react';
6import { syncHistoryWithStore, RouterStore } from 'mobx-react-router'; 6import { syncHistoryWithStore, RouterStore } from 'mobx-react-router';
7import { Router, Route, hashHistory, IndexRedirect } from 'react-router'; 7import {
8 Router, Route, hashHistory, IndexRedirect,
9} from 'react-router';
8 10
9import '@babel/polyfill'; 11import '@babel/polyfill';
10import smoothScroll from 'smoothscroll-polyfill'; 12import smoothScroll from 'smoothscroll-polyfill';
diff --git a/src/components/auth/AuthLayout.js b/src/components/auth/AuthLayout.js
index 4fb0e6a59..ac8fdbe5b 100644
--- a/src/components/auth/AuthLayout.js
+++ b/src/components/auth/AuthLayout.js
@@ -1,7 +1,6 @@
1import React, { Component } from 'react'; 1import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react'; 3import { observer } from 'mobx-react';
4import { RouteTransition } from 'react-router-transition';
5import { intlShape } from 'react-intl'; 4import { intlShape } from 'react-intl';
6import { TitleBar } from 'electron-react-titlebar'; 5import { TitleBar } from 'electron-react-titlebar';
7 6
@@ -16,7 +15,6 @@ import { isWindows } from '../../environment';
16export default @observer class AuthLayout extends Component { 15export default @observer class AuthLayout extends Component {
17 static propTypes = { 16 static propTypes = {
18 children: oneOrManyChildElements.isRequired, 17 children: oneOrManyChildElements.isRequired,
19 pathname: PropTypes.string.isRequired,
20 error: globalErrorPropType.isRequired, 18 error: globalErrorPropType.isRequired,
21 isOnline: PropTypes.bool.isRequired, 19 isOnline: PropTypes.bool.isRequired,
22 isAPIHealthy: PropTypes.bool.isRequired, 20 isAPIHealthy: PropTypes.bool.isRequired,
@@ -33,7 +31,6 @@ export default @observer class AuthLayout extends Component {
33 render() { 31 render() {
34 const { 32 const {
35 children, 33 children,
36 pathname,
37 error, 34 error,
38 isOnline, 35 isOnline,
39 isAPIHealthy, 36 isAPIHealthy,
@@ -46,8 +43,8 @@ export default @observer class AuthLayout extends Component {
46 43
47 return ( 44 return (
48 <div className={darkMode ? 'theme__dark' : ''}> 45 <div className={darkMode ? 'theme__dark' : ''}>
49 {isWindows && !isFullScreen && <TitleBar menu={window.franz.menu.template} icon={'assets/images/logo.svg'} />} 46 {isWindows && !isFullScreen && <TitleBar menu={window.franz.menu.template} icon="assets/images/logo.svg" />}
50 <div className={'auth'}> 47 <div className="auth">
51 {!isOnline && ( 48 {!isOnline && (
52 <InfoBar 49 <InfoBar
53 type="warning" 50 type="warning"
@@ -69,22 +66,10 @@ export default @observer class AuthLayout extends Component {
69 </InfoBar> 66 </InfoBar>
70 )} 67 )}
71 <div className="auth__layout"> 68 <div className="auth__layout">
72 <RouteTransition 69 {/* Inject globalError into children */}
73 pathname={pathname} 70 {React.cloneElement(children, {
74 atEnter={{ opacity: 0 }} 71 error,
75 atLeave={{ opacity: 0 }} 72 })}
76 atActive={{ opacity: 1 }}
77 mapStyles={styles => ({
78 transform: `translateX(${styles.translateX}%)`,
79 opacity: styles.opacity,
80 })}
81 component="span"
82 >
83 {/* Inject globalError into children */}
84 {React.cloneElement(children, {
85 error,
86 })}
87 </RouteTransition>
88 </div> 73 </div>
89 {/* </div> */} 74 {/* </div> */}
90 <Link to="https://adlk.io" className="auth__adlk" target="_blank"> 75 <Link to="https://adlk.io" className="auth__adlk" target="_blank">
diff --git a/src/components/auth/Invite.js b/src/components/auth/Invite.js
index 96821a61a..fd957ee73 100644
--- a/src/components/auth/Invite.js
+++ b/src/components/auth/Invite.js
@@ -1,4 +1,4 @@
1import React, { Component } from 'react'; 1import React, { Component, Fragment } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react'; 3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, intlShape } from 'react-intl';
@@ -127,7 +127,7 @@ export default @observer class Invite extends Component {
127 }); 127 });
128 128
129 const renderForm = ( 129 const renderForm = (
130 <div> 130 <Fragment>
131 {this.state.showSuccessInfo && isInviteSuccessful && ( 131 {this.state.showSuccessInfo && isInviteSuccessful && (
132 <Appear> 132 <Appear>
133 <Infobox 133 <Infobox
@@ -141,11 +141,13 @@ export default @observer class Invite extends Component {
141 )} 141 )}
142 142
143 <form className="franz-form auth__form" onSubmit={e => this.submit(e)}> 143 <form className="franz-form auth__form" onSubmit={e => this.submit(e)}>
144 {!embed && (<img 144 {!embed && (
145 src="./assets/images/logo.svg" 145 <img
146 className="auth__logo" 146 src="./assets/images/logo.svg"
147 alt="" 147 className="auth__logo"
148 />)} 148 alt=""
149 />
150 )}
149 <h1 className={embed && 'invite__embed'}> 151 <h1 className={embed && 'invite__embed'}>
150 {intl.formatMessage(messages.headline)} 152 {intl.formatMessage(messages.headline)}
151 </h1> 153 </h1>
@@ -164,14 +166,16 @@ export default @observer class Invite extends Component {
164 label={intl.formatMessage(messages.submitButtonLabel)} 166 label={intl.formatMessage(messages.submitButtonLabel)}
165 loaded={!isLoadingInvite} 167 loaded={!isLoadingInvite}
166 /> 168 />
167 {!embed && (<Link 169 {!embed && (
168 to="/" 170 <Link
169 className="franz-form__button franz-form__button--secondary auth__button auth__button--skip" 171 to="/"
170 > 172 className="franz-form__button franz-form__button--secondary auth__button auth__button--skip"
171 {intl.formatMessage(messages.skipButtonLabel)} 173 >
172 </Link>)} 174 {intl.formatMessage(messages.skipButtonLabel)}
175 </Link>
176 )}
173 </form> 177 </form>
174 </div> 178 </Fragment>
175 ); 179 );
176 180
177 return ( 181 return (
diff --git a/src/components/auth/Login.js b/src/components/auth/Login.js
index f465b35a5..5d21f8b60 100644
--- a/src/components/auth/Login.js
+++ b/src/components/auth/Login.js
@@ -11,11 +11,8 @@ import Button from '../ui/Button';
11import Link from '../ui/Link'; 11import Link from '../ui/Link';
12import Infobox from '../ui/Infobox'; 12import Infobox from '../ui/Infobox';
13 13
14
15import { globalError as globalErrorPropType } from '../../prop-types'; 14import { globalError as globalErrorPropType } from '../../prop-types';
16 15
17// import Appear from '../ui/effects/Appear';
18
19const messages = defineMessages({ 16const messages = defineMessages({
20 headline: { 17 headline: {
21 id: 'login.headline', 18 id: 'login.headline',
@@ -86,18 +83,18 @@ export default @observer class Login extends Component {
86 }, 83 },
87 }, this.context.intl); 84 }, this.context.intl);
88 85
86 emailField = null;
87
89 submit(e) { 88 submit(e) {
90 e.preventDefault(); 89 e.preventDefault();
91 this.form.submit({ 90 this.form.submit({
92 onSuccess: (form) => { 91 onSuccess: (form) => {
93 this.props.onSubmit(form.values()); 92 this.props.onSubmit(form.values());
94 }, 93 },
95 onError: () => {}, 94 onError: () => { },
96 }); 95 });
97 } 96 }
98 97
99 emailField = null;
100
101 render() { 98 render() {
102 const { form } = this; 99 const { form } = this;
103 const { intl } = this.context; 100 const { intl } = this.context;
diff --git a/src/components/auth/Pricing.js b/src/components/auth/Pricing.js
index f08129568..7ab14f429 100644
--- a/src/components/auth/Pricing.js
+++ b/src/components/auth/Pricing.js
@@ -69,18 +69,29 @@ export default @observer class Signup extends Component {
69 donor.amount ? ( 69 donor.amount ? (
70 <span> 70 <span>
71 <p> 71 <p>
72 Thank you so much for your previous donation of <strong>$ {donor.amount}</strong>. 72 Thank you so much for your previous donation of
73 {' '}
74 <strong>
75 $
76 {donor.amount}
77 </strong>
78 .
73 <br /> 79 <br />
74 Your support allowed us to get where we are today. 80 Your support allowed us to get where we are today.
75 <br /> 81 <br />
76 </p> 82 </p>
77 <p> 83 <p>
78 As an early supporter, you get <strong>a lifetime premium supporter license</strong> without any 84 As an early supporter, you get
85 {' '}
86 <strong>a lifetime premium supporter license</strong>
87 {' '}
88 without any
79 additional charges. 89 additional charges.
80 </p> 90 </p>
81 <p> 91 <p>
82 However, If you want to keep supporting us, you are more than welcome to subscribe to a plan. 92 However, If you want to keep supporting us, you are more than welcome to subscribe to a plan.
83 <br /><br /> 93 <br />
94 <br />
84 </p> 95 </p>
85 </span> 96 </span>
86 ) : ( 97 ) : (
@@ -113,12 +124,6 @@ export default @observer class Signup extends Component {
113 hideInfo={Boolean(donor.amount)} 124 hideInfo={Boolean(donor.amount)}
114 skipButtonLabel={intl.formatMessage(messages.skipPayment)} 125 skipButtonLabel={intl.formatMessage(messages.skipPayment)}
115 /> 126 />
116 {/* <Link
117 to={inviteRoute}
118 className="franz-form__button franz-form__button--secondary auth__button auth__button--skip"
119 >
120 {intl.formatMessage(messages.skipPayment)}
121 </Link> */}
122 </Appear> 127 </Appear>
123 </Loader> 128 </Loader>
124 </form> 129 </form>
diff --git a/src/components/auth/Signup.js b/src/components/auth/Signup.js
index bbcad8b67..d9b83eeb8 100644
--- a/src/components/auth/Signup.js
+++ b/src/components/auth/Signup.js
@@ -199,7 +199,8 @@ export default @observer class Signup extends Component {
199 className="link" 199 className="link"
200 > 200 >
201 {intl.formatMessage(messages.privacy)} 201 {intl.formatMessage(messages.privacy)}
202 </Link>. 202 </Link>
203 .
203 </p> 204 </p>
204 </form> 205 </form>
205 <div className="auth__links"> 206 <div className="auth__links">
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js
index 3ababe54a..dbe0bb4b6 100644
--- a/src/components/layout/AppLayout.js
+++ b/src/components/layout/AppLayout.js
@@ -6,6 +6,8 @@ import { TitleBar } from 'electron-react-titlebar';
6 6
7import InfoBar from '../ui/InfoBar'; 7import InfoBar from '../ui/InfoBar';
8import { Component as DelayApp } from '../../features/delayApp'; 8import { Component as DelayApp } from '../../features/delayApp';
9import ErrorBoundary from '../util/ErrorBoundary';
10
9import globalMessages from '../../i18n/globalMessages'; 11import globalMessages from '../../i18n/globalMessages';
10 12
11import { isWindows } from '../../environment'; 13import { isWindows } from '../../environment';
@@ -94,74 +96,78 @@ export default @observer class AppLayout extends Component {
94 const { intl } = this.context; 96 const { intl } = this.context;
95 97
96 return ( 98 return (
97 <div className={(darkMode ? 'theme__dark' : '')}> 99 <ErrorBoundary>
98 <div className="app"> 100 <div className={(darkMode ? 'theme__dark' : '')}>
99 {isWindows && !isFullScreen && <TitleBar menu={window.franz.menu.template} icon={'assets/images/logo.svg'} />} 101 <div className="app">
100 <div className="app__content"> 102 {isWindows && !isFullScreen && <TitleBar menu={window.franz.menu.template} icon="assets/images/logo.svg" />}
101 {sidebar} 103 <div className="app__content">
102 <div className="app__service"> 104 {sidebar}
103 {news.length > 0 && news.map(item => ( 105 <div className="app__service">
104 <InfoBar 106 {news.length > 0 && news.map(item => (
105 key={item.id} 107 <InfoBar
106 position="top" 108 key={item.id}
107 type={item.type} 109 position="top"
108 sticky={item.sticky} 110 type={item.type}
109 onHide={() => removeNewsItem({ newsId: item.id })} 111 sticky={item.sticky}
110 > 112 onHide={() => removeNewsItem({ newsId: item.id })}
111 <span dangerouslySetInnerHTML={createMarkup(item.message)} /> 113 >
112 </InfoBar> 114 <span dangerouslySetInnerHTML={createMarkup(item.message)} />
113 ))} 115 </InfoBar>
114 {!isOnline && ( 116 ))}
115 <InfoBar 117 {!isOnline && (
116 type="danger" 118 <InfoBar
117 > 119 type="danger"
118 <span className="mdi mdi-flash" /> 120 >
119 {intl.formatMessage(globalMessages.notConnectedToTheInternet)} 121 <span className="mdi mdi-flash" />
120 </InfoBar> 122 {intl.formatMessage(globalMessages.notConnectedToTheInternet)}
121 )} 123 </InfoBar>
122 {!areRequiredRequestsSuccessful && showRequiredRequestsError && ( 124 )}
123 <InfoBar 125 {!areRequiredRequestsSuccessful && showRequiredRequestsError && (
124 type="danger" 126 <InfoBar
125 ctaLabel="Try again" 127 type="danger"
126 ctaLoading={areRequiredRequestsLoading} 128 ctaLabel="Try again"
127 sticky 129 ctaLoading={areRequiredRequestsLoading}
128 onClick={retryRequiredRequests} 130 sticky
129 > 131 onClick={retryRequiredRequests}
130 <span className="mdi mdi-flash" /> 132 >
131 {intl.formatMessage(messages.requiredRequestsFailed)} 133 <span className="mdi mdi-flash" />
132 </InfoBar> 134 {intl.formatMessage(messages.requiredRequestsFailed)}
133 )} 135 </InfoBar>
134 {showServicesUpdatedInfoBar && ( 136 )}
135 <InfoBar 137 {showServicesUpdatedInfoBar && (
136 type="primary" 138 <InfoBar
137 ctaLabel={intl.formatMessage(messages.buttonReloadServices)} 139 type="primary"
138 onClick={reloadServicesAfterUpdate} 140 ctaLabel={intl.formatMessage(messages.buttonReloadServices)}
139 sticky 141 onClick={reloadServicesAfterUpdate}
140 > 142 sticky
141 <span className="mdi mdi-power-plug" /> 143 >
142 {intl.formatMessage(messages.servicesUpdated)} 144 <span className="mdi mdi-power-plug" />
143 </InfoBar> 145 {intl.formatMessage(messages.servicesUpdated)}
144 )} 146 </InfoBar>
145 {appUpdateIsDownloaded && ( 147 )}
146 <InfoBar 148 {appUpdateIsDownloaded && (
147 type="primary" 149 <InfoBar
148 ctaLabel={intl.formatMessage(messages.buttonInstallUpdate)} 150 type="primary"
149 onClick={installAppUpdate} 151 ctaLabel={intl.formatMessage(messages.buttonInstallUpdate)}
150 sticky 152 onClick={installAppUpdate}
151 > 153 sticky
152 <span className="mdi mdi-information" /> 154 >
153 {intl.formatMessage(messages.updateAvailable)} <a href="https://meetfranz.com/changelog" target="_blank"> 155 <span className="mdi mdi-information" />
154 <u>{intl.formatMessage(messages.changelog)}</u> 156 {intl.formatMessage(messages.updateAvailable)}
155 </a> 157 {' '}
156 </InfoBar> 158 <a href="https://meetfranz.com/changelog" target="_blank">
157 )} 159 <u>{intl.formatMessage(messages.changelog)}</u>
158 {isDelayAppScreenVisible && (<DelayApp />)} 160 </a>
159 {services} 161 </InfoBar>
162 )}
163 {isDelayAppScreenVisible && (<DelayApp />)}
164 {services}
165 </div>
160 </div> 166 </div>
161 </div> 167 </div>
168 {children}
162 </div> 169 </div>
163 {children} 170 </ErrorBoundary>
164 </div>
165 ); 171 );
166 } 172 }
167} 173}
diff --git a/src/components/layout/Sidebar.js b/src/components/layout/Sidebar.js
index 6ea95bf88..609a3b604 100644
--- a/src/components/layout/Sidebar.js
+++ b/src/components/layout/Sidebar.js
@@ -65,6 +65,7 @@ export default @observer class Sidebar extends Component {
65 disableToolTip={() => this.disableToolTip()} 65 disableToolTip={() => this.disableToolTip()}
66 /> 66 />
67 <button 67 <button
68 type="button"
68 onClick={toggleMuteApp} 69 onClick={toggleMuteApp}
69 className={`sidebar__button sidebar__button--audio ${isAppMuted ? 'is-muted' : ''}`} 70 className={`sidebar__button sidebar__button--audio ${isAppMuted ? 'is-muted' : ''}`}
70 data-tip={`${intl.formatMessage(isAppMuted ? messages.unmute : messages.mute)} (${ctrlKey}+Shift+M)`} 71 data-tip={`${intl.formatMessage(isAppMuted ? messages.unmute : messages.mute)} (${ctrlKey}+Shift+M)`}
@@ -72,6 +73,7 @@ export default @observer class Sidebar extends Component {
72 <i className={`mdi mdi-bell${isAppMuted ? '-off' : ''}`} /> 73 <i className={`mdi mdi-bell${isAppMuted ? '-off' : ''}`} />
73 </button> 74 </button>
74 <button 75 <button
76 type="button"
75 onClick={() => openSettings({ path: 'recipes' })} 77 onClick={() => openSettings({ path: 'recipes' })}
76 className="sidebar__button sidebar__button--new-service" 78 className="sidebar__button sidebar__button--new-service"
77 data-tip={`${intl.formatMessage(messages.addNewService)} (${ctrlKey}+N)`} 79 data-tip={`${intl.formatMessage(messages.addNewService)} (${ctrlKey}+N)`}
@@ -79,6 +81,7 @@ export default @observer class Sidebar extends Component {
79 <i className="mdi mdi-plus-box" /> 81 <i className="mdi mdi-plus-box" />
80 </button> 82 </button>
81 <button 83 <button
84 type="button"
82 onClick={() => openSettings({ path: 'app' })} 85 onClick={() => openSettings({ path: 'app' })}
83 className="sidebar__button sidebar__button--settings" 86 className="sidebar__button sidebar__button--settings"
84 data-tip={`${intl.formatMessage(messages.settings)} (${ctrlKey}+,)`} 87 data-tip={`${intl.formatMessage(messages.settings)} (${ctrlKey}+,)`}
diff --git a/src/components/services/content/ErrorHandlers/WebviewErrorHandler.js b/src/components/services/content/ErrorHandlers/WebviewErrorHandler.js
new file mode 100644
index 000000000..415a8d1b5
--- /dev/null
+++ b/src/components/services/content/ErrorHandlers/WebviewErrorHandler.js
@@ -0,0 +1,84 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import injectSheet from 'react-jss';
6
7import Button from '../../../ui/Button';
8
9import styles from './styles';
10
11const messages = defineMessages({
12 headline: {
13 id: 'service.errorHandler.headline',
14 defaultMessage: '!!!Oh no!',
15 },
16 text: {
17 id: 'service.errorHandler.text',
18 defaultMessage: '!!!{name} has failed to load.',
19 },
20 action: {
21 id: 'service.errorHandler.action',
22 defaultMessage: '!!!Reload {name}',
23 },
24 editAction: {
25 id: 'service.errorHandler.editAction',
26 defaultMessage: '!!!Edit {name}',
27 },
28 errorMessage: {
29 id: 'service.errorHandler.message',
30 defaultMessage: '!!!Error:',
31 },
32});
33
34export default @injectSheet(styles) @observer class WebviewCrashHandler extends Component {
35 static propTypes = {
36 name: PropTypes.string.isRequired,
37 reload: PropTypes.func.isRequired,
38 edit: PropTypes.func.isRequired,
39 errorMessage: PropTypes.string.isRequired,
40 classes: PropTypes.object.isRequired,
41 };
42
43 static contextTypes = {
44 intl: intlShape,
45 };
46
47 render() {
48 const {
49 name,
50 reload,
51 edit,
52 errorMessage,
53 classes,
54 } = this.props;
55 const { intl } = this.context;
56
57 return (
58 <div className={classes.component}>
59 <h1>{intl.formatMessage(messages.headline)}</h1>
60 <p>{intl.formatMessage(messages.text, { name })}</p>
61 <p>
62 <strong>
63 {intl.formatMessage(messages.errorMessage)}
64:
65 </strong>
66 {' '}
67 {errorMessage}
68 </p>
69 <div className={classes.buttonContainer}>
70 <Button
71 label={intl.formatMessage(messages.editAction, { name })}
72 buttonType="inverted"
73 onClick={() => edit()}
74 />
75 <Button
76 label={intl.formatMessage(messages.action, { name })}
77 buttonType="inverted"
78 onClick={() => reload()}
79 />
80 </div>
81 </div>
82 );
83 }
84}
diff --git a/src/components/services/content/ErrorHandlers/styles.js b/src/components/services/content/ErrorHandlers/styles.js
new file mode 100644
index 000000000..f11386798
--- /dev/null
+++ b/src/components/services/content/ErrorHandlers/styles.js
@@ -0,0 +1,25 @@
1export default {
2 component: {
3 left: 0,
4 position: 'absolute',
5 top: 0,
6 width: '100%',
7 zIndex: 0,
8 alignItems: 'center',
9 // background: $theme-gray-lighter;
10 display: 'flex',
11 flexDirection: 'column',
12 justifyContent: 'center',
13 textAlign: 'center',
14 },
15 buttonContainer: {
16 display: 'flex',
17 flexDirection: 'row',
18 height: 'auto',
19 margin: [40, 0, 20],
20
21 '& button': {
22 margin: [0, 10, 0, 10],
23 },
24 },
25};
diff --git a/src/components/services/content/ServiceDisabled.js b/src/components/services/content/ServiceDisabled.js
index 58fb38d8c..d0f12256e 100644
--- a/src/components/services/content/ServiceDisabled.js
+++ b/src/components/services/content/ServiceDisabled.js
@@ -27,6 +27,7 @@ export default @observer class ServiceDisabled extends Component {
27 }; 27 };
28 28
29 countdownInterval = null; 29 countdownInterval = null;
30
30 countdownIntervalTimeout = 1000; 31 countdownIntervalTimeout = 1000;
31 32
32 render() { 33 render() {
diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js
index 7163209ee..b1a2c0207 100644
--- a/src/components/services/content/ServiceWebview.js
+++ b/src/components/services/content/ServiceWebview.js
@@ -1,4 +1,4 @@
1import React, { Component } from 'react'; 1import React, { Component, Fragment } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { autorun } from 'mobx'; 3import { autorun } from 'mobx';
4import { observer } from 'mobx-react'; 4import { observer } from 'mobx-react';
@@ -7,7 +7,9 @@ import classnames from 'classnames';
7 7
8import ServiceModel from '../../../models/Service'; 8import ServiceModel from '../../../models/Service';
9import StatusBarTargetUrl from '../../ui/StatusBarTargetUrl'; 9import StatusBarTargetUrl from '../../ui/StatusBarTargetUrl';
10import WebviewLoader from '../../ui/WebviewLoader';
10import WebviewCrashHandler from './WebviewCrashHandler'; 11import WebviewCrashHandler from './WebviewCrashHandler';
12import WebviewErrorHandler from './ErrorHandlers/WebviewErrorHandler';
11import ServiceDisabled from './ServiceDisabled'; 13import ServiceDisabled from './ServiceDisabled';
12 14
13export default @observer class ServiceWebview extends Component { 15export default @observer class ServiceWebview extends Component {
@@ -15,8 +17,10 @@ export default @observer class ServiceWebview extends Component {
15 service: PropTypes.instanceOf(ServiceModel).isRequired, 17 service: PropTypes.instanceOf(ServiceModel).isRequired,
16 setWebviewReference: PropTypes.func.isRequired, 18 setWebviewReference: PropTypes.func.isRequired,
17 reload: PropTypes.func.isRequired, 19 reload: PropTypes.func.isRequired,
20 edit: PropTypes.func.isRequired,
18 isAppMuted: PropTypes.bool.isRequired, 21 isAppMuted: PropTypes.bool.isRequired,
19 enable: PropTypes.func.isRequired, 22 enable: PropTypes.func.isRequired,
23 isActive: PropTypes.bool,
20 }; 24 };
21 25
22 static defaultProps = { 26 static defaultProps = {
@@ -29,8 +33,12 @@ export default @observer class ServiceWebview extends Component {
29 statusBarVisible: false, 33 statusBarVisible: false,
30 }; 34 };
31 35
36 autorunDisposer = null;
37
38 webview = null;
39
32 componentDidMount() { 40 componentDidMount() {
33 autorun(() => { 41 this.autorunDisposer = autorun(() => {
34 if (this.props.service.isActive) { 42 if (this.props.service.isActive) {
35 this.setState({ forceRepaint: true }); 43 this.setState({ forceRepaint: true });
36 setTimeout(() => { 44 setTimeout(() => {
@@ -40,6 +48,10 @@ export default @observer class ServiceWebview extends Component {
40 }); 48 });
41 } 49 }
42 50
51 componentWillUnmount() {
52 this.autorunDisposer();
53 }
54
43 updateTargetUrl = (event) => { 55 updateTargetUrl = (event) => {
44 let visible = true; 56 let visible = true;
45 if (event.url === '' || event.url === '#') { 57 if (event.url === '' || event.url === '#') {
@@ -51,13 +63,12 @@ export default @observer class ServiceWebview extends Component {
51 }); 63 });
52 } 64 }
53 65
54 webview = null;
55
56 render() { 66 render() {
57 const { 67 const {
58 service, 68 service,
59 setWebviewReference, 69 setWebviewReference,
60 reload, 70 reload,
71 edit,
61 isAppMuted, 72 isAppMuted,
62 enable, 73 enable,
63 } = this.props; 74 } = this.props;
@@ -78,25 +89,47 @@ export default @observer class ServiceWebview extends Component {
78 89
79 return ( 90 return (
80 <div className={webviewClasses}> 91 <div className={webviewClasses}>
81 {service.hasCrashed && ( 92 {service.isActive && service.isEnabled && (
82 <WebviewCrashHandler 93 <Fragment>
83 name={service.recipe.name} 94 {service.hasCrashed && (
84 webview={service.webview} 95 <WebviewCrashHandler
85 reload={reload} 96 name={service.recipe.name}
86 /> 97 webview={service.webview}
98 reload={reload}
99 />
100 )}
101 {service.isEnabled && service.isLoading && service.isFirstLoad && (
102 <WebviewLoader
103 loaded={false}
104 name={service.name}
105 />
106 )}
107 {service.isError && (
108 <WebviewErrorHandler
109 name={service.recipe.name}
110 errorMessage={service.errorMessage}
111 reload={reload}
112 edit={edit}
113 />
114 )}
115 </Fragment>
87 )} 116 )}
88 {!service.isEnabled ? ( 117 {!service.isEnabled ? (
89 <ServiceDisabled 118 <Fragment>
90 name={service.recipe.name} 119 {service.isActive && (
91 webview={service.webview} 120 <ServiceDisabled
92 enable={enable} 121 name={service.recipe.name}
93 /> 122 webview={service.webview}
123 enable={enable}
124 />
125 )}
126 </Fragment>
94 ) : ( 127 ) : (
95 <Webview 128 <Webview
96 ref={(element) => { this.webview = element; }} 129 ref={(element) => { this.webview = element; }}
97 autosize 130 autosize
98 src={service.url} 131 src={service.url}
99 preload="./webview/plugin.js" 132 preload="./webview/recipe.js"
100 partition={`persist:service-${service.id}`} 133 partition={`persist:service-${service.id}`}
101 onDidAttach={() => setWebviewReference({ 134 onDidAttach={() => setWebviewReference({
102 serviceId: service.id, 135 serviceId: service.id,
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js
index 4cbd51043..1aeb17e03 100644
--- a/src/components/services/content/Services.js
+++ b/src/components/services/content/Services.js
@@ -20,18 +20,18 @@ const messages = defineMessages({
20 20
21export default @observer class Services extends Component { 21export default @observer class Services extends Component {
22 static propTypes = { 22 static propTypes = {
23 services: MobxPropTypes.arrayOrObservableArray.isRequired, 23 services: MobxPropTypes.arrayOrObservableArray,
24 setWebviewReference: PropTypes.func.isRequired, 24 setWebviewReference: PropTypes.func.isRequired,
25 handleIPCMessage: PropTypes.func.isRequired, 25 handleIPCMessage: PropTypes.func.isRequired,
26 openWindow: PropTypes.func.isRequired, 26 openWindow: PropTypes.func.isRequired,
27 reload: PropTypes.func.isRequired, 27 reload: PropTypes.func.isRequired,
28 openSettings: PropTypes.func.isRequired,
28 isAppMuted: PropTypes.bool.isRequired, 29 isAppMuted: PropTypes.bool.isRequired,
29 update: PropTypes.func.isRequired, 30 update: PropTypes.func.isRequired,
30 }; 31 };
31 32
32 static defaultProps = { 33 static defaultProps = {
33 services: [], 34 services: [],
34 activeService: '',
35 }; 35 };
36 36
37 static contextTypes = { 37 static contextTypes = {
@@ -45,6 +45,7 @@ export default @observer class Services extends Component {
45 setWebviewReference, 45 setWebviewReference,
46 openWindow, 46 openWindow,
47 reload, 47 reload,
48 openSettings,
48 isAppMuted, 49 isAppMuted,
49 update, 50 update,
50 } = this.props; 51 } = this.props;
@@ -79,6 +80,7 @@ export default @observer class Services extends Component {
79 setWebviewReference={setWebviewReference} 80 setWebviewReference={setWebviewReference}
80 openWindow={openWindow} 81 openWindow={openWindow}
81 reload={() => reload({ serviceId: service.id })} 82 reload={() => reload({ serviceId: service.id })}
83 edit={() => openSettings({ path: `services/edit/${service.id}` })}
82 isAppMuted={isAppMuted} 84 isAppMuted={isAppMuted}
83 enable={() => update({ 85 enable={() => update({
84 serviceId: service.id, 86 serviceId: service.id,
diff --git a/src/components/services/content/WebviewCrashHandler.js b/src/components/services/content/WebviewCrashHandler.js
index 3be1fccf4..42bc3c877 100644
--- a/src/components/services/content/WebviewCrashHandler.js
+++ b/src/components/services/content/WebviewCrashHandler.js
@@ -38,13 +38,18 @@ export default @observer class WebviewCrashHandler extends Component {
38 countdown: 10000, 38 countdown: 10000,
39 } 39 }
40 40
41 countdownInterval = null;
42
43 countdownIntervalTimeout = 1000;
44
45
41 componentDidMount() { 46 componentDidMount() {
42 const { reload } = this.props; 47 const { reload } = this.props;
43 48
44 this.countdownInterval = setInterval(() => { 49 this.countdownInterval = setInterval(() => {
45 this.setState({ 50 this.setState(prevState => ({
46 countdown: this.state.countdown - this.countdownIntervalTimeout, 51 countdown: prevState.countdown - this.countdownIntervalTimeout,
47 }); 52 }));
48 53
49 if (this.state.countdown <= 0) { 54 if (this.state.countdown <= 0) {
50 reload(); 55 reload();
@@ -53,9 +58,6 @@ export default @observer class WebviewCrashHandler extends Component {
53 }, this.countdownIntervalTimeout); 58 }, this.countdownIntervalTimeout);
54 } 59 }
55 60
56 countdownInterval = null;
57 countdownIntervalTimeout = 1000;
58
59 render() { 61 render() {
60 const { name, reload } = this.props; 62 const { name, reload } = this.props;
61 const { intl } = this.context; 63 const { intl } = this.context;
@@ -70,10 +72,12 @@ export default @observer class WebviewCrashHandler extends Component {
70 buttonType="inverted" 72 buttonType="inverted"
71 onClick={() => reload()} 73 onClick={() => reload()}
72 /> 74 />
73 <p className="footnote">{intl.formatMessage(messages.autoReload, { 75 <p className="footnote">
74 name, 76 {intl.formatMessage(messages.autoReload, {
75 seconds: this.state.countdown / 1000, 77 name,
76 })}</p> 78 seconds: this.state.countdown / 1000,
79 })}
80 </p>
77 </div> 81 </div>
78 ); 82 );
79 } 83 }
diff --git a/src/components/settings/SettingsLayout.js b/src/components/settings/SettingsLayout.js
index 3cb08feb1..72ba7b2e3 100644
--- a/src/components/settings/SettingsLayout.js
+++ b/src/components/settings/SettingsLayout.js
@@ -2,6 +2,7 @@ import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react'; 3import { observer } from 'mobx-react';
4 4
5import ErrorBoundary from '../util/ErrorBoundary';
5import { oneOrManyChildElements } from '../../prop-types'; 6import { oneOrManyChildElements } from '../../prop-types';
6import Appear from '../ui/effects/Appear'; 7import Appear from '../ui/effects/Appear';
7 8
@@ -36,18 +37,22 @@ export default @observer class SettingsLayout extends Component {
36 return ( 37 return (
37 <Appear transitionName="fadeIn-fast"> 38 <Appear transitionName="fadeIn-fast">
38 <div className="settings-wrapper"> 39 <div className="settings-wrapper">
39 <button 40 <ErrorBoundary>
40 className="settings-wrapper__action"
41 onClick={closeSettings}
42 />
43 <div className="settings franz-form">
44 {navigation}
45 {children}
46 <button 41 <button
47 className="settings__close mdi mdi-close" 42 type="button"
43 className="settings-wrapper__action"
48 onClick={closeSettings} 44 onClick={closeSettings}
49 /> 45 />
50 </div> 46 <div className="settings franz-form">
47 {navigation}
48 {children}
49 <button
50 type="button"
51 className="settings__close mdi mdi-close"
52 onClick={closeSettings}
53 />
54 </div>
55 </ErrorBoundary>
51 </div> 56 </div>
52 </Appear> 57 </Appear>
53 ); 58 );
diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js
index 06c7074dd..9c9543749 100644
--- a/src/components/settings/account/AccountDashboard.js
+++ b/src/components/settings/account/AccountDashboard.js
@@ -1,4 +1,4 @@
1import React, { Component } from 'react'; 1import React, { Component, Fragment } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; 3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, intlShape } from 'react-intl';
@@ -132,21 +132,19 @@ export default @observer class AccountDashboard extends Component {
132 )} 132 )}
133 133
134 {!isLoading && userInfoRequestFailed && ( 134 {!isLoading && userInfoRequestFailed && (
135 <div> 135 <Infobox
136 <Infobox 136 icon="alert"
137 icon="alert" 137 type="danger"
138 type="danger" 138 ctaLabel={intl.formatMessage(messages.tryReloadUserInfoRequest)}
139 ctaLabel={intl.formatMessage(messages.tryReloadUserInfoRequest)} 139 ctaLoading={isLoading}
140 ctaLoading={isLoading} 140 ctaOnClick={retryUserInfoRequest}
141 ctaOnClick={retryUserInfoRequest} 141 >
142 > 142 {intl.formatMessage(messages.userInfoRequestFailed)}
143 {intl.formatMessage(messages.userInfoRequestFailed)} 143 </Infobox>
144 </Infobox>
145 </div>
146 )} 144 )}
147 145
148 {!userInfoRequestFailed && ( 146 {!userInfoRequestFailed && (
149 <div> 147 <Fragment>
150 {!isLoading && ( 148 {!isLoading && (
151 <div className="account"> 149 <div className="account">
152 <div className="account__box account__box--flex"> 150 <div className="account__box account__box--flex">
@@ -169,7 +167,8 @@ export default @observer class AccountDashboard extends Component {
169 {`${user.firstname} ${user.lastname}`} 167 {`${user.firstname} ${user.lastname}`}
170 </h2> 168 </h2>
171 {user.organization && `${user.organization}, `} 169 {user.organization && `${user.organization}, `}
172 {user.email}<br /> 170 {user.email}
171 <br />
173 {!user.isEnterprise && !user.isPremium && ( 172 {!user.isEnterprise && !user.isPremium && (
174 <span className="badge badge">{intl.formatMessage(messages.accountTypeBasic)}</span> 173 <span className="badge badge">{intl.formatMessage(messages.accountTypeBasic)}</span>
175 )} 174 )}
@@ -194,7 +193,7 @@ export default @observer class AccountDashboard extends Component {
194 ) : ( 193 ) : (
195 <div className="account franz-form"> 194 <div className="account franz-form">
196 {orders.length > 0 && ( 195 {orders.length > 0 && (
197 <div> 196 <Fragment>
198 <div className="account__box"> 197 <div className="account__box">
199 <h2>{intl.formatMessage(messages.headlineSubscription)}</h2> 198 <h2>{intl.formatMessage(messages.headlineSubscription)}</h2>
200 <div className="account__subscription"> 199 <div className="account__subscription">
@@ -219,6 +218,7 @@ export default @observer class AccountDashboard extends Component {
219 </td> 218 </td>
220 <td className="invoices__action"> 219 <td className="invoices__action">
221 <button 220 <button
221 type="button"
222 onClick={() => openExternalUrl(order.invoiceUrl)} 222 onClick={() => openExternalUrl(order.invoiceUrl)}
223 > 223 >
224 {intl.formatMessage(messages.invoiceDownload)} 224 {intl.formatMessage(messages.invoiceDownload)}
@@ -229,7 +229,7 @@ export default @observer class AccountDashboard extends Component {
229 </tbody> 229 </tbody>
230 </table> 230 </table>
231 </div> 231 </div>
232 </div> 232 </Fragment>
233 )} 233 )}
234 </div> 234 </div>
235 ) 235 )
@@ -262,20 +262,6 @@ export default @observer class AccountDashboard extends Component {
262 </div> 262 </div>
263 )} 263 )}
264 264
265 {user.isMiner && (
266 <div className="account franz-form">
267 <div className="account__box account__box">
268 <h2>Miner Info</h2>
269 <div className="account__subscription">
270 <div>
271 <p>To maintain a high security level for all our Franz users, we had to remove the miner. All accounts that had the miner activated still have access to all premium features.</p>
272 <p>Every financial support is still much appreciated.</p>
273 </div>
274 </div>
275 </div>
276 </div>
277 )}
278
279 {!user.isEnterprise && !user.isPremium && ( 265 {!user.isEnterprise && !user.isPremium && (
280 isLoadingPlans ? ( 266 isLoadingPlans ? (
281 <Loader /> 267 <Loader />
@@ -312,7 +298,7 @@ export default @observer class AccountDashboard extends Component {
312 </div> 298 </div>
313 </div> 299 </div>
314 )} 300 )}
315 </div> 301 </Fragment>
316 )} 302 )}
317 </div> 303 </div>
318 <ReactTooltip place="right" type="dark" effect="solid" /> 304 <ReactTooltip place="right" type="dark" effect="solid" />
diff --git a/src/components/settings/navigation/SettingsNavigation.js b/src/components/settings/navigation/SettingsNavigation.js
index b86d94ac7..953f702f8 100644
--- a/src/components/settings/navigation/SettingsNavigation.js
+++ b/src/components/settings/navigation/SettingsNavigation.js
@@ -59,7 +59,9 @@ export default @inject('stores') @observer class SettingsNavigation extends Comp
59 className="settings-navigation__link" 59 className="settings-navigation__link"
60 activeClassName="is-active" 60 activeClassName="is-active"
61 > 61 >
62 {intl.formatMessage(messages.yourServices)} <span className="badge">{serviceCount}</span> 62 {intl.formatMessage(messages.yourServices)}
63 {' '}
64 <span className="badge">{serviceCount}</span>
63 </Link> 65 </Link>
64 <Link 66 <Link
65 to="/settings/user" 67 to="/settings/user"
@@ -93,4 +95,3 @@ export default @inject('stores') @observer class SettingsNavigation extends Comp
93 ); 95 );
94 } 96 }
95} 97}
96
diff --git a/src/components/settings/recipes/RecipeItem.js b/src/components/settings/recipes/RecipeItem.js
index dae8891b3..3bb0852b2 100644
--- a/src/components/settings/recipes/RecipeItem.js
+++ b/src/components/settings/recipes/RecipeItem.js
@@ -15,6 +15,7 @@ export default @observer class RecipeItem extends Component {
15 15
16 return ( 16 return (
17 <button 17 <button
18 type="button"
18 className="recipe-teaser" 19 className="recipe-teaser"
19 onClick={onClick} 20 onClick={onClick}
20 > 21 >
diff --git a/src/components/settings/recipes/RecipesDashboard.js b/src/components/settings/recipes/RecipesDashboard.js
index cd783200f..00cd725cf 100644
--- a/src/components/settings/recipes/RecipesDashboard.js
+++ b/src/components/settings/recipes/RecipesDashboard.js
@@ -129,11 +129,17 @@ export default @observer class RecipesDashboard extends Component {
129 activeClassName={`${!searchNeedle ? 'badge--primary' : ''}`} 129 activeClassName={`${!searchNeedle ? 'badge--primary' : ''}`}
130 onClick={() => resetSearch()} 130 onClick={() => resetSearch()}
131 > 131 >
132 {intl.formatMessage(messages.devRecipes)} ({devRecipesCount}) 132 {intl.formatMessage(messages.devRecipes)}
133 {' '}
134(
135 {devRecipesCount}
136)
133 </Link> 137 </Link>
134 )} 138 )}
135 <a href={FRANZ_SERVICE_REQUEST} target="_blank" className="link recipes__service-request"> 139 <a href={FRANZ_SERVICE_REQUEST} target="_blank" className="link recipes__service-request">
136 {intl.formatMessage(messages.missingService)} <i className="mdi mdi-open-in-new" /> 140 {intl.formatMessage(messages.missingService)}
141 {' '}
142 <i className="mdi mdi-open-in-new" />
137 </a> 143 </a>
138 </div> 144 </div>
139 {/* )} */} 145 {/* )} */}
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js
index d16ec35b8..468d85c45 100644
--- a/src/components/settings/services/EditServiceForm.js
+++ b/src/components/settings/services/EditServiceForm.js
@@ -1,4 +1,4 @@
1import React, { Component } from 'react'; 1import React, { Component, Fragment } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react'; 3import { observer } from 'mobx-react';
4import { Link } from 'react-router'; 4import { Link } from 'react-router';
@@ -14,6 +14,7 @@ import Input from '../../ui/Input';
14import Toggle from '../../ui/Toggle'; 14import Toggle from '../../ui/Toggle';
15import Button from '../../ui/Button'; 15import Button from '../../ui/Button';
16import ImageUpload from '../../ui/ImageUpload'; 16import ImageUpload from '../../ui/ImageUpload';
17import Select from '../../ui/Select';
17 18
18import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer'; 19import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer';
19 20
@@ -96,7 +97,11 @@ const messages = defineMessages({
96 }, 97 },
97 headlineProxy: { 98 headlineProxy: {
98 id: 'settings.service.form.proxy.headline', 99 id: 'settings.service.form.proxy.headline',
99 defaultMessage: '!!!Proxy Settings', 100 defaultMessage: '!!!HTTP/HTTPS Proxy Settings',
101 },
102 proxyRestartInfo: {
103 id: 'settings.service.form.proxy.restartInfo',
104 defaultMessage: '!!!Please restart Franz after changing proxy Settings.',
100 }, 105 },
101 proxyInfo: { 106 proxyInfo: {
102 id: 'settings.service.form.proxy.info', 107 id: 'settings.service.form.proxy.info',
@@ -129,6 +134,7 @@ export default @observer class EditServiceForm extends Component {
129 static defaultProps = { 134 static defaultProps = {
130 service: {}, 135 service: {},
131 }; 136 };
137
132 static contextTypes = { 138 static contextTypes = {
133 intl: intlShape, 139 intl: intlShape,
134 }; 140 };
@@ -270,14 +276,14 @@ export default @observer class EditServiceForm extends Component {
270 {recipe.hasCustomUrl && ( 276 {recipe.hasCustomUrl && (
271 <TabItem title={intl.formatMessage(messages.tabOnPremise)}> 277 <TabItem title={intl.formatMessage(messages.tabOnPremise)}>
272 {user.isPremium || recipe.author.find(a => a.email === user.email) ? ( 278 {user.isPremium || recipe.author.find(a => a.email === user.email) ? (
273 <div> 279 <Fragment>
274 <Input field={form.$('customUrl')} /> 280 <Input field={form.$('customUrl')} />
275 {form.error === 'url-validation-error' && ( 281 {form.error === 'url-validation-error' && (
276 <p className="franz-form__error"> 282 <p className="franz-form__error">
277 {intl.formatMessage(messages.customUrlValidationError, { name: recipe.name })} 283 {intl.formatMessage(messages.customUrlValidationError, { name: recipe.name })}
278 </p> 284 </p>
279 )} 285 )}
280 </div> 286 </Fragment>
281 ) : ( 287 ) : (
282 <div className="center premium-info"> 288 <div className="center premium-info">
283 <p>{intl.formatMessage(messages.customUrlPremiumInfo)}</p> 289 <p>{intl.formatMessage(messages.customUrlPremiumInfo)}</p>
@@ -307,12 +313,12 @@ export default @observer class EditServiceForm extends Component {
307 <h3>{intl.formatMessage(messages.headlineBadges)}</h3> 313 <h3>{intl.formatMessage(messages.headlineBadges)}</h3>
308 <Toggle field={form.$('isBadgeEnabled')} /> 314 <Toggle field={form.$('isBadgeEnabled')} />
309 {recipe.hasIndirectMessages && form.$('isBadgeEnabled').value && ( 315 {recipe.hasIndirectMessages && form.$('isBadgeEnabled').value && (
310 <div> 316 <Fragment>
311 <Toggle field={form.$('isIndirectMessageBadgeEnabled')} /> 317 <Toggle field={form.$('isIndirectMessageBadgeEnabled')} />
312 <p className="settings__help"> 318 <p className="settings__help">
313 {intl.formatMessage(messages.indirectMessageInfo)} 319 {intl.formatMessage(messages.indirectMessageInfo)}
314 </p> 320 </p>
315 </div> 321 </Fragment>
316 )} 322 )}
317 </div> 323 </div>
318 324
@@ -333,6 +339,12 @@ export default @observer class EditServiceForm extends Component {
333 </div> 339 </div>
334 </div> 340 </div>
335 341
342 <PremiumFeatureContainer>
343 <div className="settings__settings-group">
344 <Select field={form.$('spellcheckerLanguage')} />
345 </div>
346 </PremiumFeatureContainer>
347
336 {isProxyFeatureEnabled && ( 348 {isProxyFeatureEnabled && (
337 <PremiumFeatureContainer condition={isProxyFeaturePremiumFeature}> 349 <PremiumFeatureContainer condition={isProxyFeaturePremiumFeature}>
338 <div className="settings__settings-group"> 350 <div className="settings__settings-group">
@@ -342,18 +354,31 @@ export default @observer class EditServiceForm extends Component {
342 </h3> 354 </h3>
343 <Toggle field={form.$('proxy.isEnabled')} /> 355 <Toggle field={form.$('proxy.isEnabled')} />
344 {form.$('proxy.isEnabled').value && ( 356 {form.$('proxy.isEnabled').value && (
345 <div> 357 <Fragment>
346 <Input field={form.$('proxy.host')} /> 358 <div className="grid">
347 <Input field={form.$('proxy.user')} /> 359 <div className="grid__row">
348 <Input 360 <Input field={form.$('proxy.host')} className="proxyHost" />
349 field={form.$('proxy.password')} 361 <Input field={form.$('proxy.port')} />
350 showPasswordToggle 362 </div>
351 /> 363 </div>
364 <div className="grid">
365 <div className="grid__row">
366 <Input field={form.$('proxy.user')} />
367 <Input
368 field={form.$('proxy.password')}
369 showPasswordToggle
370 />
371 </div>
372 </div>
373 <p>
374 <span className="mdi mdi-information" />
375 {intl.formatMessage(messages.proxyRestartInfo)}
376 </p>
352 <p> 377 <p>
353 <span className="mdi mdi-information" /> 378 <span className="mdi mdi-information" />
354 {intl.formatMessage(messages.proxyInfo)} 379 {intl.formatMessage(messages.proxyInfo)}
355 </p> 380 </p>
356 </div> 381 </Fragment>
357 )} 382 )}
358 </div> 383 </div>
359 </PremiumFeatureContainer> 384 </PremiumFeatureContainer>
diff --git a/src/components/settings/services/ServiceItem.js b/src/components/settings/services/ServiceItem.js
index 84080519b..ebc618a00 100644
--- a/src/components/settings/services/ServiceItem.js
+++ b/src/components/settings/services/ServiceItem.js
@@ -27,6 +27,7 @@ export default @observer class ServiceItem extends Component {
27 service: PropTypes.instanceOf(ServiceModel).isRequired, 27 service: PropTypes.instanceOf(ServiceModel).isRequired,
28 goToServiceForm: PropTypes.func.isRequired, 28 goToServiceForm: PropTypes.func.isRequired,
29 }; 29 };
30
30 static contextTypes = { 31 static contextTypes = {
31 intl: intlShape, 32 intl: intlShape,
32 }; 33 };
diff --git a/src/components/settings/services/ServicesDashboard.js b/src/components/settings/services/ServicesDashboard.js
index e7dfaf106..a12df7372 100644
--- a/src/components/settings/services/ServicesDashboard.js
+++ b/src/components/settings/services/ServicesDashboard.js
@@ -101,17 +101,15 @@ export default @observer class ServicesDashboard extends Component {
101 /> 101 />
102 )} 102 )}
103 {!isLoading && servicesRequestFailed && ( 103 {!isLoading && servicesRequestFailed && (
104 <div> 104 <Infobox
105 <Infobox 105 icon="alert"
106 icon="alert" 106 type="danger"
107 type="danger" 107 ctaLabel={intl.formatMessage(messages.tryReloadServices)}
108 ctaLabel={intl.formatMessage(messages.tryReloadServices)} 108 ctaLoading={isLoading}
109 ctaLoading={isLoading} 109 ctaOnClick={retryServicesRequest}
110 ctaOnClick={retryServicesRequest} 110 >
111 > 111 {intl.formatMessage(messages.servicesRequestFailed)}
112 {intl.formatMessage(messages.servicesRequestFailed)} 112 </Infobox>
113 </Infobox>
114 </div>
115 )} 113 )}
116 114
117 {status.length > 0 && status.includes('updated') && ( 115 {status.length > 0 && status.includes('updated') && (
diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js
index 1ec2ab614..a92e559f3 100644
--- a/src/components/settings/settings/EditSettingsForm.js
+++ b/src/components/settings/settings/EditSettingsForm.js
@@ -1,5 +1,5 @@
1import { remote } from 'electron'; 1import { remote } from 'electron';
2import React, { Component } from 'react'; 2import React, { Component, Fragment } from 'react';
3import PropTypes from 'prop-types'; 3import PropTypes from 'prop-types';
4import { observer } from 'mobx-react'; 4import { observer } from 'mobx-react';
5import { defineMessages, intlShape } from 'react-intl'; 5import { defineMessages, intlShape } from 'react-intl';
@@ -171,21 +171,23 @@ export default @observer class EditSettingsForm extends Component {
171 <PremiumFeatureContainer 171 <PremiumFeatureContainer
172 condition={isSpellcheckerPremiumFeature} 172 condition={isSpellcheckerPremiumFeature}
173 > 173 >
174 <div> 174 <Fragment>
175 <Toggle 175 <Toggle
176 field={form.$('enableSpellchecking')} 176 field={form.$('enableSpellchecking')}
177 /> 177 />
178 {form.$('enableSpellchecking').value && ( 178 {form.$('enableSpellchecking').value && (
179 <Select field={form.$('spellcheckerLanguage')} /> 179 <Select field={form.$('spellcheckerLanguage')} />
180 )} 180 )}
181 </div> 181 </Fragment>
182 </PremiumFeatureContainer> 182 </PremiumFeatureContainer>
183 <a 183 <a
184 href={FRANZ_TRANSLATION} 184 href={FRANZ_TRANSLATION}
185 target="_blank" 185 target="_blank"
186 className="link" 186 className="link"
187 > 187 >
188 {intl.formatMessage(messages.translationHelp)} <i className="mdi mdi-open-in-new" /> 188 {intl.formatMessage(messages.translationHelp)}
189 {' '}
190 <i className="mdi mdi-open-in-new" />
189 </a> 191 </a>
190 192
191 {/* Advanced */} 193 {/* Advanced */}
@@ -233,7 +235,9 @@ export default @observer class EditSettingsForm extends Component {
233 )} 235 )}
234 <br /> 236 <br />
235 <Toggle field={form.$('beta')} /> 237 <Toggle field={form.$('beta')} />
236 {intl.formatMessage(messages.currentVersion)} {remote.app.getVersion()} 238 {intl.formatMessage(messages.currentVersion)}
239 {' '}
240 {remote.app.getVersion()}
237 </form> 241 </form>
238 </div> 242 </div>
239 </div> 243 </div>
diff --git a/src/components/settings/user/EditUserForm.js b/src/components/settings/user/EditUserForm.js
index b825f844a..0e3ac6b10 100644
--- a/src/components/settings/user/EditUserForm.js
+++ b/src/components/settings/user/EditUserForm.js
@@ -48,10 +48,6 @@ export default @observer class EditServiceForm extends Component {
48 isEnterprise: PropTypes.bool.isRequired, 48 isEnterprise: PropTypes.bool.isRequired,
49 }; 49 };
50 50
51 static defaultProps = {
52 service: {},
53 };
54
55 static contextTypes = { 51 static contextTypes = {
56 intl: intlShape, 52 intl: intlShape,
57 }; 53 };
diff --git a/src/components/subscription/SubscriptionForm.js b/src/components/subscription/SubscriptionForm.js
index 12e8471ff..90da8ddc3 100644
--- a/src/components/subscription/SubscriptionForm.js
+++ b/src/components/subscription/SubscriptionForm.js
@@ -1,4 +1,4 @@
1import React, { Component } from 'react'; 1import React, { Component, Fragment } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; 3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, intlShape } from 'react-intl';
@@ -81,8 +81,7 @@ export default @observer class SubscriptionForm extends Component {
81 hideInfo: PropTypes.bool.isRequired, 81 hideInfo: PropTypes.bool.isRequired,
82 }; 82 };
83 83
84 static defaultProps ={ 84 static defaultProps = {
85 content: '',
86 showSkipOption: false, 85 showSkipOption: false,
87 skipAction: () => null, 86 skipAction: () => null,
88 skipButtonLabel: '', 87 skipButtonLabel: '',
@@ -158,35 +157,33 @@ export default @observer class SubscriptionForm extends Component {
158 <Radio field={this.form.$('paymentTier')} showLabel={false} className="paymentTiers" /> 157 <Radio field={this.form.$('paymentTier')} showLabel={false} className="paymentTiers" />
159 {!hideInfo && ( 158 {!hideInfo && (
160 <div className="subscription__premium-info"> 159 <div className="subscription__premium-info">
161 <div> 160 <p>
162 <p> 161 <strong>{intl.formatMessage(messages.includedFeatures)}</strong>
163 <strong>{intl.formatMessage(messages.includedFeatures)}</strong> 162 </p>
164 </p> 163 <div className="subscription">
165 <div className="subscription"> 164 <ul className="subscription__premium-features">
166 <ul className="subscription__premium-features"> 165 <li>{intl.formatMessage(messages.features.onpremise)}</li>
167 <li>{intl.formatMessage(messages.features.onpremise)}</li> 166 <li>
168 <li> 167 {intl.formatMessage(messages.features.noInterruptions)}
169 {intl.formatMessage(messages.features.noInterruptions)} 168 </li>
170 </li> 169 <li>
171 <li> 170 {intl.formatMessage(messages.features.spellchecker)}
172 {intl.formatMessage(messages.features.spellchecker)} 171 </li>
173 </li> 172 <li>
174 <li> 173 {intl.formatMessage(messages.features.proxy)}
175 {intl.formatMessage(messages.features.proxy)} 174 </li>
176 </li> 175 <li>
177 <li> 176 {intl.formatMessage(messages.features.ads)}
178 {intl.formatMessage(messages.features.ads)} 177 </li>
179 </li> 178 </ul>
180 </ul>
181 </div>
182 </div> 179 </div>
183 </div> 180 </div>
184 )} 181 )}
185 <div> 182 <Fragment>
186 {error.code === 'no-payment-session' && ( 183 {error.code === 'no-payment-session' && (
187 <p className="error-message center">{intl.formatMessage(messages.paymentSessionError)}</p> 184 <p className="error-message center">{intl.formatMessage(messages.paymentSessionError)}</p>
188 )} 185 )}
189 </div> 186 </Fragment>
190 {showSkipOption && this.form.$('paymentTier').value === 'skip' ? ( 187 {showSkipOption && this.form.$('paymentTier').value === 'skip' ? (
191 <Button 188 <Button
192 label={skipButtonLabel} 189 label={skipButtonLabel}
diff --git a/src/components/subscription/SubscriptionPopup.js b/src/components/subscription/SubscriptionPopup.js
index f3c63e7ee..b5d7c4b2d 100644
--- a/src/components/subscription/SubscriptionPopup.js
+++ b/src/components/subscription/SubscriptionPopup.js
@@ -46,7 +46,9 @@ export default @observer class SubscriptionPopup extends Component {
46 } 46 }
47 47
48 render() { 48 render() {
49 const { url, closeWindow, completeCheck, isCompleted } = this.props; 49 const {
50 url, closeWindow, completeCheck, isCompleted,
51 } = this.props;
50 const { intl } = this.context; 52 const { intl } = this.context;
51 53
52 return ( 54 return (
diff --git a/src/components/ui/AppLoader.js b/src/components/ui/AppLoader.js
deleted file mode 100644
index ac3cdcb05..000000000
--- a/src/components/ui/AppLoader.js
+++ /dev/null
@@ -1,15 +0,0 @@
1import React from 'react';
2
3import Appear from '../../components/ui/effects/Appear';
4import Loader from '../../components/ui/Loader';
5
6export default function () {
7 return (
8 <div className="app-loader">
9 <Appear>
10 <h1 className="app-loader__title">Franz</h1>
11 <Loader color="#FFF" />
12 </Appear>
13 </div>
14 );
15}
diff --git a/src/components/ui/AppLoader/index.js b/src/components/ui/AppLoader/index.js
new file mode 100644
index 000000000..61053f6d1
--- /dev/null
+++ b/src/components/ui/AppLoader/index.js
@@ -0,0 +1,70 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import injectSheet, { withTheme } from 'react-jss';
4import classnames from 'classnames';
5
6import FullscreenLoader from '../FullscreenLoader';
7import { shuffleArray } from '../../../helpers/array-helpers';
8
9import styles from './styles';
10
11const textList = shuffleArray([
12 'Looking for Sisi',
13 'Contacting the herald',
14 'Saddling the unicorn',
15 'Learning the Waltz',
16 'Visiting Horst & Grete',
17 'Twisting my moustache',
18 'Playing the trumpet',
19 'Traveling through space & time',
20]);
21
22export default @injectSheet(styles) @withTheme class AppLoader extends Component {
23 static propTypes = {
24 classes: PropTypes.object.isRequired,
25 theme: PropTypes.object.isRequired,
26 }
27
28 state = {
29 step: 0,
30 }
31
32 interval = null;
33
34 componentDidMount() {
35 this.interval = setInterval(() => {
36 this.setState(prevState => ({
37 step: prevState.step === textList.length - 1 ? 0 : prevState.step + 1,
38 }));
39 }, 2500);
40 }
41
42 componentWillUnmount() {
43 clearInterval(this.interval);
44 }
45
46 render() {
47 const { classes, theme } = this.props;
48 const { step } = this.state;
49
50 return (
51 <FullscreenLoader
52 title="Franz"
53 className={classes.component}
54 spinnerColor={theme.colorAppLoaderSpinner}
55 >
56 {textList.map((text, i) => (
57 <span
58 key={text}
59 className={classnames({
60 [`${classes.slogan}`]: true,
61 [`${classes.visible}`]: step === i,
62 })}
63 >
64 {text}
65 </span>
66 ))}
67 </FullscreenLoader>
68 );
69 }
70}
diff --git a/src/components/ui/AppLoader/styles.js b/src/components/ui/AppLoader/styles.js
new file mode 100644
index 000000000..755a56b40
--- /dev/null
+++ b/src/components/ui/AppLoader/styles.js
@@ -0,0 +1,16 @@
1export default {
2 component: {
3 color: '#FFF',
4 },
5 slogan: {
6 display: 'block',
7 opacity: 0,
8 transition: 'opacity 1s ease',
9 position: 'absolute',
10 textAlign: 'center',
11 width: '100%',
12 },
13 visible: {
14 opacity: 1,
15 },
16};
diff --git a/src/components/ui/Button.js b/src/components/ui/Button.js
index 309e05bb4..ffc7f7051 100644
--- a/src/components/ui/Button.js
+++ b/src/components/ui/Button.js
@@ -62,6 +62,8 @@ export default @observer class Button extends Component {
62 } 62 }
63 63
64 return ( 64 return (
65 // disabling rule as button has type defined in `buttonProps`
66 /* eslint-disable react/button-has-type */
65 <button {...buttonProps}> 67 <button {...buttonProps}>
66 <Loader 68 <Loader
67 loaded={loaded} 69 loaded={loaded}
@@ -72,6 +74,7 @@ export default @observer class Button extends Component {
72 /> 74 />
73 {label} 75 {label}
74 </button> 76 </button>
77 /* eslint-enable react/button-has-type */
75 ); 78 );
76 } 79 }
77} 80}
diff --git a/src/components/ui/FullscreenLoader/index.js b/src/components/ui/FullscreenLoader/index.js
new file mode 100644
index 000000000..6ecf4d395
--- /dev/null
+++ b/src/components/ui/FullscreenLoader/index.js
@@ -0,0 +1,56 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import injectSheet, { withTheme } from 'react-jss';
5import classnames from 'classnames';
6
7import Loader from '../Loader';
8
9import styles from './styles';
10
11export default @observer @withTheme @injectSheet(styles) class FullscreenLoader extends Component {
12 static propTypes = {
13 className: PropTypes.string,
14 title: PropTypes.string.isRequired,
15 classes: PropTypes.object.isRequired,
16 theme: PropTypes.object.isRequired,
17 spinnerColor: PropTypes.string,
18 children: PropTypes.node,
19 }
20
21 static defaultProps = {
22 className: null,
23 spinnerColor: null,
24 children: null,
25 }
26
27 render() {
28 const {
29 classes,
30 title,
31 children,
32 spinnerColor,
33 className,
34 theme,
35 } = this.props;
36
37 return (
38 <div className={classes.wrapper}>
39 <div
40 className={classnames({
41 [`${classes.component}`]: true,
42 [`${className}`]: className,
43 })}
44 >
45 <h1 className={classes.title}>{title}</h1>
46 <Loader color={spinnerColor || theme.colorFullscreenLoaderSpinner} />
47 {children && (
48 <div className={classes.content}>
49 {children}
50 </div>
51 )}
52 </div>
53 </div>
54 );
55 }
56}
diff --git a/src/components/ui/FullscreenLoader/styles.js b/src/components/ui/FullscreenLoader/styles.js
new file mode 100644
index 000000000..64d24e4ce
--- /dev/null
+++ b/src/components/ui/FullscreenLoader/styles.js
@@ -0,0 +1,23 @@
1export default {
2 wrapper: {
3 display: 'flex',
4 alignItems: 'center',
5 position: 'absolute',
6 width: '100%',
7 },
8 component: {
9 width: '100%',
10 display: 'flex',
11 flexDirection: 'column',
12 alignItems: 'center',
13 textAlign: 'center',
14 height: 'auto',
15 },
16 title: {
17 fontSize: 35,
18 },
19 content: {
20 marginTop: 20,
21 width: '100%',
22 },
23};
diff --git a/src/components/ui/ImageUpload.js b/src/components/ui/ImageUpload.js
index 76f77d631..83a05554b 100644
--- a/src/components/ui/ImageUpload.js
+++ b/src/components/ui/ImageUpload.js
@@ -1,4 +1,4 @@
1import React, { Component } from 'react'; 1import React, { Component, Fragment } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react'; 3import { observer } from 'mobx-react';
4import { Field } from 'mobx-react-form'; 4import { Field } from 'mobx-react-form';
@@ -23,6 +23,8 @@ export default @observer class ImageUpload extends Component {
23 path: null, 23 path: null,
24 } 24 }
25 25
26 dropzoneRef = null;
27
26 onDrop(acceptedFiles) { 28 onDrop(acceptedFiles) {
27 const { field } = this.props; 29 const { field } = this.props;
28 30
@@ -36,8 +38,6 @@ export default @observer class ImageUpload extends Component {
36 field.set(''); 38 field.set('');
37 } 39 }
38 40
39 dropzoneRef = null;
40
41 render() { 41 render() {
42 const { 42 const {
43 field, 43 field,
@@ -57,7 +57,7 @@ export default @observer class ImageUpload extends Component {
57 <label className="franz-form__label" htmlFor="iconUpload">{field.label}</label> 57 <label className="franz-form__label" htmlFor="iconUpload">{field.label}</label>
58 <div className="image-upload"> 58 <div className="image-upload">
59 {(field.value && field.value !== 'delete') || this.state.path ? ( 59 {(field.value && field.value !== 'delete') || this.state.path ? (
60 <div> 60 <Fragment>
61 <div 61 <div
62 className="image-upload__preview" 62 className="image-upload__preview"
63 style={({ 63 style={({
@@ -84,7 +84,7 @@ export default @observer class ImageUpload extends Component {
84 </button> 84 </button>
85 <div className="image-upload__action-background" /> 85 <div className="image-upload__action-background" />
86 </div> 86 </div>
87 </div> 87 </Fragment>
88 ) : ( 88 ) : (
89 <Dropzone 89 <Dropzone
90 ref={(node) => { this.dropzoneRef = node; }} 90 ref={(node) => { this.dropzoneRef = node; }}
diff --git a/src/components/ui/InfoBar.js b/src/components/ui/InfoBar.js
index 94a1ddf76..612399e9f 100644
--- a/src/components/ui/InfoBar.js
+++ b/src/components/ui/InfoBar.js
@@ -5,7 +5,7 @@ import classnames from 'classnames';
5import Loader from 'react-loader'; 5import Loader from 'react-loader';
6 6
7// import { oneOrManyChildElements } from '../../prop-types'; 7// import { oneOrManyChildElements } from '../../prop-types';
8import Appear from '../ui/effects/Appear'; 8import Appear from './effects/Appear';
9 9
10export default @observer class InfoBar extends Component { 10export default @observer class InfoBar extends Component {
11 static propTypes = { 11 static propTypes = {
@@ -64,6 +64,7 @@ export default @observer class InfoBar extends Component {
64 {children} 64 {children}
65 {ctaLabel && ( 65 {ctaLabel && (
66 <button 66 <button
67 type="button"
67 className="info-bar__cta" 68 className="info-bar__cta"
68 onClick={onClick} 69 onClick={onClick}
69 > 70 >
@@ -80,6 +81,7 @@ export default @observer class InfoBar extends Component {
80 </div> 81 </div>
81 {!sticky && ( 82 {!sticky && (
82 <button 83 <button
84 type="button"
83 className="info-bar__close mdi mdi-close" 85 className="info-bar__close mdi mdi-close"
84 onClick={onHide} 86 onClick={onHide}
85 /> 87 />
diff --git a/src/components/ui/Infobox.js b/src/components/ui/Infobox.js
index 77051f567..a33c6474a 100644
--- a/src/components/ui/Infobox.js
+++ b/src/components/ui/Infobox.js
@@ -61,6 +61,7 @@ export default @observer class Infobox extends Component {
61 <button 61 <button
62 className="infobox__cta" 62 className="infobox__cta"
63 onClick={ctaOnClick} 63 onClick={ctaOnClick}
64 type="button"
64 > 65 >
65 <Loader 66 <Loader
66 loaded={!ctaLoading} 67 loaded={!ctaLoading}
@@ -74,6 +75,7 @@ export default @observer class Infobox extends Component {
74 )} 75 )}
75 {dismissable && ( 76 {dismissable && (
76 <button 77 <button
78 type="button"
77 onClick={() => this.setState({ 79 onClick={() => this.setState({
78 dismissed: true, 80 dismissed: true,
79 })} 81 })}
diff --git a/src/components/ui/Input.js b/src/components/ui/Input.js
index 7bf6e1b00..9b070c4df 100644
--- a/src/components/ui/Input.js
+++ b/src/components/ui/Input.js
@@ -33,6 +33,8 @@ export default @observer class Input extends Component {
33 passwordScore: 0, 33 passwordScore: 0,
34 } 34 }
35 35
36 inputElement = null;
37
36 componentDidMount() { 38 componentDidMount() {
37 if (this.props.focus) { 39 if (this.props.focus) {
38 this.focus(); 40 this.focus();
@@ -53,8 +55,6 @@ export default @observer class Input extends Component {
53 this.inputElement.focus(); 55 this.inputElement.focus();
54 } 56 }
55 57
56 inputElement = null;
57
58 render() { 58 render() {
59 const { 59 const {
60 field, 60 field,
@@ -110,7 +110,7 @@ export default @observer class Input extends Component {
110 'mdi-eye': !this.state.showPassword, 110 'mdi-eye': !this.state.showPassword,
111 'mdi-eye-off': this.state.showPassword, 111 'mdi-eye-off': this.state.showPassword,
112 })} 112 })}
113 onClick={() => this.setState({ showPassword: !this.state.showPassword })} 113 onClick={() => this.setState(prevState => ({ showPassword: !prevState.showPassword }))}
114 tabIndex="-1" 114 tabIndex="-1"
115 /> 115 />
116 )} 116 )}
diff --git a/src/components/ui/Link.js b/src/components/ui/Link.js
index 0602290f1..b88686d5e 100644
--- a/src/components/ui/Link.js
+++ b/src/components/ui/Link.js
@@ -72,5 +72,4 @@ Link.wrappedComponent.defaultProps = {
72 activeClassName: '', 72 activeClassName: '',
73 strictFilter: false, 73 strictFilter: false,
74 target: '', 74 target: '',
75 openInBrowser: false,
76}; 75};
diff --git a/src/components/ui/PremiumFeatureContainer/index.js b/src/components/ui/PremiumFeatureContainer/index.js
index 73984be94..67cd6af0b 100644
--- a/src/components/ui/PremiumFeatureContainer/index.js
+++ b/src/components/ui/PremiumFeatureContainer/index.js
@@ -73,4 +73,3 @@ PremiumFeatureContainer.wrappedComponent.propTypes = {
73 }).isRequired, 73 }).isRequired,
74 }).isRequired, 74 }).isRequired,
75}; 75};
76
diff --git a/src/components/ui/PremiumFeatureContainer/styles.js b/src/components/ui/PremiumFeatureContainer/styles.js
index 16c40d0ec..81d6666c6 100644
--- a/src/components/ui/PremiumFeatureContainer/styles.js
+++ b/src/components/ui/PremiumFeatureContainer/styles.js
@@ -5,6 +5,7 @@ export default theme => ({
5 margin: [0, 0, 20, -20], 5 margin: [0, 0, 20, -20],
6 padding: 20, 6 padding: 20,
7 'border-radius': theme.borderRadius, 7 'border-radius': theme.borderRadius,
8 pointerEvents: 'none',
8 }, 9 },
9 titleContainer: { 10 titleContainer: {
10 display: 'flex', 11 display: 'flex',
@@ -20,6 +21,7 @@ export default theme => ({
20 'border-radius': theme.borderRadiusSmall, 21 'border-radius': theme.borderRadiusSmall,
21 padding: [2, 4], 22 padding: [2, 4],
22 'font-size': 12, 23 'font-size': 12,
24 pointerEvents: 'initial',
23 }, 25 },
24 content: { 26 content: {
25 opacity: 0.5, 27 opacity: 0.5,
diff --git a/src/components/ui/Radio.js b/src/components/ui/Radio.js
index 63ca6f9b8..ba13aca63 100644
--- a/src/components/ui/Radio.js
+++ b/src/components/ui/Radio.js
@@ -18,6 +18,8 @@ export default @observer class Radio extends Component {
18 showLabel: true, 18 showLabel: true,
19 }; 19 };
20 20
21 inputElement = null;
22
21 componentDidMount() { 23 componentDidMount() {
22 if (this.props.focus) { 24 if (this.props.focus) {
23 this.focus(); 25 this.focus();
@@ -28,8 +30,6 @@ export default @observer class Radio extends Component {
28 this.inputElement.focus(); 30 this.inputElement.focus();
29 } 31 }
30 32
31 inputElement = null;
32
33 render() { 33 render() {
34 const { 34 const {
35 field, 35 field,
diff --git a/src/components/ui/SearchInput.js b/src/components/ui/SearchInput.js
index 5a9571d27..78d6aae8b 100644
--- a/src/components/ui/SearchInput.js
+++ b/src/components/ui/SearchInput.js
@@ -2,7 +2,6 @@ import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react'; 3import { observer } from 'mobx-react';
4import classnames from 'classnames'; 4import classnames from 'classnames';
5import uuidv1 from 'uuid/v1';
6import { debounce } from 'lodash'; 5import { debounce } from 'lodash';
7 6
8export default @observer class SearchInput extends Component { 7export default @observer class SearchInput extends Component {
@@ -22,7 +21,7 @@ export default @observer class SearchInput extends Component {
22 value: '', 21 value: '',
23 placeholder: '', 22 placeholder: '',
24 className: '', 23 className: '',
25 name: uuidv1(), 24 name: 'searchInput',
26 throttle: false, 25 throttle: false,
27 throttleDelay: 250, 26 throttleDelay: 250,
28 onChange: () => null, 27 onChange: () => null,
@@ -30,6 +29,8 @@ export default @observer class SearchInput extends Component {
30 autoFocus: false, 29 autoFocus: false,
31 } 30 }
32 31
32 input = null;
33
33 constructor(props) { 34 constructor(props) {
34 super(props); 35 super(props);
35 36
@@ -74,8 +75,6 @@ export default @observer class SearchInput extends Component {
74 onReset(); 75 onReset();
75 } 76 }
76 77
77 input = null;
78
79 render() { 78 render() {
80 const { className, name, placeholder } = this.props; 79 const { className, name, placeholder } = this.props;
81 const { value } = this.state; 80 const { value } = this.state;
@@ -90,15 +89,17 @@ export default @observer class SearchInput extends Component {
90 <label 89 <label
91 htmlFor={name} 90 htmlFor={name}
92 className="mdi mdi-magnify" 91 className="mdi mdi-magnify"
93 /> 92 >
94 <input 93 <input
95 name={name} 94 name={name}
96 type="text" 95 id={name}
97 placeholder={placeholder} 96 type="text"
98 value={value} 97 placeholder={placeholder}
99 onChange={e => this.onChange(e)} 98 value={value}
100 ref={(ref) => { this.input = ref; }} 99 onChange={e => this.onChange(e)}
101 /> 100 ref={(ref) => { this.input = ref; }}
101 />
102 </label>
102 {value.length > 0 && ( 103 {value.length > 0 && (
103 <span 104 <span
104 className="mdi mdi-close-circle-outline" 105 className="mdi mdi-close-circle-outline"
diff --git a/src/components/ui/Select.js b/src/components/ui/Select.js
index abcad417e..da52243ca 100644
--- a/src/components/ui/Select.js
+++ b/src/components/ui/Select.js
@@ -9,12 +9,13 @@ export default @observer class Select extends Component {
9 field: PropTypes.instanceOf(Field).isRequired, 9 field: PropTypes.instanceOf(Field).isRequired,
10 className: PropTypes.string, 10 className: PropTypes.string,
11 showLabel: PropTypes.bool, 11 showLabel: PropTypes.bool,
12 disabled: PropTypes.bool,
12 }; 13 };
13 14
14 static defaultProps = { 15 static defaultProps = {
15 className: null, 16 className: null,
16 focus: false,
17 showLabel: true, 17 showLabel: true,
18 disabled: false,
18 }; 19 };
19 20
20 render() { 21 render() {
@@ -22,6 +23,7 @@ export default @observer class Select extends Component {
22 field, 23 field,
23 className, 24 className,
24 showLabel, 25 showLabel,
26 disabled,
25 } = this.props; 27 } = this.props;
26 28
27 return ( 29 return (
@@ -29,6 +31,7 @@ export default @observer class Select extends Component {
29 className={classnames({ 31 className={classnames({
30 'franz-form__field': true, 32 'franz-form__field': true,
31 'has-error': field.error, 33 'has-error': field.error,
34 'is-disabled': disabled,
32 [`${className}`]: className, 35 [`${className}`]: className,
33 })} 36 })}
34 > 37 >
@@ -45,12 +48,13 @@ export default @observer class Select extends Component {
45 id={field.id} 48 id={field.id}
46 defaultValue={field.value} 49 defaultValue={field.value}
47 className="franz-form__select" 50 className="franz-form__select"
51 disabled={field.disabled || disabled}
48 > 52 >
49 {field.options.map(type => ( 53 {field.options.map(type => (
50 <option 54 <option
51 key={type.value} 55 key={type.value}
52 value={type.value} 56 value={type.value}
53 // selected={field.value === } 57 disabled={type.disabled}
54 > 58 >
55 {type.label} 59 {type.label}
56 </option> 60 </option>
diff --git a/src/components/ui/StatusBarTargetUrl.js b/src/components/ui/StatusBarTargetUrl.js
index 4285a343c..6fc50fe5c 100644
--- a/src/components/ui/StatusBarTargetUrl.js
+++ b/src/components/ui/StatusBarTargetUrl.js
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
3import { observer } from 'mobx-react'; 3import { observer } from 'mobx-react';
4import classnames from 'classnames'; 4import classnames from 'classnames';
5 5
6import Appear from '../ui/effects/Appear'; 6import Appear from './effects/Appear';
7 7
8export default @observer class StatusBarTargetUrl extends Component { 8export default @observer class StatusBarTargetUrl extends Component {
9 static propTypes = { 9 static propTypes = {
@@ -13,7 +13,6 @@ export default @observer class StatusBarTargetUrl extends Component {
13 13
14 static defaultProps = { 14 static defaultProps = {
15 className: '', 15 className: '',
16 position: 'bottom',
17 text: '', 16 text: '',
18 }; 17 };
19 18
diff --git a/src/components/ui/Tabs/TabItem.js b/src/components/ui/Tabs/TabItem.js
index 9ff9f009e..16881a7f7 100644
--- a/src/components/ui/Tabs/TabItem.js
+++ b/src/components/ui/Tabs/TabItem.js
@@ -1,4 +1,4 @@
1import React, { Component } from 'react'; 1import React, { Component, Fragment } from 'react';
2 2
3import { oneOrManyChildElements } from '../../../prop-types'; 3import { oneOrManyChildElements } from '../../../prop-types';
4 4
@@ -11,7 +11,7 @@ export default class TabItem extends Component {
11 const { children } = this.props; 11 const { children } = this.props;
12 12
13 return ( 13 return (
14 <div>{children}</div> 14 <Fragment>{children}</Fragment>
15 ); 15 );
16 } 16 }
17} 17}
diff --git a/src/components/ui/WebviewLoader/index.js b/src/components/ui/WebviewLoader/index.js
new file mode 100644
index 000000000..3a3dbbe49
--- /dev/null
+++ b/src/components/ui/WebviewLoader/index.js
@@ -0,0 +1,25 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import injectSheet from 'react-jss';
5
6import FullscreenLoader from '../FullscreenLoader';
7
8import styles from './styles';
9
10export default @observer @injectSheet(styles) class WebviewLoader extends Component {
11 static propTypes = {
12 name: PropTypes.string.isRequired,
13 classes: PropTypes.object.isRequired,
14 }
15
16 render() {
17 const { classes, name } = this.props;
18 return (
19 <FullscreenLoader
20 className={classes.component}
21 title={`Loading ${name}`}
22 />
23 );
24 }
25}
diff --git a/src/components/ui/WebviewLoader/styles.js b/src/components/ui/WebviewLoader/styles.js
new file mode 100644
index 000000000..dbd75db8a
--- /dev/null
+++ b/src/components/ui/WebviewLoader/styles.js
@@ -0,0 +1,9 @@
1export default theme => ({
2 component: {
3 background: theme.colorWebviewLoaderBackground,
4 padding: 20,
5 width: 'auto',
6 margin: [0, 'auto'],
7 borderRadius: 6,
8 },
9});
diff --git a/src/components/util/ErrorBoundary/index.js b/src/components/util/ErrorBoundary/index.js
new file mode 100644
index 000000000..5db0db226
--- /dev/null
+++ b/src/components/util/ErrorBoundary/index.js
@@ -0,0 +1,60 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import injectSheet from 'react-jss';
4import { defineMessages, intlShape } from 'react-intl';
5
6import Button from '../../ui/Button';
7
8import styles from './styles';
9
10const messages = defineMessages({
11 headline: {
12 id: 'app.errorHandler.headline',
13 defaultMessage: '!!!Something went wrong.',
14 },
15 action: {
16 id: 'app.errorHandler.action',
17 defaultMessage: '!!!Reload',
18 },
19});
20
21export default @injectSheet(styles) class ErrorBoundary extends Component {
22 state = {
23 hasError: false,
24 }
25
26 static propTypes = {
27 classes: PropTypes.object.isRequired,
28 children: PropTypes.node.isRequired,
29 }
30
31 static contextTypes = {
32 intl: intlShape,
33 };
34
35 componentDidCatch() {
36 this.setState({ hasError: true });
37 }
38
39 render() {
40 const { classes } = this.props;
41 const { intl } = this.context;
42
43 if (this.state.hasError) {
44 return (
45 <div className={classes.component}>
46 <h1 className={classes.title}>
47 {intl.formatMessage(messages.headline)}
48 </h1>
49 <Button
50 label={intl.formatMessage(messages.action)}
51 buttonType="inverted"
52 onClick={() => window.location.reload()}
53 />
54 </div>
55 );
56 }
57
58 return this.props.children;
59 }
60}
diff --git a/src/components/util/ErrorBoundary/styles.js b/src/components/util/ErrorBoundary/styles.js
new file mode 100644
index 000000000..0960546ff
--- /dev/null
+++ b/src/components/util/ErrorBoundary/styles.js
@@ -0,0 +1,13 @@
1export default theme => ({
2 component: {
3 display: 'flex',
4 width: '100%',
5 alignItems: 'center',
6 justifyContent: 'center',
7 flexDirection: 'column',
8 },
9 title: {
10 fontSize: 20,
11 color: theme.colorText,
12 },
13});
diff --git a/src/config.js b/src/config.js
index d981f9c6a..789ddd1a0 100644
--- a/src/config.js
+++ b/src/config.js
@@ -56,4 +56,5 @@ export const FILE_SYSTEM_SETTINGS_TYPES = [
56 56
57export const SETTINGS_PATH = path.join(app.getPath('userData'), 'config'); 57export const SETTINGS_PATH = path.join(app.getPath('userData'), 'config');
58 58
59export const DICTIONARY_PATH = path.join(app.getPath('userData'), 'dicts'); 59// Replacing app.asar is not beautiful but unforunately necessary
60export const DICTIONARY_PATH = path.join(__dirname, 'dictionaries').replace('app.asar', 'app.asar.unpacked');
diff --git a/src/containers/auth/AuthLayoutContainer.js b/src/containers/auth/AuthLayoutContainer.js
index b73598f3d..762929dc6 100644
--- a/src/containers/auth/AuthLayoutContainer.js
+++ b/src/containers/auth/AuthLayoutContainer.js
@@ -18,7 +18,9 @@ export default @inject('stores', 'actions') @observer class AuthLayoutContainer
18 }; 18 };
19 19
20 render() { 20 render() {
21 const { stores, actions, children, location } = this.props; 21 const {
22 stores, actions, children, location,
23 } = this.props;
22 const { app, features, globalError } = stores; 24 const { app, features, globalError } = stores;
23 25
24 const isLoadingBaseFeatures = features.defaultFeaturesRequest.isExecuting 26 const isLoadingBaseFeatures = features.defaultFeaturesRequest.isExecuting
diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js
index e1423bdaa..430b49b55 100644
--- a/src/containers/layout/AppLayoutContainer.js
+++ b/src/containers/layout/AppLayoutContainer.js
@@ -75,7 +75,9 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
75 75
76 if (isLoadingFeatures || isLoadingServices) { 76 if (isLoadingFeatures || isLoadingServices) {
77 return ( 77 return (
78 <AppLoader /> 78 <ThemeProvider theme={ui.theme}>
79 <AppLoader />
80 </ThemeProvider>
79 ); 81 );
80 } 82 }
81 83
@@ -105,6 +107,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
105 setWebviewReference={setWebviewReference} 107 setWebviewReference={setWebviewReference}
106 openWindow={openWindow} 108 openWindow={openWindow}
107 reload={reload} 109 reload={reload}
110 openSettings={openSettings}
108 isAppMuted={settings.all.app.isAppMuted} 111 isAppMuted={settings.all.app.isAppMuted}
109 update={updateService} 112 update={updateService}
110 /> 113 />
diff --git a/src/containers/settings/AccountScreen.js b/src/containers/settings/AccountScreen.js
index 5818af0b1..019b3d7d6 100644
--- a/src/containers/settings/AccountScreen.js
+++ b/src/containers/settings/AccountScreen.js
@@ -9,6 +9,7 @@ import AppStore from '../../stores/AppStore';
9import { gaPage } from '../../lib/analytics'; 9import { gaPage } from '../../lib/analytics';
10 10
11import AccountDashboard from '../../components/settings/account/AccountDashboard'; 11import AccountDashboard from '../../components/settings/account/AccountDashboard';
12import ErrorBoundary from '../../components/util/ErrorBoundary';
12 13
13const { BrowserWindow } = remote; 14const { BrowserWindow } = remote;
14 15
@@ -67,22 +68,24 @@ export default @inject('stores', 'actions') @observer class AccountScreen extend
67 const isLoadingPlans = payment.plansRequest.isExecuting; 68 const isLoadingPlans = payment.plansRequest.isExecuting;
68 69
69 return ( 70 return (
70 <AccountDashboard 71 <ErrorBoundary>
71 user={user.data} 72 <AccountDashboard
72 orders={payment.orders} 73 user={user.data}
73 isLoading={isLoadingUserInfo} 74 orders={payment.orders}
74 isLoadingOrdersInfo={isLoadingOrdersInfo} 75 isLoading={isLoadingUserInfo}
75 isLoadingPlans={isLoadingPlans} 76 isLoadingOrdersInfo={isLoadingOrdersInfo}
76 userInfoRequestFailed={user.getUserInfoRequest.wasExecuted && user.getUserInfoRequest.isError} 77 isLoadingPlans={isLoadingPlans}
77 retryUserInfoRequest={() => this.reloadData()} 78 userInfoRequestFailed={user.getUserInfoRequest.wasExecuted && user.getUserInfoRequest.isError}
78 isCreatingPaymentDashboardUrl={payment.createDashboardUrlRequest.isExecuting} 79 retryUserInfoRequest={() => this.reloadData()}
79 openDashboard={price => this.handlePaymentDashboard(price)} 80 isCreatingPaymentDashboardUrl={payment.createDashboardUrlRequest.isExecuting}
80 openExternalUrl={url => openExternalUrl({ url })} 81 openDashboard={price => this.handlePaymentDashboard(price)}
81 onCloseSubscriptionWindow={() => this.onCloseWindow()} 82 openExternalUrl={url => openExternalUrl({ url })}
82 deleteAccount={userActions.delete} 83 onCloseSubscriptionWindow={() => this.onCloseWindow()}
83 isLoadingDeleteAccount={user.deleteAccountRequest.isExecuting} 84 deleteAccount={userActions.delete}
84 isDeleteAccountSuccessful={user.deleteAccountRequest.wasExecuted && !user.deleteAccountRequest.isError} 85 isLoadingDeleteAccount={user.deleteAccountRequest.isExecuting}
85 /> 86 isDeleteAccountSuccessful={user.deleteAccountRequest.wasExecuted && !user.deleteAccountRequest.isError}
87 />
88 </ErrorBoundary>
86 ); 89 );
87 } 90 }
88} 91}
diff --git a/src/containers/settings/EditServiceScreen.js b/src/containers/settings/EditServiceScreen.js
index 639e8b070..b46908344 100644
--- a/src/containers/settings/EditServiceScreen.js
+++ b/src/containers/settings/EditServiceScreen.js
@@ -13,10 +13,15 @@ import { gaPage } from '../../lib/analytics';
13 13
14import ServiceError from '../../components/settings/services/ServiceError'; 14import ServiceError from '../../components/settings/services/ServiceError';
15import EditServiceForm from '../../components/settings/services/EditServiceForm'; 15import EditServiceForm from '../../components/settings/services/EditServiceForm';
16import ErrorBoundary from '../../components/util/ErrorBoundary';
17
16import { required, url, oneRequired } from '../../helpers/validation-helpers'; 18import { required, url, oneRequired } from '../../helpers/validation-helpers';
19import { getSelectOptions } from '../../helpers/i18n-helpers';
17 20
18import { config as proxyFeature } from '../../features/serviceProxy'; 21import { config as proxyFeature } from '../../features/serviceProxy';
19 22
23import { SPELLCHECKER_LOCALES } from '../../i18n/languages';
24
20const messages = defineMessages({ 25const messages = defineMessages({
21 name: { 26 name: {
22 id: 'settings.service.form.name', 27 id: 'settings.service.form.name',
@@ -66,6 +71,10 @@ const messages = defineMessages({
66 id: 'settings.service.form.proxy.host', 71 id: 'settings.service.form.proxy.host',
67 defaultMessage: '!!!Proxy Host/IP', 72 defaultMessage: '!!!Proxy Host/IP',
68 }, 73 },
74 proxyPort: {
75 id: 'settings.service.form.proxy.port',
76 defaultMessage: '!!!Port',
77 },
69 proxyUser: { 78 proxyUser: {
70 id: 'settings.service.form.proxy.user', 79 id: 'settings.service.form.proxy.user',
71 defaultMessage: '!!!User', 80 defaultMessage: '!!!User',
@@ -74,6 +83,14 @@ const messages = defineMessages({
74 id: 'settings.service.form.proxy.password', 83 id: 'settings.service.form.proxy.password',
75 defaultMessage: '!!!Password', 84 defaultMessage: '!!!Password',
76 }, 85 },
86 spellcheckerLanguage: {
87 id: 'settings.service.form.spellcheckerLanguage',
88 defaultMessage: '!!!Spell checking Language',
89 },
90 spellcheckerSystemDefault: {
91 id: 'settings.service.form.spellcheckerLanguage.default',
92 defaultMessage: '!!!Use System Default ({default})',
93 },
77}); 94});
78 95
79export default @inject('stores', 'actions') @observer class EditServiceScreen extends Component { 96export default @inject('stores', 'actions') @observer class EditServiceScreen extends Component {
@@ -101,6 +118,11 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
101 } 118 }
102 119
103 prepareForm(recipe, service, proxy) { 120 prepareForm(recipe, service, proxy) {
121 const spellcheckerLanguage = getSelectOptions({
122 locales: SPELLCHECKER_LOCALES,
123 resetToDefaultText: this.context.intl.formatMessage(messages.spellcheckerSystemDefault, { default: SPELLCHECKER_LOCALES[this.props.stores.settings.app.spellcheckerLanguage] }),
124 });
125
104 const { intl } = this.context; 126 const { intl } = this.context;
105 const config = { 127 const config = {
106 fields: { 128 fields: {
@@ -138,7 +160,13 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
138 isDarkModeEnabled: { 160 isDarkModeEnabled: {
139 label: intl.formatMessage(messages.enableDarkMode), 161 label: intl.formatMessage(messages.enableDarkMode),
140 value: service.isDarkModeEnabled, 162 value: service.isDarkModeEnabled,
141 default: this.props.stores.settings.all.app.darkMode, 163 default: this.props.stores.settings.app.darkMode,
164 },
165 spellcheckerLanguage: {
166 label: intl.formatMessage(messages.spellcheckerLanguage),
167 value: service.spellcheckerLanguage,
168 options: spellcheckerLanguage,
169 disabled: !this.props.stores.settings.app.enableSpellchecking,
142 }, 170 },
143 }, 171 },
144 }; 172 };
@@ -209,6 +237,11 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
209 value: serviceProxyConfig.host, 237 value: serviceProxyConfig.host,
210 default: '', 238 default: '',
211 }, 239 },
240 port: {
241 label: intl.formatMessage(messages.proxyPort),
242 value: serviceProxyConfig.port,
243 default: '',
244 },
212 user: { 245 user: {
213 label: intl.formatMessage(messages.proxyUser), 246 label: intl.formatMessage(messages.proxyUser),
214 value: serviceProxyConfig.user, 247 value: serviceProxyConfig.user,
@@ -280,20 +313,22 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
280 const form = this.prepareForm(recipe, service, proxyFeature); 313 const form = this.prepareForm(recipe, service, proxyFeature);
281 314
282 return ( 315 return (
283 <EditServiceForm 316 <ErrorBoundary>
284 action={action} 317 <EditServiceForm
285 recipe={recipe} 318 action={action}
286 service={service} 319 recipe={recipe}
287 user={user.data} 320 service={service}
288 form={form} 321 user={user.data}
289 status={services.actionStatus} 322 form={form}
290 isSaving={services.updateServiceRequest.isExecuting || services.createServiceRequest.isExecuting} 323 status={services.actionStatus}
291 isDeleting={services.deleteServiceRequest.isExecuting} 324 isSaving={services.updateServiceRequest.isExecuting || services.createServiceRequest.isExecuting}
292 onSubmit={d => this.onSubmit(d)} 325 isDeleting={services.deleteServiceRequest.isExecuting}
293 onDelete={() => this.deleteService()} 326 onSubmit={d => this.onSubmit(d)}
294 isProxyFeatureEnabled={proxyFeature.isEnabled} 327 onDelete={() => this.deleteService()}
295 isProxyFeaturePremiumFeature={proxyFeature.isPremium} 328 isProxyFeatureEnabled={proxyFeature.isEnabled}
296 /> 329 isProxyFeaturePremiumFeature={proxyFeature.isPremium}
330 />
331 </ErrorBoundary>
297 ); 332 );
298 } 333 }
299} 334}
diff --git a/src/containers/settings/EditSettingsScreen.js b/src/containers/settings/EditSettingsScreen.js
index ea1d319d9..f1706a721 100644
--- a/src/containers/settings/EditSettingsScreen.js
+++ b/src/containers/settings/EditSettingsScreen.js
@@ -12,8 +12,11 @@ import { gaPage } from '../../lib/analytics';
12import { DEFAULT_APP_SETTINGS } from '../../config'; 12import { DEFAULT_APP_SETTINGS } from '../../config';
13import { config as spellcheckerConfig } from '../../features/spellchecker'; 13import { config as spellcheckerConfig } from '../../features/spellchecker';
14 14
15import { getSelectOptions } from '../../helpers/i18n-helpers';
16
15 17
16import EditSettingsForm from '../../components/settings/settings/EditSettingsForm'; 18import EditSettingsForm from '../../components/settings/settings/EditSettingsForm';
19import ErrorBoundary from '../../components/util/ErrorBoundary';
17 20
18const messages = defineMessages({ 21const messages = defineMessages({
19 autoLaunchOnStart: { 22 autoLaunchOnStart: {
@@ -116,20 +119,12 @@ export default @inject('stores', 'actions') @observer class EditSettingsScreen e
116 const { app, settings, user } = this.props.stores; 119 const { app, settings, user } = this.props.stores;
117 const { intl } = this.context; 120 const { intl } = this.context;
118 121
119 const locales = []; 122 const locales = getSelectOptions({
120 Object.keys(APP_LOCALES).sort(Intl.Collator().compare).forEach((key) => { 123 locales: APP_LOCALES,
121 locales.push({
122 value: key,
123 label: APP_LOCALES[key],
124 });
125 }); 124 });
126 125
127 const spellcheckingLanguages = []; 126 const spellcheckingLanguages = getSelectOptions({
128 Object.keys(SPELLCHECKER_LOCALES).sort(Intl.Collator().compare).forEach((key) => { 127 locales: SPELLCHECKER_LOCALES,
129 spellcheckingLanguages.push({
130 value: key,
131 label: SPELLCHECKER_LOCALES[key],
132 });
133 }); 128 });
134 129
135 const config = { 130 const config = {
@@ -222,20 +217,22 @@ export default @inject('stores', 'actions') @observer class EditSettingsScreen e
222 const form = this.prepareForm(); 217 const form = this.prepareForm();
223 218
224 return ( 219 return (
225 <EditSettingsForm 220 <ErrorBoundary>
226 form={form} 221 <EditSettingsForm
227 checkForUpdates={checkForUpdates} 222 form={form}
228 installUpdate={installUpdate} 223 checkForUpdates={checkForUpdates}
229 isCheckingForUpdates={updateStatus === updateStatusTypes.CHECKING} 224 installUpdate={installUpdate}
230 isUpdateAvailable={updateStatus === updateStatusTypes.AVAILABLE} 225 isCheckingForUpdates={updateStatus === updateStatusTypes.CHECKING}
231 noUpdateAvailable={updateStatus === updateStatusTypes.NOT_AVAILABLE} 226 isUpdateAvailable={updateStatus === updateStatusTypes.AVAILABLE}
232 updateIsReadyToInstall={updateStatus === updateStatusTypes.DOWNLOADED} 227 noUpdateAvailable={updateStatus === updateStatusTypes.NOT_AVAILABLE}
233 onSubmit={d => this.onSubmit(d)} 228 updateIsReadyToInstall={updateStatus === updateStatusTypes.DOWNLOADED}
234 cacheSize={cacheSize} 229 onSubmit={d => this.onSubmit(d)}
235 isClearingAllCache={isClearingAllCache} 230 cacheSize={cacheSize}
236 onClearAllCache={clearAllCache} 231 isClearingAllCache={isClearingAllCache}
237 isSpellcheckerPremiumFeature={spellcheckerConfig.isPremiumFeature} 232 onClearAllCache={clearAllCache}
238 /> 233 isSpellcheckerPremiumFeature={spellcheckerConfig.isPremiumFeature}
234 />
235 </ErrorBoundary>
239 ); 236 );
240 } 237 }
241} 238}
diff --git a/src/containers/settings/EditUserScreen.js b/src/containers/settings/EditUserScreen.js
index 3da3e8d2c..3d35effc5 100644
--- a/src/containers/settings/EditUserScreen.js
+++ b/src/containers/settings/EditUserScreen.js
@@ -6,6 +6,8 @@ import { defineMessages, intlShape } from 'react-intl';
6import UserStore from '../../stores/UserStore'; 6import UserStore from '../../stores/UserStore';
7import Form from '../../lib/Form'; 7import Form from '../../lib/Form';
8import EditUserForm from '../../components/settings/user/EditUserForm'; 8import EditUserForm from '../../components/settings/user/EditUserForm';
9import ErrorBoundary from '../../components/util/ErrorBoundary';
10
9import { required, email, minLength } from '../../helpers/validation-helpers'; 11import { required, email, minLength } from '../../helpers/validation-helpers';
10import { gaPage } from '../../lib/analytics'; 12import { gaPage } from '../../lib/analytics';
11 13
@@ -140,14 +142,15 @@ export default @inject('stores', 'actions') @observer class EditUserScreen exten
140 const form = this.prepareForm(user.data); 142 const form = this.prepareForm(user.data);
141 143
142 return ( 144 return (
143 <EditUserForm 145 <ErrorBoundary>
144 // user={user.data} 146 <EditUserForm
145 status={user.actionStatus} 147 // user={user.data}
146 form={form} 148 status={user.actionStatus}
147 isEnterprise={user.data.isEnterprise} 149 form={form}
148 isSaving={user.updateUserInfoRequest.isExecuting} 150 isSaving={user.updateUserInfoRequest.isExecuting}
149 onSubmit={d => this.onSubmit(d)} 151 onSubmit={d => this.onSubmit(d)}
150 /> 152 />
153 </ErrorBoundary>
151 ); 154 );
152 } 155 }
153} 156}
diff --git a/src/containers/settings/InviteScreen.js b/src/containers/settings/InviteScreen.js
index 38ca6ec74..cd36610e4 100644
--- a/src/containers/settings/InviteScreen.js
+++ b/src/containers/settings/InviteScreen.js
@@ -3,6 +3,8 @@ import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react'; 3import { inject, observer } from 'mobx-react';
4 4
5import Invite from '../../components/auth/Invite'; 5import Invite from '../../components/auth/Invite';
6import ErrorBoundary from '../../components/util/ErrorBoundary';
7
6import { gaPage } from '../../lib/analytics'; 8import { gaPage } from '../../lib/analytics';
7 9
8export default @inject('stores', 'actions') @observer class InviteScreen extends Component { 10export default @inject('stores', 'actions') @observer class InviteScreen extends Component {
@@ -19,12 +21,14 @@ export default @inject('stores', 'actions') @observer class InviteScreen extends
19 const { user } = this.props.stores; 21 const { user } = this.props.stores;
20 22
21 return ( 23 return (
22 <Invite 24 <ErrorBoundary>
23 onSubmit={actions.user.invite} 25 <Invite
24 isLoadingInvite={user.inviteRequest.isExecuting} 26 onSubmit={actions.user.invite}
25 isInviteSuccessful={user.inviteRequest.wasExecuted && !user.inviteRequest.isError} 27 isLoadingInvite={user.inviteRequest.isExecuting}
26 embed 28 isInviteSuccessful={user.inviteRequest.wasExecuted && !user.inviteRequest.isError}
27 /> 29 embed
30 />
31 </ErrorBoundary>
28 ); 32 );
29 } 33 }
30} 34}
diff --git a/src/containers/settings/RecipesScreen.js b/src/containers/settings/RecipesScreen.js
index 4efe81505..b3d758c87 100644
--- a/src/containers/settings/RecipesScreen.js
+++ b/src/containers/settings/RecipesScreen.js
@@ -10,12 +10,13 @@ import UserStore from '../../stores/UserStore';
10import { gaPage } from '../../lib/analytics'; 10import { gaPage } from '../../lib/analytics';
11 11
12import RecipesDashboard from '../../components/settings/recipes/RecipesDashboard'; 12import RecipesDashboard from '../../components/settings/recipes/RecipesDashboard';
13import ErrorBoundary from '../../components/util/ErrorBoundary';
13 14
14export default @inject('stores', 'actions') @observer class RecipesScreen extends Component { 15export default @inject('stores', 'actions') @observer class RecipesScreen extends Component {
15 static propTypes = { 16 static propTypes = {
16 params: PropTypes.shape({ 17 params: PropTypes.shape({
17 filter: PropTypes.string, 18 filter: PropTypes.string,
18 }).isRequired, 19 }),
19 }; 20 };
20 21
21 static defaultProps = { 22 static defaultProps = {
@@ -29,10 +30,12 @@ export default @inject('stores', 'actions') @observer class RecipesScreen extend
29 currentFilter: 'featured', 30 currentFilter: 'featured',
30 }; 31 };
31 32
33 autorunDisposer = null;
34
32 componentDidMount() { 35 componentDidMount() {
33 gaPage('Settings/Recipe Dashboard/Featured'); 36 gaPage('Settings/Recipe Dashboard/Featured');
34 37
35 autorun(() => { 38 this.autorunDisposer = autorun(() => {
36 const { filter } = this.props.params; 39 const { filter } = this.props.params;
37 const { currentFilter } = this.state; 40 const { currentFilter } = this.state;
38 41
@@ -51,6 +54,7 @@ export default @inject('stores', 'actions') @observer class RecipesScreen extend
51 54
52 componentWillUnmount() { 55 componentWillUnmount() {
53 this.props.stores.services.resetStatus(); 56 this.props.stores.services.resetStatus();
57 this.autorunDisposer();
54 } 58 }
55 59
56 searchRecipes(needle) { 60 searchRecipes(needle) {
@@ -68,7 +72,9 @@ export default @inject('stores', 'actions') @observer class RecipesScreen extend
68 } 72 }
69 73
70 render() { 74 render() {
71 const { recipePreviews, recipes, services, user } = this.props.stores; 75 const {
76 recipePreviews, recipes, services, user,
77 } = this.props.stores;
72 const { showAddServiceInterface } = this.props.actions.service; 78 const { showAddServiceInterface } = this.props.actions.service;
73 79
74 const { filter } = this.props.params; 80 const { filter } = this.props.params;
@@ -90,19 +96,21 @@ export default @inject('stores', 'actions') @observer class RecipesScreen extend
90 || recipePreviews.searchRecipePreviewsRequest.isExecuting; 96 || recipePreviews.searchRecipePreviewsRequest.isExecuting;
91 97
92 return ( 98 return (
93 <RecipesDashboard 99 <ErrorBoundary>
94 recipes={allRecipes} 100 <RecipesDashboard
95 isLoading={isLoading} 101 recipes={allRecipes}
96 addedServiceCount={services.all.length} 102 isLoading={isLoading}
97 isPremium={user.data.isPremium} 103 addedServiceCount={services.all.length}
98 hasLoadedRecipes={recipePreviews.featuredRecipePreviewsRequest.wasExecuted} 104 isPremium={user.data.isPremium}
99 showAddServiceInterface={showAddServiceInterface} 105 hasLoadedRecipes={recipePreviews.featuredRecipePreviewsRequest.wasExecuted}
100 searchRecipes={e => this.searchRecipes(e)} 106 showAddServiceInterface={showAddServiceInterface}
101 resetSearch={() => this.resetSearch()} 107 searchRecipes={e => this.searchRecipes(e)}
102 searchNeedle={this.state.needle} 108 resetSearch={() => this.resetSearch()}
103 serviceStatus={services.actionStatus} 109 searchNeedle={this.state.needle}
104 devRecipesCount={recipePreviews.dev.length} 110 serviceStatus={services.actionStatus}
105 /> 111 devRecipesCount={recipePreviews.dev.length}
112 />
113 </ErrorBoundary>
106 ); 114 );
107 } 115 }
108} 116}
diff --git a/src/containers/settings/ServicesScreen.js b/src/containers/settings/ServicesScreen.js
index c1a133ef7..b70a5506e 100644
--- a/src/containers/settings/ServicesScreen.js
+++ b/src/containers/settings/ServicesScreen.js
@@ -9,6 +9,7 @@ import ServiceStore from '../../stores/ServicesStore';
9import { gaPage } from '../../lib/analytics'; 9import { gaPage } from '../../lib/analytics';
10 10
11import ServicesDashboard from '../../components/settings/services/ServicesDashboard'; 11import ServicesDashboard from '../../components/settings/services/ServicesDashboard';
12import ErrorBoundary from '../../components/util/ErrorBoundary';
12 13
13export default @inject('stores', 'actions') @observer class ServicesScreen extends Component { 14export default @inject('stores', 'actions') @observer class ServicesScreen extends Component {
14 componentDidMount() { 15 componentDidMount() {
@@ -40,20 +41,22 @@ export default @inject('stores', 'actions') @observer class ServicesScreen exten
40 } 41 }
41 42
42 return ( 43 return (
43 <ServicesDashboard 44 <ErrorBoundary>
44 user={user.data} 45 <ServicesDashboard
45 services={allServices} 46 user={user.data}
46 status={services.actionStatus} 47 services={allServices}
47 deleteService={() => this.deleteService()} 48 status={services.actionStatus}
48 toggleService={toggleService} 49 deleteService={() => this.deleteService()}
49 isLoading={isLoading} 50 toggleService={toggleService}
50 filterServices={filter} 51 isLoading={isLoading}
51 resetFilter={resetFilter} 52 filterServices={filter}
52 goTo={router.push} 53 resetFilter={resetFilter}
53 servicesRequestFailed={services.allServicesRequest.wasExecuted && services.allServicesRequest.isError} 54 goTo={router.push}
54 retryServicesRequest={() => services.allServicesRequest.reload()} 55 servicesRequestFailed={services.allServicesRequest.wasExecuted && services.allServicesRequest.isError}
55 searchNeedle={services.filterNeedle} 56 retryServicesRequest={() => services.allServicesRequest.reload()}
56 /> 57 searchNeedle={services.filterNeedle}
58 />
59 </ErrorBoundary>
57 ); 60 );
58 } 61 }
59} 62}
diff --git a/src/containers/settings/SettingsWindow.js b/src/containers/settings/SettingsWindow.js
index 55589d0be..6d9e0ee77 100644
--- a/src/containers/settings/SettingsWindow.js
+++ b/src/containers/settings/SettingsWindow.js
@@ -6,6 +6,7 @@ import ServicesStore from '../../stores/ServicesStore';
6 6
7import Layout from '../../components/settings/SettingsLayout'; 7import Layout from '../../components/settings/SettingsLayout';
8import Navigation from '../../components/settings/navigation/SettingsNavigation'; 8import Navigation from '../../components/settings/navigation/SettingsNavigation';
9import ErrorBoundary from '../../components/util/ErrorBoundary';
9 10
10export default @inject('stores', 'actions') @observer class SettingsContainer extends Component { 11export default @inject('stores', 'actions') @observer class SettingsContainer extends Component {
11 render() { 12 render() {
@@ -19,12 +20,14 @@ export default @inject('stores', 'actions') @observer class SettingsContainer ex
19 ); 20 );
20 21
21 return ( 22 return (
22 <Layout 23 <ErrorBoundary>
23 navigation={navigation} 24 <Layout
24 closeSettings={closeSettings} 25 navigation={navigation}
25 > 26 closeSettings={closeSettings}
26 {children} 27 >
27 </Layout> 28 {children}
29 </Layout>
30 </ErrorBoundary>
28 ); 31 );
29 } 32 }
30} 33}
diff --git a/src/containers/subscription/SubscriptionFormScreen.js b/src/containers/subscription/SubscriptionFormScreen.js
index 50ed19bef..3eb7b6255 100644
--- a/src/containers/subscription/SubscriptionFormScreen.js
+++ b/src/containers/subscription/SubscriptionFormScreen.js
@@ -12,7 +12,7 @@ const { BrowserWindow } = remote;
12export default @inject('stores', 'actions') @observer class SubscriptionFormScreen extends Component { 12export default @inject('stores', 'actions') @observer class SubscriptionFormScreen extends Component {
13 static propTypes = { 13 static propTypes = {
14 onCloseWindow: PropTypes.func, 14 onCloseWindow: PropTypes.func,
15 content: PropTypes.oneOrManyChildElements, 15 content: PropTypes.node,
16 showSkipOption: PropTypes.bool, 16 showSkipOption: PropTypes.bool,
17 skipAction: PropTypes.func, 17 skipAction: PropTypes.func,
18 skipButtonLabel: PropTypes.string, 18 skipButtonLabel: PropTypes.string,
diff --git a/src/electron/Settings.js b/src/electron/Settings.js
index ed9733bfe..63f43b6b7 100644
--- a/src/electron/Settings.js
+++ b/src/electron/Settings.js
@@ -8,6 +8,7 @@ const debug = require('debug')('Franz:Settings');
8 8
9export default class Settings { 9export default class Settings {
10 type = ''; 10 type = '';
11
11 @observable store = {}; 12 @observable store = {};
12 13
13 constructor(type, defaultState = {}) { 14 constructor(type, defaultState = {}) {
diff --git a/src/electron/ipc-api/download.js b/src/electron/ipc-api/download.js
index 9e504834d..e6703af2d 100644
--- a/src/electron/ipc-api/download.js
+++ b/src/electron/ipc-api/download.js
@@ -12,7 +12,7 @@ function decodeBase64Image(dataString) {
12 return new Error('Invalid input string'); 12 return new Error('Invalid input string');
13 } 13 }
14 14
15 return new Buffer(matches[2], 'base64'); 15 return Buffer.from(matches[2], 'base64');
16} 16}
17 17
18export default (params) => { 18export default (params) => {
diff --git a/src/features/delayApp/Component.js b/src/features/delayApp/Component.js
index 403340c7b..6e0532c9a 100644
--- a/src/features/delayApp/Component.js
+++ b/src/features/delayApp/Component.js
@@ -6,7 +6,7 @@ import injectSheet from 'react-jss';
6 6
7import Button from '../../components/ui/Button'; 7import Button from '../../components/ui/Button';
8 8
9import { config } from './'; 9import { config } from '.';
10import styles from './styles'; 10import styles from './styles';
11 11
12const messages = defineMessages({ 12const messages = defineMessages({
@@ -38,11 +38,15 @@ export default @inject('actions') @injectSheet(styles) @observer class DelayApp
38 countdown: config.delayDuration, 38 countdown: config.delayDuration,
39 } 39 }
40 40
41 countdownInterval = null;
42
43 countdownIntervalTimeout = 1000;
44
41 componentDidMount() { 45 componentDidMount() {
42 this.countdownInterval = setInterval(() => { 46 this.countdownInterval = setInterval(() => {
43 this.setState({ 47 this.setState(prevState => ({
44 countdown: this.state.countdown - this.countdownIntervalTimeout, 48 countdown: prevState.countdown - this.countdownIntervalTimeout,
45 }); 49 }));
46 50
47 if (this.state.countdown <= 0) { 51 if (this.state.countdown <= 0) {
48 // reload(); 52 // reload();
@@ -55,9 +59,6 @@ export default @inject('actions') @injectSheet(styles) @observer class DelayApp
55 clearInterval(this.countdownInterval); 59 clearInterval(this.countdownInterval);
56 } 60 }
57 61
58 countdownInterval = null;
59 countdownIntervalTimeout = 1000;
60
61 render() { 62 render() {
62 const { classes, actions } = this.props; 63 const { classes, actions } = this.props;
63 const { intl } = this.context; 64 const { intl } = this.context;
@@ -71,9 +72,11 @@ export default @inject('actions') @injectSheet(styles) @observer class DelayApp
71 buttonType="inverted" 72 buttonType="inverted"
72 onClick={() => actions.ui.openSettings({ path: 'user' })} 73 onClick={() => actions.ui.openSettings({ path: 'user' })}
73 /> 74 />
74 <p className="footnote">{intl.formatMessage(messages.text, { 75 <p className="footnote">
75 seconds: this.state.countdown / 1000, 76 {intl.formatMessage(messages.text, {
76 })}</p> 77 seconds: this.state.countdown / 1000,
78 })}
79 </p>
77 </div> 80 </div>
78 ); 81 );
79 } 82 }
diff --git a/src/features/delayApp/index.js b/src/features/delayApp/index.js
index 9ffa1d2fd..d5c544b78 100644
--- a/src/features/delayApp/index.js
+++ b/src/features/delayApp/index.js
@@ -67,4 +67,3 @@ export default function init(stores) {
67} 67}
68 68
69export const Component = DelayAppComponent; 69export const Component = DelayAppComponent;
70
diff --git a/src/features/serviceProxy/index.js b/src/features/serviceProxy/index.js
index ee0b4e79c..4bea327ad 100644
--- a/src/features/serviceProxy/index.js
+++ b/src/features/serviceProxy/index.js
@@ -23,15 +23,18 @@ export default function init(stores) {
23 23
24 const services = stores.services.enabled; 24 const services = stores.services.enabled;
25 const isPremiumUser = stores.user.data.isPremium; 25 const isPremiumUser = stores.user.data.isPremium;
26 const proxySettings = stores.settings.proxy;
27
28 debug('Service Proxy autorun');
26 29
27 services.forEach((service) => { 30 services.forEach((service) => {
28 const s = session.fromPartition(`persist:service-${service.id}`); 31 const s = session.fromPartition(`persist:service-${service.id}`);
29 32
30 if (config.isEnabled && (isPremiumUser || !config.isPremium)) { 33 if (config.isEnabled && (isPremiumUser || !config.isPremium)) {
31 const serviceProxyConfig = stores.settings.proxy[service.id]; 34 const serviceProxyConfig = proxySettings[service.id];
32 35
33 if (serviceProxyConfig && serviceProxyConfig.isEnabled && serviceProxyConfig.host) { 36 if (serviceProxyConfig && serviceProxyConfig.isEnabled && serviceProxyConfig.host) {
34 const proxyHost = serviceProxyConfig.host; 37 const proxyHost = `${serviceProxyConfig.host}${serviceProxyConfig.port ? `:${serviceProxyConfig.port}` : ''}`;
35 debug(`Setting proxy config from service settings for "${service.name}" (${service.id}) to`, proxyHost); 38 debug(`Setting proxy config from service settings for "${service.name}" (${service.id}) to`, proxyHost);
36 39
37 s.setProxy({ proxyRules: proxyHost }, () => { 40 s.setProxy({ proxyRules: proxyHost }, () => {
@@ -42,4 +45,3 @@ export default function init(stores) {
42 }); 45 });
43 }); 46 });
44} 47}
45
diff --git a/src/helpers/array-helpers.js b/src/helpers/array-helpers.js
new file mode 100644
index 000000000..ffb3b63dc
--- /dev/null
+++ b/src/helpers/array-helpers.js
@@ -0,0 +1,4 @@
1export const shuffleArray = arr => arr
2 .map(a => [Math.random(), a])
3 .sort((a, b) => a[0] - b[0])
4 .map(a => a[1]);
diff --git a/src/helpers/i18n-helpers.js b/src/helpers/i18n-helpers.js
index 00a2061c1..091b86b06 100644
--- a/src/helpers/i18n-helpers.js
+++ b/src/helpers/i18n-helpers.js
@@ -1,4 +1,6 @@
1export function getLocale({ locale, locales, defaultLocale, fallbackLocale }) { 1export function getLocale({
2 locale, locales, defaultLocale, fallbackLocale,
3}) {
2 let localeStr = locale; 4 let localeStr = locale;
3 if (locales[locale] === undefined) { 5 if (locales[locale] === undefined) {
4 let localeFuzzy; 6 let localeFuzzy;
@@ -25,3 +27,29 @@ export function getLocale({ locale, locales, defaultLocale, fallbackLocale }) {
25 27
26 return localeStr; 28 return localeStr;
27} 29}
30
31export function getSelectOptions({ locales, resetToDefaultText = '' }) {
32 let options = [];
33
34 if (resetToDefaultText) {
35 options = [
36 {
37 value: '',
38 label: resetToDefaultText,
39 }, {
40 value: '───',
41 label: '───',
42 disabled: true,
43 },
44 ];
45 }
46
47 Object.keys(locales).sort(Intl.Collator().compare).forEach((key) => {
48 options.push({
49 value: key,
50 label: locales[key],
51 });
52 });
53
54 return options;
55}
diff --git a/src/i18n/locales/ca.json b/src/i18n/locales/ca.json
index 117e66d76..ed6f7bacd 100644
--- a/src/i18n/locales/ca.json
+++ b/src/i18n/locales/ca.json
@@ -1,7 +1,9 @@
1{ 1{
2 "feature.delayApp.action" : "Get a Franz Supporter License", 2 "app.errorHandler.action" : "Recarrega",
3 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting", 3 "app.errorHandler.headline" : "Something went wrong",
4 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.", 4 "feature.delayApp.action" : "Aconsegueix una llicència de suport per a Franz",
5 "feature.delayApp.headline" : "Si us plau, compra una llicència de suport per a Franz per saltar l'espera",
6 "feature.delayApp.text" : "Franz continuarà en {seconds} segons",
5 "global.api.unhealthy" : "No es pot connectar amb els serveis en línia de Franz", 7 "global.api.unhealthy" : "No es pot connectar amb els serveis en línia de Franz",
6 "global.notConnectedToTheInternet" : "No esteu connectat a Internet.", 8 "global.notConnectedToTheInternet" : "No esteu connectat a Internet.",
7 "import.headline" : "Importa els teus serveis Franz 4", 9 "import.headline" : "Importa els teus serveis Franz 4",
@@ -79,7 +81,7 @@
79 "password.noUser" : "No s'ha trobat cap usuari amb aquesta adreça de correu electrònic", 81 "password.noUser" : "No s'ha trobat cap usuari amb aquesta adreça de correu electrònic",
80 "password.submit.label" : "Enviar", 82 "password.submit.label" : "Enviar",
81 "password.successInfo" : "Comproveu el vostre correu electrònic", 83 "password.successInfo" : "Comproveu el vostre correu electrònic",
82 "premiumFeature.button.upgradeAccount" : "Upgrade account", 84 "premiumFeature.button.upgradeAccount" : "Millorar el teu compte",
83 "pricing.headline" : "Donar suport a Franz", 85 "pricing.headline" : "Donar suport a Franz",
84 "pricing.link.skipPayment" : "No vull donar suport al desenvolupament de Franz.", 86 "pricing.link.skipPayment" : "No vull donar suport al desenvolupament de Franz.",
85 "pricing.submit.label" : "Vull donar suport al desenvolupament de Franz", 87 "pricing.submit.label" : "Vull donar suport al desenvolupament de Franz",
@@ -90,6 +92,11 @@
90 "service.crashHandler.text" : "{name} ha causat un error.", 92 "service.crashHandler.text" : "{name} ha causat un error.",
91 "service.disabledHandler.action" : "Activar {name}", 93 "service.disabledHandler.action" : "Activar {name}",
92 "service.disabledHandler.headline" : "{name} està desactivat", 94 "service.disabledHandler.headline" : "{name} està desactivat",
95 "service.errorHandler.action" : "Recarrega {name}",
96 "service.errorHandler.editAction" : "Edita {name}",
97 "service.errorHandler.headline" : "Oh no!",
98 "service.errorHandler.message" : "Error",
99 "service.errorHandler.text" : "{name} has failed to load.",
93 "services.getStarted" : "Introducció", 100 "services.getStarted" : "Introducció",
94 "services.welcome" : "Benvingut a Franz", 101 "services.welcome" : "Benvingut a Franz",
95 "settings.account.account.editButton" : "Editar Compte", 102 "settings.account.account.editButton" : "Editar Compte",
@@ -120,7 +127,7 @@
120 "settings.app.form.autoLaunchInBackground" : "Obrir en segon plà", 127 "settings.app.form.autoLaunchInBackground" : "Obrir en segon plà",
121 "settings.app.form.autoLaunchOnStart" : "Iniciar Franz a l'inici", 128 "settings.app.form.autoLaunchOnStart" : "Iniciar Franz a l'inici",
122 "settings.app.form.beta" : "Inclou versions beta", 129 "settings.app.form.beta" : "Inclou versions beta",
123 "settings.app.form.darkMode" : "Join the Dark Side", 130 "settings.app.form.darkMode" : "Uneix-te al Cantó Fosc",
124 "settings.app.form.enableGPUAcceleration" : "Activar acceleració GPU", 131 "settings.app.form.enableGPUAcceleration" : "Activar acceleració GPU",
125 "settings.app.form.enableMenuBar" : "Mostra Franz a la barra de menú", 132 "settings.app.form.enableMenuBar" : "Mostra Franz a la barra de menú",
126 "settings.app.form.enableSpellchecking" : "Habilita la comprobació ortogràfica", 133 "settings.app.form.enableSpellchecking" : "Habilita la comprobació ortogràfica",
@@ -131,7 +138,7 @@
131 "settings.app.form.runInBackground" : "Mantén a Franz en segon pla en tancar la finestra", 138 "settings.app.form.runInBackground" : "Mantén a Franz en segon pla en tancar la finestra",
132 "settings.app.form.showDisabledServices" : "Mostra les pestanyes dels serveis desactivats", 139 "settings.app.form.showDisabledServices" : "Mostra les pestanyes dels serveis desactivats",
133 "settings.app.form.showMessagesBadgesWhenMuted" : "Mostra la insígnia de missatges no llegits quan les notificacions estiguin desactivades", 140 "settings.app.form.showMessagesBadgesWhenMuted" : "Mostra la insígnia de missatges no llegits quan les notificacions estiguin desactivades",
134 "settings.app.form.spellcheckerLanguage" : "Spell checking language", 141 "settings.app.form.spellcheckerLanguage" : "Corrector ortogràfic",
135 "settings.app.headline" : "Configuració", 142 "settings.app.headline" : "Configuració",
136 "settings.app.headlineAdvanced" : "Avançat", 143 "settings.app.headlineAdvanced" : "Avançat",
137 "settings.app.headlineAppearance" : "Aparença", 144 "settings.app.headlineAppearance" : "Aparença",
@@ -172,7 +179,7 @@
172 "settings.service.form.editServiceHeadline" : "Edita {name}", 179 "settings.service.form.editServiceHeadline" : "Edita {name}",
173 "settings.service.form.enableAudio" : "Activa l'àudio", 180 "settings.service.form.enableAudio" : "Activa l'àudio",
174 "settings.service.form.enableBadge" : "Mostra les insígnies als missatges no llegits.", 181 "settings.service.form.enableBadge" : "Mostra les insígnies als missatges no llegits.",
175 "settings.service.form.enableDarkMode" : "Enable Dark Mode", 182 "settings.service.form.enableDarkMode" : "Activar el Mode Fosc",
176 "settings.service.form.enableNotification" : "Activa les notificacions", 183 "settings.service.form.enableNotification" : "Activa les notificacions",
177 "settings.service.form.enableService" : "Activa el servei", 184 "settings.service.form.enableService" : "Activa el servei",
178 "settings.service.form.headlineBadges" : "Insígnies de missatges no llegits", 185 "settings.service.form.headlineBadges" : "Insígnies de missatges no llegits",
@@ -185,13 +192,17 @@
185 "settings.service.form.indirectMessages" : "Mostra la insígnia de missatge per a tots els missatges nous", 192 "settings.service.form.indirectMessages" : "Mostra la insígnia de missatge per a tots els missatges nous",
186 "settings.service.form.isMutedInfo" : "Quan es desactiva, tots els sons de notificació i reproducció d'àudio es silenciaran", 193 "settings.service.form.isMutedInfo" : "Quan es desactiva, tots els sons de notificació i reproducció d'àudio es silenciaran",
187 "settings.service.form.name" : "Nom", 194 "settings.service.form.name" : "Nom",
188 "settings.service.form.proxy.headline" : "Proxy Settings", 195 "settings.service.form.proxy.headline" : "HTTP\/HTTPS Proxy Settings",
189 "settings.service.form.proxy.host" : "Proxy Host\/IP", 196 "settings.service.form.proxy.host" : "Host Proxy \/ IP",
190 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.", 197 "settings.service.form.proxy.info" : "Els ajustaments del Proxy no es sincronitzaran amb els servidors de Franz",
191 "settings.service.form.proxy.isEnabled" : "Use Proxy", 198 "settings.service.form.proxy.isEnabled" : "Utilitzar Proxy",
192 "settings.service.form.proxy.password" : "Password (optional)", 199 "settings.service.form.proxy.password" : "Contrasenya (opcional)",
193 "settings.service.form.proxy.user" : "User (optional)", 200 "settings.service.form.proxy.port" : "Port",
201 "settings.service.form.proxy.restartInfo" : "Please restart Franz after changing proxy Settings.",
202 "settings.service.form.proxy.user" : "Usuari (opcional)",
194 "settings.service.form.saveButton" : "Desa el servei", 203 "settings.service.form.saveButton" : "Desa el servei",
204 "settings.service.form.spellcheckerLanguage" : "Spell checking Language",
205 "settings.service.form.spellcheckerLanguage.default" : "Use System Default ({default})",
195 "settings.service.form.tabHosted" : "Allotjat", 206 "settings.service.form.tabHosted" : "Allotjat",
196 "settings.service.form.tabOnPremise" : "Allotjat per si mateix ⭐️", 207 "settings.service.form.tabOnPremise" : "Allotjat per si mateix ⭐️",
197 "settings.service.form.team" : "Equip", 208 "settings.service.form.team" : "Equip",
@@ -234,11 +245,11 @@
234 "subscription.features.ads" : "Sense anuncis, mai!", 245 "subscription.features.ads" : "Sense anuncis, mai!",
235 "subscription.features.comingSoon" : "properament", 246 "subscription.features.comingSoon" : "properament",
236 "subscription.features.encryptedSync" : "Sincronització de sessió xifrada", 247 "subscription.features.encryptedSync" : "Sincronització de sessió xifrada",
237 "subscription.features.noInterruptions" : "No app delays & nagging to upgrade license", 248 "subscription.features.noInterruptions" : "Sense retards ni molestes actualitzacions de llicències ",
238 "subscription.features.onpremise" : "Afegiu serveis en premissa\/allotjats com HipChat", 249 "subscription.features.onpremise" : "Afegiu serveis en premissa\/allotjats com HipChat",
239 "subscription.features.onpremise.mattermost" : "Add on-premise\/hosted services like Mattermost", 250 "subscription.features.onpremise.mattermost" : "Afegir serveis allotjats com Mattermost",
240 "subscription.features.proxy" : "Proxy support for services", 251 "subscription.features.proxy" : "Suport de Proxy per a serveis",
241 "subscription.features.spellchecker" : "Support for spellchecker", 252 "subscription.features.spellchecker" : "Suport per corrector ortogràfic",
242 "subscription.includedFeatures" : "El compte de pagament Franz Premium Supporter inclou", 253 "subscription.includedFeatures" : "El compte de pagament Franz Premium Supporter inclou",
243 "subscription.paymentSessionError" : "No s'ha pogut inicialitzar el formulari de pagament", 254 "subscription.paymentSessionError" : "No s'ha pogut inicialitzar el formulari de pagament",
244 "subscription.submit.label" : "Vull donar suport al desenvolupament de Franz", 255 "subscription.submit.label" : "Vull donar suport al desenvolupament de Franz",
diff --git a/src/i18n/locales/cs.json b/src/i18n/locales/cs.json
index eb8088e28..b8bfd2526 100644
--- a/src/i18n/locales/cs.json
+++ b/src/i18n/locales/cs.json
@@ -1,4 +1,6 @@
1{ 1{
2 "app.errorHandler.action" : "Obnovit",
3 "app.errorHandler.headline" : "Something went wrong",
2 "feature.delayApp.action" : "Get a Franz Supporter License", 4 "feature.delayApp.action" : "Get a Franz Supporter License",
3 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting", 5 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting",
4 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.", 6 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.",
@@ -90,6 +92,11 @@
90 "service.crashHandler.text" : "{name} způsobil chybu.", 92 "service.crashHandler.text" : "{name} způsobil chybu.",
91 "service.disabledHandler.action" : "Zapnout {name}", 93 "service.disabledHandler.action" : "Zapnout {name}",
92 "service.disabledHandler.headline" : "{name} je vypnutý", 94 "service.disabledHandler.headline" : "{name} je vypnutý",
95 "service.errorHandler.action" : "Načíst znovu {name}",
96 "service.errorHandler.editAction" : "Upravit {name}",
97 "service.errorHandler.headline" : "Ale ne!",
98 "service.errorHandler.message" : "Chyba",
99 "service.errorHandler.text" : "{name} has failed to load.",
93 "services.getStarted" : "Začněme", 100 "services.getStarted" : "Začněme",
94 "services.welcome" : "Vítejte v programu Franz", 101 "services.welcome" : "Vítejte v programu Franz",
95 "settings.account.account.editButton" : "Upravit účet", 102 "settings.account.account.editButton" : "Upravit účet",
@@ -185,13 +192,17 @@
185 "settings.service.form.indirectMessages" : "Zobrazovat znak zprávy pro všechny nové zprávy", 192 "settings.service.form.indirectMessages" : "Zobrazovat znak zprávy pro všechny nové zprávy",
186 "settings.service.form.isMutedInfo" : "Pokud je vypnuto, všechny zvuky notifikací a jiného audia budou ztišeny", 193 "settings.service.form.isMutedInfo" : "Pokud je vypnuto, všechny zvuky notifikací a jiného audia budou ztišeny",
187 "settings.service.form.name" : "Jméno", 194 "settings.service.form.name" : "Jméno",
188 "settings.service.form.proxy.headline" : "Proxy Settings", 195 "settings.service.form.proxy.headline" : "HTTP\/HTTPS Proxy Settings",
189 "settings.service.form.proxy.host" : "Proxy Host\/IP", 196 "settings.service.form.proxy.host" : "Proxy Host\/IP",
190 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.", 197 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.",
191 "settings.service.form.proxy.isEnabled" : "Use Proxy", 198 "settings.service.form.proxy.isEnabled" : "Use Proxy",
192 "settings.service.form.proxy.password" : "Password (optional)", 199 "settings.service.form.proxy.password" : "Password (optional)",
200 "settings.service.form.proxy.port" : "Port",
201 "settings.service.form.proxy.restartInfo" : "Please restart Franz after changing proxy Settings.",
193 "settings.service.form.proxy.user" : "User (optional)", 202 "settings.service.form.proxy.user" : "User (optional)",
194 "settings.service.form.saveButton" : "Uložit službu", 203 "settings.service.form.saveButton" : "Uložit službu",
204 "settings.service.form.spellcheckerLanguage" : "Spell checking Language",
205 "settings.service.form.spellcheckerLanguage.default" : "Use System Default ({default})",
195 "settings.service.form.tabHosted" : "Hostováno", 206 "settings.service.form.tabHosted" : "Hostováno",
196 "settings.service.form.tabOnPremise" : "Samostatně hostované ⭐️", 207 "settings.service.form.tabOnPremise" : "Samostatně hostované ⭐️",
197 "settings.service.form.team" : "Tým", 208 "settings.service.form.team" : "Tým",
diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json
index 6aa909519..b5abb56d4 100644
--- a/src/i18n/locales/de.json
+++ b/src/i18n/locales/de.json
@@ -1,4 +1,6 @@
1{ 1{
2 "app.errorHandler.action" : "Neuladen",
3 "app.errorHandler.headline" : "Es trat ein Fehler auf",
2 "feature.delayApp.action" : "Upgrade deinen Account", 4 "feature.delayApp.action" : "Upgrade deinen Account",
3 "feature.delayApp.headline" : "Erspare dir das Warten mit einer Franz Supporter Lizenz", 5 "feature.delayApp.headline" : "Erspare dir das Warten mit einer Franz Supporter Lizenz",
4 "feature.delayApp.text" : "Es geht in {seconds} Sekunden weiter.", 6 "feature.delayApp.text" : "Es geht in {seconds} Sekunden weiter.",
@@ -48,7 +50,7 @@
48 "menu.edit.startDictation" : "Diktat starten", 50 "menu.edit.startDictation" : "Diktat starten",
49 "menu.edit.startSpeaking" : "Sprachausgabe starten", 51 "menu.edit.startSpeaking" : "Sprachausgabe starten",
50 "menu.edit.stopSpeaking" : "Sprachausgabe stoppen", 52 "menu.edit.stopSpeaking" : "Sprachausgabe stoppen",
51 "menu.edit.undo" : "Widerrufen", 53 "menu.edit.undo" : "Rückgängig",
52 "menu.file" : "Datei", 54 "menu.file" : "Datei",
53 "menu.help" : "Hilfe", 55 "menu.help" : "Hilfe",
54 "menu.help.changelog" : "Changelog", 56 "menu.help.changelog" : "Changelog",
@@ -90,6 +92,11 @@
90 "service.crashHandler.text" : "{name} hat einen Fehler verursacht.", 92 "service.crashHandler.text" : "{name} hat einen Fehler verursacht.",
91 "service.disabledHandler.action" : "{name} aktivieren", 93 "service.disabledHandler.action" : "{name} aktivieren",
92 "service.disabledHandler.headline" : "{name} ist deaktiviert", 94 "service.disabledHandler.headline" : "{name} ist deaktiviert",
95 "service.errorHandler.action" : "{name} neuladen",
96 "service.errorHandler.editAction" : "{name} bearbeiten",
97 "service.errorHandler.headline" : "Oh nein!",
98 "service.errorHandler.message" : "Fehler",
99 "service.errorHandler.text" : "{name} konnte nicht geladen werden.",
93 "services.getStarted" : "Loslegen", 100 "services.getStarted" : "Loslegen",
94 "services.welcome" : "Willkommen bei Franz.", 101 "services.welcome" : "Willkommen bei Franz.",
95 "settings.account.account.editButton" : "Konto bearbeiten", 102 "settings.account.account.editButton" : "Konto bearbeiten",
@@ -124,10 +131,10 @@
124 "settings.app.form.enableGPUAcceleration" : "Hardwarebeschleunigung aktivieren", 131 "settings.app.form.enableGPUAcceleration" : "Hardwarebeschleunigung aktivieren",
125 "settings.app.form.enableMenuBar" : "Franz in Menüleiste anzeigen", 132 "settings.app.form.enableMenuBar" : "Franz in Menüleiste anzeigen",
126 "settings.app.form.enableSpellchecking" : "Rechtschreibprüfung aktivieren", 133 "settings.app.form.enableSpellchecking" : "Rechtschreibprüfung aktivieren",
127 "settings.app.form.enableSystemTray" : "Franz in der Taskleiste anzeigen", 134 "settings.app.form.enableSystemTray" : "Franz im Infobereich anzeigen",
128 "settings.app.form.hideDockIcon" : "Franz Icon im Dock ausblenden", 135 "settings.app.form.hideDockIcon" : "Franz Icon im Dock ausblenden",
129 "settings.app.form.language" : "Sprache", 136 "settings.app.form.language" : "Sprache",
130 "settings.app.form.minimizeToSystemTray" : "Franz in die Systemleiste minimieren", 137 "settings.app.form.minimizeToSystemTray" : "Franz in den Infobereich minimieren",
131 "settings.app.form.runInBackground" : "Franz im Hintergrund behalten, wenn das Fenster geschlossen wird", 138 "settings.app.form.runInBackground" : "Franz im Hintergrund behalten, wenn das Fenster geschlossen wird",
132 "settings.app.form.showDisabledServices" : "Deaktivierte Services-Tabs anzeigen", 139 "settings.app.form.showDisabledServices" : "Deaktivierte Services-Tabs anzeigen",
133 "settings.app.form.showMessagesBadgesWhenMuted" : "Ungelesene Nachrichten zeigen, wenn die Benachrichtigungen deaktiviert sind", 140 "settings.app.form.showMessagesBadgesWhenMuted" : "Ungelesene Nachrichten zeigen, wenn die Benachrichtigungen deaktiviert sind",
@@ -154,11 +161,11 @@
154 "settings.recipes.all" : "Alle Dienste", 161 "settings.recipes.all" : "Alle Dienste",
155 "settings.recipes.dev" : "Entwicklung", 162 "settings.recipes.dev" : "Entwicklung",
156 "settings.recipes.headline" : "Verfügbare Dienste", 163 "settings.recipes.headline" : "Verfügbare Dienste",
157 "settings.recipes.missingService" : "Fehlt ein Service?", 164 "settings.recipes.missingService" : "Fehlt ein Dienst?",
158 "settings.recipes.mostPopular" : "Am beliebtesten", 165 "settings.recipes.mostPopular" : "Am beliebtesten",
159 "settings.recipes.nothingFound" : "Entschuldigung, aber der von Dir gesuchte Dienst konnte nicht gefunden werden.", 166 "settings.recipes.nothingFound" : "Entschuldigung, aber der von Dir gesuchte Dienst konnte nicht gefunden werden.",
160 "settings.recipes.servicesSuccessfulAddedInfo" : "Dienst erfolgreich hinzugefügt", 167 "settings.recipes.servicesSuccessfulAddedInfo" : "Dienst erfolgreich hinzugefügt",
161 "settings.searchService" : "Service suchen", 168 "settings.searchService" : "Dienst suchen",
162 "settings.service.error.goBack" : "Zurück zu den Diensten", 169 "settings.service.error.goBack" : "Zurück zu den Diensten",
163 "settings.service.error.headline" : "Fehler", 170 "settings.service.error.headline" : "Fehler",
164 "settings.service.error.message" : "Das Dienst-Rezept konnte nicht geladen werden.", 171 "settings.service.error.message" : "Das Dienst-Rezept konnte nicht geladen werden.",
@@ -185,13 +192,17 @@
185 "settings.service.form.indirectMessages" : "Nachrichten-Badge für alle neuen Nachrichten anzeigen", 192 "settings.service.form.indirectMessages" : "Nachrichten-Badge für alle neuen Nachrichten anzeigen",
186 "settings.service.form.isMutedInfo" : "Sämtliche Wiedergabe von Tönen wird deaktiviert", 193 "settings.service.form.isMutedInfo" : "Sämtliche Wiedergabe von Tönen wird deaktiviert",
187 "settings.service.form.name" : "Name", 194 "settings.service.form.name" : "Name",
188 "settings.service.form.proxy.headline" : "Proxy Einstellungen", 195 "settings.service.form.proxy.headline" : "HTTP\/HTTPS Proxy Einstellungen",
189 "settings.service.form.proxy.host" : "Proxy Host\/IP", 196 "settings.service.form.proxy.host" : "Proxy Host\/IP",
190 "settings.service.form.proxy.info" : "Proxy Einstellungen werden nicht mit den Franz Servern synchronisiert.", 197 "settings.service.form.proxy.info" : "Proxy Einstellungen werden nicht mit den Franz Servern synchronisiert.",
191 "settings.service.form.proxy.isEnabled" : "Proxy Server verwenden", 198 "settings.service.form.proxy.isEnabled" : "Proxy Server verwenden",
192 "settings.service.form.proxy.password" : "Passwort (optional)", 199 "settings.service.form.proxy.password" : "Passwort (optional)",
200 "settings.service.form.proxy.port" : "Port",
201 "settings.service.form.proxy.restartInfo" : "Bitte starte Franz nach dem Ändern der Proxy Einstellungen neu.",
193 "settings.service.form.proxy.user" : "Benutzer (optional)", 202 "settings.service.form.proxy.user" : "Benutzer (optional)",
194 "settings.service.form.saveButton" : "Dienst speichern", 203 "settings.service.form.saveButton" : "Dienst speichern",
204 "settings.service.form.spellcheckerLanguage" : "Sprache für Rechtschreibprüfung",
205 "settings.service.form.spellcheckerLanguage.default" : "Standard benutzen ({default})",
195 "settings.service.form.tabHosted" : "Gehostet", 206 "settings.service.form.tabHosted" : "Gehostet",
196 "settings.service.form.tabOnPremise" : "Selbst gehostet ⭐️", 207 "settings.service.form.tabOnPremise" : "Selbst gehostet ⭐️",
197 "settings.service.form.team" : "Team", 208 "settings.service.form.team" : "Team",
@@ -238,7 +249,7 @@
238 "subscription.features.onpremise" : "Integration von gehosteten Diensten, wie HipChat", 249 "subscription.features.onpremise" : "Integration von gehosteten Diensten, wie HipChat",
239 "subscription.features.onpremise.mattermost" : "Integration von gehosteten Diensten, wie Mattermost", 250 "subscription.features.onpremise.mattermost" : "Integration von gehosteten Diensten, wie Mattermost",
240 "subscription.features.proxy" : "Proxy Support für Dienste", 251 "subscription.features.proxy" : "Proxy Support für Dienste",
241 "subscription.features.spellchecker" : "Support for spellchecker", 252 "subscription.features.spellchecker" : "Unterstützung für Rechtschreibprüfung",
242 "subscription.includedFeatures" : "Bezahlte Franz Premium Support Konten beinhalten", 253 "subscription.includedFeatures" : "Bezahlte Franz Premium Support Konten beinhalten",
243 "subscription.paymentSessionError" : "Das Zahlungs-Formular konnte nicht geladen werden.", 254 "subscription.paymentSessionError" : "Das Zahlungs-Formular konnte nicht geladen werden.",
244 "subscription.submit.label" : "Ich möchte die Entwicklung von Franz unterstützen", 255 "subscription.submit.label" : "Ich möchte die Entwicklung von Franz unterstützen",
diff --git a/src/i18n/locales/el.json b/src/i18n/locales/el.json
index 31852d20c..671eecd41 100644
--- a/src/i18n/locales/el.json
+++ b/src/i18n/locales/el.json
@@ -1,4 +1,6 @@
1{ 1{
2 "app.errorHandler.action" : "Επαναφόρτωση",
3 "app.errorHandler.headline" : "Something went wrong",
2 "feature.delayApp.action" : "Get a Franz Supporter License", 4 "feature.delayApp.action" : "Get a Franz Supporter License",
3 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting", 5 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting",
4 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.", 6 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.",
@@ -90,6 +92,11 @@
90 "service.crashHandler.text" : "Το {name} προκάλεσε ένα σφάλμα.", 92 "service.crashHandler.text" : "Το {name} προκάλεσε ένα σφάλμα.",
91 "service.disabledHandler.action" : "Ενεργοποίηση {name}", 93 "service.disabledHandler.action" : "Ενεργοποίηση {name}",
92 "service.disabledHandler.headline" : "{name} είναι απενεργοποιημένο", 94 "service.disabledHandler.headline" : "{name} είναι απενεργοποιημένο",
95 "service.errorHandler.action" : "Επαναφόρτωση {name}",
96 "service.errorHandler.editAction" : "Επεξεργασία {name}",
97 "service.errorHandler.headline" : "Ω, όχι!",
98 "service.errorHandler.message" : "Σφάλμα",
99 "service.errorHandler.text" : "{name} has failed to load.",
93 "services.getStarted" : "Ξεκινήστε", 100 "services.getStarted" : "Ξεκινήστε",
94 "services.welcome" : "Καλώς ορίσατε στον Franz", 101 "services.welcome" : "Καλώς ορίσατε στον Franz",
95 "settings.account.account.editButton" : "Επεξεργασία λογαριασμού", 102 "settings.account.account.editButton" : "Επεξεργασία λογαριασμού",
@@ -185,13 +192,17 @@
185 "settings.service.form.indirectMessages" : "Εμφάνιση ειδοποίησης μηνύματος για όλα τα νέα μηνύματα", 192 "settings.service.form.indirectMessages" : "Εμφάνιση ειδοποίησης μηνύματος για όλα τα νέα μηνύματα",
186 "settings.service.form.isMutedInfo" : "Όλοι οι ήχοι καθώς και η αναπαραγωγή ήχου θα απενεργοποιηθούν", 193 "settings.service.form.isMutedInfo" : "Όλοι οι ήχοι καθώς και η αναπαραγωγή ήχου θα απενεργοποιηθούν",
187 "settings.service.form.name" : "Όνομα", 194 "settings.service.form.name" : "Όνομα",
188 "settings.service.form.proxy.headline" : "Proxy Settings", 195 "settings.service.form.proxy.headline" : "HTTP\/HTTPS Proxy Settings",
189 "settings.service.form.proxy.host" : "Proxy Host\/IP", 196 "settings.service.form.proxy.host" : "Proxy Host\/IP",
190 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.", 197 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.",
191 "settings.service.form.proxy.isEnabled" : "Use Proxy", 198 "settings.service.form.proxy.isEnabled" : "Use Proxy",
192 "settings.service.form.proxy.password" : "Password (optional)", 199 "settings.service.form.proxy.password" : "Password (optional)",
200 "settings.service.form.proxy.port" : "Port",
201 "settings.service.form.proxy.restartInfo" : "Please restart Franz after changing proxy Settings.",
193 "settings.service.form.proxy.user" : "User (optional)", 202 "settings.service.form.proxy.user" : "User (optional)",
194 "settings.service.form.saveButton" : "Αποθήκευση υπηρεσίας", 203 "settings.service.form.saveButton" : "Αποθήκευση υπηρεσίας",
204 "settings.service.form.spellcheckerLanguage" : "Spell checking Language",
205 "settings.service.form.spellcheckerLanguage.default" : "Use System Default ({default})",
195 "settings.service.form.tabHosted" : "Φιλοξενείται", 206 "settings.service.form.tabHosted" : "Φιλοξενείται",
196 "settings.service.form.tabOnPremise" : "Αυτο-φιλοξενείται ⭐️", 207 "settings.service.form.tabOnPremise" : "Αυτο-φιλοξενείται ⭐️",
197 "settings.service.form.team" : "Ομάδα", 208 "settings.service.form.team" : "Ομάδα",
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json
index aa55f5dfe..99df6a0ca 100644
--- a/src/i18n/locales/en-US.json
+++ b/src/i18n/locales/en-US.json
@@ -133,12 +133,16 @@
133 "settings.service.form.iconDelete": "Delete", 133 "settings.service.form.iconDelete": "Delete",
134 "settings.service.form.iconUpload": "Drop your image, or click here", 134 "settings.service.form.iconUpload": "Drop your image, or click here",
135 "settings.service.form.enableDarkMode": "Enable Dark Mode", 135 "settings.service.form.enableDarkMode": "Enable Dark Mode",
136 "settings.service.form.proxy.headline": "Proxy Settings", 136 "settings.service.form.proxy.headline": "HTTP/HTTPS Proxy Settings",
137 "settings.service.form.proxy.isEnabled": "Use Proxy", 137 "settings.service.form.proxy.isEnabled": "Use Proxy",
138 "settings.service.form.proxy.host": "Proxy Host/IP", 138 "settings.service.form.proxy.host": "Proxy Host/IP",
139 "settings.service.form.proxy.port": "Port",
139 "settings.service.form.proxy.user": "User (optional)", 140 "settings.service.form.proxy.user": "User (optional)",
140 "settings.service.form.proxy.password": "Password (optional)", 141 "settings.service.form.proxy.password": "Password (optional)",
141 "settings.service.form.proxy.info": "Proxy settings will not synced with the Franz servers.", 142 "settings.service.form.proxy.info": "Proxy settings will not synced with the Franz servers.",
143 "settings.service.form.proxy.restartInfo": "Please restart Franz after changing proxy Settings.",
144 "settings.service.form.spellcheckerLanguage": "Spell checking Language",
145 "settings.service.form.spellcheckerLanguage.default": "Use System Default ({default})",
142 "settings.service.error.headline": "Error", 146 "settings.service.error.headline": "Error",
143 "settings.service.error.goBack": "Back to services", 147 "settings.service.error.goBack": "Back to services",
144 "settings.service.error.message": "Could not load service recipe.", 148 "settings.service.error.message": "Could not load service recipe.",
@@ -210,6 +214,11 @@
210 "service.crashHandler.text": "{name} has caused an error.", 214 "service.crashHandler.text": "{name} has caused an error.",
211 "service.crashHandler.action": "Reload {name}", 215 "service.crashHandler.action": "Reload {name}",
212 "service.crashHandler.autoReload": "Trying to automatically restore {name} in {seconds} seconds", 216 "service.crashHandler.autoReload": "Trying to automatically restore {name} in {seconds} seconds",
217 "service.errorHandler.headline": "Oh no!",
218 "service.errorHandler.text": "{name} has failed to load.",
219 "service.errorHandler.message": "Error",
220 "service.errorHandler.action": "Reload {name}",
221 "service.errorHandler.editAction": "Edit {name}",
213 "service.disabledHandler.headline": "{name} is disabled", 222 "service.disabledHandler.headline": "{name} is disabled",
214 "service.disabledHandler.action": "Enable {name}", 223 "service.disabledHandler.action": "Enable {name}",
215 "menu.edit": "Edit", 224 "menu.edit": "Edit",
@@ -262,5 +271,7 @@
262 "feature.delayApp.headline": "Please purchase a Franz Supporter License to skip waiting", 271 "feature.delayApp.headline": "Please purchase a Franz Supporter License to skip waiting",
263 "feature.delayApp.action": "Get a Franz Supporter License", 272 "feature.delayApp.action": "Get a Franz Supporter License",
264 "feature.delayApp.text": "Franz will continue in {seconds} seconds.", 273 "feature.delayApp.text": "Franz will continue in {seconds} seconds.",
265 "premiumFeature.button.upgradeAccount": "Upgrade account" 274 "premiumFeature.button.upgradeAccount": "Upgrade account",
275 "app.errorHandler.headline": "Something went wrong",
276 "app.errorHandler.action": "Reload"
266} 277}
diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json
index a5984dd1c..669b4d2dd 100644
--- a/src/i18n/locales/es.json
+++ b/src/i18n/locales/es.json
@@ -1,7 +1,9 @@
1{ 1{
2 "feature.delayApp.action" : "Get a Franz Supporter License", 2 "app.errorHandler.action" : "Recargar",
3 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting", 3 "app.errorHandler.headline" : "Something went wrong",
4 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.", 4 "feature.delayApp.action" : "Consigue una Licencia de Soporte de Franz",
5 "feature.delayApp.headline" : "Por favor, compra una Licencia de Soporte de Franz para saltar la espera",
6 "feature.delayApp.text" : "Franz continuará en {seconds} segundos.",
5 "global.api.unhealthy" : "No es posible conectarse a los servicios en línea de Franz.", 7 "global.api.unhealthy" : "No es posible conectarse a los servicios en línea de Franz.",
6 "global.notConnectedToTheInternet" : "No estás conectado a Internet", 8 "global.notConnectedToTheInternet" : "No estás conectado a Internet",
7 "import.headline" : "Importa tus servicios de Franz 4", 9 "import.headline" : "Importa tus servicios de Franz 4",
@@ -79,7 +81,7 @@
79 "password.noUser" : "No se encontró un usuario con esa dirección de correo electrónico", 81 "password.noUser" : "No se encontró un usuario con esa dirección de correo electrónico",
80 "password.submit.label" : "Enviar", 82 "password.submit.label" : "Enviar",
81 "password.successInfo" : "Por favor revisa tu correo electrónico", 83 "password.successInfo" : "Por favor revisa tu correo electrónico",
82 "premiumFeature.button.upgradeAccount" : "Upgrade account", 84 "premiumFeature.button.upgradeAccount" : "Mejora tu cuenta",
83 "pricing.headline" : "Apoya a Franz", 85 "pricing.headline" : "Apoya a Franz",
84 "pricing.link.skipPayment" : "No quiero apoyar el desarrollo de Franz.", 86 "pricing.link.skipPayment" : "No quiero apoyar el desarrollo de Franz.",
85 "pricing.submit.label" : "Quiero apoyar el desarrollo de Franz", 87 "pricing.submit.label" : "Quiero apoyar el desarrollo de Franz",
@@ -90,6 +92,11 @@
90 "service.crashHandler.text" : "{name} ha causado un error.", 92 "service.crashHandler.text" : "{name} ha causado un error.",
91 "service.disabledHandler.action" : "Activar {name}", 93 "service.disabledHandler.action" : "Activar {name}",
92 "service.disabledHandler.headline" : "{name} está desactivado", 94 "service.disabledHandler.headline" : "{name} está desactivado",
95 "service.errorHandler.action" : "Recargar {name}",
96 "service.errorHandler.editAction" : "Editar {name}",
97 "service.errorHandler.headline" : "¡Oh, no!",
98 "service.errorHandler.message" : "Error",
99 "service.errorHandler.text" : "{name} has failed to load.",
93 "services.getStarted" : "Primeros pasos", 100 "services.getStarted" : "Primeros pasos",
94 "services.welcome" : "Bienvenido a Franz", 101 "services.welcome" : "Bienvenido a Franz",
95 "settings.account.account.editButton" : "Editar cuenta", 102 "settings.account.account.editButton" : "Editar cuenta",
@@ -120,7 +127,7 @@
120 "settings.app.form.autoLaunchInBackground" : "Abrir en segundo plano", 127 "settings.app.form.autoLaunchInBackground" : "Abrir en segundo plano",
121 "settings.app.form.autoLaunchOnStart" : "Iniciar Franz al iniciar", 128 "settings.app.form.autoLaunchOnStart" : "Iniciar Franz al iniciar",
122 "settings.app.form.beta" : "Incluir versiones beta", 129 "settings.app.form.beta" : "Incluir versiones beta",
123 "settings.app.form.darkMode" : "Join the Dark Side", 130 "settings.app.form.darkMode" : "Únete al Lado Oscuro",
124 "settings.app.form.enableGPUAcceleration" : "Habilitar aceleración de GPU", 131 "settings.app.form.enableGPUAcceleration" : "Habilitar aceleración de GPU",
125 "settings.app.form.enableMenuBar" : "Mostrar a Franz en la barra de menús", 132 "settings.app.form.enableMenuBar" : "Mostrar a Franz en la barra de menús",
126 "settings.app.form.enableSpellchecking" : "Activar corrección ortográfica", 133 "settings.app.form.enableSpellchecking" : "Activar corrección ortográfica",
@@ -131,7 +138,7 @@
131 "settings.app.form.runInBackground" : "Mantener Franz en segundo plano al cerrar la ventana", 138 "settings.app.form.runInBackground" : "Mantener Franz en segundo plano al cerrar la ventana",
132 "settings.app.form.showDisabledServices" : "Mostrar pestañas de servicios desactivados", 139 "settings.app.form.showDisabledServices" : "Mostrar pestañas de servicios desactivados",
133 "settings.app.form.showMessagesBadgesWhenMuted" : "Mostrar la insignia de mensajes sin leer cuando las notificaciones están desactivadas", 140 "settings.app.form.showMessagesBadgesWhenMuted" : "Mostrar la insignia de mensajes sin leer cuando las notificaciones están desactivadas",
134 "settings.app.form.spellcheckerLanguage" : "Spell checking language", 141 "settings.app.form.spellcheckerLanguage" : "Corrector de ortografía",
135 "settings.app.headline" : "Configuración", 142 "settings.app.headline" : "Configuración",
136 "settings.app.headlineAdvanced" : "Avanzado", 143 "settings.app.headlineAdvanced" : "Avanzado",
137 "settings.app.headlineAppearance" : "Apariencia", 144 "settings.app.headlineAppearance" : "Apariencia",
@@ -172,7 +179,7 @@
172 "settings.service.form.editServiceHeadline" : "Editar {name}", 179 "settings.service.form.editServiceHeadline" : "Editar {name}",
173 "settings.service.form.enableAudio" : "Habilitar audio", 180 "settings.service.form.enableAudio" : "Habilitar audio",
174 "settings.service.form.enableBadge" : "Mostrar señal de mensajes no leídos", 181 "settings.service.form.enableBadge" : "Mostrar señal de mensajes no leídos",
175 "settings.service.form.enableDarkMode" : "Enable Dark Mode", 182 "settings.service.form.enableDarkMode" : "Habilitar modo oscuro",
176 "settings.service.form.enableNotification" : "Activar notificaciones", 183 "settings.service.form.enableNotification" : "Activar notificaciones",
177 "settings.service.form.enableService" : "Activar servicio", 184 "settings.service.form.enableService" : "Activar servicio",
178 "settings.service.form.headlineBadges" : "Insignias de mensaje no leídos", 185 "settings.service.form.headlineBadges" : "Insignias de mensaje no leídos",
@@ -185,13 +192,17 @@
185 "settings.service.form.indirectMessages" : "Mostrar señal de notificación para todos los mensajes nuevos", 192 "settings.service.form.indirectMessages" : "Mostrar señal de notificación para todos los mensajes nuevos",
186 "settings.service.form.isMutedInfo" : "Cuando estén desactivados, todos los sonidos de notificación y la reproducción de audio serán silenciados", 193 "settings.service.form.isMutedInfo" : "Cuando estén desactivados, todos los sonidos de notificación y la reproducción de audio serán silenciados",
187 "settings.service.form.name" : "Nombre", 194 "settings.service.form.name" : "Nombre",
188 "settings.service.form.proxy.headline" : "Proxy Settings", 195 "settings.service.form.proxy.headline" : "HTTP\/HTTPS Proxy Settings",
189 "settings.service.form.proxy.host" : "Proxy Host\/IP", 196 "settings.service.form.proxy.host" : "Proxy Host\/IP",
190 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.", 197 "settings.service.form.proxy.info" : "Las configuraciones de Proxy no se sincronizarán con los servidores de Franz.",
191 "settings.service.form.proxy.isEnabled" : "Use Proxy", 198 "settings.service.form.proxy.isEnabled" : "Use Proxy",
192 "settings.service.form.proxy.password" : "Password (optional)", 199 "settings.service.form.proxy.password" : "Contraseña (opcional)",
193 "settings.service.form.proxy.user" : "User (optional)", 200 "settings.service.form.proxy.port" : "Port",
201 "settings.service.form.proxy.restartInfo" : "Please restart Franz after changing proxy Settings.",
202 "settings.service.form.proxy.user" : "Usuario (opcional)",
194 "settings.service.form.saveButton" : "Guardar servicio", 203 "settings.service.form.saveButton" : "Guardar servicio",
204 "settings.service.form.spellcheckerLanguage" : "Spell checking Language",
205 "settings.service.form.spellcheckerLanguage.default" : "Use System Default ({default})",
195 "settings.service.form.tabHosted" : "Alojado", 206 "settings.service.form.tabHosted" : "Alojado",
196 "settings.service.form.tabOnPremise" : "Auto alojado ⭐️", 207 "settings.service.form.tabOnPremise" : "Auto alojado ⭐️",
197 "settings.service.form.team" : "Equipo", 208 "settings.service.form.team" : "Equipo",
@@ -234,11 +245,11 @@
234 "subscription.features.ads" : "¡Sin publicidad, para siempre!", 245 "subscription.features.ads" : "¡Sin publicidad, para siempre!",
235 "subscription.features.comingSoon" : "próximamente", 246 "subscription.features.comingSoon" : "próximamente",
236 "subscription.features.encryptedSync" : "Sincronización de sesión encriptada", 247 "subscription.features.encryptedSync" : "Sincronización de sesión encriptada",
237 "subscription.features.noInterruptions" : "No app delays & nagging to upgrade license", 248 "subscription.features.noInterruptions" : "Sin retrasos en la app ni molestas actualizaciones de licencias",
238 "subscription.features.onpremise" : "Añade servicios locales\/autoalojados como HipChat", 249 "subscription.features.onpremise" : "Añade servicios locales\/autoalojados como HipChat",
239 "subscription.features.onpremise.mattermost" : "Add on-premise\/hosted services like Mattermost", 250 "subscription.features.onpremise.mattermost" : "Agregar servicios de almacenamiento como Mattermost",
240 "subscription.features.proxy" : "Proxy support for services", 251 "subscription.features.proxy" : "Soporte Proxy para servicios",
241 "subscription.features.spellchecker" : "Support for spellchecker", 252 "subscription.features.spellchecker" : "Soporte para corrector de ortografía",
242 "subscription.includedFeatures" : "La Cuenta pagada de Colaborador Premium de Franz incluye", 253 "subscription.includedFeatures" : "La Cuenta pagada de Colaborador Premium de Franz incluye",
243 "subscription.paymentSessionError" : "No se pudo inicializar el formulario de pago", 254 "subscription.paymentSessionError" : "No se pudo inicializar el formulario de pago",
244 "subscription.submit.label" : "Quiero apoyar el desarrollo de Franz", 255 "subscription.submit.label" : "Quiero apoyar el desarrollo de Franz",
diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json
index 6964d27d2..a83767f1e 100644
--- a/src/i18n/locales/fr.json
+++ b/src/i18n/locales/fr.json
@@ -1,7 +1,9 @@
1{ 1{
2 "feature.delayApp.action" : "Get a Franz Supporter License", 2 "app.errorHandler.action" : "Recharger",
3 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting", 3 "app.errorHandler.headline" : "Une erreur s'est produite.",
4 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.", 4 "feature.delayApp.action" : "Obtenez une licence de Supporter Franz",
5 "feature.delayApp.headline" : "Veuillez acheter une licence de Supporter Franz pour sauter le temps d'attente",
6 "feature.delayApp.text" : "Franz continuera dans {seconds} secondes.",
5 "global.api.unhealthy" : "Impossible de se connecter aux services en ligne de Franz", 7 "global.api.unhealthy" : "Impossible de se connecter aux services en ligne de Franz",
6 "global.notConnectedToTheInternet" : "Vous n'êtes pas connecté à Internet.", 8 "global.notConnectedToTheInternet" : "Vous n'êtes pas connecté à Internet.",
7 "import.headline" : "Importez vos services depuis la version 4 de Franz.", 9 "import.headline" : "Importez vos services depuis la version 4 de Franz.",
@@ -79,7 +81,7 @@
79 "password.noUser" : "Aucun utilisateur n'a été trouvé avec cette adresse e-mail", 81 "password.noUser" : "Aucun utilisateur n'a été trouvé avec cette adresse e-mail",
80 "password.submit.label" : "Soumettre", 82 "password.submit.label" : "Soumettre",
81 "password.successInfo" : "Merci de consulter vos e-mails", 83 "password.successInfo" : "Merci de consulter vos e-mails",
82 "premiumFeature.button.upgradeAccount" : "Upgrade account", 84 "premiumFeature.button.upgradeAccount" : "Mettre à niveau mon compte",
83 "pricing.headline" : "Soutenez Franz", 85 "pricing.headline" : "Soutenez Franz",
84 "pricing.link.skipPayment" : "Je ne veux pas soutenir le développement de Franz.", 86 "pricing.link.skipPayment" : "Je ne veux pas soutenir le développement de Franz.",
85 "pricing.submit.label" : "Je veux soutenir le développement de Franz", 87 "pricing.submit.label" : "Je veux soutenir le développement de Franz",
@@ -90,6 +92,11 @@
90 "service.crashHandler.text" : "{name} a causé une erreur.", 92 "service.crashHandler.text" : "{name} a causé une erreur.",
91 "service.disabledHandler.action" : "Activer {name}", 93 "service.disabledHandler.action" : "Activer {name}",
92 "service.disabledHandler.headline" : "{name} est désactivé", 94 "service.disabledHandler.headline" : "{name} est désactivé",
95 "service.errorHandler.action" : "Recharger {name}",
96 "service.errorHandler.editAction" : "Modifier {name}",
97 "service.errorHandler.headline" : "Oh non !",
98 "service.errorHandler.message" : "Erreur",
99 "service.errorHandler.text" : "Le chargement de {name} a échoué.",
93 "services.getStarted" : "Commencer", 100 "services.getStarted" : "Commencer",
94 "services.welcome" : "Bienvenue dans Franz", 101 "services.welcome" : "Bienvenue dans Franz",
95 "settings.account.account.editButton" : "Modifier le compte", 102 "settings.account.account.editButton" : "Modifier le compte",
@@ -120,7 +127,7 @@
120 "settings.app.form.autoLaunchInBackground" : "Ouvrir en arrière-plan", 127 "settings.app.form.autoLaunchInBackground" : "Ouvrir en arrière-plan",
121 "settings.app.form.autoLaunchOnStart" : "Lancer Franz au démarrage", 128 "settings.app.form.autoLaunchOnStart" : "Lancer Franz au démarrage",
122 "settings.app.form.beta" : "Accepter les versions bêta", 129 "settings.app.form.beta" : "Accepter les versions bêta",
123 "settings.app.form.darkMode" : "Join the Dark Side", 130 "settings.app.form.darkMode" : "Rejoins le côté obscur",
124 "settings.app.form.enableGPUAcceleration" : "Activer l'accélération GPU", 131 "settings.app.form.enableGPUAcceleration" : "Activer l'accélération GPU",
125 "settings.app.form.enableMenuBar" : "Afficher Franz dans la barre des menus", 132 "settings.app.form.enableMenuBar" : "Afficher Franz dans la barre des menus",
126 "settings.app.form.enableSpellchecking" : "Activer la vérification orthographique", 133 "settings.app.form.enableSpellchecking" : "Activer la vérification orthographique",
@@ -131,7 +138,7 @@
131 "settings.app.form.runInBackground" : "Garder Franz ouvert en arrière-plan à la fermeture de la fenêtre", 138 "settings.app.form.runInBackground" : "Garder Franz ouvert en arrière-plan à la fermeture de la fenêtre",
132 "settings.app.form.showDisabledServices" : "Afficher les onglets des services désactivés", 139 "settings.app.form.showDisabledServices" : "Afficher les onglets des services désactivés",
133 "settings.app.form.showMessagesBadgesWhenMuted" : "Afficher les badges de messages non lus quand les notifications sont désactivées.", 140 "settings.app.form.showMessagesBadgesWhenMuted" : "Afficher les badges de messages non lus quand les notifications sont désactivées.",
134 "settings.app.form.spellcheckerLanguage" : "Spell checking language", 141 "settings.app.form.spellcheckerLanguage" : "Langue de la vérification orthographique",
135 "settings.app.headline" : "Paramètres", 142 "settings.app.headline" : "Paramètres",
136 "settings.app.headlineAdvanced" : "Paramètres avancés", 143 "settings.app.headlineAdvanced" : "Paramètres avancés",
137 "settings.app.headlineAppearance" : "Apparence", 144 "settings.app.headlineAppearance" : "Apparence",
@@ -172,7 +179,7 @@
172 "settings.service.form.editServiceHeadline" : "Modifier {name}", 179 "settings.service.form.editServiceHeadline" : "Modifier {name}",
173 "settings.service.form.enableAudio" : "Activer l'audio", 180 "settings.service.form.enableAudio" : "Activer l'audio",
174 "settings.service.form.enableBadge" : "Afficher le badge des messages non lus", 181 "settings.service.form.enableBadge" : "Afficher le badge des messages non lus",
175 "settings.service.form.enableDarkMode" : "Enable Dark Mode", 182 "settings.service.form.enableDarkMode" : "Activer le mode sombre",
176 "settings.service.form.enableNotification" : "Activer les notifications", 183 "settings.service.form.enableNotification" : "Activer les notifications",
177 "settings.service.form.enableService" : "Activer le service", 184 "settings.service.form.enableService" : "Activer le service",
178 "settings.service.form.headlineBadges" : "Badge des messages non lus", 185 "settings.service.form.headlineBadges" : "Badge des messages non lus",
@@ -185,13 +192,17 @@
185 "settings.service.form.indirectMessages" : "Afficher le badge des messages pour tous les nouveaux messages", 192 "settings.service.form.indirectMessages" : "Afficher le badge des messages pour tous les nouveaux messages",
186 "settings.service.form.isMutedInfo" : "Lorsque désactivé, tous les sons de notifications ainsi que l'audio sont coupés", 193 "settings.service.form.isMutedInfo" : "Lorsque désactivé, tous les sons de notifications ainsi que l'audio sont coupés",
187 "settings.service.form.name" : "Nom", 194 "settings.service.form.name" : "Nom",
188 "settings.service.form.proxy.headline" : "Proxy Settings", 195 "settings.service.form.proxy.headline" : "Paramètres Proxy HTTP\/HTTPS",
189 "settings.service.form.proxy.host" : "Proxy Host\/IP", 196 "settings.service.form.proxy.host" : "Hôte\/IP du proxy",
190 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.", 197 "settings.service.form.proxy.info" : "Les paramètres de proxy ne seront pas synchronisés avec les serveurs de Franz.",
191 "settings.service.form.proxy.isEnabled" : "Use Proxy", 198 "settings.service.form.proxy.isEnabled" : "Utiliser un proxy",
192 "settings.service.form.proxy.password" : "Password (optional)", 199 "settings.service.form.proxy.password" : "Mot de passe (facultatif)",
193 "settings.service.form.proxy.user" : "User (optional)", 200 "settings.service.form.proxy.port" : "Port",
201 "settings.service.form.proxy.restartInfo" : "Veuillez redémarrer Franz après avoir modifier les paramètres proxy.",
202 "settings.service.form.proxy.user" : "Utilisateur (facultatif)",
194 "settings.service.form.saveButton" : "Enregistrer le service", 203 "settings.service.form.saveButton" : "Enregistrer le service",
204 "settings.service.form.spellcheckerLanguage" : "Veuillez vérifier l'épellation Langage",
205 "settings.service.form.spellcheckerLanguage.default" : "Par défaut ({default})",
195 "settings.service.form.tabHosted" : "Hébergé", 206 "settings.service.form.tabHosted" : "Hébergé",
196 "settings.service.form.tabOnPremise" : "Auto-hébergé ⭐️", 207 "settings.service.form.tabOnPremise" : "Auto-hébergé ⭐️",
197 "settings.service.form.team" : "Équipe", 208 "settings.service.form.team" : "Équipe",
@@ -234,11 +245,11 @@
234 "subscription.features.ads" : "Plus de pubs !", 245 "subscription.features.ads" : "Plus de pubs !",
235 "subscription.features.comingSoon" : "Bientôt disponible", 246 "subscription.features.comingSoon" : "Bientôt disponible",
236 "subscription.features.encryptedSync" : "Synchronisation de session cryptée", 247 "subscription.features.encryptedSync" : "Synchronisation de session cryptée",
237 "subscription.features.noInterruptions" : "No app delays & nagging to upgrade license", 248 "subscription.features.noInterruptions" : "Aucun délai dans l'application ni de harcèlement pour mettre à niveau la licence",
238 "subscription.features.onpremise" : "Ajouter des services locaux\/hébergés comme HipChat", 249 "subscription.features.onpremise" : "Ajouter des services locaux\/hébergés comme HipChat",
239 "subscription.features.onpremise.mattermost" : "Add on-premise\/hosted services like Mattermost", 250 "subscription.features.onpremise.mattermost" : "Ajouter des services auto-hébergés comme Mattermost",
240 "subscription.features.proxy" : "Proxy support for services", 251 "subscription.features.proxy" : "Support proxy pour les services",
241 "subscription.features.spellchecker" : "Support for spellchecker", 252 "subscription.features.spellchecker" : "Prise en charge du correcteur orthographique",
242 "subscription.includedFeatures" : "Le compte payant Supporter Premium Franz inclut", 253 "subscription.includedFeatures" : "Le compte payant Supporter Premium Franz inclut",
243 "subscription.paymentSessionError" : "Initialisation du paiement impossible", 254 "subscription.paymentSessionError" : "Initialisation du paiement impossible",
244 "subscription.submit.label" : "Je souhaite aider au développement de Franz", 255 "subscription.submit.label" : "Je souhaite aider au développement de Franz",
diff --git a/src/i18n/locales/ga.json b/src/i18n/locales/ga.json
index 3bb838ba3..0d3d8623e 100644
--- a/src/i18n/locales/ga.json
+++ b/src/i18n/locales/ga.json
@@ -1,4 +1,6 @@
1{ 1{
2 "app.errorHandler.action" : "Athlódáil",
3 "app.errorHandler.headline" : "Something went wrong",
2 "feature.delayApp.action" : "Get a Franz Supporter License", 4 "feature.delayApp.action" : "Get a Franz Supporter License",
3 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting", 5 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting",
4 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.", 6 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.",
@@ -90,6 +92,11 @@
90 "service.crashHandler.text" : "Tá {name} tar éis earráid a dhéanamh.", 92 "service.crashHandler.text" : "Tá {name} tar éis earráid a dhéanamh.",
91 "service.disabledHandler.action" : "Cumasaigh {name}", 93 "service.disabledHandler.action" : "Cumasaigh {name}",
92 "service.disabledHandler.headline" : "Tá {name} díchumasaithe", 94 "service.disabledHandler.headline" : "Tá {name} díchumasaithe",
95 "service.errorHandler.action" : "Athlódáil {name}",
96 "service.errorHandler.editAction" : "Cuir {name} in eagar",
97 "service.errorHandler.headline" : "Oró, ní hea!",
98 "service.errorHandler.message" : "Earráid",
99 "service.errorHandler.text" : "{name} has failed to load.",
93 "services.getStarted" : "Cuir tús", 100 "services.getStarted" : "Cuir tús",
94 "services.welcome" : "Fáilte go Franz", 101 "services.welcome" : "Fáilte go Franz",
95 "settings.account.account.editButton" : "Cuir cuntas in eagar", 102 "settings.account.account.editButton" : "Cuir cuntas in eagar",
@@ -185,13 +192,17 @@
185 "settings.service.form.indirectMessages" : "Taispeáin comhartha theachtaireachta do gach teachtaireacht nua", 192 "settings.service.form.indirectMessages" : "Taispeáin comhartha theachtaireachta do gach teachtaireacht nua",
186 "settings.service.form.isMutedInfo" : "Tachtar gach fuaim fógraí agus athchasadh fuaime", 193 "settings.service.form.isMutedInfo" : "Tachtar gach fuaim fógraí agus athchasadh fuaime",
187 "settings.service.form.name" : "Ainm", 194 "settings.service.form.name" : "Ainm",
188 "settings.service.form.proxy.headline" : "Proxy Settings", 195 "settings.service.form.proxy.headline" : "HTTP\/HTTPS Proxy Settings",
189 "settings.service.form.proxy.host" : "Proxy Host\/IP", 196 "settings.service.form.proxy.host" : "Proxy Host\/IP",
190 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.", 197 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.",
191 "settings.service.form.proxy.isEnabled" : "Use Proxy", 198 "settings.service.form.proxy.isEnabled" : "Use Proxy",
192 "settings.service.form.proxy.password" : "Password (optional)", 199 "settings.service.form.proxy.password" : "Password (optional)",
200 "settings.service.form.proxy.port" : "Port",
201 "settings.service.form.proxy.restartInfo" : "Please restart Franz after changing proxy Settings.",
193 "settings.service.form.proxy.user" : "User (optional)", 202 "settings.service.form.proxy.user" : "User (optional)",
194 "settings.service.form.saveButton" : "Sábháil seirbhís", 203 "settings.service.form.saveButton" : "Sábháil seirbhís",
204 "settings.service.form.spellcheckerLanguage" : "Spell checking Language",
205 "settings.service.form.spellcheckerLanguage.default" : "Use System Default ({default})",
195 "settings.service.form.tabHosted" : "Óstáilte", 206 "settings.service.form.tabHosted" : "Óstáilte",
196 "settings.service.form.tabOnPremise" : "Féinóstáilte ⭐️", 207 "settings.service.form.tabOnPremise" : "Féinóstáilte ⭐️",
197 "settings.service.form.team" : "Foireann", 208 "settings.service.form.team" : "Foireann",
diff --git a/src/i18n/locales/hr.json b/src/i18n/locales/hr.json
index ae9f25695..2ff69755d 100644
--- a/src/i18n/locales/hr.json
+++ b/src/i18n/locales/hr.json
@@ -1,4 +1,6 @@
1{ 1{
2 "app.errorHandler.action" : "Ponovno učitavanje",
3 "app.errorHandler.headline" : "Something went wrong",
2 "feature.delayApp.action" : "Get a Franz Supporter License", 4 "feature.delayApp.action" : "Get a Franz Supporter License",
3 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting", 5 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting",
4 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.", 6 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.",
@@ -90,6 +92,11 @@
90 "service.crashHandler.text" : "{name} je izazvalo grešku. ", 92 "service.crashHandler.text" : "{name} je izazvalo grešku. ",
91 "service.disabledHandler.action" : "Omogući {name}", 93 "service.disabledHandler.action" : "Omogući {name}",
92 "service.disabledHandler.headline" : "{name} je onemogućen\/o", 94 "service.disabledHandler.headline" : "{name} je onemogućen\/o",
95 "service.errorHandler.action" : "Osvježi okvir",
96 "service.errorHandler.editAction" : "Uredite {ime}",
97 "service.errorHandler.headline" : "O, ne! ",
98 "service.errorHandler.message" : "Greška",
99 "service.errorHandler.text" : "{name} has failed to load.",
93 "services.getStarted" : "Započnimo!", 100 "services.getStarted" : "Započnimo!",
94 "services.welcome" : "Dobrodošli u Franz", 101 "services.welcome" : "Dobrodošli u Franz",
95 "settings.account.account.editButton" : "Uredi račun", 102 "settings.account.account.editButton" : "Uredi račun",
@@ -185,13 +192,17 @@
185 "settings.service.form.indirectMessages" : "Prikaži značku na svim novim porukuama", 192 "settings.service.form.indirectMessages" : "Prikaži značku na svim novim porukuama",
186 "settings.service.form.isMutedInfo" : "Kada je onemogućeno, sve obavijesti, svi zvukovi i sva pozadinska podrška će biti nečujna. ", 193 "settings.service.form.isMutedInfo" : "Kada je onemogućeno, sve obavijesti, svi zvukovi i sva pozadinska podrška će biti nečujna. ",
187 "settings.service.form.name" : "Ime", 194 "settings.service.form.name" : "Ime",
188 "settings.service.form.proxy.headline" : "Proxy Settings", 195 "settings.service.form.proxy.headline" : "HTTP\/HTTPS Proxy Settings",
189 "settings.service.form.proxy.host" : "Proxy Host\/IP", 196 "settings.service.form.proxy.host" : "Proxy Host\/IP",
190 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.", 197 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.",
191 "settings.service.form.proxy.isEnabled" : "Use Proxy", 198 "settings.service.form.proxy.isEnabled" : "Use Proxy",
192 "settings.service.form.proxy.password" : "Password (optional)", 199 "settings.service.form.proxy.password" : "Password (optional)",
200 "settings.service.form.proxy.port" : "Port",
201 "settings.service.form.proxy.restartInfo" : "Please restart Franz after changing proxy Settings.",
193 "settings.service.form.proxy.user" : "User (optional)", 202 "settings.service.form.proxy.user" : "User (optional)",
194 "settings.service.form.saveButton" : "Sačuvaj uslugu\/e", 203 "settings.service.form.saveButton" : "Sačuvaj uslugu\/e",
204 "settings.service.form.spellcheckerLanguage" : "Spell checking Language",
205 "settings.service.form.spellcheckerLanguage.default" : "Use System Default ({default})",
195 "settings.service.form.tabHosted" : "Hostovano", 206 "settings.service.form.tabHosted" : "Hostovano",
196 "settings.service.form.tabOnPremise" : "Samo-hostovano ⭐️", 207 "settings.service.form.tabOnPremise" : "Samo-hostovano ⭐️",
197 "settings.service.form.team" : "Tim", 208 "settings.service.form.team" : "Tim",
diff --git a/src/i18n/locales/hu.json b/src/i18n/locales/hu.json
index 365dec9c6..0b396cf3b 100644
--- a/src/i18n/locales/hu.json
+++ b/src/i18n/locales/hu.json
@@ -1,4 +1,6 @@
1{ 1{
2 "app.errorHandler.action" : "Újratöltés",
3 "app.errorHandler.headline" : "Something went wrong",
2 "feature.delayApp.action" : "Get a Franz Supporter License", 4 "feature.delayApp.action" : "Get a Franz Supporter License",
3 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting", 5 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting",
4 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.", 6 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.",
@@ -90,6 +92,11 @@
90 "service.crashHandler.text" : "{name} hibát okozott.", 92 "service.crashHandler.text" : "{name} hibát okozott.",
91 "service.disabledHandler.action" : "{name} engedélyezése", 93 "service.disabledHandler.action" : "{name} engedélyezése",
92 "service.disabledHandler.headline" : "{name} letiltva", 94 "service.disabledHandler.headline" : "{name} letiltva",
95 "service.errorHandler.action" : "{name} újratöltése",
96 "service.errorHandler.editAction" : "{name} szerkesztése",
97 "service.errorHandler.headline" : "Jajj ne!",
98 "service.errorHandler.message" : "Hiba",
99 "service.errorHandler.text" : "{name} has failed to load.",
93 "services.getStarted" : "Kezdj hozzá", 100 "services.getStarted" : "Kezdj hozzá",
94 "services.welcome" : "Üdvözöl a Franz", 101 "services.welcome" : "Üdvözöl a Franz",
95 "settings.account.account.editButton" : "Fiók szerkesztése", 102 "settings.account.account.editButton" : "Fiók szerkesztése",
@@ -185,13 +192,17 @@
185 "settings.service.form.indirectMessages" : "Üzenet kitűző megjelenítése minden üzenethez", 192 "settings.service.form.indirectMessages" : "Üzenet kitűző megjelenítése minden üzenethez",
186 "settings.service.form.isMutedInfo" : "Ha kikapcsolod, minden értesítési és lejátszott hang némításra kerül.", 193 "settings.service.form.isMutedInfo" : "Ha kikapcsolod, minden értesítési és lejátszott hang némításra kerül.",
187 "settings.service.form.name" : "Név", 194 "settings.service.form.name" : "Név",
188 "settings.service.form.proxy.headline" : "Proxy Settings", 195 "settings.service.form.proxy.headline" : "HTTP\/HTTPS Proxy Settings",
189 "settings.service.form.proxy.host" : "Proxy Host\/IP", 196 "settings.service.form.proxy.host" : "Proxy Host\/IP",
190 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.", 197 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.",
191 "settings.service.form.proxy.isEnabled" : "Use Proxy", 198 "settings.service.form.proxy.isEnabled" : "Use Proxy",
192 "settings.service.form.proxy.password" : "Password (optional)", 199 "settings.service.form.proxy.password" : "Password (optional)",
200 "settings.service.form.proxy.port" : "Port",
201 "settings.service.form.proxy.restartInfo" : "Please restart Franz after changing proxy Settings.",
193 "settings.service.form.proxy.user" : "User (optional)", 202 "settings.service.form.proxy.user" : "User (optional)",
194 "settings.service.form.saveButton" : "Szolgáltatás mentése", 203 "settings.service.form.saveButton" : "Szolgáltatás mentése",
204 "settings.service.form.spellcheckerLanguage" : "Spell checking Language",
205 "settings.service.form.spellcheckerLanguage.default" : "Use System Default ({default})",
195 "settings.service.form.tabHosted" : "Üzemeltetett", 206 "settings.service.form.tabHosted" : "Üzemeltetett",
196 "settings.service.form.tabOnPremise" : "Egyénileg üzemeltetett", 207 "settings.service.form.tabOnPremise" : "Egyénileg üzemeltetett",
197 "settings.service.form.team" : "Csapat", 208 "settings.service.form.team" : "Csapat",
diff --git a/src/i18n/locales/id.json b/src/i18n/locales/id.json
index 437c1304f..11596d142 100644
--- a/src/i18n/locales/id.json
+++ b/src/i18n/locales/id.json
@@ -1,9 +1,11 @@
1{ 1{
2 "feature.delayApp.action" : "Get a Franz Supporter License", 2 "app.errorHandler.action" : "Muat Ulang",
3 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting", 3 "app.errorHandler.headline" : "Something went wrong",
4 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.", 4 "feature.delayApp.action" : "Dapatkan Franz Supporter License",
5 "feature.delayApp.headline" : "Beli Franz Supporter License agar tidak perlu menunggu",
6 "feature.delayApp.text" : "Franz akan melanjutkan dalam {seconds} detik.",
5 "global.api.unhealthy" : "Tidak dapat tersambung ke layanan Franz", 7 "global.api.unhealthy" : "Tidak dapat tersambung ke layanan Franz",
6 "global.notConnectedToTheInternet" : "Anda tidak mempunyai koneksi internet", 8 "global.notConnectedToTheInternet" : "Anda tidak tersambung ke internet.",
7 "import.headline" : "Impor layanan Franz 4 Anda", 9 "import.headline" : "Impor layanan Franz 4 Anda",
8 "import.notSupportedHeadline" : "Layanan belum didukung di Franz 5", 10 "import.notSupportedHeadline" : "Layanan belum didukung di Franz 5",
9 "import.skip.label" : "Saya ingin menambahkan layanan secara manual", 11 "import.skip.label" : "Saya ingin menambahkan layanan secara manual",
@@ -79,7 +81,7 @@
79 "password.noUser" : "Tidak ditemukan pengguna dengan email tersebut", 81 "password.noUser" : "Tidak ditemukan pengguna dengan email tersebut",
80 "password.submit.label" : "Kirim", 82 "password.submit.label" : "Kirim",
81 "password.successInfo" : "Periksa email Anda", 83 "password.successInfo" : "Periksa email Anda",
82 "premiumFeature.button.upgradeAccount" : "Upgrade account", 84 "premiumFeature.button.upgradeAccount" : "Tingkatkan akun",
83 "pricing.headline" : "Dukung Franz", 85 "pricing.headline" : "Dukung Franz",
84 "pricing.link.skipPayment" : "Saya tidak ingin mendukung pengembangan Franz.", 86 "pricing.link.skipPayment" : "Saya tidak ingin mendukung pengembangan Franz.",
85 "pricing.submit.label" : "Saya ingin mendukung pengembangan Franz", 87 "pricing.submit.label" : "Saya ingin mendukung pengembangan Franz",
@@ -90,6 +92,11 @@
90 "service.crashHandler.text" : "{name} menyebabkan kesalahan.", 92 "service.crashHandler.text" : "{name} menyebabkan kesalahan.",
91 "service.disabledHandler.action" : "Aktifkan {name}", 93 "service.disabledHandler.action" : "Aktifkan {name}",
92 "service.disabledHandler.headline" : "{name} dinonaktifkan", 94 "service.disabledHandler.headline" : "{name} dinonaktifkan",
95 "service.errorHandler.action" : "Muat Ulang {name}",
96 "service.errorHandler.editAction" : "Edit {nama}",
97 "service.errorHandler.headline" : "Ya Ampun!",
98 "service.errorHandler.message" : "Kesalahan",
99 "service.errorHandler.text" : "{name} has failed to load.",
93 "services.getStarted" : "Ayo mulai!", 100 "services.getStarted" : "Ayo mulai!",
94 "services.welcome" : "Selamat datang di Franz", 101 "services.welcome" : "Selamat datang di Franz",
95 "settings.account.account.editButton" : "Edit akun", 102 "settings.account.account.editButton" : "Edit akun",
@@ -120,7 +127,7 @@
120 "settings.app.form.autoLaunchInBackground" : "Buka di latar belakang", 127 "settings.app.form.autoLaunchInBackground" : "Buka di latar belakang",
121 "settings.app.form.autoLaunchOnStart" : "Jalankan Franz saat komputer dimulai", 128 "settings.app.form.autoLaunchOnStart" : "Jalankan Franz saat komputer dimulai",
122 "settings.app.form.beta" : "Sertakan versi beta", 129 "settings.app.form.beta" : "Sertakan versi beta",
123 "settings.app.form.darkMode" : "Join the Dark Side", 130 "settings.app.form.darkMode" : "Sisi Kelam menunggu Anda (Mode Gelap)",
124 "settings.app.form.enableGPUAcceleration" : "Aktifkan Akselerasi GPU", 131 "settings.app.form.enableGPUAcceleration" : "Aktifkan Akselerasi GPU",
125 "settings.app.form.enableMenuBar" : "Tampilkan Franz di Bilah Menu", 132 "settings.app.form.enableMenuBar" : "Tampilkan Franz di Bilah Menu",
126 "settings.app.form.enableSpellchecking" : "Aktifkan pemeriksaan ejaan", 133 "settings.app.form.enableSpellchecking" : "Aktifkan pemeriksaan ejaan",
@@ -131,7 +138,7 @@
131 "settings.app.form.runInBackground" : "Tetap jalankan Franz di latar belakang saat menutup jendela", 138 "settings.app.form.runInBackground" : "Tetap jalankan Franz di latar belakang saat menutup jendela",
132 "settings.app.form.showDisabledServices" : "Tampilkan tab layanan yang dinonaktifkan", 139 "settings.app.form.showDisabledServices" : "Tampilkan tab layanan yang dinonaktifkan",
133 "settings.app.form.showMessagesBadgesWhenMuted" : "Tampilkan lencana pesan belum dibaca saat pemberitahuan dinonaktifkan", 140 "settings.app.form.showMessagesBadgesWhenMuted" : "Tampilkan lencana pesan belum dibaca saat pemberitahuan dinonaktifkan",
134 "settings.app.form.spellcheckerLanguage" : "Spell checking language", 141 "settings.app.form.spellcheckerLanguage" : "Periksa ejaan",
135 "settings.app.headline" : "Pengaturan", 142 "settings.app.headline" : "Pengaturan",
136 "settings.app.headlineAdvanced" : "Tingkat Lanjut", 143 "settings.app.headlineAdvanced" : "Tingkat Lanjut",
137 "settings.app.headlineAppearance" : "Tampilan", 144 "settings.app.headlineAppearance" : "Tampilan",
@@ -172,7 +179,7 @@
172 "settings.service.form.editServiceHeadline" : "Edit {nama}", 179 "settings.service.form.editServiceHeadline" : "Edit {nama}",
173 "settings.service.form.enableAudio" : "Aktifkan audio", 180 "settings.service.form.enableAudio" : "Aktifkan audio",
174 "settings.service.form.enableBadge" : "Tampilkan lencana pesan belum dibaca", 181 "settings.service.form.enableBadge" : "Tampilkan lencana pesan belum dibaca",
175 "settings.service.form.enableDarkMode" : "Enable Dark Mode", 182 "settings.service.form.enableDarkMode" : "Aktifkan Mode Gelap",
176 "settings.service.form.enableNotification" : "Aktifkan pemberitahuan", 183 "settings.service.form.enableNotification" : "Aktifkan pemberitahuan",
177 "settings.service.form.enableService" : "Aktifkan layanan", 184 "settings.service.form.enableService" : "Aktifkan layanan",
178 "settings.service.form.headlineBadges" : "Lencana pesan belum dibaca", 185 "settings.service.form.headlineBadges" : "Lencana pesan belum dibaca",
@@ -185,13 +192,17 @@
185 "settings.service.form.indirectMessages" : "Tampilkan lencana pesan untuk semua pesan baru", 192 "settings.service.form.indirectMessages" : "Tampilkan lencana pesan untuk semua pesan baru",
186 "settings.service.form.isMutedInfo" : "Saat dinonaktifkan, semua suara pemberitahuan dan pemutaran audio akan dibisukan", 193 "settings.service.form.isMutedInfo" : "Saat dinonaktifkan, semua suara pemberitahuan dan pemutaran audio akan dibisukan",
187 "settings.service.form.name" : "Nama", 194 "settings.service.form.name" : "Nama",
188 "settings.service.form.proxy.headline" : "Proxy Settings", 195 "settings.service.form.proxy.headline" : "HTTP\/HTTPS Proxy Settings",
189 "settings.service.form.proxy.host" : "Proxy Host\/IP", 196 "settings.service.form.proxy.host" : "Host\/IP Proksi",
190 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.", 197 "settings.service.form.proxy.info" : "Pengaturan proksi tidak akan disinkronkan dengan server Franz.",
191 "settings.service.form.proxy.isEnabled" : "Use Proxy", 198 "settings.service.form.proxy.isEnabled" : "Gunakan Proksi",
192 "settings.service.form.proxy.password" : "Password (optional)", 199 "settings.service.form.proxy.password" : "Sandi (opsional)",
193 "settings.service.form.proxy.user" : "User (optional)", 200 "settings.service.form.proxy.port" : "Port",
201 "settings.service.form.proxy.restartInfo" : "Please restart Franz after changing proxy Settings.",
202 "settings.service.form.proxy.user" : "Pengguna (opsional)",
194 "settings.service.form.saveButton" : "Simpan layanan", 203 "settings.service.form.saveButton" : "Simpan layanan",
204 "settings.service.form.spellcheckerLanguage" : "Spell checking Language",
205 "settings.service.form.spellcheckerLanguage.default" : "Use System Default ({default})",
195 "settings.service.form.tabHosted" : "Hosted", 206 "settings.service.form.tabHosted" : "Hosted",
196 "settings.service.form.tabOnPremise" : "Hosted mandiri", 207 "settings.service.form.tabOnPremise" : "Hosted mandiri",
197 "settings.service.form.team" : "Tim", 208 "settings.service.form.team" : "Tim",
@@ -234,11 +245,11 @@
234 "subscription.features.ads" : "Tanpa iklan, selamanya!", 245 "subscription.features.ads" : "Tanpa iklan, selamanya!",
235 "subscription.features.comingSoon" : "segera hadir", 246 "subscription.features.comingSoon" : "segera hadir",
236 "subscription.features.encryptedSync" : "Sinkronisasi sesi terenkripsi", 247 "subscription.features.encryptedSync" : "Sinkronisasi sesi terenkripsi",
237 "subscription.features.noInterruptions" : "No app delays & nagging to upgrade license", 248 "subscription.features.noInterruptions" : "Tanpa menunggu dan ditanya untuk meningkatkan lisensi",
238 "subscription.features.onpremise" : "Layanan add-on premise\/hosted seperti HipChat", 249 "subscription.features.onpremise" : "Integrasi layanan hosted, misalnya HipChat",
239 "subscription.features.onpremise.mattermost" : "Add on-premise\/hosted services like Mattermost", 250 "subscription.features.onpremise.mattermost" : "Integrasi layanan hosted, misalnya Mattermost",
240 "subscription.features.proxy" : "Proxy support for services", 251 "subscription.features.proxy" : "Dukungan proksi untuk layanan",
241 "subscription.features.spellchecker" : "Support for spellchecker", 252 "subscription.features.spellchecker" : "Dukungan pengecek ejaan",
242 "subscription.includedFeatures" : "Franz Premium Supporter Account berbayar menyertakan", 253 "subscription.includedFeatures" : "Franz Premium Supporter Account berbayar menyertakan",
243 "subscription.paymentSessionError" : "Tidak bisa menginisialisasi formulir pembayaran", 254 "subscription.paymentSessionError" : "Tidak bisa menginisialisasi formulir pembayaran",
244 "subscription.submit.label" : "Saya ingin mendukung pengembangan Franz", 255 "subscription.submit.label" : "Saya ingin mendukung pengembangan Franz",
diff --git a/src/i18n/locales/it.json b/src/i18n/locales/it.json
index 958d1470d..47cbd8f1e 100644
--- a/src/i18n/locales/it.json
+++ b/src/i18n/locales/it.json
@@ -1,7 +1,9 @@
1{ 1{
2 "feature.delayApp.action" : "Get a Franz Supporter License", 2 "app.errorHandler.action" : "Ricarica",
3 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting", 3 "app.errorHandler.headline" : "Something went wrong",
4 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.", 4 "feature.delayApp.action" : "Ricevi una Licenza Supporter di Franz",
5 "feature.delayApp.headline" : "Per favore, compra una Licenza Supporter di Franz per saltare l'attesa",
6 "feature.delayApp.text" : "Franz continuerà a funzionare tra {seconds} secondi.",
5 "global.api.unhealthy" : "Impossibile connettersi ai servizi online di Franz", 7 "global.api.unhealthy" : "Impossibile connettersi ai servizi online di Franz",
6 "global.notConnectedToTheInternet" : "Non sei connesso a Internet.", 8 "global.notConnectedToTheInternet" : "Non sei connesso a Internet.",
7 "import.headline" : "Importa i servizi di Franz 4", 9 "import.headline" : "Importa i servizi di Franz 4",
@@ -90,6 +92,11 @@
90 "service.crashHandler.text" : "{name} ha causato un errore.", 92 "service.crashHandler.text" : "{name} ha causato un errore.",
91 "service.disabledHandler.action" : "Attiva {name}", 93 "service.disabledHandler.action" : "Attiva {name}",
92 "service.disabledHandler.headline" : "{name} è disattivato", 94 "service.disabledHandler.headline" : "{name} è disattivato",
95 "service.errorHandler.action" : "Ricarica {name}",
96 "service.errorHandler.editAction" : "Modifica {name}",
97 "service.errorHandler.headline" : "Oh no!",
98 "service.errorHandler.message" : "Errore",
99 "service.errorHandler.text" : "{name} has failed to load.",
93 "services.getStarted" : "Iniziamo", 100 "services.getStarted" : "Iniziamo",
94 "services.welcome" : "Benvenuto su Franz", 101 "services.welcome" : "Benvenuto su Franz",
95 "settings.account.account.editButton" : "Modifica account", 102 "settings.account.account.editButton" : "Modifica account",
@@ -120,7 +127,7 @@
120 "settings.app.form.autoLaunchInBackground" : "Apri in background", 127 "settings.app.form.autoLaunchInBackground" : "Apri in background",
121 "settings.app.form.autoLaunchOnStart" : "Esegui Franz all'avvio", 128 "settings.app.form.autoLaunchOnStart" : "Esegui Franz all'avvio",
122 "settings.app.form.beta" : "Includi versioni beta", 129 "settings.app.form.beta" : "Includi versioni beta",
123 "settings.app.form.darkMode" : "Join the Dark Side", 130 "settings.app.form.darkMode" : "Unisciti al Lato Oscuro.",
124 "settings.app.form.enableGPUAcceleration" : "Attiva Accelerazione GPU", 131 "settings.app.form.enableGPUAcceleration" : "Attiva Accelerazione GPU",
125 "settings.app.form.enableMenuBar" : "Mostra Franz nella Barra del Menu", 132 "settings.app.form.enableMenuBar" : "Mostra Franz nella Barra del Menu",
126 "settings.app.form.enableSpellchecking" : "Attiva controllo ortografico", 133 "settings.app.form.enableSpellchecking" : "Attiva controllo ortografico",
@@ -172,7 +179,7 @@
172 "settings.service.form.editServiceHeadline" : "Modifica {name}", 179 "settings.service.form.editServiceHeadline" : "Modifica {name}",
173 "settings.service.form.enableAudio" : "Attiva audio", 180 "settings.service.form.enableAudio" : "Attiva audio",
174 "settings.service.form.enableBadge" : "Mostra l'etichetta dei messaggi non letti", 181 "settings.service.form.enableBadge" : "Mostra l'etichetta dei messaggi non letti",
175 "settings.service.form.enableDarkMode" : "Enable Dark Mode", 182 "settings.service.form.enableDarkMode" : "Attiva la modalità scura.",
176 "settings.service.form.enableNotification" : "Attiva le notifiche", 183 "settings.service.form.enableNotification" : "Attiva le notifiche",
177 "settings.service.form.enableService" : "Attiva il servizio", 184 "settings.service.form.enableService" : "Attiva il servizio",
178 "settings.service.form.headlineBadges" : "Etichetta dei messaggi non letti", 185 "settings.service.form.headlineBadges" : "Etichetta dei messaggi non letti",
@@ -185,13 +192,17 @@
185 "settings.service.form.indirectMessages" : "Mostra l'etichetta per tutti i nuovi messaggi", 192 "settings.service.form.indirectMessages" : "Mostra l'etichetta per tutti i nuovi messaggi",
186 "settings.service.form.isMutedInfo" : "Se disattivato, tutte le notifiche sonore e le riproduzioni audio saranno mutate", 193 "settings.service.form.isMutedInfo" : "Se disattivato, tutte le notifiche sonore e le riproduzioni audio saranno mutate",
187 "settings.service.form.name" : "Nome", 194 "settings.service.form.name" : "Nome",
188 "settings.service.form.proxy.headline" : "Proxy Settings", 195 "settings.service.form.proxy.headline" : "HTTP\/HTTPS Proxy Settings",
189 "settings.service.form.proxy.host" : "Proxy Host\/IP", 196 "settings.service.form.proxy.host" : "Proxy Host\/IP",
190 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.", 197 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.",
191 "settings.service.form.proxy.isEnabled" : "Use Proxy", 198 "settings.service.form.proxy.isEnabled" : "Use Proxy",
192 "settings.service.form.proxy.password" : "Password (optional)", 199 "settings.service.form.proxy.password" : "Password (optional)",
200 "settings.service.form.proxy.port" : "Port",
201 "settings.service.form.proxy.restartInfo" : "Please restart Franz after changing proxy Settings.",
193 "settings.service.form.proxy.user" : "User (optional)", 202 "settings.service.form.proxy.user" : "User (optional)",
194 "settings.service.form.saveButton" : "Salva servizio", 203 "settings.service.form.saveButton" : "Salva servizio",
204 "settings.service.form.spellcheckerLanguage" : "Spell checking Language",
205 "settings.service.form.spellcheckerLanguage.default" : "Use System Default ({default})",
195 "settings.service.form.tabHosted" : "Hosted", 206 "settings.service.form.tabHosted" : "Hosted",
196 "settings.service.form.tabOnPremise" : "Self hosted ⭐️", 207 "settings.service.form.tabOnPremise" : "Self hosted ⭐️",
197 "settings.service.form.team" : "Gruppo", 208 "settings.service.form.team" : "Gruppo",
diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json
index 57b767ef1..6eea64c3a 100644
--- a/src/i18n/locales/ja.json
+++ b/src/i18n/locales/ja.json
@@ -1,7 +1,9 @@
1{ 1{
2 "feature.delayApp.action" : "Get a Franz Supporter License", 2 "app.errorHandler.action" : "再読み込み",
3 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting", 3 "app.errorHandler.headline" : "Something went wrong",
4 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.", 4 "feature.delayApp.action" : "Franz サポーターライセンスを購入する",
5 "feature.delayApp.headline" : "Franz をすぐに起動するには、Franz サポーターライセンスを購入してください。",
6 "feature.delayApp.text" : "Franz はあと{seconds}秒後に起動します。",
5 "global.api.unhealthy" : "Franzのオンラインサービスに接続できません。", 7 "global.api.unhealthy" : "Franzのオンラインサービスに接続できません。",
6 "global.notConnectedToTheInternet" : "インターネットに接続されていません。", 8 "global.notConnectedToTheInternet" : "インターネットに接続されていません。",
7 "import.headline" : "Franz 4のサービスをインポートして下さい", 9 "import.headline" : "Franz 4のサービスをインポートして下さい",
@@ -79,7 +81,7 @@
79 "password.noUser" : "このメールアドレスはまだ登録されていません", 81 "password.noUser" : "このメールアドレスはまだ登録されていません",
80 "password.submit.label" : "送信", 82 "password.submit.label" : "送信",
81 "password.successInfo" : "メールを確認して下さい", 83 "password.successInfo" : "メールを確認して下さい",
82 "premiumFeature.button.upgradeAccount" : "Upgrade account", 84 "premiumFeature.button.upgradeAccount" : "アカウントをアップグレード",
83 "pricing.headline" : "Franzを支援する", 85 "pricing.headline" : "Franzを支援する",
84 "pricing.link.skipPayment" : "Franzの開発を支援したくない。", 86 "pricing.link.skipPayment" : "Franzの開発を支援したくない。",
85 "pricing.submit.label" : "Franzの開発を支援したい。", 87 "pricing.submit.label" : "Franzの開発を支援したい。",
@@ -90,6 +92,11 @@
90 "service.crashHandler.text" : "{name}はエラーを起こしました。", 92 "service.crashHandler.text" : "{name}はエラーを起こしました。",
91 "service.disabledHandler.action" : "{name}を有効にする", 93 "service.disabledHandler.action" : "{name}を有効にする",
92 "service.disabledHandler.headline" : "{name}は無効です", 94 "service.disabledHandler.headline" : "{name}は無効です",
95 "service.errorHandler.action" : "{name}を再読み込み",
96 "service.errorHandler.editAction" : "{name}を編集",
97 "service.errorHandler.headline" : "しまった!",
98 "service.errorHandler.message" : "エラー",
99 "service.errorHandler.text" : "{name} has failed to load.",
93 "services.getStarted" : "はじめる", 100 "services.getStarted" : "はじめる",
94 "services.welcome" : "Franzにようこそ", 101 "services.welcome" : "Franzにようこそ",
95 "settings.account.account.editButton" : "アカウントの編集", 102 "settings.account.account.editButton" : "アカウントの編集",
@@ -120,7 +127,7 @@
120 "settings.app.form.autoLaunchInBackground" : "バックグラウンドで開く", 127 "settings.app.form.autoLaunchInBackground" : "バックグラウンドで開く",
121 "settings.app.form.autoLaunchOnStart" : "システム起動時にFranzを開く", 128 "settings.app.form.autoLaunchOnStart" : "システム起動時にFranzを開く",
122 "settings.app.form.beta" : "Betaバージョンを含める", 129 "settings.app.form.beta" : "Betaバージョンを含める",
123 "settings.app.form.darkMode" : "Join the Dark Side", 130 "settings.app.form.darkMode" : "ダークモードを有効にする",
124 "settings.app.form.enableGPUAcceleration" : "GPUアクセラレーションを有効にする", 131 "settings.app.form.enableGPUAcceleration" : "GPUアクセラレーションを有効にする",
125 "settings.app.form.enableMenuBar" : "メニューバーにFranzを表示する", 132 "settings.app.form.enableMenuBar" : "メニューバーにFranzを表示する",
126 "settings.app.form.enableSpellchecking" : "スペルチェックを有効にする", 133 "settings.app.form.enableSpellchecking" : "スペルチェックを有効にする",
@@ -131,7 +138,7 @@
131 "settings.app.form.runInBackground" : "ウインドウを閉じた際にFranzをバックグラウンドで実行させておく", 138 "settings.app.form.runInBackground" : "ウインドウを閉じた際にFranzをバックグラウンドで実行させておく",
132 "settings.app.form.showDisabledServices" : "無効化されたサービスのタブを表示する", 139 "settings.app.form.showDisabledServices" : "無効化されたサービスのタブを表示する",
133 "settings.app.form.showMessagesBadgesWhenMuted" : "通知の無効時に未読メッセージ件数を表示する", 140 "settings.app.form.showMessagesBadgesWhenMuted" : "通知の無効時に未読メッセージ件数を表示する",
134 "settings.app.form.spellcheckerLanguage" : "Spell checking language", 141 "settings.app.form.spellcheckerLanguage" : "スペルチェックする言語",
135 "settings.app.headline" : "設定", 142 "settings.app.headline" : "設定",
136 "settings.app.headlineAdvanced" : "詳細", 143 "settings.app.headlineAdvanced" : "詳細",
137 "settings.app.headlineAppearance" : "表示スタイル", 144 "settings.app.headlineAppearance" : "表示スタイル",
@@ -172,7 +179,7 @@
172 "settings.service.form.editServiceHeadline" : "{name}を編集", 179 "settings.service.form.editServiceHeadline" : "{name}を編集",
173 "settings.service.form.enableAudio" : "オーディオを有効にする", 180 "settings.service.form.enableAudio" : "オーディオを有効にする",
174 "settings.service.form.enableBadge" : "未読件数の通知バッジを表示する", 181 "settings.service.form.enableBadge" : "未読件数の通知バッジを表示する",
175 "settings.service.form.enableDarkMode" : "Enable Dark Mode", 182 "settings.service.form.enableDarkMode" : "ダークモードを有効にする",
176 "settings.service.form.enableNotification" : "通知を有効にする", 183 "settings.service.form.enableNotification" : "通知を有効にする",
177 "settings.service.form.enableService" : "サービスを有効にする", 184 "settings.service.form.enableService" : "サービスを有効にする",
178 "settings.service.form.headlineBadges" : "未読件数の通知バッジ", 185 "settings.service.form.headlineBadges" : "未読件数の通知バッジ",
@@ -185,13 +192,17 @@
185 "settings.service.form.indirectMessages" : "すべての新規メッセージについてバッジを表示する", 192 "settings.service.form.indirectMessages" : "すべての新規メッセージについてバッジを表示する",
186 "settings.service.form.isMutedInfo" : "無効化されている場合、全ての通知音やオーディオ再生は無音になります", 193 "settings.service.form.isMutedInfo" : "無効化されている場合、全ての通知音やオーディオ再生は無音になります",
187 "settings.service.form.name" : "サービス名", 194 "settings.service.form.name" : "サービス名",
188 "settings.service.form.proxy.headline" : "Proxy Settings", 195 "settings.service.form.proxy.headline" : "HTTP\/HTTPS Proxy Settings",
189 "settings.service.form.proxy.host" : "Proxy Host\/IP", 196 "settings.service.form.proxy.host" : "プロキシサーバー\/IP",
190 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.", 197 "settings.service.form.proxy.info" : "プロキシ設定はFranz アカウントで同期されません。",
191 "settings.service.form.proxy.isEnabled" : "Use Proxy", 198 "settings.service.form.proxy.isEnabled" : "プロキシ設定を有効にする",
192 "settings.service.form.proxy.password" : "Password (optional)", 199 "settings.service.form.proxy.password" : "パスワード(任意)",
193 "settings.service.form.proxy.user" : "User (optional)", 200 "settings.service.form.proxy.port" : "Port",
201 "settings.service.form.proxy.restartInfo" : "Please restart Franz after changing proxy Settings.",
202 "settings.service.form.proxy.user" : "ユーザー名(任意)",
194 "settings.service.form.saveButton" : "サービスの保存", 203 "settings.service.form.saveButton" : "サービスの保存",
204 "settings.service.form.spellcheckerLanguage" : "Spell checking Language",
205 "settings.service.form.spellcheckerLanguage.default" : "Use System Default ({default})",
195 "settings.service.form.tabHosted" : "ホスト", 206 "settings.service.form.tabHosted" : "ホスト",
196 "settings.service.form.tabOnPremise" : "セルフホスト ⭐️", 207 "settings.service.form.tabOnPremise" : "セルフホスト ⭐️",
197 "settings.service.form.team" : "チーム", 208 "settings.service.form.team" : "チーム",
@@ -234,11 +245,11 @@
234 "subscription.features.ads" : "広告は一切ありません!", 245 "subscription.features.ads" : "広告は一切ありません!",
235 "subscription.features.comingSoon" : "まもなく登場", 246 "subscription.features.comingSoon" : "まもなく登場",
236 "subscription.features.encryptedSync" : "暗号化されたセッションの同期", 247 "subscription.features.encryptedSync" : "暗号化されたセッションの同期",
237 "subscription.features.noInterruptions" : "No app delays & nagging to upgrade license", 248 "subscription.features.noInterruptions" : "待ち時間なしでFranz をお使いいただけます",
238 "subscription.features.onpremise" : "HipChatのようなオンプレミス\/ホスト型サービスの追加", 249 "subscription.features.onpremise" : "HipChatのようなオンプレミス\/ホスト型サービスの追加",
239 "subscription.features.onpremise.mattermost" : "Add on-premise\/hosted services like Mattermost", 250 "subscription.features.onpremise.mattermost" : "Mattermost のようなオンプレミス(自社運用)型のサービスを追加できるようになります",
240 "subscription.features.proxy" : "Proxy support for services", 251 "subscription.features.proxy" : "プロキシ設定が利用可能",
241 "subscription.features.spellchecker" : "Support for spellchecker", 252 "subscription.features.spellchecker" : "スペルチェック機能をお使いいただけます",
242 "subscription.includedFeatures" : "Franz Premium Supporter有料アカウントには以下が含まれます", 253 "subscription.includedFeatures" : "Franz Premium Supporter有料アカウントには以下が含まれます",
243 "subscription.paymentSessionError" : "支払いフォームを初期化出来ません", 254 "subscription.paymentSessionError" : "支払いフォームを初期化出来ません",
244 "subscription.submit.label" : "Franzの開発を支援したい", 255 "subscription.submit.label" : "Franzの開発を支援したい",
diff --git a/src/i18n/locales/ka.json b/src/i18n/locales/ka.json
index d521c838a..632ca618e 100644
--- a/src/i18n/locales/ka.json
+++ b/src/i18n/locales/ka.json
@@ -1,4 +1,6 @@
1{ 1{
2 "app.errorHandler.action" : "ჩატვირთვა",
3 "app.errorHandler.headline" : "Something went wrong",
2 "feature.delayApp.action" : "Get a Franz Supporter License", 4 "feature.delayApp.action" : "Get a Franz Supporter License",
3 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting", 5 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting",
4 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.", 6 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.",
@@ -90,6 +92,11 @@
90 "service.crashHandler.text" : "{name} has caused an error.", 92 "service.crashHandler.text" : "{name} has caused an error.",
91 "service.disabledHandler.action" : "Enable {name}", 93 "service.disabledHandler.action" : "Enable {name}",
92 "service.disabledHandler.headline" : "{name} is disabled", 94 "service.disabledHandler.headline" : "{name} is disabled",
95 "service.errorHandler.action" : "Reload {name}",
96 "service.errorHandler.editAction" : "შეასწორე {name}",
97 "service.errorHandler.headline" : "Oh no!",
98 "service.errorHandler.message" : "შეცდომა",
99 "service.errorHandler.text" : "{name} has failed to load.",
93 "services.getStarted" : "დაწყება", 100 "services.getStarted" : "დაწყება",
94 "services.welcome" : "მოგესალმებით Franz-ზე", 101 "services.welcome" : "მოგესალმებით Franz-ზე",
95 "settings.account.account.editButton" : "ანგარიშის მართვა", 102 "settings.account.account.editButton" : "ანგარიშის მართვა",
@@ -185,13 +192,17 @@
185 "settings.service.form.indirectMessages" : "აჩვენე შეტყობინებების ნიშანი ყოველი ახალი შეტყობინებისთვის", 192 "settings.service.form.indirectMessages" : "აჩვენე შეტყობინებების ნიშანი ყოველი ახალი შეტყობინებისთვის",
186 "settings.service.form.isMutedInfo" : "When disabled, all notification sounds and audio playback are muted", 193 "settings.service.form.isMutedInfo" : "When disabled, all notification sounds and audio playback are muted",
187 "settings.service.form.name" : "სახელი", 194 "settings.service.form.name" : "სახელი",
188 "settings.service.form.proxy.headline" : "Proxy Settings", 195 "settings.service.form.proxy.headline" : "HTTP\/HTTPS Proxy Settings",
189 "settings.service.form.proxy.host" : "Proxy Host\/IP", 196 "settings.service.form.proxy.host" : "Proxy Host\/IP",
190 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.", 197 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.",
191 "settings.service.form.proxy.isEnabled" : "Use Proxy", 198 "settings.service.form.proxy.isEnabled" : "Use Proxy",
192 "settings.service.form.proxy.password" : "Password (optional)", 199 "settings.service.form.proxy.password" : "Password (optional)",
200 "settings.service.form.proxy.port" : "Port",
201 "settings.service.form.proxy.restartInfo" : "Please restart Franz after changing proxy Settings.",
193 "settings.service.form.proxy.user" : "User (optional)", 202 "settings.service.form.proxy.user" : "User (optional)",
194 "settings.service.form.saveButton" : "სერვისის შენახვა", 203 "settings.service.form.saveButton" : "სერვისის შენახვა",
204 "settings.service.form.spellcheckerLanguage" : "Spell checking Language",
205 "settings.service.form.spellcheckerLanguage.default" : "Use System Default ({default})",
195 "settings.service.form.tabHosted" : "დაჰოსტილი", 206 "settings.service.form.tabHosted" : "დაჰოსტილი",
196 "settings.service.form.tabOnPremise" : "თვით დაჰოსტილი ⭐️", 207 "settings.service.form.tabOnPremise" : "თვით დაჰოსტილი ⭐️",
197 "settings.service.form.team" : "გუნდი", 208 "settings.service.form.team" : "გუნდი",
diff --git a/src/i18n/locales/nl-BE.json b/src/i18n/locales/nl-BE.json
index 5807f11f6..c38a7f024 100644
--- a/src/i18n/locales/nl-BE.json
+++ b/src/i18n/locales/nl-BE.json
@@ -1,4 +1,6 @@
1{ 1{
2 "app.errorHandler.action" : "Herladen",
3 "app.errorHandler.headline" : "Something went wrong",
2 "feature.delayApp.action" : "Get a Franz Supporter License", 4 "feature.delayApp.action" : "Get a Franz Supporter License",
3 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting", 5 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting",
4 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.", 6 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.",
@@ -90,6 +92,11 @@
90 "service.crashHandler.text" : "{name} heeft een probleem veroorzaakt.", 92 "service.crashHandler.text" : "{name} heeft een probleem veroorzaakt.",
91 "service.disabledHandler.action" : "Activeer {name}", 93 "service.disabledHandler.action" : "Activeer {name}",
92 "service.disabledHandler.headline" : "{name} is uitgeschakeld", 94 "service.disabledHandler.headline" : "{name} is uitgeschakeld",
95 "service.errorHandler.action" : "{naam} herladen",
96 "service.errorHandler.editAction" : "{name} aanpassen",
97 "service.errorHandler.headline" : "Oh nee!",
98 "service.errorHandler.message" : "Fout",
99 "service.errorHandler.text" : "{name} has failed to load.",
93 "services.getStarted" : "Beginnen", 100 "services.getStarted" : "Beginnen",
94 "services.welcome" : "Welkom bij Franz", 101 "services.welcome" : "Welkom bij Franz",
95 "settings.account.account.editButton" : "Bewerk account", 102 "settings.account.account.editButton" : "Bewerk account",
@@ -185,13 +192,17 @@
185 "settings.service.form.indirectMessages" : "Toon berichten-badge voor alle nieuwe berichten", 192 "settings.service.form.indirectMessages" : "Toon berichten-badge voor alle nieuwe berichten",
186 "settings.service.form.isMutedInfo" : "When disabled, all notification sounds and audio playback are muted", 193 "settings.service.form.isMutedInfo" : "When disabled, all notification sounds and audio playback are muted",
187 "settings.service.form.name" : "Naam", 194 "settings.service.form.name" : "Naam",
188 "settings.service.form.proxy.headline" : "Proxy Settings", 195 "settings.service.form.proxy.headline" : "HTTP\/HTTPS Proxy Settings",
189 "settings.service.form.proxy.host" : "Proxy Host\/IP", 196 "settings.service.form.proxy.host" : "Proxy Host\/IP",
190 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.", 197 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.",
191 "settings.service.form.proxy.isEnabled" : "Use Proxy", 198 "settings.service.form.proxy.isEnabled" : "Use Proxy",
192 "settings.service.form.proxy.password" : "Password (optional)", 199 "settings.service.form.proxy.password" : "Password (optional)",
200 "settings.service.form.proxy.port" : "Port",
201 "settings.service.form.proxy.restartInfo" : "Please restart Franz after changing proxy Settings.",
193 "settings.service.form.proxy.user" : "User (optional)", 202 "settings.service.form.proxy.user" : "User (optional)",
194 "settings.service.form.saveButton" : "Service bewaren", 203 "settings.service.form.saveButton" : "Service bewaren",
204 "settings.service.form.spellcheckerLanguage" : "Spell checking Language",
205 "settings.service.form.spellcheckerLanguage.default" : "Use System Default ({default})",
195 "settings.service.form.tabHosted" : "Gehost", 206 "settings.service.form.tabHosted" : "Gehost",
196 "settings.service.form.tabOnPremise" : "Intern gehost ⭐️", 207 "settings.service.form.tabOnPremise" : "Intern gehost ⭐️",
197 "settings.service.form.team" : "Team", 208 "settings.service.form.team" : "Team",
diff --git a/src/i18n/locales/nl.json b/src/i18n/locales/nl.json
index a6db3d773..940f24b0b 100644
--- a/src/i18n/locales/nl.json
+++ b/src/i18n/locales/nl.json
@@ -1,7 +1,9 @@
1{ 1{
2 "feature.delayApp.action" : "Get a Franz Supporter License", 2 "app.errorHandler.action" : "Laad opnieuw",
3 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting", 3 "app.errorHandler.headline" : "Something went wrong",
4 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.", 4 "feature.delayApp.action" : "Sponsor Franz",
5 "feature.delayApp.headline" : "Sponsor Franz om wachten over te slaan",
6 "feature.delayApp.text" : "Franz gaat over {seconds} seconden verder.",
5 "global.api.unhealthy" : "Kan geen verbinding maken met de Franz-services", 7 "global.api.unhealthy" : "Kan geen verbinding maken met de Franz-services",
6 "global.notConnectedToTheInternet" : "U bent niet verbonden met het internet.", 8 "global.notConnectedToTheInternet" : "U bent niet verbonden met het internet.",
7 "import.headline" : "Importeer uw Franz 4-services", 9 "import.headline" : "Importeer uw Franz 4-services",
@@ -79,7 +81,7 @@
79 "password.noUser" : "Geen gebruiker bekend met dat e-mailadres", 81 "password.noUser" : "Geen gebruiker bekend met dat e-mailadres",
80 "password.submit.label" : "Verzenden", 82 "password.submit.label" : "Verzenden",
81 "password.successInfo" : "Controleer alsjeblieft je e-mail", 83 "password.successInfo" : "Controleer alsjeblieft je e-mail",
82 "premiumFeature.button.upgradeAccount" : "Upgrade account", 84 "premiumFeature.button.upgradeAccount" : "Upgrade Account",
83 "pricing.headline" : "Steun Franz", 85 "pricing.headline" : "Steun Franz",
84 "pricing.link.skipPayment" : "Ik wil de ontwikkeling van Franz niet ondersteunen.", 86 "pricing.link.skipPayment" : "Ik wil de ontwikkeling van Franz niet ondersteunen.",
85 "pricing.submit.label" : "Ik wil de ontwikkeling van Franz steunen", 87 "pricing.submit.label" : "Ik wil de ontwikkeling van Franz steunen",
@@ -90,6 +92,11 @@
90 "service.crashHandler.text" : "{name} heeft een fout veroorzaakt.", 92 "service.crashHandler.text" : "{name} heeft een fout veroorzaakt.",
91 "service.disabledHandler.action" : "Activeer {name}", 93 "service.disabledHandler.action" : "Activeer {name}",
92 "service.disabledHandler.headline" : "{name} is uitgeschakeld", 94 "service.disabledHandler.headline" : "{name} is uitgeschakeld",
95 "service.errorHandler.action" : "Laad {name} opnieuw",
96 "service.errorHandler.editAction" : "Bewerk {name}",
97 "service.errorHandler.headline" : "Oh nee!",
98 "service.errorHandler.message" : "Fout",
99 "service.errorHandler.text" : "{name} has failed to load.",
93 "services.getStarted" : "Begin", 100 "services.getStarted" : "Begin",
94 "services.welcome" : "Welkom bij Franz", 101 "services.welcome" : "Welkom bij Franz",
95 "settings.account.account.editButton" : "Bewerk account", 102 "settings.account.account.editButton" : "Bewerk account",
@@ -120,7 +127,7 @@
120 "settings.app.form.autoLaunchInBackground" : "Open op de achtergrond", 127 "settings.app.form.autoLaunchInBackground" : "Open op de achtergrond",
121 "settings.app.form.autoLaunchOnStart" : "Open Franz bij opstarten", 128 "settings.app.form.autoLaunchOnStart" : "Open Franz bij opstarten",
122 "settings.app.form.beta" : "Inclusief bètaversies", 129 "settings.app.form.beta" : "Inclusief bètaversies",
123 "settings.app.form.darkMode" : "Join the Dark Side", 130 "settings.app.form.darkMode" : "Stap over naar de donkere kant",
124 "settings.app.form.enableGPUAcceleration" : "Schakel videokaart acceleratie in ", 131 "settings.app.form.enableGPUAcceleration" : "Schakel videokaart acceleratie in ",
125 "settings.app.form.enableMenuBar" : "Toon Franz in menubalk", 132 "settings.app.form.enableMenuBar" : "Toon Franz in menubalk",
126 "settings.app.form.enableSpellchecking" : "Zet spellingcontrole aan", 133 "settings.app.form.enableSpellchecking" : "Zet spellingcontrole aan",
@@ -131,7 +138,7 @@
131 "settings.app.form.runInBackground" : "Houd Franz op de achtergrond wanneer het venster gesloten wordt", 138 "settings.app.form.runInBackground" : "Houd Franz op de achtergrond wanneer het venster gesloten wordt",
132 "settings.app.form.showDisabledServices" : "Toon uitgeschakelde services", 139 "settings.app.form.showDisabledServices" : "Toon uitgeschakelde services",
133 "settings.app.form.showMessagesBadgesWhenMuted" : "Toon badge met ongelezen berichten wanneer meldingen zijn uitgeschakeld", 140 "settings.app.form.showMessagesBadgesWhenMuted" : "Toon badge met ongelezen berichten wanneer meldingen zijn uitgeschakeld",
134 "settings.app.form.spellcheckerLanguage" : "Spell checking language", 141 "settings.app.form.spellcheckerLanguage" : "Spelling checken",
135 "settings.app.headline" : "Instellingen", 142 "settings.app.headline" : "Instellingen",
136 "settings.app.headlineAdvanced" : "Geavanceerd", 143 "settings.app.headlineAdvanced" : "Geavanceerd",
137 "settings.app.headlineAppearance" : "Weergave", 144 "settings.app.headlineAppearance" : "Weergave",
@@ -172,7 +179,7 @@
172 "settings.service.form.editServiceHeadline" : "Bewerk {name}", 179 "settings.service.form.editServiceHeadline" : "Bewerk {name}",
173 "settings.service.form.enableAudio" : "Audio inschakelen", 180 "settings.service.form.enableAudio" : "Audio inschakelen",
174 "settings.service.form.enableBadge" : "Toon badges met ongelezen berichten", 181 "settings.service.form.enableBadge" : "Toon badges met ongelezen berichten",
175 "settings.service.form.enableDarkMode" : "Enable Dark Mode", 182 "settings.service.form.enableDarkMode" : "Dark mode aanzetten",
176 "settings.service.form.enableNotification" : "Meldingen inschakelen", 183 "settings.service.form.enableNotification" : "Meldingen inschakelen",
177 "settings.service.form.enableService" : "Service inschakelen", 184 "settings.service.form.enableService" : "Service inschakelen",
178 "settings.service.form.headlineBadges" : "Ongelezen berichten badges", 185 "settings.service.form.headlineBadges" : "Ongelezen berichten badges",
@@ -185,13 +192,17 @@
185 "settings.service.form.indirectMessages" : "Toon berichtenbadge voor alle nieuwe berichten", 192 "settings.service.form.indirectMessages" : "Toon berichtenbadge voor alle nieuwe berichten",
186 "settings.service.form.isMutedInfo" : "Indien uitgeschakeld zullen alle meldinggeluiden en afgespeelde audio uitgeschakeld zijn", 193 "settings.service.form.isMutedInfo" : "Indien uitgeschakeld zullen alle meldinggeluiden en afgespeelde audio uitgeschakeld zijn",
187 "settings.service.form.name" : "Naam", 194 "settings.service.form.name" : "Naam",
188 "settings.service.form.proxy.headline" : "Proxy Settings", 195 "settings.service.form.proxy.headline" : "HTTP\/HTTPS Proxy Settings",
189 "settings.service.form.proxy.host" : "Proxy Host\/IP", 196 "settings.service.form.proxy.host" : "Proxy Host\/IP",
190 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.", 197 "settings.service.form.proxy.info" : "Proxy instellingen worden niet gesynchroniseerd met de Franz servers",
191 "settings.service.form.proxy.isEnabled" : "Use Proxy", 198 "settings.service.form.proxy.isEnabled" : "Proxy gebruiken",
192 "settings.service.form.proxy.password" : "Password (optional)", 199 "settings.service.form.proxy.password" : "Wachtwoord (optioneel)",
193 "settings.service.form.proxy.user" : "User (optional)", 200 "settings.service.form.proxy.port" : "Port",
201 "settings.service.form.proxy.restartInfo" : "Please restart Franz after changing proxy Settings.",
202 "settings.service.form.proxy.user" : "Gebruiker (optioneel)",
194 "settings.service.form.saveButton" : "Service opslaan", 203 "settings.service.form.saveButton" : "Service opslaan",
204 "settings.service.form.spellcheckerLanguage" : "Spell checking Language",
205 "settings.service.form.spellcheckerLanguage.default" : "Use System Default ({default})",
195 "settings.service.form.tabHosted" : "Gehost", 206 "settings.service.form.tabHosted" : "Gehost",
196 "settings.service.form.tabOnPremise" : "Zelf-gehost ⭐️", 207 "settings.service.form.tabOnPremise" : "Zelf-gehost ⭐️",
197 "settings.service.form.team" : "Team", 208 "settings.service.form.team" : "Team",
@@ -234,11 +245,11 @@
234 "subscription.features.ads" : "Geen advertenties, nooit!", 245 "subscription.features.ads" : "Geen advertenties, nooit!",
235 "subscription.features.comingSoon" : "komt binnenkort", 246 "subscription.features.comingSoon" : "komt binnenkort",
236 "subscription.features.encryptedSync" : "Beveiligde sessie synchronisatie", 247 "subscription.features.encryptedSync" : "Beveiligde sessie synchronisatie",
237 "subscription.features.noInterruptions" : "No app delays & nagging to upgrade license", 248 "subscription.features.noInterruptions" : "Geen haperingen & pop ups over upgrades",
238 "subscription.features.onpremise" : "Add on-geschikt\/gehoste services zoals HipChat", 249 "subscription.features.onpremise" : "Add on-geschikt\/gehoste services zoals HipChat",
239 "subscription.features.onpremise.mattermost" : "Add on-premise\/hosted services like Mattermost", 250 "subscription.features.onpremise.mattermost" : "Add on-premise\/hosted services like Mattermost",
240 "subscription.features.proxy" : "Proxy support for services", 251 "subscription.features.proxy" : "Proxy understeuning voor diensten",
241 "subscription.features.spellchecker" : "Support for spellchecker", 252 "subscription.features.spellchecker" : "Ondersteuning voor spellingscheck",
242 "subscription.includedFeatures" : "Betaald Franz Premium Supporter Account bevat", 253 "subscription.includedFeatures" : "Betaald Franz Premium Supporter Account bevat",
243 "subscription.paymentSessionError" : "Kan betaalformulier niet initialiseren", 254 "subscription.paymentSessionError" : "Kan betaalformulier niet initialiseren",
244 "subscription.submit.label" : "Ik wil de ontwikkeling van Franz ondersteunen", 255 "subscription.submit.label" : "Ik wil de ontwikkeling van Franz ondersteunen",
diff --git a/src/i18n/locales/pl.json b/src/i18n/locales/pl.json
index 60ced5933..d45e5ce24 100644
--- a/src/i18n/locales/pl.json
+++ b/src/i18n/locales/pl.json
@@ -1,7 +1,9 @@
1{ 1{
2 "feature.delayApp.action" : "Get a Franz Supporter License", 2 "app.errorHandler.action" : "Przeładuj",
3 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting", 3 "app.errorHandler.headline" : "Something went wrong",
4 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.", 4 "feature.delayApp.action" : "Uzyskaj licencję Franz Supporter",
5 "feature.delayApp.headline" : "Kup licencję Franz Supporter , aby nie czekać",
6 "feature.delayApp.text" : "Franz będzie kontynuował za {seconds} sekund.",
5 "global.api.unhealthy" : "Nie można połączyć się z usługami Franz online", 7 "global.api.unhealthy" : "Nie można połączyć się z usługami Franz online",
6 "global.notConnectedToTheInternet" : "Nie masz połączenia z Internetem.", 8 "global.notConnectedToTheInternet" : "Nie masz połączenia z Internetem.",
7 "import.headline" : "Importuj usługi Franz 4", 9 "import.headline" : "Importuj usługi Franz 4",
@@ -90,6 +92,11 @@
90 "service.crashHandler.text" : "{name} spowodował problem.", 92 "service.crashHandler.text" : "{name} spowodował problem.",
91 "service.disabledHandler.action" : "Włącz {name}", 93 "service.disabledHandler.action" : "Włącz {name}",
92 "service.disabledHandler.headline" : "{name} jest wyłączony", 94 "service.disabledHandler.headline" : "{name} jest wyłączony",
95 "service.errorHandler.action" : "Przeładuj {name}",
96 "service.errorHandler.editAction" : "Edytuj {name}",
97 "service.errorHandler.headline" : "O nie!",
98 "service.errorHandler.message" : "Błąd",
99 "service.errorHandler.text" : "{name} has failed to load.",
93 "services.getStarted" : "Zacznij", 100 "services.getStarted" : "Zacznij",
94 "services.welcome" : "Witaj w programie Franz", 101 "services.welcome" : "Witaj w programie Franz",
95 "settings.account.account.editButton" : "Modyfikuj konta", 102 "settings.account.account.editButton" : "Modyfikuj konta",
@@ -120,7 +127,7 @@
120 "settings.app.form.autoLaunchInBackground" : "Otwórz w tle", 127 "settings.app.form.autoLaunchInBackground" : "Otwórz w tle",
121 "settings.app.form.autoLaunchOnStart" : "Uruchom Franz na początku", 128 "settings.app.form.autoLaunchOnStart" : "Uruchom Franz na początku",
122 "settings.app.form.beta" : "Uwzględnij wersje beta", 129 "settings.app.form.beta" : "Uwzględnij wersje beta",
123 "settings.app.form.darkMode" : "Join the Dark Side", 130 "settings.app.form.darkMode" : "Przejdź na Ciemną Stronę",
124 "settings.app.form.enableGPUAcceleration" : "Włącz akcelerację GPU", 131 "settings.app.form.enableGPUAcceleration" : "Włącz akcelerację GPU",
125 "settings.app.form.enableMenuBar" : "Pokaż Franz na pasku menu", 132 "settings.app.form.enableMenuBar" : "Pokaż Franz na pasku menu",
126 "settings.app.form.enableSpellchecking" : "Włącz sprawdzanie pisowni", 133 "settings.app.form.enableSpellchecking" : "Włącz sprawdzanie pisowni",
@@ -131,7 +138,7 @@
131 "settings.app.form.runInBackground" : "Zachowaj aplikację Franz w tle po zamknięciu okna", 138 "settings.app.form.runInBackground" : "Zachowaj aplikację Franz w tle po zamknięciu okna",
132 "settings.app.form.showDisabledServices" : "Wyłącz wyświetlanie zakładek z usługami", 139 "settings.app.form.showDisabledServices" : "Wyłącz wyświetlanie zakładek z usługami",
133 "settings.app.form.showMessagesBadgesWhenMuted" : "Pokaż licznik nieprzeczytanych wiadomości gdy powiadomienia są wyłączone", 140 "settings.app.form.showMessagesBadgesWhenMuted" : "Pokaż licznik nieprzeczytanych wiadomości gdy powiadomienia są wyłączone",
134 "settings.app.form.spellcheckerLanguage" : "Spell checking language", 141 "settings.app.form.spellcheckerLanguage" : "Język słownika",
135 "settings.app.headline" : "Ustawienia", 142 "settings.app.headline" : "Ustawienia",
136 "settings.app.headlineAdvanced" : "Zaawansowane", 143 "settings.app.headlineAdvanced" : "Zaawansowane",
137 "settings.app.headlineAppearance" : "Wygląd", 144 "settings.app.headlineAppearance" : "Wygląd",
@@ -172,7 +179,7 @@
172 "settings.service.form.editServiceHeadline" : "Edytuj {name}", 179 "settings.service.form.editServiceHeadline" : "Edytuj {name}",
173 "settings.service.form.enableAudio" : "Włącz dźwięk", 180 "settings.service.form.enableAudio" : "Włącz dźwięk",
174 "settings.service.form.enableBadge" : "Pokaż znacznik nieprzeczytanych wiadomości", 181 "settings.service.form.enableBadge" : "Pokaż znacznik nieprzeczytanych wiadomości",
175 "settings.service.form.enableDarkMode" : "Enable Dark Mode", 182 "settings.service.form.enableDarkMode" : "Włącz Ciemny motyw",
176 "settings.service.form.enableNotification" : "Aktywuj powiadomienia", 183 "settings.service.form.enableNotification" : "Aktywuj powiadomienia",
177 "settings.service.form.enableService" : "Aktywuj usługę", 184 "settings.service.form.enableService" : "Aktywuj usługę",
178 "settings.service.form.headlineBadges" : "Znaczniki nieprzeczytanych wiadomości", 185 "settings.service.form.headlineBadges" : "Znaczniki nieprzeczytanych wiadomości",
@@ -185,13 +192,17 @@
185 "settings.service.form.indirectMessages" : "Pokaż ikonę wiadomości dla wszystkich nowych wiadomości", 192 "settings.service.form.indirectMessages" : "Pokaż ikonę wiadomości dla wszystkich nowych wiadomości",
186 "settings.service.form.isMutedInfo" : "Kiedy nieaktywne, wszystkie dźwięki powiadomień są wyciszone", 193 "settings.service.form.isMutedInfo" : "Kiedy nieaktywne, wszystkie dźwięki powiadomień są wyciszone",
187 "settings.service.form.name" : "Nazwa", 194 "settings.service.form.name" : "Nazwa",
188 "settings.service.form.proxy.headline" : "Proxy Settings", 195 "settings.service.form.proxy.headline" : "HTTP\/HTTPS Proxy Settings",
189 "settings.service.form.proxy.host" : "Proxy Host\/IP", 196 "settings.service.form.proxy.host" : "Host Proxy\/IP",
190 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.", 197 "settings.service.form.proxy.info" : "Ustawienia proxy nie będą zsynchronizowane z serwerami Franza.",
191 "settings.service.form.proxy.isEnabled" : "Use Proxy", 198 "settings.service.form.proxy.isEnabled" : "Użyj Proxy",
192 "settings.service.form.proxy.password" : "Password (optional)", 199 "settings.service.form.proxy.password" : "Hasło (opcjonalnie)",
193 "settings.service.form.proxy.user" : "User (optional)", 200 "settings.service.form.proxy.port" : "Port",
201 "settings.service.form.proxy.restartInfo" : "Please restart Franz after changing proxy Settings.",
202 "settings.service.form.proxy.user" : "Użytkownik (opcjonalnie)",
194 "settings.service.form.saveButton" : "Zapisz usługę", 203 "settings.service.form.saveButton" : "Zapisz usługę",
204 "settings.service.form.spellcheckerLanguage" : "Spell checking Language",
205 "settings.service.form.spellcheckerLanguage.default" : "Use System Default ({default})",
195 "settings.service.form.tabHosted" : "Hostowane", 206 "settings.service.form.tabHosted" : "Hostowane",
196 "settings.service.form.tabOnPremise" : "Hostowane lokalnie ⭐️", 207 "settings.service.form.tabOnPremise" : "Hostowane lokalnie ⭐️",
197 "settings.service.form.team" : "Zespół", 208 "settings.service.form.team" : "Zespół",
@@ -237,7 +248,7 @@
237 "subscription.features.noInterruptions" : "No app delays & nagging to upgrade license", 248 "subscription.features.noInterruptions" : "No app delays & nagging to upgrade license",
238 "subscription.features.onpremise" : "Dodawanie lokalnych\/hostowanych usług takich jak HipChat", 249 "subscription.features.onpremise" : "Dodawanie lokalnych\/hostowanych usług takich jak HipChat",
239 "subscription.features.onpremise.mattermost" : "Add on-premise\/hosted services like Mattermost", 250 "subscription.features.onpremise.mattermost" : "Add on-premise\/hosted services like Mattermost",
240 "subscription.features.proxy" : "Proxy support for services", 251 "subscription.features.proxy" : "Obsługa proxy dla usług",
241 "subscription.features.spellchecker" : "Support for spellchecker", 252 "subscription.features.spellchecker" : "Support for spellchecker",
242 "subscription.includedFeatures" : "Płatne konto Franz Premium obejmuje", 253 "subscription.includedFeatures" : "Płatne konto Franz Premium obejmuje",
243 "subscription.paymentSessionError" : "Nie można wczytać formularza płatności\"", 254 "subscription.paymentSessionError" : "Nie można wczytać formularza płatności\"",
diff --git a/src/i18n/locales/pt-BR.json b/src/i18n/locales/pt-BR.json
index 70f6431df..c0cf0039f 100644
--- a/src/i18n/locales/pt-BR.json
+++ b/src/i18n/locales/pt-BR.json
@@ -1,7 +1,9 @@
1{ 1{
2 "feature.delayApp.action" : "Get a Franz Supporter License", 2 "app.errorHandler.action" : "Recarregar",
3 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting", 3 "app.errorHandler.headline" : "Something went wrong",
4 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.", 4 "feature.delayApp.action" : "Adquira uma licença Franz Supporter",
5 "feature.delayApp.headline" : "Por favor, adquira uma licença Franz Supporter para pular o tempo de espera",
6 "feature.delayApp.text" : "Franz continuará em {seconds} segundos.",
5 "global.api.unhealthy" : "Não foi possível conectar-se aos serviços on-line do Franz.", 7 "global.api.unhealthy" : "Não foi possível conectar-se aos serviços on-line do Franz.",
6 "global.notConnectedToTheInternet" : "Você não está conectado à internet", 8 "global.notConnectedToTheInternet" : "Você não está conectado à internet",
7 "import.headline" : "Importe seus serviços do Franz 4 ", 9 "import.headline" : "Importe seus serviços do Franz 4 ",
@@ -79,7 +81,7 @@
79 "password.noUser" : "Nenhum usuário com este e-mail foi encontrado", 81 "password.noUser" : "Nenhum usuário com este e-mail foi encontrado",
80 "password.submit.label" : "Enviar", 82 "password.submit.label" : "Enviar",
81 "password.successInfo" : "Por favor, verifique o seu e-mail", 83 "password.successInfo" : "Por favor, verifique o seu e-mail",
82 "premiumFeature.button.upgradeAccount" : "Upgrade account", 84 "premiumFeature.button.upgradeAccount" : "Atualizar conta",
83 "pricing.headline" : "Apoie o Franz", 85 "pricing.headline" : "Apoie o Franz",
84 "pricing.link.skipPayment" : "Eu não quero apoiar o desenvolvimento do Franz.", 86 "pricing.link.skipPayment" : "Eu não quero apoiar o desenvolvimento do Franz.",
85 "pricing.submit.label" : "Eu quero apoiar o desenvolvimento do Franz", 87 "pricing.submit.label" : "Eu quero apoiar o desenvolvimento do Franz",
@@ -90,6 +92,11 @@
90 "service.crashHandler.text" : "{name} causou um erro.", 92 "service.crashHandler.text" : "{name} causou um erro.",
91 "service.disabledHandler.action" : "Ativar {name}", 93 "service.disabledHandler.action" : "Ativar {name}",
92 "service.disabledHandler.headline" : "{name} está desativado", 94 "service.disabledHandler.headline" : "{name} está desativado",
95 "service.errorHandler.action" : "Recarregar {name}",
96 "service.errorHandler.editAction" : "Editar {name}",
97 "service.errorHandler.headline" : "Ah, não!",
98 "service.errorHandler.message" : "Erro",
99 "service.errorHandler.text" : "{name} has failed to load.",
93 "services.getStarted" : "Iniciar", 100 "services.getStarted" : "Iniciar",
94 "services.welcome" : "Bem-vindo ao Franz!", 101 "services.welcome" : "Bem-vindo ao Franz!",
95 "settings.account.account.editButton" : "Editar conta", 102 "settings.account.account.editButton" : "Editar conta",
@@ -120,7 +127,7 @@
120 "settings.app.form.autoLaunchInBackground" : "Abrir em segundo plano", 127 "settings.app.form.autoLaunchInBackground" : "Abrir em segundo plano",
121 "settings.app.form.autoLaunchOnStart" : "Abrir o Franz ao iniciar o sistema", 128 "settings.app.form.autoLaunchOnStart" : "Abrir o Franz ao iniciar o sistema",
122 "settings.app.form.beta" : "Incluir versões beta", 129 "settings.app.form.beta" : "Incluir versões beta",
123 "settings.app.form.darkMode" : "Join the Dark Side", 130 "settings.app.form.darkMode" : "Venha para o Lado Negro da força",
124 "settings.app.form.enableGPUAcceleration" : "Ativar Aceleração de GPU", 131 "settings.app.form.enableGPUAcceleration" : "Ativar Aceleração de GPU",
125 "settings.app.form.enableMenuBar" : "Mostrar Franz na Barra de Menu", 132 "settings.app.form.enableMenuBar" : "Mostrar Franz na Barra de Menu",
126 "settings.app.form.enableSpellchecking" : "Ativar correção ortográfica", 133 "settings.app.form.enableSpellchecking" : "Ativar correção ortográfica",
@@ -131,7 +138,7 @@
131 "settings.app.form.runInBackground" : "Manter o Franz no fundo quando fechar a janela", 138 "settings.app.form.runInBackground" : "Manter o Franz no fundo quando fechar a janela",
132 "settings.app.form.showDisabledServices" : "Mostrar abas de serviços desativados", 139 "settings.app.form.showDisabledServices" : "Mostrar abas de serviços desativados",
133 "settings.app.form.showMessagesBadgesWhenMuted" : "Mostrar ícone de mensagem não lida quando as notificações estiverem desativadas", 140 "settings.app.form.showMessagesBadgesWhenMuted" : "Mostrar ícone de mensagem não lida quando as notificações estiverem desativadas",
134 "settings.app.form.spellcheckerLanguage" : "Spell checking language", 141 "settings.app.form.spellcheckerLanguage" : "Idioma de verificação ortográfica",
135 "settings.app.headline" : "Configurações", 142 "settings.app.headline" : "Configurações",
136 "settings.app.headlineAdvanced" : "Avançado", 143 "settings.app.headlineAdvanced" : "Avançado",
137 "settings.app.headlineAppearance" : "Aparência", 144 "settings.app.headlineAppearance" : "Aparência",
@@ -172,7 +179,7 @@
172 "settings.service.form.editServiceHeadline" : "Editar {name}", 179 "settings.service.form.editServiceHeadline" : "Editar {name}",
173 "settings.service.form.enableAudio" : "Ativar áudio", 180 "settings.service.form.enableAudio" : "Ativar áudio",
174 "settings.service.form.enableBadge" : "Mostrar aviso de mensagens não lidas", 181 "settings.service.form.enableBadge" : "Mostrar aviso de mensagens não lidas",
175 "settings.service.form.enableDarkMode" : "Enable Dark Mode", 182 "settings.service.form.enableDarkMode" : "Ativar modo noturno",
176 "settings.service.form.enableNotification" : "Ativar notificações", 183 "settings.service.form.enableNotification" : "Ativar notificações",
177 "settings.service.form.enableService" : "Ativar serviço", 184 "settings.service.form.enableService" : "Ativar serviço",
178 "settings.service.form.headlineBadges" : "Mensagens não lidas", 185 "settings.service.form.headlineBadges" : "Mensagens não lidas",
@@ -185,13 +192,17 @@
185 "settings.service.form.indirectMessages" : "Mostrar avisos para todas as mensagens", 192 "settings.service.form.indirectMessages" : "Mostrar avisos para todas as mensagens",
186 "settings.service.form.isMutedInfo" : "Quando desativado, as notificações sonoras e áudios ficarão em silêncio", 193 "settings.service.form.isMutedInfo" : "Quando desativado, as notificações sonoras e áudios ficarão em silêncio",
187 "settings.service.form.name" : "Nome", 194 "settings.service.form.name" : "Nome",
188 "settings.service.form.proxy.headline" : "Proxy Settings", 195 "settings.service.form.proxy.headline" : "HTTP\/HTTPS Proxy Settings",
189 "settings.service.form.proxy.host" : "Proxy Host\/IP", 196 "settings.service.form.proxy.host" : "Proxy Host\/IP",
190 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.", 197 "settings.service.form.proxy.info" : "As configurações de proxy não serão sincronizadas com os servidores do Franz.",
191 "settings.service.form.proxy.isEnabled" : "Use Proxy", 198 "settings.service.form.proxy.isEnabled" : "Usar o Proxy",
192 "settings.service.form.proxy.password" : "Password (optional)", 199 "settings.service.form.proxy.password" : "Senha (opcional)",
193 "settings.service.form.proxy.user" : "User (optional)", 200 "settings.service.form.proxy.port" : "Port",
201 "settings.service.form.proxy.restartInfo" : "Please restart Franz after changing proxy Settings.",
202 "settings.service.form.proxy.user" : "Usuário (opcional)",
194 "settings.service.form.saveButton" : "Salvar serviço", 203 "settings.service.form.saveButton" : "Salvar serviço",
204 "settings.service.form.spellcheckerLanguage" : "Spell checking Language",
205 "settings.service.form.spellcheckerLanguage.default" : "Use System Default ({default})",
195 "settings.service.form.tabHosted" : "Hospedado", 206 "settings.service.form.tabHosted" : "Hospedado",
196 "settings.service.form.tabOnPremise" : "Auto-hospedado ⭐️", 207 "settings.service.form.tabOnPremise" : "Auto-hospedado ⭐️",
197 "settings.service.form.team" : "Equipe", 208 "settings.service.form.team" : "Equipe",
@@ -237,8 +248,8 @@
237 "subscription.features.noInterruptions" : "No app delays & nagging to upgrade license", 248 "subscription.features.noInterruptions" : "No app delays & nagging to upgrade license",
238 "subscription.features.onpremise" : "Adicionar serviços locais\/hospedados como o HipChat", 249 "subscription.features.onpremise" : "Adicionar serviços locais\/hospedados como o HipChat",
239 "subscription.features.onpremise.mattermost" : "Add on-premise\/hosted services like Mattermost", 250 "subscription.features.onpremise.mattermost" : "Add on-premise\/hosted services like Mattermost",
240 "subscription.features.proxy" : "Proxy support for services", 251 "subscription.features.proxy" : "Suporte de proxy para serviços",
241 "subscription.features.spellchecker" : "Support for spellchecker", 252 "subscription.features.spellchecker" : "Suporte para corretor ortográfico",
242 "subscription.includedFeatures" : "A conta Apoiador Franz Premium inclui", 253 "subscription.includedFeatures" : "A conta Apoiador Franz Premium inclui",
243 "subscription.paymentSessionError" : "Não foi possível abrir o formulário de pagamento", 254 "subscription.paymentSessionError" : "Não foi possível abrir o formulário de pagamento",
244 "subscription.submit.label" : "Eu quero apoiar o desenvolvimento do Franz", 255 "subscription.submit.label" : "Eu quero apoiar o desenvolvimento do Franz",
diff --git a/src/i18n/locales/pt.json b/src/i18n/locales/pt.json
index 71978ed6b..80e8094f5 100644
--- a/src/i18n/locales/pt.json
+++ b/src/i18n/locales/pt.json
@@ -1,4 +1,6 @@
1{ 1{
2 "app.errorHandler.action" : "Recarregar",
3 "app.errorHandler.headline" : "Something went wrong",
2 "feature.delayApp.action" : "Get a Franz Supporter License", 4 "feature.delayApp.action" : "Get a Franz Supporter License",
3 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting", 5 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting",
4 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.", 6 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.",
@@ -90,6 +92,11 @@
90 "service.crashHandler.text" : "{name} causou um erro.", 92 "service.crashHandler.text" : "{name} causou um erro.",
91 "service.disabledHandler.action" : "Ativar {name}", 93 "service.disabledHandler.action" : "Ativar {name}",
92 "service.disabledHandler.headline" : "{name} está desativado", 94 "service.disabledHandler.headline" : "{name} está desativado",
95 "service.errorHandler.action" : "Recarregar {name}",
96 "service.errorHandler.editAction" : "Editar {name}",
97 "service.errorHandler.headline" : "Oh não!",
98 "service.errorHandler.message" : "Erro",
99 "service.errorHandler.text" : "{name} has failed to load.",
93 "services.getStarted" : "Vamos começar", 100 "services.getStarted" : "Vamos começar",
94 "services.welcome" : "Bem-vindo ao Franz", 101 "services.welcome" : "Bem-vindo ao Franz",
95 "settings.account.account.editButton" : "Editar conta", 102 "settings.account.account.editButton" : "Editar conta",
@@ -185,13 +192,17 @@
185 "settings.service.form.indirectMessages" : "Mostrar o emblema da mensagem para todas as novas mensagens", 192 "settings.service.form.indirectMessages" : "Mostrar o emblema da mensagem para todas as novas mensagens",
186 "settings.service.form.isMutedInfo" : "Quando desativado, todos sons e reproduções de áudio serão silenciados", 193 "settings.service.form.isMutedInfo" : "Quando desativado, todos sons e reproduções de áudio serão silenciados",
187 "settings.service.form.name" : "Nome", 194 "settings.service.form.name" : "Nome",
188 "settings.service.form.proxy.headline" : "Proxy Settings", 195 "settings.service.form.proxy.headline" : "HTTP\/HTTPS Proxy Settings",
189 "settings.service.form.proxy.host" : "Proxy Host\/IP", 196 "settings.service.form.proxy.host" : "Proxy Host\/IP",
190 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.", 197 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.",
191 "settings.service.form.proxy.isEnabled" : "Use Proxy", 198 "settings.service.form.proxy.isEnabled" : "Use Proxy",
192 "settings.service.form.proxy.password" : "Password (optional)", 199 "settings.service.form.proxy.password" : "Password (optional)",
200 "settings.service.form.proxy.port" : "Port",
201 "settings.service.form.proxy.restartInfo" : "Please restart Franz after changing proxy Settings.",
193 "settings.service.form.proxy.user" : "User (optional)", 202 "settings.service.form.proxy.user" : "User (optional)",
194 "settings.service.form.saveButton" : "Guardar serviço", 203 "settings.service.form.saveButton" : "Guardar serviço",
204 "settings.service.form.spellcheckerLanguage" : "Spell checking Language",
205 "settings.service.form.spellcheckerLanguage.default" : "Use System Default ({default})",
195 "settings.service.form.tabHosted" : "Com domínio", 206 "settings.service.form.tabHosted" : "Com domínio",
196 "settings.service.form.tabOnPremise" : "Com domínio próprio ⭐️", 207 "settings.service.form.tabOnPremise" : "Com domínio próprio ⭐️",
197 "settings.service.form.team" : "Equipa", 208 "settings.service.form.team" : "Equipa",
diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json
index 78030dc15..b605afc4d 100644
--- a/src/i18n/locales/ru.json
+++ b/src/i18n/locales/ru.json
@@ -1,4 +1,6 @@
1{ 1{
2 "app.errorHandler.action" : "Перезагрузить",
3 "app.errorHandler.headline" : "Something went wrong",
2 "feature.delayApp.action" : "Get a Franz Supporter License", 4 "feature.delayApp.action" : "Get a Franz Supporter License",
3 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting", 5 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting",
4 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.", 6 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.",
@@ -90,6 +92,11 @@
90 "service.crashHandler.text" : "{name} вызвало ошибку.", 92 "service.crashHandler.text" : "{name} вызвало ошибку.",
91 "service.disabledHandler.action" : "Включить {name}", 93 "service.disabledHandler.action" : "Включить {name}",
92 "service.disabledHandler.headline" : "{name} выключено", 94 "service.disabledHandler.headline" : "{name} выключено",
95 "service.errorHandler.action" : "Перезагрузить {name}",
96 "service.errorHandler.editAction" : "Редактирование {name}",
97 "service.errorHandler.headline" : "О, нет!",
98 "service.errorHandler.message" : "Ошибка",
99 "service.errorHandler.text" : "{name} has failed to load.",
93 "services.getStarted" : "Начать работу", 100 "services.getStarted" : "Начать работу",
94 "services.welcome" : "Добро пожаловать во Franz", 101 "services.welcome" : "Добро пожаловать во Franz",
95 "settings.account.account.editButton" : "Редактировать аккаунт", 102 "settings.account.account.editButton" : "Редактировать аккаунт",
@@ -185,13 +192,17 @@
185 "settings.service.form.indirectMessages" : "Показывать значок уведомлений для всех новых сообщений", 192 "settings.service.form.indirectMessages" : "Показывать значок уведомлений для всех новых сообщений",
186 "settings.service.form.isMutedInfo" : "Когда выключено, все звуковые уведомления будут отключены", 193 "settings.service.form.isMutedInfo" : "Когда выключено, все звуковые уведомления будут отключены",
187 "settings.service.form.name" : "Название", 194 "settings.service.form.name" : "Название",
188 "settings.service.form.proxy.headline" : "Proxy Settings", 195 "settings.service.form.proxy.headline" : "HTTP\/HTTPS Proxy Settings",
189 "settings.service.form.proxy.host" : "Proxy Host\/IP", 196 "settings.service.form.proxy.host" : "Proxy Host\/IP",
190 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.", 197 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.",
191 "settings.service.form.proxy.isEnabled" : "Use Proxy", 198 "settings.service.form.proxy.isEnabled" : "Use Proxy",
192 "settings.service.form.proxy.password" : "Password (optional)", 199 "settings.service.form.proxy.password" : "Password (optional)",
200 "settings.service.form.proxy.port" : "Port",
201 "settings.service.form.proxy.restartInfo" : "Please restart Franz after changing proxy Settings.",
193 "settings.service.form.proxy.user" : "User (optional)", 202 "settings.service.form.proxy.user" : "User (optional)",
194 "settings.service.form.saveButton" : "Сохранить сервис", 203 "settings.service.form.saveButton" : "Сохранить сервис",
204 "settings.service.form.spellcheckerLanguage" : "Spell checking Language",
205 "settings.service.form.spellcheckerLanguage.default" : "Use System Default ({default})",
195 "settings.service.form.tabHosted" : "Размещено", 206 "settings.service.form.tabHosted" : "Размещено",
196 "settings.service.form.tabOnPremise" : "Свой хостинг ⭐️", 207 "settings.service.form.tabOnPremise" : "Свой хостинг ⭐️",
197 "settings.service.form.team" : "Команда", 208 "settings.service.form.team" : "Команда",
diff --git a/src/i18n/locales/sk.json b/src/i18n/locales/sk.json
index 70e3e7f2d..ca7335fe9 100644
--- a/src/i18n/locales/sk.json
+++ b/src/i18n/locales/sk.json
@@ -1,4 +1,6 @@
1{ 1{
2 "app.errorHandler.action" : "Obnoviť",
3 "app.errorHandler.headline" : "Something went wrong",
2 "feature.delayApp.action" : "Get a Franz Supporter License", 4 "feature.delayApp.action" : "Get a Franz Supporter License",
3 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting", 5 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting",
4 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.", 6 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.",
@@ -90,6 +92,11 @@
90 "service.crashHandler.text" : "{name} spôsobil chybu.", 92 "service.crashHandler.text" : "{name} spôsobil chybu.",
91 "service.disabledHandler.action" : "Zapnúť {name}", 93 "service.disabledHandler.action" : "Zapnúť {name}",
92 "service.disabledHandler.headline" : "{name} je vypnuté", 94 "service.disabledHandler.headline" : "{name} je vypnuté",
95 "service.errorHandler.action" : "Znovu načítať {name}",
96 "service.errorHandler.editAction" : "Upraviť {name}",
97 "service.errorHandler.headline" : "Ale nie!",
98 "service.errorHandler.message" : "Chyba",
99 "service.errorHandler.text" : "{name} has failed to load.",
93 "services.getStarted" : "Začíname", 100 "services.getStarted" : "Začíname",
94 "services.welcome" : "Vítajte v aplikácii Franz", 101 "services.welcome" : "Vítajte v aplikácii Franz",
95 "settings.account.account.editButton" : "Upraviť účet", 102 "settings.account.account.editButton" : "Upraviť účet",
@@ -185,13 +192,17 @@
185 "settings.service.form.indirectMessages" : "Zobraziť symbol správy pre všetky nové správy", 192 "settings.service.form.indirectMessages" : "Zobraziť symbol správy pre všetky nové správy",
186 "settings.service.form.isMutedInfo" : "Ak je vypnuté, všetky zvuky oznámení a iné prehrávania budú stíšené", 193 "settings.service.form.isMutedInfo" : "Ak je vypnuté, všetky zvuky oznámení a iné prehrávania budú stíšené",
187 "settings.service.form.name" : "Meno", 194 "settings.service.form.name" : "Meno",
188 "settings.service.form.proxy.headline" : "Proxy Settings", 195 "settings.service.form.proxy.headline" : "HTTP\/HTTPS Proxy Settings",
189 "settings.service.form.proxy.host" : "Proxy Host\/IP", 196 "settings.service.form.proxy.host" : "Proxy Host\/IP",
190 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.", 197 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.",
191 "settings.service.form.proxy.isEnabled" : "Use Proxy", 198 "settings.service.form.proxy.isEnabled" : "Use Proxy",
192 "settings.service.form.proxy.password" : "Password (optional)", 199 "settings.service.form.proxy.password" : "Password (optional)",
200 "settings.service.form.proxy.port" : "Port",
201 "settings.service.form.proxy.restartInfo" : "Please restart Franz after changing proxy Settings.",
193 "settings.service.form.proxy.user" : "User (optional)", 202 "settings.service.form.proxy.user" : "User (optional)",
194 "settings.service.form.saveButton" : "Uložiť službu", 203 "settings.service.form.saveButton" : "Uložiť službu",
204 "settings.service.form.spellcheckerLanguage" : "Spell checking Language",
205 "settings.service.form.spellcheckerLanguage.default" : "Use System Default ({default})",
195 "settings.service.form.tabHosted" : "Hostované", 206 "settings.service.form.tabHosted" : "Hostované",
196 "settings.service.form.tabOnPremise" : "Vlastné hostovanie ⭐️", 207 "settings.service.form.tabOnPremise" : "Vlastné hostovanie ⭐️",
197 "settings.service.form.team" : "Tím", 208 "settings.service.form.team" : "Tím",
diff --git a/src/i18n/locales/sr.json b/src/i18n/locales/sr.json
index dd4c74277..df0b849c4 100644
--- a/src/i18n/locales/sr.json
+++ b/src/i18n/locales/sr.json
@@ -1,4 +1,6 @@
1{ 1{
2 "app.errorHandler.action" : "Ponovno učitavanje",
3 "app.errorHandler.headline" : "Something went wrong",
2 "feature.delayApp.action" : "Get a Franz Supporter License", 4 "feature.delayApp.action" : "Get a Franz Supporter License",
3 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting", 5 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting",
4 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.", 6 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.",
@@ -90,6 +92,11 @@
90 "service.crashHandler.text" : "{ime} je izazvalo grešku. ", 92 "service.crashHandler.text" : "{ime} je izazvalo grešku. ",
91 "service.disabledHandler.action" : "Omogući {ime} ", 93 "service.disabledHandler.action" : "Omogući {ime} ",
92 "service.disabledHandler.headline" : "{ime} je onemogućen\/o", 94 "service.disabledHandler.headline" : "{ime} je onemogućen\/o",
95 "service.errorHandler.action" : "Osvježi {ime}",
96 "service.errorHandler.editAction" : "Uredite {ime}",
97 "service.errorHandler.headline" : "O, ne! ",
98 "service.errorHandler.message" : "Greška ",
99 "service.errorHandler.text" : "{name} has failed to load.",
93 "services.getStarted" : "Započnimo! ", 100 "services.getStarted" : "Započnimo! ",
94 "services.welcome" : "Dobrodošli u Franz", 101 "services.welcome" : "Dobrodošli u Franz",
95 "settings.account.account.editButton" : "Uredi račun", 102 "settings.account.account.editButton" : "Uredi račun",
@@ -185,13 +192,17 @@
185 "settings.service.form.indirectMessages" : "Prikaži značku na svim novim porukuama", 192 "settings.service.form.indirectMessages" : "Prikaži značku na svim novim porukuama",
186 "settings.service.form.isMutedInfo" : "Kada je onemogućeno, sve obavijesti, svi zvukovi i sva pozadinska podrška će biti nečujna.", 193 "settings.service.form.isMutedInfo" : "Kada je onemogućeno, sve obavijesti, svi zvukovi i sva pozadinska podrška će biti nečujna.",
187 "settings.service.form.name" : "Ime", 194 "settings.service.form.name" : "Ime",
188 "settings.service.form.proxy.headline" : "Proxy Settings", 195 "settings.service.form.proxy.headline" : "HTTP\/HTTPS Proxy Settings",
189 "settings.service.form.proxy.host" : "Proxy Host\/IP", 196 "settings.service.form.proxy.host" : "Proxy Host\/IP",
190 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.", 197 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.",
191 "settings.service.form.proxy.isEnabled" : "Use Proxy", 198 "settings.service.form.proxy.isEnabled" : "Use Proxy",
192 "settings.service.form.proxy.password" : "Password (optional)", 199 "settings.service.form.proxy.password" : "Password (optional)",
200 "settings.service.form.proxy.port" : "Port",
201 "settings.service.form.proxy.restartInfo" : "Please restart Franz after changing proxy Settings.",
193 "settings.service.form.proxy.user" : "User (optional)", 202 "settings.service.form.proxy.user" : "User (optional)",
194 "settings.service.form.saveButton" : "Sačuvaj uslugu\/e", 203 "settings.service.form.saveButton" : "Sačuvaj uslugu\/e",
204 "settings.service.form.spellcheckerLanguage" : "Spell checking Language",
205 "settings.service.form.spellcheckerLanguage.default" : "Use System Default ({default})",
195 "settings.service.form.tabHosted" : "Hostovano", 206 "settings.service.form.tabHosted" : "Hostovano",
196 "settings.service.form.tabOnPremise" : "Samo-hostovano ⭐️", 207 "settings.service.form.tabOnPremise" : "Samo-hostovano ⭐️",
197 "settings.service.form.team" : "Tim", 208 "settings.service.form.team" : "Tim",
diff --git a/src/i18n/locales/tr.json b/src/i18n/locales/tr.json
index 130b51ddf..9e7619454 100644
--- a/src/i18n/locales/tr.json
+++ b/src/i18n/locales/tr.json
@@ -1,11 +1,13 @@
1{ 1{
2 "feature.delayApp.action" : "Get a Franz Supporter License", 2 "app.errorHandler.action" : "Yeniden Yükle",
3 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting", 3 "app.errorHandler.headline" : "Something went wrong",
4 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.", 4 "feature.delayApp.action" : "Franz Destek Lisansı'nı alın.",
5 "feature.delayApp.headline" : "Beklememek için Franz Destek Lisansı'nı satın alın.",
6 "feature.delayApp.text" : "Franz {seconds} saniye sonra devam edecek.",
5 "global.api.unhealthy" : "Franz hizmetlerine şu anda erişilemiyor.", 7 "global.api.unhealthy" : "Franz hizmetlerine şu anda erişilemiyor.",
6 "global.notConnectedToTheInternet" : "İnternete bağlı değilsiniz.", 8 "global.notConnectedToTheInternet" : "İnternete bağlı değilsiniz.",
7 "import.headline" : "Franz 4 servislerinizi ekleyin.", 9 "import.headline" : "Franz 4 servislerinizi ekleyin.",
8 "import.notSupportedHeadline" : "Servisler henüz Franz 5'de desteklenmiyor.", 10 "import.notSupportedHeadline" : "Servisler henüz Franz 5'te desteklenmiyor.",
9 "import.skip.label" : "Servisleri kendim eklemek istiyorum", 11 "import.skip.label" : "Servisleri kendim eklemek istiyorum",
10 "import.submit.label" : "Servisleri içe aktar", 12 "import.submit.label" : "Servisleri içe aktar",
11 "infobar.buttonChangelog" : "Yeni ne var?", 13 "infobar.buttonChangelog" : "Yeni ne var?",
@@ -90,6 +92,11 @@
90 "service.crashHandler.text" : "{name} bir hataya neden oldu.", 92 "service.crashHandler.text" : "{name} bir hataya neden oldu.",
91 "service.disabledHandler.action" : "{name} aktif", 93 "service.disabledHandler.action" : "{name} aktif",
92 "service.disabledHandler.headline" : "{name} devredışı", 94 "service.disabledHandler.headline" : "{name} devredışı",
95 "service.errorHandler.action" : "{name} yeniden yükle",
96 "service.errorHandler.editAction" : "{name} düzenle",
97 "service.errorHandler.headline" : "Aman Tanrım hayır!",
98 "service.errorHandler.message" : "Hata",
99 "service.errorHandler.text" : "{name} has failed to load.",
93 "services.getStarted" : "Haydi başlayalım", 100 "services.getStarted" : "Haydi başlayalım",
94 "services.welcome" : "Franz'a Hoşgeldiniz", 101 "services.welcome" : "Franz'a Hoşgeldiniz",
95 "settings.account.account.editButton" : "Hesabı düzenle", 102 "settings.account.account.editButton" : "Hesabı düzenle",
@@ -185,13 +192,17 @@
185 "settings.service.form.indirectMessages" : "Mesaj rozetini tüm yeni mesajlar için göster", 192 "settings.service.form.indirectMessages" : "Mesaj rozetini tüm yeni mesajlar için göster",
186 "settings.service.form.isMutedInfo" : "Devre dışı bırakıldığında, tüm bildirim sesleri sessize alınır", 193 "settings.service.form.isMutedInfo" : "Devre dışı bırakıldığında, tüm bildirim sesleri sessize alınır",
187 "settings.service.form.name" : "İsim", 194 "settings.service.form.name" : "İsim",
188 "settings.service.form.proxy.headline" : "Proxy Settings", 195 "settings.service.form.proxy.headline" : "HTTP\/HTTPS Proxy Settings",
189 "settings.service.form.proxy.host" : "Proxy Host\/IP", 196 "settings.service.form.proxy.host" : "Proxy Host\/IP",
190 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.", 197 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.",
191 "settings.service.form.proxy.isEnabled" : "Use Proxy", 198 "settings.service.form.proxy.isEnabled" : "Use Proxy",
192 "settings.service.form.proxy.password" : "Password (optional)", 199 "settings.service.form.proxy.password" : "Password (optional)",
200 "settings.service.form.proxy.port" : "Port",
201 "settings.service.form.proxy.restartInfo" : "Please restart Franz after changing proxy Settings.",
193 "settings.service.form.proxy.user" : "User (optional)", 202 "settings.service.form.proxy.user" : "User (optional)",
194 "settings.service.form.saveButton" : "Servisi kaydet", 203 "settings.service.form.saveButton" : "Servisi kaydet",
204 "settings.service.form.spellcheckerLanguage" : "Spell checking Language",
205 "settings.service.form.spellcheckerLanguage.default" : "Use System Default ({default})",
195 "settings.service.form.tabHosted" : "Barındırılan", 206 "settings.service.form.tabHosted" : "Barındırılan",
196 "settings.service.form.tabOnPremise" : "Kendi barındırılan", 207 "settings.service.form.tabOnPremise" : "Kendi barındırılan",
197 "settings.service.form.team" : "Takım", 208 "settings.service.form.team" : "Takım",
diff --git a/src/i18n/locales/uk.json b/src/i18n/locales/uk.json
index 94c6f7e54..7d51b380e 100644
--- a/src/i18n/locales/uk.json
+++ b/src/i18n/locales/uk.json
@@ -1,4 +1,6 @@
1{ 1{
2 "app.errorHandler.action" : "Перезавантажити",
3 "app.errorHandler.headline" : "Something went wrong",
2 "feature.delayApp.action" : "Get a Franz Supporter License", 4 "feature.delayApp.action" : "Get a Franz Supporter License",
3 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting", 5 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting",
4 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.", 6 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.",
@@ -90,6 +92,11 @@
90 "service.crashHandler.text" : "{name} призвів до помилки.", 92 "service.crashHandler.text" : "{name} призвів до помилки.",
91 "service.disabledHandler.action" : "Увімкнути {name} ", 93 "service.disabledHandler.action" : "Увімкнути {name} ",
92 "service.disabledHandler.headline" : "{name} вимкнено", 94 "service.disabledHandler.headline" : "{name} вимкнено",
95 "service.errorHandler.action" : "Перезавантажити {name}",
96 "service.errorHandler.editAction" : "Редагувати {name}",
97 "service.errorHandler.headline" : "О, ні!",
98 "service.errorHandler.message" : "Помилка",
99 "service.errorHandler.text" : "{name} has failed to load.",
93 "services.getStarted" : "Почати", 100 "services.getStarted" : "Почати",
94 "services.welcome" : "Ласкаво просимо в Franz", 101 "services.welcome" : "Ласкаво просимо в Franz",
95 "settings.account.account.editButton" : "Редагувати акаунт", 102 "settings.account.account.editButton" : "Редагувати акаунт",
@@ -185,13 +192,17 @@
185 "settings.service.form.indirectMessages" : "Показувати значок повідомлення для всіх нових повідомлень", 192 "settings.service.form.indirectMessages" : "Показувати значок повідомлення для всіх нових повідомлень",
186 "settings.service.form.isMutedInfo" : "Коли вимкнено, всі сповищення та відтворення ігноруються", 193 "settings.service.form.isMutedInfo" : "Коли вимкнено, всі сповищення та відтворення ігноруються",
187 "settings.service.form.name" : "Ім'я", 194 "settings.service.form.name" : "Ім'я",
188 "settings.service.form.proxy.headline" : "Proxy Settings", 195 "settings.service.form.proxy.headline" : "HTTP\/HTTPS Proxy Settings",
189 "settings.service.form.proxy.host" : "Proxy Host\/IP", 196 "settings.service.form.proxy.host" : "Proxy Host\/IP",
190 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.", 197 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.",
191 "settings.service.form.proxy.isEnabled" : "Use Proxy", 198 "settings.service.form.proxy.isEnabled" : "Use Proxy",
192 "settings.service.form.proxy.password" : "Password (optional)", 199 "settings.service.form.proxy.password" : "Password (optional)",
200 "settings.service.form.proxy.port" : "Port",
201 "settings.service.form.proxy.restartInfo" : "Please restart Franz after changing proxy Settings.",
193 "settings.service.form.proxy.user" : "User (optional)", 202 "settings.service.form.proxy.user" : "User (optional)",
194 "settings.service.form.saveButton" : "Зберегти сервіс", 203 "settings.service.form.saveButton" : "Зберегти сервіс",
204 "settings.service.form.spellcheckerLanguage" : "Spell checking Language",
205 "settings.service.form.spellcheckerLanguage.default" : "Use System Default ({default})",
195 "settings.service.form.tabHosted" : "Розміщений", 206 "settings.service.form.tabHosted" : "Розміщений",
196 "settings.service.form.tabOnPremise" : "Самостійно розміщений ⭐️", 207 "settings.service.form.tabOnPremise" : "Самостійно розміщений ⭐️",
197 "settings.service.form.team" : "Команда", 208 "settings.service.form.team" : "Команда",
diff --git a/src/i18n/locales/zh-TW.json b/src/i18n/locales/zh-TW.json
index 23bff247d..cfb1d3dce 100644
--- a/src/i18n/locales/zh-TW.json
+++ b/src/i18n/locales/zh-TW.json
@@ -1,11 +1,13 @@
1{ 1{
2 "app.errorHandler.action" : "重新載入",
3 "app.errorHandler.headline" : "有些東西出錯了",
2 "feature.delayApp.action" : "Get a Franz Supporter License", 4 "feature.delayApp.action" : "Get a Franz Supporter License",
3 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting", 5 "feature.delayApp.headline" : "Please purchase a Franz Supporter License to skip waiting",
4 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.", 6 "feature.delayApp.text" : "Franz will continue in {seconds} seconds.",
5 "global.api.unhealthy" : "無法連線至Franz的伺服器。", 7 "global.api.unhealthy" : "無法連線至Franz的伺服器。",
6 "global.notConnectedToTheInternet" : "你沒有連上網路。", 8 "global.notConnectedToTheInternet" : "你沒有連上網路。",
7 "import.headline" : "匯入Franz 4的服務", 9 "import.headline" : "匯入 Franz 4 的服務",
8 "import.notSupportedHeadline" : "Franz 5尚未支援此服務", 10 "import.notSupportedHeadline" : "Franz 5 尚未支援此服務",
9 "import.skip.label" : "手動添加服務", 11 "import.skip.label" : "手動添加服務",
10 "import.submit.label" : "匯入服務", 12 "import.submit.label" : "匯入服務",
11 "infobar.buttonChangelog" : "有什麼新功能呢?", 13 "infobar.buttonChangelog" : "有什麼新功能呢?",
@@ -79,7 +81,7 @@
79 "password.noUser" : "找不到該電子郵件地址的用戶", 81 "password.noUser" : "找不到該電子郵件地址的用戶",
80 "password.submit.label" : "送出", 82 "password.submit.label" : "送出",
81 "password.successInfo" : "請檢查您的電子郵件", 83 "password.successInfo" : "請檢查您的電子郵件",
82 "premiumFeature.button.upgradeAccount" : "Upgrade account", 84 "premiumFeature.button.upgradeAccount" : "升級帳號",
83 "pricing.headline" : "支持Franz", 85 "pricing.headline" : "支持Franz",
84 "pricing.link.skipPayment" : "我不想支持Franz的開發。", 86 "pricing.link.skipPayment" : "我不想支持Franz的開發。",
85 "pricing.submit.label" : "我想支持Franz的開發。", 87 "pricing.submit.label" : "我想支持Franz的開發。",
@@ -90,6 +92,11 @@
90 "service.crashHandler.text" : "{name}導致了一個錯誤。", 92 "service.crashHandler.text" : "{name}導致了一個錯誤。",
91 "service.disabledHandler.action" : "啟用{name}", 93 "service.disabledHandler.action" : "啟用{name}",
92 "service.disabledHandler.headline" : "{name}已停用", 94 "service.disabledHandler.headline" : "{name}已停用",
95 "service.errorHandler.action" : "重新載入{name}",
96 "service.errorHandler.editAction" : "編輯{名稱}",
97 "service.errorHandler.headline" : "噢不!",
98 "service.errorHandler.message" : "錯誤",
99 "service.errorHandler.text" : "{name} has failed to load.",
93 "services.getStarted" : "開始", 100 "services.getStarted" : "開始",
94 "services.welcome" : "歡迎使用Franz", 101 "services.welcome" : "歡迎使用Franz",
95 "settings.account.account.editButton" : "編輯帳號", 102 "settings.account.account.editButton" : "編輯帳號",
@@ -131,7 +138,7 @@
131 "settings.app.form.runInBackground" : "當關閉視窗時保持Franz在背景運作", 138 "settings.app.form.runInBackground" : "當關閉視窗時保持Franz在背景運作",
132 "settings.app.form.showDisabledServices" : "顯示停用的服務標籤", 139 "settings.app.form.showDisabledServices" : "顯示停用的服務標籤",
133 "settings.app.form.showMessagesBadgesWhenMuted" : "當通知關閉時,標記未讀的訊息", 140 "settings.app.form.showMessagesBadgesWhenMuted" : "當通知關閉時,標記未讀的訊息",
134 "settings.app.form.spellcheckerLanguage" : "Spell checking language", 141 "settings.app.form.spellcheckerLanguage" : "拼字檢查語言",
135 "settings.app.headline" : "設定", 142 "settings.app.headline" : "設定",
136 "settings.app.headlineAdvanced" : "進階", 143 "settings.app.headlineAdvanced" : "進階",
137 "settings.app.headlineAppearance" : "外觀", 144 "settings.app.headlineAppearance" : "外觀",
@@ -172,7 +179,7 @@
172 "settings.service.form.editServiceHeadline" : "編輯{名稱}", 179 "settings.service.form.editServiceHeadline" : "編輯{名稱}",
173 "settings.service.form.enableAudio" : "啟用音訊", 180 "settings.service.form.enableAudio" : "啟用音訊",
174 "settings.service.form.enableBadge" : "顯示未讀訊息圖示", 181 "settings.service.form.enableBadge" : "顯示未讀訊息圖示",
175 "settings.service.form.enableDarkMode" : "Enable Dark Mode", 182 "settings.service.form.enableDarkMode" : "開啟深色模式",
176 "settings.service.form.enableNotification" : "啟用通知", 183 "settings.service.form.enableNotification" : "啟用通知",
177 "settings.service.form.enableService" : "啟用服務", 184 "settings.service.form.enableService" : "啟用服務",
178 "settings.service.form.headlineBadges" : "未讀訊息圖示", 185 "settings.service.form.headlineBadges" : "未讀訊息圖示",
@@ -185,13 +192,17 @@
185 "settings.service.form.indirectMessages" : "顯示所有新消息的消息標誌", 192 "settings.service.form.indirectMessages" : "顯示所有新消息的消息標誌",
186 "settings.service.form.isMutedInfo" : "停用時,所有通知聲和聲音播放都將靜音", 193 "settings.service.form.isMutedInfo" : "停用時,所有通知聲和聲音播放都將靜音",
187 "settings.service.form.name" : "名稱", 194 "settings.service.form.name" : "名稱",
188 "settings.service.form.proxy.headline" : "Proxy Settings", 195 "settings.service.form.proxy.headline" : "HTTP\/HTTPS Proxy Settings",
189 "settings.service.form.proxy.host" : "Proxy Host\/IP", 196 "settings.service.form.proxy.host" : "Proxy Host\/IP",
190 "settings.service.form.proxy.info" : "Proxy settings will not synced with the Franz servers.", 197 "settings.service.form.proxy.info" : "Proxy 設定不會與 Franz 伺服器同步",
191 "settings.service.form.proxy.isEnabled" : "Use Proxy", 198 "settings.service.form.proxy.isEnabled" : "使用 Proxy",
192 "settings.service.form.proxy.password" : "Password (optional)", 199 "settings.service.form.proxy.password" : "密碼 (選填)",
193 "settings.service.form.proxy.user" : "User (optional)", 200 "settings.service.form.proxy.port" : "Port",
201 "settings.service.form.proxy.restartInfo" : "Please restart Franz after changing proxy Settings.",
202 "settings.service.form.proxy.user" : "使用者 (選填)",
194 "settings.service.form.saveButton" : "保存服務", 203 "settings.service.form.saveButton" : "保存服務",
204 "settings.service.form.spellcheckerLanguage" : "Spell checking Language",
205 "settings.service.form.spellcheckerLanguage.default" : "Use System Default ({default})",
195 "settings.service.form.tabHosted" : "託管", 206 "settings.service.form.tabHosted" : "託管",
196 "settings.service.form.tabOnPremise" : "自我託管⭐️", 207 "settings.service.form.tabOnPremise" : "自我託管⭐️",
197 "settings.service.form.team" : "團隊", 208 "settings.service.form.team" : "團隊",
@@ -238,7 +249,7 @@
238 "subscription.features.onpremise" : "添加本地\/託管服務如HipChat", 249 "subscription.features.onpremise" : "添加本地\/託管服務如HipChat",
239 "subscription.features.onpremise.mattermost" : "Add on-premise\/hosted services like Mattermost", 250 "subscription.features.onpremise.mattermost" : "Add on-premise\/hosted services like Mattermost",
240 "subscription.features.proxy" : "Proxy support for services", 251 "subscription.features.proxy" : "Proxy support for services",
241 "subscription.features.spellchecker" : "Support for spellchecker", 252 "subscription.features.spellchecker" : "支援拼字檢查",
242 "subscription.includedFeatures" : "包含高級Franz付費帳戶", 253 "subscription.includedFeatures" : "包含高級Franz付費帳戶",
243 "subscription.paymentSessionError" : "無法初始化付款表單", 254 "subscription.paymentSessionError" : "無法初始化付款表單",
244 "subscription.submit.label" : "我想支持Franz的開發", 255 "subscription.submit.label" : "我想支持Franz的開發",
diff --git a/src/index.js b/src/index.js
index 7f3fe5b44..830166dcf 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,10 +1,14 @@
1import { app, BrowserWindow, shell, ipcMain } from 'electron'; 1import {
2 app, BrowserWindow, shell, ipcMain,
3} from 'electron';
2 4
3import fs from 'fs-extra'; 5import fs from 'fs-extra';
4import path from 'path'; 6import path from 'path';
5import windowStateKeeper from 'electron-window-state'; 7import windowStateKeeper from 'electron-window-state';
6 8
7import { isDevMode, isMac, isWindows, isLinux } from './environment'; 9import {
10 isDevMode, isMac, isWindows, isLinux,
11} from './environment';
8 12
9// DEV MODE: Save user data into FranzDev 13// DEV MODE: Save user data into FranzDev
10if (isDevMode) { 14if (isDevMode) {
diff --git a/src/lib/Tray.js b/src/lib/Tray.js
index 588fa75bf..669b02709 100644
--- a/src/lib/Tray.js
+++ b/src/lib/Tray.js
@@ -1,4 +1,6 @@
1import { app, Tray, Menu, systemPreferences, nativeImage } from 'electron'; 1import {
2 app, Tray, Menu, systemPreferences, nativeImage,
3} from 'electron';
2import path from 'path'; 4import path from 'path';
3 5
4const FILE_EXTENSION = process.platform === 'win32' ? 'ico' : 'png'; 6const FILE_EXTENSION = process.platform === 'win32' ? 'ico' : 'png';
@@ -7,7 +9,9 @@ const INDICATOR_TRAY_UNREAD = 'tray-unread';
7 9
8export default class TrayIcon { 10export default class TrayIcon {
9 trayIcon = null; 11 trayIcon = null;
12
10 indicator = 0; 13 indicator = 0;
14
11 themeChangeSubscriberId = null; 15 themeChangeSubscriberId = null;
12 16
13 show() { 17 show() {
@@ -79,7 +83,7 @@ export default class TrayIcon {
79 } 83 }
80 84
81 return nativeImage.createFromPath(path.join( 85 return nativeImage.createFromPath(path.join(
82 __dirname, '..', 'assets', 'images', type, platform, `${asset}.${FILE_EXTENSION}`), 86 __dirname, '..', 'assets', 'images', type, platform, `${asset}.${FILE_EXTENSION}`,
83 ); 87 ));
84 } 88 }
85} 89}
diff --git a/src/models/News.js b/src/models/News.js
index caf1d70e5..acacb97dd 100644
--- a/src/models/News.js
+++ b/src/models/News.js
@@ -2,8 +2,11 @@
2 2
3export default class News { 3export default class News {
4 id = ''; 4 id = '';
5
5 message = ''; 6 message = '';
7
6 type = 'primary'; 8 type = 'primary';
9
7 sticky = false; 10 sticky = false;
8 11
9 constructor(data) { 12 constructor(data) {
diff --git a/src/models/Order.js b/src/models/Order.js
index 0e10b01d6..f7624c627 100644
--- a/src/models/Order.js
+++ b/src/models/Order.js
@@ -1,9 +1,14 @@
1export default class Order { 1export default class Order {
2 id = ''; 2 id = '';
3
3 subscriptionId = ''; 4 subscriptionId = '';
5
4 name = ''; 6 name = '';
7
5 invoiceUrl = ''; 8 invoiceUrl = '';
9
6 price = ''; 10 price = '';
11
7 date = ''; 12 date = '';
8 13
9 constructor(data) { 14 constructor(data) {
diff --git a/src/models/Plan.js b/src/models/Plan.js
index c7b4a0962..3dedf0d5e 100644
--- a/src/models/Plan.js
+++ b/src/models/Plan.js
@@ -5,6 +5,7 @@ export default class Plan {
5 id: '', 5 id: '',
6 price: 0, 6 price: 0,
7 } 7 }
8
8 year = { 9 year = {
9 id: '', 10 id: '',
10 price: 0, 11 price: 0,
diff --git a/src/models/Recipe.js b/src/models/Recipe.js
index 43c44514c..b0d60e75e 100644
--- a/src/models/Recipe.js
+++ b/src/models/Recipe.js
@@ -5,21 +5,33 @@ import path from 'path';
5 5
6export default class Recipe { 6export default class Recipe {
7 id = ''; 7 id = '';
8
8 name = ''; 9 name = '';
10
9 description = ''; 11 description = '';
12
10 version = ''; 13 version = '';
14
11 path = ''; 15 path = '';
12 16
13 serviceURL = ''; 17 serviceURL = '';
14 18
15 hasDirectMessages = true; 19 hasDirectMessages = true;
20
16 hasIndirectMessages = false; 21 hasIndirectMessages = false;
22
17 hasNotificationSound = false; 23 hasNotificationSound = false;
24
18 hasTeamId = false; 25 hasTeamId = false;
26
19 hasPredefinedUrl = false; 27 hasPredefinedUrl = false;
28
20 hasCustomUrl = false; 29 hasCustomUrl = false;
30
21 hasHostedOption = false; 31 hasHostedOption = false;
32
22 urlInputPrefix = ''; 33 urlInputPrefix = '';
34
23 urlInputSuffix = ''; 35 urlInputSuffix = '';
24 36
25 message = ''; 37 message = '';
diff --git a/src/models/RecipePreview.js b/src/models/RecipePreview.js
index 7470d757a..cfb22f860 100644
--- a/src/models/RecipePreview.js
+++ b/src/models/RecipePreview.js
@@ -2,8 +2,12 @@
2 2
3export default class RecipePreview { 3export default class RecipePreview {
4 id = ''; 4 id = '';
5
5 name = ''; 6 name = '';
6 icon = ''; // TODO: check if this isn't replaced by `icons` 7
8 icon = '';
9
10 // TODO: check if this isn't replaced by `icons`
7 featured = false; 11 featured = false;
8 12
9 constructor(data) { 13 constructor(data) {
diff --git a/src/models/Service.js b/src/models/Service.js
index 41180dd76..4cc6102ff 100644
--- a/src/models/Service.js
+++ b/src/models/Service.js
@@ -2,11 +2,17 @@ import { computed, observable, autorun } from 'mobx';
2import path from 'path'; 2import path from 'path';
3import normalizeUrl from 'normalize-url'; 3import normalizeUrl from 'normalize-url';
4 4
5const debug = require('debug')('Franz:Service');
6
5export default class Service { 7export default class Service {
6 id = ''; 8 id = '';
9
7 recipe = ''; 10 recipe = '';
11
8 webview = null; 12 webview = null;
13
9 timer = null; 14 timer = null;
15
10 events = {}; 16 events = {};
11 17
12 isAttached = false; 18 isAttached = false;
@@ -14,22 +20,45 @@ export default class Service {
14 @observable isActive = false; // Is current webview active 20 @observable isActive = false; // Is current webview active
15 21
16 @observable name = ''; 22 @observable name = '';
23
17 @observable unreadDirectMessageCount = 0; 24 @observable unreadDirectMessageCount = 0;
25
18 @observable unreadIndirectMessageCount = 0; 26 @observable unreadIndirectMessageCount = 0;
19 27
20 @observable order = 99; 28 @observable order = 99;
29
21 @observable isEnabled = true; 30 @observable isEnabled = true;
31
22 @observable isMuted = false; 32 @observable isMuted = false;
33
23 @observable team = ''; 34 @observable team = '';
35
24 @observable customUrl = ''; 36 @observable customUrl = '';
37
25 @observable isNotificationEnabled = true; 38 @observable isNotificationEnabled = true;
39
26 @observable isBadgeEnabled = true; 40 @observable isBadgeEnabled = true;
41
27 @observable isIndirectMessageBadgeEnabled = true; 42 @observable isIndirectMessageBadgeEnabled = true;
43
28 @observable iconUrl = ''; 44 @observable iconUrl = '';
45
29 @observable hasCustomUploadedIcon = false; 46 @observable hasCustomUploadedIcon = false;
47
30 @observable hasCrashed = false; 48 @observable hasCrashed = false;
49
31 @observable isDarkModeEnabled = false; 50 @observable isDarkModeEnabled = false;
32 51
52 @observable spellcheckerLanguage = null;
53
54 @observable isFirstLoad = true;
55
56 @observable isLoading = true;
57
58 @observable isError = false;
59
60 @observable errorMessage = '';
61
33 constructor(data, recipe) { 62 constructor(data, recipe) {
34 if (!data) { 63 if (!data) {
35 console.error('Service config not valid'); 64 console.error('Service config not valid');
@@ -71,6 +100,8 @@ export default class Service {
71 100
72 this.proxy = data.proxy !== undefined ? data.proxy : this.proxy; 101 this.proxy = data.proxy !== undefined ? data.proxy : this.proxy;
73 102
103 this.spellcheckerLanguage = data.spellcheckerLanguage !== undefined ? data.spellcheckerLanguage : this.spellcheckerLanguage;
104
74 this.recipe = recipe; 105 this.recipe = recipe;
75 106
76 autorun(() => { 107 autorun(() => {
@@ -147,9 +178,29 @@ export default class Service {
147 178
148 this.webview.addEventListener('did-start-loading', () => { 179 this.webview.addEventListener('did-start-loading', () => {
149 this.hasCrashed = false; 180 this.hasCrashed = false;
181 this.isLoading = true;
182 this.isError = false;
183 });
184
185 this.webview.addEventListener('did-frame-finish-load', () => {
186 this.isLoading = false;
187
188 if (!this.isError) {
189 this.isFirstLoad = false;
190 }
191 });
192
193 this.webview.addEventListener('did-fail-load', (event) => {
194 debug('Service failed to load', this.name, event);
195 if (event.isMainFrame) {
196 this.isError = true;
197 this.errorMessage = event.errorDescription;
198 this.isLoading = false;
199 }
150 }); 200 });
151 201
152 this.webview.addEventListener('crashed', () => { 202 this.webview.addEventListener('crashed', () => {
203 debug('Service crashed', this.name);
153 this.hasCrashed = true; 204 this.hasCrashed = true;
154 }); 205 });
155 } 206 }
diff --git a/src/models/User.js b/src/models/User.js
index 3e4aa187d..bec78fc16 100644
--- a/src/models/User.js
+++ b/src/models/User.js
@@ -2,19 +2,34 @@ import { observable } from 'mobx';
2 2
3export default class User { 3export default class User {
4 id = null; 4 id = null;
5
5 @observable email = null; 6 @observable email = null;
7
6 @observable firstname = null; 8 @observable firstname = null;
9
7 @observable lastname = null; 10 @observable lastname = null;
11
8 @observable organization = null; 12 @observable organization = null;
13
9 @observable accountType = null; 14 @observable accountType = null;
10 @observable emailIsConfirmed = true; // better assume it's confirmed to avoid noise 15
16 @observable emailIsConfirmed = true;
17
18 // better assume it's confirmed to avoid noise
11 @observable subscription = {}; 19 @observable subscription = {};
20
12 @observable isSubscriptionOwner = false; 21 @observable isSubscriptionOwner = false;
22
13 @observable isPremium = false; 23 @observable isPremium = false;
24
14 @observable beta = false; 25 @observable beta = false;
26
15 @observable donor = {}; 27 @observable donor = {};
28
16 @observable isDonor = false; 29 @observable isDonor = false;
30
17 @observable isMiner = false; 31 @observable isMiner = false;
32
18 @observable locale = false; 33 @observable locale = false;
19 34
20 constructor(data) { 35 constructor(data) {
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js
index 6f156a96d..dd4642d70 100644
--- a/src/stores/AppStore.js
+++ b/src/stores/AppStore.js
@@ -38,12 +38,15 @@ export default class AppStore extends Store {
38 }; 38 };
39 39
40 @observable healthCheckRequest = new Request(this.api.app, 'health'); 40 @observable healthCheckRequest = new Request(this.api.app, 'health');
41
41 @observable getAppCacheSizeRequest = new Request(this.api.local, 'getAppCacheSize'); 42 @observable getAppCacheSizeRequest = new Request(this.api.local, 'getAppCacheSize');
43
42 @observable clearAppCacheRequest = new Request(this.api.local, 'clearAppCache'); 44 @observable clearAppCacheRequest = new Request(this.api.local, 'clearAppCache');
43 45
44 @observable autoLaunchOnStart = true; 46 @observable autoLaunchOnStart = true;
45 47
46 @observable isOnline = navigator.onLine; 48 @observable isOnline = navigator.onLine;
49
47 @observable timeOfflineStart; 50 @observable timeOfflineStart;
48 51
49 @observable updateStatus = null; 52 @observable updateStatus = null;
@@ -150,19 +153,22 @@ export default class AppStore extends Store {
150 key( 153 key(
151 '⌘+pagedown, ctrl+pagedown, ⌘+alt+right, ctrl+tab', () => { 154 '⌘+pagedown, ctrl+pagedown, ⌘+alt+right, ctrl+tab', () => {
152 this.actions.service.setActiveNext(); 155 this.actions.service.setActiveNext();
153 }); 156 },
157 );
154 158
155 // Set active the prev service 159 // Set active the prev service
156 key( 160 key(
157 '⌘+pageup, ctrl+pageup, ⌘+alt+left, ctrl+shift+tab', () => { 161 '⌘+pageup, ctrl+pageup, ⌘+alt+left, ctrl+shift+tab', () => {
158 this.actions.service.setActivePrev(); 162 this.actions.service.setActivePrev();
159 }); 163 },
164 );
160 165
161 // Global Mute 166 // Global Mute
162 key( 167 key(
163 '⌘+shift+m ctrl+shift+m', () => { 168 '⌘+shift+m ctrl+shift+m', () => {
164 this.actions.app.toggleMuteApp(); 169 this.actions.app.toggleMuteApp();
165 }); 170 },
171 );
166 172
167 this.locale = this._getDefaultLocale(); 173 this.locale = this._getDefaultLocale();
168 174
@@ -182,7 +188,9 @@ export default class AppStore extends Store {
182 } 188 }
183 189
184 // Actions 190 // Actions
185 @action _notify({ title, options, notificationId, serviceId = null }) { 191 @action _notify({
192 title, options, notificationId, serviceId = null,
193 }) {
186 if (this.stores.settings.all.app.isAppMuted) return; 194 if (this.stores.settings.all.app.isAppMuted) return;
187 195
188 const notification = new window.Notification(title, options); 196 const notification = new window.Notification(title, options);
diff --git a/src/stores/DictionaryStore.js b/src/stores/DictionaryStore.js
deleted file mode 100644
index b9c5f2abf..000000000
--- a/src/stores/DictionaryStore.js
+++ /dev/null
@@ -1,45 +0,0 @@
1import { observable } from 'mobx';
2import { createDownloader } from 'hunspell-dict-downloader';
3
4import Store from './lib/Store';
5
6import { DICTIONARY_PATH } from '../config';
7
8const debug = require('debug')('Franz:DictionaryStore');
9
10export default class DictionaryStore extends Store {
11 @observable available = []
12 @observable installed = []
13
14 _dictDownloader = null
15
16 constructor(...args) {
17 super(...args);
18
19 this.registerReactions([
20 this._downloadDictForUserLocale.bind(this),
21 ]);
22 }
23
24 async setup() {
25 this._dictDownloader = await createDownloader(DICTIONARY_PATH);
26 debug('dicts', this._dictDownloader);
27
28 this.available = this._dictDownloader.availableDictionaries;
29 this.installed = this._dictDownloader.installedDictionaries;
30
31 if (!this.installed.includes('en-us')) {
32 this._dictDownloader.installDictionary('en-us');
33 }
34 }
35
36 _downloadDictForUserLocale() {
37 const spellcheckerLanguage = this.stores.settings.app.spellcheckerLanguage;
38
39 debug('trying to Downloading dict for', spellcheckerLanguage);
40 if (!this.installed.includes(spellcheckerLanguage) && this.available.includes(spellcheckerLanguage) && spellcheckerLanguage !== 'en-us') {
41 debug('Downloading dict for', spellcheckerLanguage);
42 this._dictDownloader.installDictionary(spellcheckerLanguage);
43 }
44 }
45}
diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js
index 10c893d3f..2a0713b6f 100644
--- a/src/stores/FeaturesStore.js
+++ b/src/stores/FeaturesStore.js
@@ -11,6 +11,7 @@ import { DEFAULT_FEATURES_CONFIG } from '../config';
11 11
12export default class FeaturesStore extends Store { 12export default class FeaturesStore extends Store {
13 @observable defaultFeaturesRequest = new CachedRequest(this.api.features, 'default'); 13 @observable defaultFeaturesRequest = new CachedRequest(this.api.features, 'default');
14
14 @observable featuresRequest = new CachedRequest(this.api.features, 'features'); 15 @observable featuresRequest = new CachedRequest(this.api.features, 'features');
15 16
16 async setup() { 17 async setup() {
diff --git a/src/stores/GlobalErrorStore.js b/src/stores/GlobalErrorStore.js
index f4b9d7838..90bf751c3 100644
--- a/src/stores/GlobalErrorStore.js
+++ b/src/stores/GlobalErrorStore.js
@@ -4,6 +4,7 @@ import Request from './lib/Request';
4 4
5export default class GlobalErrorStore extends Store { 5export default class GlobalErrorStore extends Store {
6 @observable error = null; 6 @observable error = null;
7
7 @observable response = {}; 8 @observable response = {};
8 9
9 constructor(...args) { 10 constructor(...args) {
diff --git a/src/stores/NewsStore.js b/src/stores/NewsStore.js
index e5091834f..6984425df 100644
--- a/src/stores/NewsStore.js
+++ b/src/stores/NewsStore.js
@@ -8,6 +8,7 @@ import { CHECK_INTERVAL } from '../config';
8 8
9export default class NewsStore extends Store { 9export default class NewsStore extends Store {
10 @observable latestNewsRequest = new CachedRequest(this.api.news, 'latest'); 10 @observable latestNewsRequest = new CachedRequest(this.api.news, 'latest');
11
11 @observable hideNewsRequest = new Request(this.api.news, 'hide'); 12 @observable hideNewsRequest = new Request(this.api.news, 'hide');
12 13
13 constructor(...args) { 14 constructor(...args) {
diff --git a/src/stores/PaymentStore.js b/src/stores/PaymentStore.js
index 9e348d14e..4cabee194 100644
--- a/src/stores/PaymentStore.js
+++ b/src/stores/PaymentStore.js
@@ -7,8 +7,11 @@ import { gaEvent } from '../lib/analytics';
7 7
8export default class PaymentStore extends Store { 8export default class PaymentStore extends Store {
9 @observable plansRequest = new CachedRequest(this.api.payment, 'plans'); 9 @observable plansRequest = new CachedRequest(this.api.payment, 'plans');
10
10 @observable createHostedPageRequest = new Request(this.api.payment, 'getHostedPage'); 11 @observable createHostedPageRequest = new Request(this.api.payment, 'getHostedPage');
12
11 @observable createDashboardUrlRequest = new Request(this.api.payment, 'getDashboardUrl'); 13 @observable createDashboardUrlRequest = new Request(this.api.payment, 'getDashboardUrl');
14
12 @observable ordersDataRequest = new CachedRequest(this.api.payment, 'getOrders'); 15 @observable ordersDataRequest = new CachedRequest(this.api.payment, 'getOrders');
13 16
14 constructor(...args) { 17 constructor(...args) {
diff --git a/src/stores/RecipePreviewsStore.js b/src/stores/RecipePreviewsStore.js
index e25936f15..10b2928e3 100644
--- a/src/stores/RecipePreviewsStore.js
+++ b/src/stores/RecipePreviewsStore.js
@@ -8,7 +8,9 @@ import { gaEvent } from '../lib/analytics';
8 8
9export default class RecipePreviewsStore extends Store { 9export default class RecipePreviewsStore extends Store {
10 @observable allRecipePreviewsRequest = new CachedRequest(this.api.recipePreviews, 'all'); 10 @observable allRecipePreviewsRequest = new CachedRequest(this.api.recipePreviews, 'all');
11
11 @observable featuredRecipePreviewsRequest = new CachedRequest(this.api.recipePreviews, 'featured'); 12 @observable featuredRecipePreviewsRequest = new CachedRequest(this.api.recipePreviews, 'featured');
13
12 @observable searchRecipePreviewsRequest = new Request(this.api.recipePreviews, 'search'); 14 @observable searchRecipePreviewsRequest = new Request(this.api.recipePreviews, 'search');
13 15
14 constructor(...args) { 16 constructor(...args) {
diff --git a/src/stores/RecipesStore.js b/src/stores/RecipesStore.js
index f2480bc8e..ab64bf79c 100644
--- a/src/stores/RecipesStore.js
+++ b/src/stores/RecipesStore.js
@@ -9,7 +9,9 @@ const debug = require('debug')('Franz:RecipeStore');
9 9
10export default class RecipesStore extends Store { 10export default class RecipesStore extends Store {
11 @observable allRecipesRequest = new CachedRequest(this.api.recipes, 'all'); 11 @observable allRecipesRequest = new CachedRequest(this.api.recipes, 'all');
12
12 @observable installRecipeRequest = new Request(this.api.recipes, 'install'); 13 @observable installRecipeRequest = new Request(this.api.recipes, 'install');
14
13 @observable getRecipeUpdatesRequest = new Request(this.api.recipes, 'update'); 15 @observable getRecipeUpdatesRequest = new Request(this.api.recipes, 'update');
14 16
15 constructor(...args) { 17 constructor(...args) {
diff --git a/src/stores/RequestStore.js b/src/stores/RequestStore.js
index bbfe6f6df..2629e0a38 100644
--- a/src/stores/RequestStore.js
+++ b/src/stores/RequestStore.js
@@ -6,10 +6,13 @@ const debug = require('debug')('Franz:RequestsStore');
6 6
7export default class RequestStore extends Store { 7export default class RequestStore extends Store {
8 @observable userInfoRequest; 8 @observable userInfoRequest;
9
9 @observable servicesRequest; 10 @observable servicesRequest;
11
10 @observable showRequiredRequestsError = false; 12 @observable showRequiredRequestsError = false;
11 13
12 retries = 0; 14 retries = 0;
15
13 retryDelay = 2000; 16 retryDelay = 2000;
14 17
15 constructor(...args) { 18 constructor(...args) {
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index 99b091589..5b70ca271 100644
--- a/src/stores/ServicesStore.js
+++ b/src/stores/ServicesStore.js
@@ -1,4 +1,6 @@
1import { action, reaction, computed, observable } from 'mobx'; 1import {
2 action, reaction, computed, observable,
3} from 'mobx';
2import { debounce, remove } from 'lodash'; 4import { debounce, remove } from 'lodash';
3 5
4import Store from './lib/Store'; 6import Store from './lib/Store';
@@ -11,10 +13,15 @@ const debug = require('debug')('Franz:ServiceStore');
11 13
12export default class ServicesStore extends Store { 14export default class ServicesStore extends Store {
13 @observable allServicesRequest = new CachedRequest(this.api.services, 'all'); 15 @observable allServicesRequest = new CachedRequest(this.api.services, 'all');
16
14 @observable createServiceRequest = new Request(this.api.services, 'create'); 17 @observable createServiceRequest = new Request(this.api.services, 'create');
18
15 @observable updateServiceRequest = new Request(this.api.services, 'update'); 19 @observable updateServiceRequest = new Request(this.api.services, 'update');
20
16 @observable reorderServicesRequest = new Request(this.api.services, 'reorder'); 21 @observable reorderServicesRequest = new Request(this.api.services, 'reorder');
22
17 @observable deleteServiceRequest = new Request(this.api.services, 'delete'); 23 @observable deleteServiceRequest = new Request(this.api.services, 'delete');
24
18 @observable clearCacheRequest = new Request(this.api.services, 'clearCache'); 25 @observable clearCacheRequest = new Request(this.api.services, 'clearCache');
19 26
20 @observable filterNeedle = null; 27 @observable filterNeedle = null;
@@ -400,6 +407,18 @@ export default class ServicesStore extends Store {
400 const url = args[0]; 407 const url = args[0];
401 408
402 this.actions.app.openExternalUrl({ url }); 409 this.actions.app.openExternalUrl({ url });
410 } else if (channel === 'set-service-spellchecker-language') {
411 if (!args) {
412 console.warn('Did not receive locale');
413 } else {
414 this.actions.service.updateService({
415 serviceId,
416 serviceData: {
417 spellcheckerLanguage: args[0] === 'reset' ? '' : args[0],
418 },
419 redirect: false,
420 });
421 }
403 } 422 }
404 } 423 }
405 424
@@ -625,7 +644,7 @@ export default class ServicesStore extends Store {
625 const service = this.one(serviceId); 644 const service = this.one(serviceId);
626 645
627 if (service.webview) { 646 if (service.webview) {
628 service.webview.send('initializeRecipe', service); 647 service.webview.send('initialize-recipe', service);
629 } 648 }
630 } 649 }
631 650
diff --git a/src/stores/SettingsStore.js b/src/stores/SettingsStore.js
index 4c01e9910..a456195bf 100644
--- a/src/stores/SettingsStore.js
+++ b/src/stores/SettingsStore.js
@@ -1,5 +1,7 @@
1import { ipcRenderer } from 'electron'; 1import { ipcRenderer } from 'electron';
2import { action, computed, observable } from 'mobx'; 2import {
3 action, computed, observable, set,
4} from 'mobx';
3import localStorage from 'mobx-localstorage'; 5import localStorage from 'mobx-localstorage';
4 6
5import Store from './lib/Store'; 7import Store from './lib/Store';
@@ -14,11 +16,13 @@ const debug = require('debug')('Franz:SettingsStore');
14 16
15export default class SettingsStore extends Store { 17export default class SettingsStore extends Store {
16 @observable appSettingsRequest = new CachedRequest(this.api.local, 'getAppSettings'); 18 @observable appSettingsRequest = new CachedRequest(this.api.local, 'getAppSettings');
19
17 @observable updateAppSettingsRequest = new Request(this.api.local, 'updateAppSettings'); 20 @observable updateAppSettingsRequest = new Request(this.api.local, 'updateAppSettings');
18 21
19 @observable fileSystemSettingsRequests = []; 22 fileSystemSettingsRequests = [];
20 23
21 fileSystemSettingsTypes = FILE_SYSTEM_SETTINGS_TYPES; 24 fileSystemSettingsTypes = FILE_SYSTEM_SETTINGS_TYPES;
25
22 @observable _fileSystemSettingsCache = { 26 @observable _fileSystemSettingsCache = {
23 app: DEFAULT_APP_SETTINGS, 27 app: DEFAULT_APP_SETTINGS,
24 proxy: {}, 28 proxy: {},
@@ -57,6 +61,21 @@ export default class SettingsStore extends Store {
57 } 61 }
58 62
59 @computed get proxy() { 63 @computed get proxy() {
64 // // We need to provide the final data structure as mobx autoruns won't work
65 // const proxySettings = observable({});
66 // this.stores.services.all.forEach((service) => {
67 // proxySettings[service.id] = {
68 // isEnabled: false,
69 // host: null,
70 // user: null,
71 // password: null,
72 // };
73 // });
74
75 // debug('this._fileSystemSettingsCache.proxy', this._fileSystemSettingsCache.proxy, proxySettings);
76
77 // return Object.assign(proxySettings, this._fileSystemSettingsCache.proxy);
78
60 return this._fileSystemSettingsCache.proxy || {}; 79 return this._fileSystemSettingsCache.proxy || {};
61 } 80 }
62 81
@@ -98,7 +117,7 @@ export default class SettingsStore extends Store {
98 data, 117 data,
99 }); 118 });
100 119
101 Object.assign(this._fileSystemSettingsCache[type], data); 120 set(this._fileSystemSettingsCache[type], data);
102 } 121 }
103 } 122 }
104 123
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js
index 26ac2c60e..7addb5760 100644
--- a/src/stores/UserStore.js
+++ b/src/stores/UserStore.js
@@ -14,29 +14,47 @@ const debug = require('debug')('Franz:UserStore');
14// TODO: split stores into UserStore and AuthStore 14// TODO: split stores into UserStore and AuthStore
15export default class UserStore extends Store { 15export default class UserStore extends Store {
16 BASE_ROUTE = '/auth'; 16 BASE_ROUTE = '/auth';
17
17 WELCOME_ROUTE = `${this.BASE_ROUTE}/welcome`; 18 WELCOME_ROUTE = `${this.BASE_ROUTE}/welcome`;
19
18 LOGIN_ROUTE = `${this.BASE_ROUTE}/login`; 20 LOGIN_ROUTE = `${this.BASE_ROUTE}/login`;
21
19 LOGOUT_ROUTE = `${this.BASE_ROUTE}/logout`; 22 LOGOUT_ROUTE = `${this.BASE_ROUTE}/logout`;
23
20 SIGNUP_ROUTE = `${this.BASE_ROUTE}/signup`; 24 SIGNUP_ROUTE = `${this.BASE_ROUTE}/signup`;
25
21 PRICING_ROUTE = `${this.BASE_ROUTE}/signup/pricing`; 26 PRICING_ROUTE = `${this.BASE_ROUTE}/signup/pricing`;
27
22 IMPORT_ROUTE = `${this.BASE_ROUTE}/signup/import`; 28 IMPORT_ROUTE = `${this.BASE_ROUTE}/signup/import`;
29
23 INVITE_ROUTE = `${this.BASE_ROUTE}/signup/invite`; 30 INVITE_ROUTE = `${this.BASE_ROUTE}/signup/invite`;
31
24 PASSWORD_ROUTE = `${this.BASE_ROUTE}/password`; 32 PASSWORD_ROUTE = `${this.BASE_ROUTE}/password`;
25 33
26 @observable loginRequest = new Request(this.api.user, 'login'); 34 @observable loginRequest = new Request(this.api.user, 'login');
35
27 @observable signupRequest = new Request(this.api.user, 'signup'); 36 @observable signupRequest = new Request(this.api.user, 'signup');
37
28 @observable passwordRequest = new Request(this.api.user, 'password'); 38 @observable passwordRequest = new Request(this.api.user, 'password');
39
29 @observable inviteRequest = new Request(this.api.user, 'invite'); 40 @observable inviteRequest = new Request(this.api.user, 'invite');
41
30 @observable getUserInfoRequest = new CachedRequest(this.api.user, 'getInfo'); 42 @observable getUserInfoRequest = new CachedRequest(this.api.user, 'getInfo');
43
31 @observable updateUserInfoRequest = new Request(this.api.user, 'updateInfo'); 44 @observable updateUserInfoRequest = new Request(this.api.user, 'updateInfo');
45
32 @observable getLegacyServicesRequest = new CachedRequest(this.api.user, 'getLegacyServices'); 46 @observable getLegacyServicesRequest = new CachedRequest(this.api.user, 'getLegacyServices');
47
33 @observable deleteAccountRequest = new CachedRequest(this.api.user, 'delete'); 48 @observable deleteAccountRequest = new CachedRequest(this.api.user, 'delete');
34 49
35 @observable isImportLegacyServicesExecuting = false; 50 @observable isImportLegacyServicesExecuting = false;
51
36 @observable isImportLegacyServicesCompleted = false; 52 @observable isImportLegacyServicesCompleted = false;
37 53
38 @observable id; 54 @observable id;
55
39 @observable authToken = localStorage.getItem('authToken') || null; 56 @observable authToken = localStorage.getItem('authToken') || null;
57
40 @observable accountType; 58 @observable accountType;
41 59
42 @observable hasCompletedSignup = null; 60 @observable hasCompletedSignup = null;
@@ -48,6 +66,7 @@ export default class UserStore extends Store {
48 logoutReasonTypes = { 66 logoutReasonTypes = {
49 SERVER: 'SERVER', 67 SERVER: 'SERVER',
50 }; 68 };
69
51 @observable logoutReason = null; 70 @observable logoutReason = null;
52 71
53 constructor(...args) { 72 constructor(...args) {
@@ -141,7 +160,9 @@ export default class UserStore extends Store {
141 gaEvent('User', 'login'); 160 gaEvent('User', 'login');
142 } 161 }
143 162
144 @action async _signup({ firstname, lastname, email, password, accountType, company }) { 163 @action async _signup({
164 firstname, lastname, email, password, accountType, company,
165 }) {
145 const authToken = await this.signupRequest.execute({ 166 const authToken = await this.signupRequest.execute({
146 firstname, 167 firstname,
147 lastname, 168 lastname,
@@ -198,10 +219,11 @@ export default class UserStore extends Store {
198 } 219 }
199 220
200 @action _logout() { 221 @action _logout() {
222 // workaround mobx issue
201 localStorage.removeItem('authToken'); 223 localStorage.removeItem('authToken');
224 window.localStorage.removeItem('authToken');
202 this.getUserInfoRequest.invalidate().reset(); 225 this.getUserInfoRequest.invalidate().reset();
203 this.authToken = null; 226 this.authToken = null;
204 // this.data = {};
205 } 227 }
206 228
207 @action async _importLegacyServices({ services }) { 229 @action async _importLegacyServices({ services }) {
diff --git a/src/stores/index.js b/src/stores/index.js
index f547d0a7a..96b844c95 100644
--- a/src/stores/index.js
+++ b/src/stores/index.js
@@ -9,7 +9,6 @@ import UIStore from './UIStore';
9import PaymentStore from './PaymentStore'; 9import PaymentStore from './PaymentStore';
10import NewsStore from './NewsStore'; 10import NewsStore from './NewsStore';
11import RequestStore from './RequestStore'; 11import RequestStore from './RequestStore';
12import DictionaryStore from './DictionaryStore';
13import GlobalErrorStore from './GlobalErrorStore'; 12import GlobalErrorStore from './GlobalErrorStore';
14 13
15export default (api, actions, router) => { 14export default (api, actions, router) => {
@@ -27,7 +26,6 @@ export default (api, actions, router) => {
27 payment: new PaymentStore(stores, api, actions), 26 payment: new PaymentStore(stores, api, actions),
28 news: new NewsStore(stores, api, actions), 27 news: new NewsStore(stores, api, actions),
29 requests: new RequestStore(stores, api, actions), 28 requests: new RequestStore(stores, api, actions),
30 dictionary: new DictionaryStore(stores, api, actions),
31 globalError: new GlobalErrorStore(stores, api, actions), 29 globalError: new GlobalErrorStore(stores, api, actions),
32 }); 30 });
33 // Initialize all stores 31 // Initialize all stores
diff --git a/src/stores/lib/CachedRequest.js b/src/stores/lib/CachedRequest.js
index c0c3d40a1..ac8b2bd81 100644
--- a/src/stores/lib/CachedRequest.js
+++ b/src/stores/lib/CachedRequest.js
@@ -5,6 +5,7 @@ import Request from './Request';
5 5
6export default class CachedRequest extends Request { 6export default class CachedRequest extends Request {
7 _apiCalls = []; 7 _apiCalls = [];
8
8 _isInvalidated = true; 9 _isInvalidated = true;
9 10
10 execute(...callArgs) { 11 execute(...callArgs) {
diff --git a/src/stores/lib/Reaction.js b/src/stores/lib/Reaction.js
index e9bc26d81..46aa4dae6 100644
--- a/src/stores/lib/Reaction.js
+++ b/src/stores/lib/Reaction.js
@@ -3,7 +3,9 @@ import { autorun } from 'mobx';
3 3
4export default class Reaction { 4export default class Reaction {
5 reaction; 5 reaction;
6
6 hasBeenStarted; 7 hasBeenStarted;
8
7 dispose; 9 dispose;
8 10
9 constructor(reaction) { 11 constructor(reaction) {
diff --git a/src/stores/lib/Request.js b/src/stores/lib/Request.js
index 4a6925cc5..04f528156 100644
--- a/src/stores/lib/Request.js
+++ b/src/stores/lib/Request.js
@@ -9,15 +9,23 @@ export default class Request {
9 } 9 }
10 10
11 @observable result = null; 11 @observable result = null;
12
12 @observable error = null; 13 @observable error = null;
14
13 @observable isExecuting = false; 15 @observable isExecuting = false;
16
14 @observable isError = false; 17 @observable isError = false;
18
15 @observable wasExecuted = false; 19 @observable wasExecuted = false;
16 20
17 _promise = Promise; 21 _promise = Promise;
22
18 _api = {}; 23 _api = {};
24
19 _method = ''; 25 _method = '';
26
20 _isWaitingForResponse = false; 27 _isWaitingForResponse = false;
28
21 _currentApiCall = null; 29 _currentApiCall = null;
22 30
23 constructor(api, method) { 31 constructor(api, method) {
diff --git a/src/stores/lib/Store.js b/src/stores/lib/Store.js
index 873da7b37..8d2fb4066 100644
--- a/src/stores/lib/Store.js
+++ b/src/stores/lib/Store.js
@@ -3,16 +3,20 @@ import Reaction from './Reaction';
3 3
4export default class Store { 4export default class Store {
5 stores = {}; 5 stores = {};
6
6 api = {}; 7 api = {};
8
7 actions = {}; 9 actions = {};
8 10
9 _reactions = []; 11 _reactions = [];
10 12
11 // status implementation 13 // status implementation
12 @observable _status = null; 14 @observable _status = null;
15
13 @computed get actionStatus() { 16 @computed get actionStatus() {
14 return this._status || []; 17 return this._status || [];
15 } 18 }
19
16 set actionStatus(status) { 20 set actionStatus(status) {
17 this._status = status; 21 this._status = status;
18 } 22 }
diff --git a/src/styles/auth.scss b/src/styles/auth.scss
index 54e264dc6..817801982 100644
--- a/src/styles/auth.scss
+++ b/src/styles/auth.scss
@@ -33,17 +33,9 @@
33 33
34 .auth__layout { 34 .auth__layout {
35 width: 100%; 35 width: 100%;
36 36 display: flex;
37 & > div { 37 align-items: center;
38 align-items: center; 38 justify-content: center;
39 display: flex;
40 justify-content: center;
41
42 & > span {
43 position: absolute;
44 width: 100%;
45 }
46 }
47 } 39 }
48 40
49 .auth__container { 41 .auth__container {
diff --git a/src/styles/content-tabs.scss b/src/styles/content-tabs.scss
index ca3820fb4..03befedcb 100644
--- a/src/styles/content-tabs.scss
+++ b/src/styles/content-tabs.scss
@@ -1,5 +1,21 @@
1@import './config.scss'; 1@import './config.scss';
2 2
3.theme__dark {
4 .content-tabs {
5 .content-tabs__content {
6 background: $dark-theme-gray-darker;
7 }
8
9 .content-tabs__tabs {
10 .content-tabs__item {
11 background: $dark-theme-gray;
12 color: #FFF;
13 border: 0;
14 }
15 }
16 }
17}
18
3.content-tabs { 19.content-tabs {
4 .content-tabs__tabs { 20 .content-tabs__tabs {
5 border-top-left-radius: $theme-border-radius-small; 21 border-top-left-radius: $theme-border-radius-small;
diff --git a/src/styles/searchInput.scss b/src/styles/searchInput.scss
index 32b9da065..91453c600 100644
--- a/src/styles/searchInput.scss
+++ b/src/styles/searchInput.scss
@@ -22,6 +22,10 @@
22 padding: 5px 10px; 22 padding: 5px 10px;
23 width: 100%; 23 width: 100%;
24 24
25 label {
26 width: 100%;
27 }
28
25 input { 29 input {
26 background: none; 30 background: none;
27 border: 0; 31 border: 0;
diff --git a/src/styles/select.scss b/src/styles/select.scss
index ed0fc0fc2..513975f9c 100644
--- a/src/styles/select.scss
+++ b/src/styles/select.scss
@@ -20,4 +20,8 @@ $toggle: "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj
20 min-width: 200px; 20 min-width: 200px;
21 padding: 10px; 21 padding: 10px;
22 -webkit-appearance: none; 22 -webkit-appearance: none;
23
24 &[disabled] {
25 opacity: 0.5;
26 }
23} 27}
diff --git a/src/styles/settings.scss b/src/styles/settings.scss
index f94ca114d..750b6bedd 100644
--- a/src/styles/settings.scss
+++ b/src/styles/settings.scss
@@ -216,6 +216,10 @@
216 letter-spacing: -0.1px; 216 letter-spacing: -0.1px;
217 } 217 }
218 } 218 }
219
220 .proxyHost {
221 flex-basis: 70%;
222 }
219 } 223 }
220 224
221 .settings__close { 225 .settings__close {
diff --git a/src/styles/welcome.scss b/src/styles/welcome.scss
index b3d6515b1..b517431f0 100644
--- a/src/styles/welcome.scss
+++ b/src/styles/welcome.scss
@@ -1,9 +1,12 @@
1.auth .welcome { 1.auth .welcome {
2 height: auto;
3
2 &__content { 4 &__content {
3 align-items: center; 5 align-items: center;
4 color: #FFF; 6 color: #FFF;
5 display: flex; 7 display: flex;
6 justify-content: center; 8 justify-content: center;
9 height: auto;
7 } 10 }
8 11
9 &__logo { width: 100px; } 12 &__logo { width: 100px; }
@@ -37,6 +40,7 @@
37 display: block; 40 display: block;
38 margin-top: 100px; 41 margin-top: 100px;
39 text-align: center; 42 text-align: center;
43 height: auto;
40 44
41 .button:first-of-type { margin-right: 25px; } 45 .button:first-of-type { margin-right: 25px; }
42 } 46 }
@@ -71,6 +75,7 @@
71 padding: 20px 20px 5px; 75 padding: 20px 20px 5px;
72 text-align: center; 76 text-align: center;
73 width: 480px; 77 width: 480px;
78 height: auto;
74 } 79 }
75 80
76 &__featured-service { 81 &__featured-service {
diff --git a/src/theme/dark/index.js b/src/theme/dark/index.js
index 496a51119..fb1713184 100644
--- a/src/theme/dark/index.js
+++ b/src/theme/dark/index.js
@@ -1,6 +1,13 @@
1import hexToRgba from 'hex-to-rgba';
2
1import * as legacyStyles from '../default/legacy'; 3import * as legacyStyles from '../default/legacy';
2 4
3export const colorBackground = legacyStyles.darkThemeGrayDarkest; 5export const colorBackground = legacyStyles.darkThemeGrayDarkest;
4export const colorBackgroundSubscriptionContainer = legacyStyles.themeBrandInfo; 6export const colorBackgroundSubscriptionContainer = legacyStyles.themeBrandInfo;
5 7
6export const colorHeadline = legacyStyles.darkThemeTextColor; 8export const colorHeadline = legacyStyles.darkThemeTextColor;
9export const colorText = legacyStyles.darkThemeTextColor;
10
11// Loader
12export const colorFullscreenLoaderSpinner = '#FFF';
13export const colorWebviewLoaderBackground = hexToRgba(legacyStyles.darkThemeGrayDarkest, 0.5);
diff --git a/src/theme/default/index.js b/src/theme/default/index.js
index 8766fb609..37827621c 100644
--- a/src/theme/default/index.js
+++ b/src/theme/default/index.js
@@ -1,3 +1,5 @@
1import hexToRgba from 'hex-to-rgba';
2
1import * as legacyStyles from './legacy'; 3import * as legacyStyles from './legacy';
2 4
3export const brandPrimary = '#3498db'; 5export const brandPrimary = '#3498db';
@@ -12,6 +14,8 @@ export const borderRadiusSmall = legacyStyles.themeBorderRadiusSmall;
12export const colorBackground = legacyStyles.themeGrayLighter; 14export const colorBackground = legacyStyles.themeGrayLighter;
13export const colorHeadline = legacyStyles.themeGrayDark; 15export const colorHeadline = legacyStyles.themeGrayDark;
14 16
17export const colorText = legacyStyles.themeTextColor;
18
15// Subscription Container Component 19// Subscription Container Component
16export const colorSubscriptionContainerBackground = 'none'; 20export const colorSubscriptionContainerBackground = 'none';
17export const colorSubscriptionContainerBorder = [1, 'solid', brandPrimary]; 21export const colorSubscriptionContainerBorder = [1, 'solid', brandPrimary];
@@ -19,3 +23,7 @@ export const colorSubscriptionContainerTitle = brandPrimary;
19export const colorSubscriptionContainerActionButtonBackground = brandPrimary; 23export const colorSubscriptionContainerActionButtonBackground = brandPrimary;
20export const colorSubscriptionContainerActionButtonColor = '#FFF'; 24export const colorSubscriptionContainerActionButtonColor = '#FFF';
21 25
26// Loader
27export const colorAppLoaderSpinner = '#FFF';
28export const colorFullscreenLoaderSpinner = legacyStyles.themeGrayDark;
29export const colorWebviewLoaderBackground = hexToRgba(legacyStyles.themeGrayLighter, 0.8);
diff --git a/src/webview/contextMenu.js b/src/webview/contextMenu.js
index ad156128c..bd099987d 100644
--- a/src/webview/contextMenu.js
+++ b/src/webview/contextMenu.js
@@ -1,9 +1,12 @@
1// This is heavily based on https://github.com/sindresorhus/electron-context-menu 1// This is heavily based on https://github.com/sindresorhus/electron-context-menu
2// ❤ @sindresorhus 2// ❤ @sindresorhus
3 3
4import { clipboard, remote, ipcRenderer, shell } from 'electron'; 4import {
5 clipboard, remote, ipcRenderer, shell,
6} from 'electron';
5 7
6import { isDevMode, isMac } from '../environment'; 8import { isDevMode, isMac } from '../environment';
9import { SPELLCHECKER_LOCALES } from '../i18n/languages';
7 10
8const debug = require('debug')('Franz:contextMenu'); 11const debug = require('debug')('Franz:contextMenu');
9 12
@@ -21,7 +24,7 @@ function delUnusedElements(menuTpl) {
21 }); 24 });
22} 25}
23 26
24const buildMenuTpl = (props, suggestions) => { 27const buildMenuTpl = (props, suggestions, isSpellcheckEnabled, defaultSpellcheckerLanguage, spellcheckerLanguage) => {
25 const { editFlags } = props; 28 const { editFlags } = props;
26 const textSelection = props.selectionText.trim(); 29 const textSelection = props.selectionText.trim();
27 const hasText = textSelection.length > 0; 30 const hasText = textSelection.length > 0;
@@ -190,6 +193,54 @@ const buildMenuTpl = (props, suggestions) => {
190 }); 193 });
191 } 194 }
192 195
196 const spellcheckingLanguages = [];
197 Object.keys(SPELLCHECKER_LOCALES).sort(Intl.Collator().compare).forEach((key) => {
198 spellcheckingLanguages.push({
199 id: `lang-${key}`,
200 label: SPELLCHECKER_LOCALES[key],
201 type: 'radio',
202 checked: spellcheckerLanguage === key,
203 click() {
204 debug('Setting service spellchecker to', key);
205 ipcRenderer.sendToHost('set-service-spellchecker-language', key);
206 },
207 });
208 });
209
210 console.log('isSpellcheckEnabled', isSpellcheckEnabled);
211
212 menuTpl.push({
213 type: 'separator',
214 }, {
215 id: 'spellchecker',
216 label: 'Spell Checking',
217 visible: isSpellcheckEnabled,
218 submenu: [
219 {
220 id: 'spellchecker',
221 label: 'Available Languages',
222 enabled: false,
223 }, {
224 type: 'separator',
225 },
226 {
227 id: 'resetToDefault',
228 label: `Reset to system default (${SPELLCHECKER_LOCALES[defaultSpellcheckerLanguage]})`,
229 type: 'radio',
230 visible: defaultSpellcheckerLanguage !== spellcheckerLanguage,
231 click() {
232 debug('Resetting service spellchecker to system default');
233 ipcRenderer.sendToHost('set-service-spellchecker-language', 'reset');
234 },
235 },
236 {
237 type: 'separator',
238 visible: defaultSpellcheckerLanguage !== spellcheckerLanguage,
239 },
240 ...spellcheckingLanguages],
241 });
242
243
193 if (isDevMode) { 244 if (isDevMode) {
194 menuTpl.push({ 245 menuTpl.push({
195 type: 'separator', 246 type: 'separator',
@@ -205,7 +256,7 @@ const buildMenuTpl = (props, suggestions) => {
205 return delUnusedElements(menuTpl); 256 return delUnusedElements(menuTpl);
206}; 257};
207 258
208export default function contextMenu(spellcheckProvider) { 259export default function contextMenu(spellcheckProvider, isSpellcheckEnabled, getDefaultSpellcheckerLanguage, getSpellcheckerLanguage) {
209 webContents.on('context-menu', (e, props) => { 260 webContents.on('context-menu', (e, props) => {
210 e.preventDefault(); 261 e.preventDefault();
211 262
@@ -216,7 +267,15 @@ export default function contextMenu(spellcheckProvider) {
216 debug('Suggestions', suggestions); 267 debug('Suggestions', suggestions);
217 } 268 }
218 269
219 const menu = Menu.buildFromTemplate(buildMenuTpl(props, suggestions.slice(0, 5))); 270 const menu = Menu.buildFromTemplate(
271 buildMenuTpl(
272 props,
273 suggestions.slice(0, 5),
274 isSpellcheckEnabled(),
275 getDefaultSpellcheckerLanguage(),
276 getSpellcheckerLanguage(),
277 ),
278 );
220 279
221 menu.popup(remote.getCurrentWindow()); 280 menu.popup(remote.getCurrentWindow());
222 }); 281 });
diff --git a/src/webview/darkmode.js b/src/webview/darkmode.js
index 9830ef33c..73c7007c6 100644
--- a/src/webview/darkmode.js
+++ b/src/webview/darkmode.js
@@ -1,7 +1,13 @@
1/* eslint no-bitwise: ["error", { "int32Hint": true }] */
2
1import path from 'path'; 3import path from 'path';
2import fs from 'fs-extra'; 4import fs from 'fs-extra';
3 5
4const ID = 'franz-theme-dark-mode'; 6const debug = require('debug')('Franz:DarkMode');
7
8const chars = [...'abcdefghijklmnopqrstuvwxyz'];
9
10const ID = [...Array(20)].map(() => chars[Math.random() * chars.length | 0]).join``;
5 11
6export function injectDarkModeStyle(recipePath) { 12export function injectDarkModeStyle(recipePath) {
7 const darkModeStyle = path.join(recipePath, 'darkmode.css'); 13 const darkModeStyle = path.join(recipePath, 'darkmode.css');
@@ -12,6 +18,8 @@ export function injectDarkModeStyle(recipePath) {
12 styles.innerHTML = data.toString(); 18 styles.innerHTML = data.toString();
13 19
14 document.querySelector('head').appendChild(styles); 20 document.querySelector('head').appendChild(styles);
21
22 debug('Injected Dark Mode style with ID', ID);
15 } 23 }
16} 24}
17 25
@@ -20,6 +28,8 @@ export function removeDarkModeStyle() {
20 28
21 if (style) { 29 if (style) {
22 style.remove(); 30 style.remove();
31
32 debug('Removed Dark Mode Style with ID', ID);
23 } 33 }
24} 34}
25 35
diff --git a/src/webview/notifications.js b/src/webview/notifications.js
index 2020bbdc6..f8fe53e1b 100644
--- a/src/webview/notifications.js
+++ b/src/webview/notifications.js
@@ -1,10 +1,13 @@
1const { ipcRenderer } = require('electron'); 1import { ipcRenderer } from 'electron';
2const uuidV1 = require('uuid/v1'); 2import uuidV1 from 'uuid/v1';
3
4const debug = require('debug')('Franz:Notifications');
3 5
4class Notification { 6class Notification {
5 static permission = 'granted'; 7 static permission = 'granted';
6 8
7 constructor(title = '', options = {}) { 9 constructor(title = '', options = {}) {
10 debug('New notification', title, options);
8 this.title = title; 11 this.title = title;
9 this.options = options; 12 this.options = options;
10 this.notificationId = uuidV1(); 13 this.notificationId = uuidV1();
diff --git a/src/webview/plugin.js b/src/webview/plugin.js
deleted file mode 100644
index 72530733d..000000000
--- a/src/webview/plugin.js
+++ /dev/null
@@ -1,90 +0,0 @@
1import { ipcRenderer } from 'electron';
2import path from 'path';
3
4import RecipeWebview from './lib/RecipeWebview';
5
6import spellchecker, { switchDict, disable as disableSpellchecker } from './spellchecker';
7import { injectDarkModeStyle, isDarkModeStyleInjected, removeDarkModeStyle } from './darkmode';
8import contextMenu from './contextMenu';
9import './notifications';
10
11const debug = require('debug')('Franz:Plugin');
12
13window.franzSettings = {};
14let serviceData;
15
16ipcRenderer.on('initializeRecipe', (e, data) => {
17 const modulePath = path.join(data.recipe.path, 'webview.js');
18 // Delete module from cache
19 delete require.cache[require.resolve(modulePath)];
20 try {
21 // eslint-disable-next-line
22 require(modulePath)(new RecipeWebview(), data);
23 debug('Initialize Recipe', data);
24
25 serviceData = data;
26
27 if (data.isDarkModeEnabled) {
28 injectDarkModeStyle(data.recipe.path);
29 debug('Add dark theme styles');
30 }
31 } catch (err) {
32 debug('Recipe initialization failed', err);
33 }
34});
35
36// Needs to run asap to intialize dictionaries
37(async () => {
38 const spellcheckingProvider = await spellchecker();
39 contextMenu(spellcheckingProvider);
40})();
41
42ipcRenderer.on('settings-update', async (e, data) => {
43 debug('Settings update received', data);
44
45 if (data.enableSpellchecking) {
46 switchDict(data.spellcheckerLanguage);
47 } else {
48 disableSpellchecker();
49 }
50
51 window.franzSettings = data;
52});
53
54ipcRenderer.on('service-settings-update', (e, data) => {
55 debug('Service settings update received', data);
56
57 if (data.isDarkModeEnabled && !isDarkModeStyleInjected()) {
58 injectDarkModeStyle(serviceData.recipe.path);
59
60 debug('Enable service dark mode');
61 } else if (!data.isDarkModeEnabled && isDarkModeStyleInjected()) {
62 removeDarkModeStyle();
63
64 debug('Disable service dark mode');
65 }
66});
67
68// Needed for current implementation of electrons 'login' event 🤦‍
69ipcRenderer.on('get-service-id', (event) => {
70 debug('Asking for service id', event);
71
72 event.sender.send('service-id', serviceData.id);
73});
74
75
76document.addEventListener('DOMContentLoaded', () => {
77 ipcRenderer.sendToHost('hello');
78}, false);
79
80// Patching window.open
81const originalWindowOpen = window.open;
82
83window.open = (url, frameName, features) => {
84 // We need to differentiate if the link should be opened in a popup or in the systems default browser
85 if (!frameName && !features) {
86 return ipcRenderer.sendToHost('new-window', url);
87 }
88
89 return originalWindowOpen(url, frameName, features);
90};
diff --git a/src/webview/recipe.js b/src/webview/recipe.js
new file mode 100644
index 000000000..944883899
--- /dev/null
+++ b/src/webview/recipe.js
@@ -0,0 +1,131 @@
1import { ipcRenderer } from 'electron';
2import path from 'path';
3import { autorun, computed, observable } from 'mobx';
4
5import RecipeWebview from './lib/RecipeWebview';
6
7import spellchecker, { switchDict, disable as disableSpellchecker } from './spellchecker';
8import { injectDarkModeStyle, isDarkModeStyleInjected, removeDarkModeStyle } from './darkmode';
9import contextMenu from './contextMenu';
10import './notifications';
11
12import { DEFAULT_APP_SETTINGS } from '../config';
13
14const debug = require('debug')('Franz:Plugin');
15
16class RecipeController {
17 @observable settings = {
18 overrideSpellcheckerLanguage: false,
19 app: DEFAULT_APP_SETTINGS,
20 service: {
21 isDarkModeEnabled: false,
22 spellcheckerLanguage: '',
23 },
24 };
25
26 spellcheckProvider = null;
27
28 ipcEvents = {
29 'initialize-recipe': 'loadRecipeModule',
30 'settings-update': 'updateAppSettings',
31 'service-settings-update': 'updateServiceSettings',
32 'get-service-id': 'serviceIdEcho',
33 }
34
35 constructor() {
36 this.initialize();
37 }
38
39 @computed get spellcheckerLanguage() {
40 return this.settings.service.spellcheckerLanguage || this.settings.app.spellcheckerLanguage;
41 }
42
43 async initialize() {
44 Object.keys(this.ipcEvents).forEach((channel) => {
45 ipcRenderer.on(channel, (event, data) => {
46 debug('Received IPC event for channel', channel, 'with', data);
47 this[this.ipcEvents[channel]](event, data);
48 });
49 });
50
51 debug('Send "hello" to host');
52 setTimeout(() => ipcRenderer.sendToHost('hello'), 100);
53
54 this.spellcheckingProvider = await spellchecker();
55 contextMenu(
56 this.spellcheckingProvider,
57 () => this.settings.app.enableSpellchecking,
58 () => this.settings.app.spellcheckerLanguage,
59 () => this.spellcheckerLanguage,
60 );
61
62 autorun(() => this.update());
63 }
64
65 loadRecipeModule(event, data) {
66 debug('loadRecipeModule');
67 const modulePath = path.join(data.recipe.path, 'webview.js');
68 // Delete module from cache
69 delete require.cache[require.resolve(modulePath)];
70 try {
71 // eslint-disable-next-line
72 require(modulePath)(new RecipeWebview(), data);
73 debug('Initialize Recipe', data);
74
75 this.settings.service = data;
76 } catch (err) {
77 console.error('Recipe initialization failed', err);
78 }
79 }
80
81 update() {
82 debug('enableSpellchecking', this.settings.app.enableSpellchecking);
83 debug('isDarkModeEnabled', this.settings.service.isDarkModeEnabled);
84 debug('System spellcheckerLanguage', this.settings.app.spellcheckerLanguage);
85 debug('Service spellcheckerLanguage', this.settings.service.spellcheckerLanguage);
86
87 if (this.settings.app.enableSpellchecking) {
88 debug('Setting spellchecker language to', this.spellcheckerLanguage);
89 switchDict(this.spellcheckerLanguage);
90 } else {
91 debug('Disable spellchecker');
92 disableSpellchecker();
93 }
94
95 if (this.settings.service.isDarkModeEnabled) {
96 debug('Enable dark mode');
97 injectDarkModeStyle(this.settings.service.recipe.path);
98 } else if (isDarkModeStyleInjected()) {
99 debug('Remove dark mode');
100 removeDarkModeStyle();
101 }
102 }
103
104 updateAppSettings(event, data) {
105 this.settings.app = Object.assign(this.settings.app, data);
106 }
107
108 updateServiceSettings(event, data) {
109 this.settings.service = Object.assign(this.settings.service, data);
110 }
111
112 serviceIdEcho(event) {
113 event.sender.send('service-id', this.settings.service.id);
114 }
115}
116
117/* eslint-disable no-new */
118new RecipeController();
119/* eslint-enable no-new */
120
121// Patching window.open
122const originalWindowOpen = window.open;
123
124window.open = (url, frameName, features) => {
125 // We need to differentiate if the link should be opened in a popup or in the systems default browser
126 if (!frameName && !features) {
127 return ipcRenderer.sendToHost('new-window', url);
128 }
129
130 return originalWindowOpen(url, frameName, features);
131};
diff --git a/src/webview/spellchecker.js b/src/webview/spellchecker.js
index b0192b7ef..becaed449 100644
--- a/src/webview/spellchecker.js
+++ b/src/webview/spellchecker.js
@@ -1,7 +1,6 @@
1import { webFrame } from 'electron'; 1import { webFrame } from 'electron';
2import fs from 'fs';
3import path from 'path';
4import { SpellCheckerProvider } from 'electron-hunspell'; 2import { SpellCheckerProvider } from 'electron-hunspell';
3import path from 'path';
5 4
6import { DICTIONARY_PATH } from '../config'; 5import { DICTIONARY_PATH } from '../config';
7 6
@@ -11,18 +10,13 @@ let provider;
11let currentDict; 10let currentDict;
12let _isEnabled = false; 11let _isEnabled = false;
13 12
14async function loadDictionaries() { 13async function loadDictionary(locale) {
15 const rawList = fs.readdirSync(DICTIONARY_PATH); 14 try {
16 15 const fileLocation = path.join(DICTIONARY_PATH, `hunspell-dict-${locale}/${locale}`);
17 const dicts = rawList.filter(item => !item.startsWith('.') && fs.lstatSync(path.join(DICTIONARY_PATH, item)).isDirectory()); 16 await provider.loadDictionary(locale, `${fileLocation}.dic`, `${fileLocation}.aff`);
18 17 debug('Loaded dictionary', locale, 'from', fileLocation);
19 debug('Found dictionaries', dicts); 18 } catch (err) {
20 19 console.error('Could not load dictionary', err);
21 for (let i = 0; i < dicts.length; i += 1) {
22 const fileLocation = `${DICTIONARY_PATH}/${dicts[i]}/${dicts[i]}`;
23 debug('Trying to load', fileLocation);
24 // eslint-disable-next-line
25 await provider.loadDictionary(dicts[i], `${fileLocation}.dic`, `${fileLocation}.aff`);
26 } 20 }
27} 21}
28 22
@@ -30,12 +24,6 @@ export async function switchDict(locale) {
30 try { 24 try {
31 debug('Trying to load dictionary', locale); 25 debug('Trying to load dictionary', locale);
32 26
33 if (!provider.availableDictionaries.includes(locale)) {
34 console.warn('Dict not available', locale);
35
36 return;
37 }
38
39 if (!provider) { 27 if (!provider) {
40 console.warn('SpellcheckProvider not initialized'); 28 console.warn('SpellcheckProvider not initialized');
41 29
@@ -48,6 +36,10 @@ export async function switchDict(locale) {
48 return; 36 return;
49 } 37 }
50 38
39 if (currentDict) {
40 provider.unloadDictionary(locale);
41 }
42 loadDictionary(locale);
51 provider.switchDictionary(locale); 43 provider.switchDictionary(locale);
52 44
53 debug('Switched dictionary to', locale); 45 debug('Switched dictionary to', locale);
@@ -66,7 +58,7 @@ export default async function initialize(languageCode = 'en-us') {
66 58
67 debug('Init spellchecker'); 59 debug('Init spellchecker');
68 await provider.initialize(); 60 await provider.initialize();
69 await loadDictionaries(); 61 // await loadDictionaries();
70 62
71 debug('Available spellchecker dictionaries', provider.availableDictionaries); 63 debug('Available spellchecker dictionaries', provider.availableDictionaries);
72 64