feat: Initialize internationalization with German translations (#819)

This commit is contained in:
Mykola Mokhnach
2019-02-20 20:08:06 +01:00
committed by Dan Graham
parent 17f78bd61d
commit de9a38832e
32 changed files with 1175 additions and 341 deletions
+15 -3
View File
@@ -77,7 +77,7 @@ npm run package-e2e-test
This will create builds in the `release/` folder that are specific for e2e testing. This only needs to be run whenever you make changes to the application.
To run the e2e tests call
To run the e2e tests call
```bash
npm run e2e
@@ -87,8 +87,8 @@ npm run e2e
#### Renderer
* The renderer uses Chromium and can be debugged using Chrome DevTools the same way a webpage is debugged
* When you run dev (`npm run dev`) the Chrome DevTools window is opened on startup.
* To open it again, after it has been closed, right click on the window and select `Inspect Element`
* When you run dev (`npm run dev`) the Chrome DevTools window is opened on startup.
* To open it again, after it has been closed, right click on the window and select `Inspect Element`
#### Main
* In Chrome Dev Tools
@@ -159,3 +159,15 @@ requests. If you are submitting a bug report, please follow the [issue template]
### Sentry
Appium Desktop has crash logs reported to it using [Sentry](https://sentry.io/appium/appiumdesktop/). This requires permission to access.
### Localization
Appium Desktop uses [i18next](https://www.i18next.com) library to manage its translations. All translatable resources are stored in `assets/locales/en/translation.json` file. The list of supported languages can be found (or altered) in `app/configs/app.config.js` module. Please read through i18next API documentation to get more information on how to manage the localized resources:
- https://www.i18next.com/translation-function/essentials
- https://www.i18next.com/translation-function/interpolation
- https://www.i18next.com/translation-function/formatting
- https://www.i18next.com/translation-function/plurals
- https://react.i18next.com/legacy-v9/trans-component
Please only edit the resources for the English language since the other languages are managed by the external Translation Management System and should only be changed by automated scripts. Use `npm run crowdin-update` command to update the original translations in the TMS. Use `npm run crowdin-sync` command to synchronize the translated files with the TMS.
+1 -1
View File
@@ -1,4 +1,4 @@
# Appium Desktop [![Travis Build Status](https://travis-ci.org/appium/appium-desktop.svg?branch=master)](https://travis-ci.org/appium/appium-desktop) [![Build Status](https://dev.azure.com/AppiumCI/Appium%20Desktop/_apis/build/status/appium.appium-desktop)](https://dev.azure.com/AppiumCI/Appium%20Desktop/_build/latest?definitionId=1) [![Build status](https://ci.appveyor.com/api/projects/status/jndqx84vqj2hw64v?svg=true)](https://ci.appveyor.com/project/dpgraham/appium-desktop-2ur5j)
# Appium Desktop [![Travis Build Status](https://travis-ci.org/appium/appium-desktop.svg?branch=master)](https://travis-ci.org/appium/appium-desktop) [![Build Status](https://dev.azure.com/AppiumCI/Appium%20Desktop/_apis/build/status/appium.appium-desktop)](https://dev.azure.com/AppiumCI/Appium%20Desktop/_build/latest?definitionId=1) [![Build status](https://ci.appveyor.com/api/projects/status/jndqx84vqj2hw64v?svg=true)](https://ci.appveyor.com/project/dpgraham/appium-desktop-2ur5j) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/appium-desktop/localized.svg)](https://crowdin.com/project/appium-desktop)
[![Greenkeeper badge](https://badges.greenkeeper.io/dpgraham/appium-desktop.svg)](https://greenkeeper.io/)
![Action screenshot](docs/images/screen-inspector-and-logs.png)
+8
View File
@@ -0,0 +1,8 @@
const config = {
platform: process.platform,
languages: ['en', 'de'],
fallbackLng: 'en',
namespace: 'translation',
};
export default config;
+32
View File
@@ -0,0 +1,32 @@
import i18n from 'i18next';
import i18nextBackend from 'i18next-node-fs-backend';
import config from './app.config';
import path from 'path';
import settings from '../shared/settings';
const i18nextOptions = {
backend: {
loadPath: path.join(__dirname, 'locales/{{lng}}/{{ns}}.json'),
addPath: path.join(__dirname, 'locales/{{lng}}/{{ns}}.json'),
jsonIndent: 2,
},
// debug: true,
// saveMissing: true,
interpolation: {
escapeValue: false
},
lng: settings.getSync('PREFERRED_LANGUAGE', 'en'),
fallbackLng: config.fallbackLng,
whitelist: config.languages,
react: {
wait: false
}
};
if (!i18n.isInitialized) {
i18n
.use(i18nextBackend)
.init(i18nextOptions);
}
export default i18n;
+34
View File
@@ -0,0 +1,34 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import config from './app.config';
import settings from '../shared/settings';
import path from 'path';
import i18nextBackend from 'i18next-node-fs-backend';
const i18nextOptions = {
backend: {
loadPath: path.join(__dirname, 'locales/{{lng}}/{{ns}}.json'),
addPath: path.join(__dirname, 'locales/{{lng}}/{{ns}}.json'),
jsonIndent: 2,
},
// debug: true,
// saveMissing: true,
interpolation: {
escapeValue: false
},
lng: settings.getSync('PREFERRED_LANGUAGE', 'en'),
fallbackLng: config.fallbackLng,
whitelist: config.languages,
react: {
wait: false
}
};
if (!i18n.isInitialized) {
i18n
.use(initReactI18next)
.use(i18nextBackend)
.init(i18nextOptions);
}
export default i18n;
+17 -16
View File
@@ -1,14 +1,14 @@
import { app, BrowserWindow, Menu } from 'electron';
import i18n from './configs/i18next.config';
import { app, BrowserWindow, Menu, webContents } from 'electron';
import { initializeIpc } from './main/appium';
import { setSavedEnv } from './main/helpers';
import menuTemplates from './main/menus';
import rebuildMenus from './main/menus';
import shellEnv from 'shell-env';
import fixPath from 'fix-path';
import { initSentry } from './shared/sentry';
import { promptUser } from './main/sentry-permission-prompt';
import settings from './shared/settings';
let menu;
let template;
let mainWindow = null;
const isDev = process.env.NODE_ENV === 'development';
@@ -81,25 +81,26 @@ app.on('ready', async () => {
const {x, y} = props;
Menu.buildFromTemplate([{
label: 'Inspect element',
label: i18n.t('Inspect element'),
click () {
mainWindow.inspectElement(x, y);
}
}]).popup(mainWindow);
});
promptUser();
i18n.on('languageChanged', async (languageCode) => {
rebuildMenus();
await settings.set('PREFERRED_LANGUAGE', languageCode);
webContents.getAllWebContents().forEach((wc) => {
wc.send('appium-language-changed', {
language: languageCode,
});
});
});
if (process.platform === 'darwin') {
template = await menuTemplates.mac(mainWindow);
menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
} else {
template = await menuTemplates.other(mainWindow);
menu = Menu.buildFromTemplate(template);
mainWindow.setMenu(menu);
}
rebuildMenus(mainWindow);
promptUser();
initializeIpc(mainWindow);
});
+18 -17
View File
@@ -11,6 +11,7 @@ import { checkUpdate } from './update-checker';
import { getFeedUrl } from './config';
import _ from 'lodash';
import env from '../../env';
import i18n from '../../configs/i18next.config';
const isDev = process.env.NODE_ENV === 'development';
const runningLocally = isDev || process.env.RUNNING_LOCALLY;
@@ -31,9 +32,9 @@ if (!runningLocally) {
const update = await checkUpdate(app.getVersion());
if (update) {
let {name, notes, pub_date: pubDate} = update;
pubDate = moment(pubDate).format('MMM Do YYYY, h:mma');
pubDate = moment(pubDate).format(i18n.t('datetimeFormat'));
let detail = `Release Date: ${pubDate}\n\nRelease Notes: ${notes.replace('*', '\n*')}`;
let detail = i18n.t('updateDetails', {pubDate, notes: notes.replace('*', '\n*')});
if (env.NO_AUTO_UPDATE) {
detail += `\n\nhttps://www.github.com/appium/appium-desktop/releases/latest`;
}
@@ -43,8 +44,10 @@ if (!runningLocally) {
if (!process.env.RUNNING_IN_SPECTRON) {
dialog.showMessageBox({
type: 'info',
buttons: env.NO_AUTO_UPDATE ? ['Ok'] : ['Install Now', 'Install Later'],
message: `Appium Desktop ${name} is available`,
buttons: env.NO_AUTO_UPDATE
? [i18n.t('OK')]
: [i18n.t('Install Now'), i18n.t('Install Later')],
message: i18n.t('appiumIsAvailable', {name}),
detail,
}, (response) => {
if (response === 0) {
@@ -70,9 +73,9 @@ if (!runningLocally) {
autoUpdater.on('update-available', () => {
dialog.showMessageBox({
type: 'info',
buttons: ['Ok'],
message: 'Update Download Started',
detail: 'Update is being downloaded now. You will be notified again when it is complete',
buttons: [i18n.t('OK')],
message: i18n.t('Update Download Started'),
detail: i18n.t('updateIsBeingDownloaded'),
});
});
@@ -81,9 +84,9 @@ if (!runningLocally) {
autoUpdater.on('update-not-available', () => {
dialog.showMessageBox({
type: 'info',
buttons: ['Ok'],
message: 'No update available',
detail: 'Appium Desktop is up-to-date',
buttons: [i18n.t('OK')],
message: i18n.t('No update available'),
detail: i18n.t('Appium Desktop is up-to-date'),
});
});
@@ -91,11 +94,9 @@ if (!runningLocally) {
autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName) => {
dialog.showMessageBox({
type: 'info',
buttons: ['Restart Now', 'Later'],
message: 'Update Downloaded',
detail: `Appium Desktop ${releaseName} has been downloaded. ` +
`Must restart to apply the updates ` +
`(note: it may take several minutes for Appium Desktop to install and restart)`,
buttons: [i18n.t('Restart Now'), i18n.t('Later')],
message: i18n.t('Update Downloaded'),
detail: i18n.t('updateIsDownloaded', {releaseName}),
}, (response) => {
// If they say yes, restart now
if (response === 0) {
@@ -108,8 +109,8 @@ if (!runningLocally) {
autoUpdater.on('error', (message) => {
dialog.showMessageBox({
type: 'error',
message: 'Could not download update',
detail: `Failed to download update. Reason: ${message}`,
message: i18n.t('Could not download update'),
detail: i18n.t('updateDownloadFailed', {message}),
});
});
+174 -126
View File
@@ -1,10 +1,13 @@
import { app, shell, dialog } from 'electron';
import { app, shell, dialog, Menu } from 'electron';
import _ from 'lodash';
import { createNewSessionWindow, createNewConfigWindow} from './appium';
import { checkNewUpdates } from './auto-updater';
import CloudProviders from '../shared/cloud-providers';
import config from '../configs/app.config';
import i18n from '../configs/i18next.config';
let menuTemplates = {mac: {}, other: {}};
let mainWindow = null;
async function getCloudProvidersViewMenu () {
const providersMenu = [];
@@ -21,47 +24,56 @@ async function getCloudProvidersViewMenu () {
return providersMenu;
}
function macMenuAppium (mainWindow) {
function languageMenu () {
return config.languages.map((languageCode) => ({
label: i18n.t(languageCode),
type: 'radio',
checked: i18n.language === languageCode,
click: () => i18n.changeLanguage(languageCode)
}));
}
function macMenuAppium () {
return {
label: 'Appium',
submenu: [{
label: 'About Appium',
label: i18n.t('About Appium'),
selector: 'orderFrontStandardAboutPanel:'
}, {
label: 'Check for updates',
label: i18n.t('Check for updates'),
click () {
checkNewUpdates(true);
}
}, {
type: 'separator'
}, {
label: 'New Session Window...',
label: i18n.t('New Session Window…'),
accelerator: 'Command+N',
click () {
createNewSessionWindow(mainWindow);
}
}, {
label: 'Configurations',
label: i18n.t('Configurations…'),
click () {
createNewConfigWindow(mainWindow);
}
}, {
type: 'separator'
}, {
label: 'Hide Appium',
label: i18n.t('Hide Appium'),
accelerator: 'Command+H',
selector: 'hide:'
}, {
label: 'Hide Others',
label: i18n.t('Hide Others'),
accelerator: 'Command+Shift+H',
selector: 'hideOtherApplications:'
}, {
label: 'Show All',
label: i18n.t('Show All'),
selector: 'unhideAllApplications:'
}, {
type: 'separator'
}, {
label: 'Quit',
label: i18n.t('Quit'),
accelerator: 'Command+Q',
click () {
app.quit();
@@ -70,46 +82,48 @@ function macMenuAppium (mainWindow) {
};
}
const macMenuEdit = {
label: 'Edit',
submenu: [{
label: 'Undo',
accelerator: 'Command+Z',
selector: 'undo:'
}, {
label: 'Redo',
accelerator: 'Shift+Command+Z',
selector: 'redo:'
}, {
type: 'separator'
}, {
label: 'Cut',
accelerator: 'Command+X',
selector: 'cut:'
}, {
label: 'Copy',
accelerator: 'Command+C',
selector: 'copy:'
}, {
label: 'Paste',
accelerator: 'Command+V',
selector: 'paste:'
}, {
label: 'Select All',
accelerator: 'Command+A',
selector: 'selectAll:'
}]
};
function macMenuEdit () {
return {
label: i18n.t('Edit'),
submenu: [{
label: i18n.t('Undo'),
accelerator: 'Command+Z',
selector: 'undo:'
}, {
label: i18n.t('Redo'),
accelerator: 'Shift+Command+Z',
selector: 'redo:'
}, {
type: 'separator'
}, {
label: i18n.t('Cut'),
accelerator: 'Command+X',
selector: 'cut:'
}, {
label: i18n.t('Copy'),
accelerator: 'Command+C',
selector: 'copy:'
}, {
label: i18n.t('Paste'),
accelerator: 'Command+V',
selector: 'paste:'
}, {
label: i18n.t('Select All'),
accelerator: 'Command+A',
selector: 'selectAll:'
}]
};
}
async function macMenuView (mainWindow) {
async function macMenuView () {
const submenu = (process.env.NODE_ENV === 'development') ? [{
label: 'Reload',
label: i18n.t('Reload'),
accelerator: 'Command+R',
click () {
mainWindow.webContents.reload();
}
}, {
label: 'Toggle Developer Tools',
label: i18n.t('Toggle Developer Tools'),
accelerator: 'Alt+Command+I',
click () {
mainWindow.toggleDevTools();
@@ -117,7 +131,7 @@ async function macMenuView (mainWindow) {
}] : [];
submenu.push({
label: 'Toggle Full Screen',
label: i18n.t('Toggle Full Screen'),
accelerator: 'Ctrl+Command+F',
click () {
mainWindow.setFullScreen(!mainWindow.isFullScreen());
@@ -125,84 +139,93 @@ async function macMenuView (mainWindow) {
});
submenu.push({
label: 'Cloud Providers',
label: i18n.t('Languages'),
submenu: languageMenu(),
});
submenu.push({
label: i18n.t('Cloud Providers'),
submenu: await getCloudProvidersViewMenu(),
});
return {
label: 'View',
label: i18n.t('View'),
submenu,
};
}
const macMenuWindow = {
label: 'Window',
submenu: [{
label: 'Minimize',
accelerator: 'Command+M',
selector: 'performMiniaturize:'
}, {
label: 'Close',
accelerator: 'Command+W',
selector: 'performClose:'
}, {
type: 'separator'
}, {
label: 'Bring All to Front',
selector: 'arrangeInFront:'
}]
};
function macMenuWindow () {
return {
label: i18n.t('Window'),
submenu: [{
label: i18n.t('Minimize'),
accelerator: 'Command+M',
selector: 'performMiniaturize:'
}, {
label: i18n.t('Close'),
accelerator: 'Command+W',
selector: 'performClose:'
}, {
type: 'separator'
}, {
label: i18n.t('Bring All to Front'),
selector: 'arrangeInFront:'
}]
};
}
const macMenuHelp = {
label: 'Help',
submenu: [{
label: 'Learn More',
click () {
shell.openExternal('http://appium.io');
}
}, {
label: 'Documentation',
click () {
shell.openExternal('https://appium.io/documentation.html');
}
}, {
label: 'Search Issues',
click () {
shell.openExternal('https://github.com/appium/appium-desktop/issues');
}
}]
};
function macMenuHelp () {
return {
label: i18n.t('Help'),
submenu: [{
label: i18n.t('Learn More'),
click () {
shell.openExternal('http://appium.io');
}
}, {
label: i18n.t('Documentation'),
click () {
shell.openExternal('https://appium.io/documentation.html');
}
}, {
label: i18n.t('Search Issues'),
click () {
shell.openExternal('https://github.com/appium/appium-desktop/issues');
}
}]
};
}
menuTemplates.mac = async (mainWindow) => [
macMenuAppium(mainWindow),
macMenuEdit,
await macMenuView(mainWindow),
macMenuWindow,
macMenuHelp
menuTemplates.mac = async () => [
macMenuAppium(),
macMenuEdit(),
await macMenuView(),
macMenuWindow(),
macMenuHelp(),
];
function otherMenuFile (mainWindow) {
function otherMenuFile () {
let fileSubmenu = [{
label: '&Open',
label: i18n.t('&Open'),
accelerator: 'Ctrl+O'
}, {
label: '&About Appium',
label: i18n.t('&About Appium'),
click () {
dialog.showMessageBox({
title: 'Appium Desktop',
message: `Version ${app.getVersion()}`,
title: i18n.t('Appium Desktop'),
message: i18n.t(`Version ${app.getVersion()}`),
});
}
}, {
type: 'separator'
}, {
label: '&New Session Window...',
label: i18n.t('&New Session Window...'),
accelerator: 'Ctrl+N',
click () {
createNewSessionWindow(mainWindow);
}
}, {
label: '&Close',
label: i18n.t('&Close'),
accelerator: 'Ctrl+W',
click () {
mainWindow.close();
@@ -212,7 +235,7 @@ function otherMenuFile (mainWindow) {
// If it's Windows, add a 'Check for Updates' menu option
if (process.platform === 'win32') {
fileSubmenu.splice(1, 0, {
label: '&Check for updates',
label: i18n.t('&Check for updates'),
click () {
checkNewUpdates(true);
}
@@ -220,16 +243,15 @@ function otherMenuFile (mainWindow) {
}
return {
label: '&File',
label: i18n.t('&File'),
submenu: fileSubmenu,
};
}
async function otherMenuView (mainWindow) {
async function otherMenuView () {
const submenu = [];
submenu.push([{
label: 'Toggle &Full Screen',
label: i18n.t('Toggle &Full Screen'),
accelerator: 'F11',
click () {
mainWindow.setFullScreen(!mainWindow.isFullScreen());
@@ -237,20 +259,25 @@ async function otherMenuView (mainWindow) {
}]);
submenu.push({
label: 'Cloud Providers',
label: i18n.t('Languages'),
submenu: languageMenu(),
});
submenu.push({
label: i18n.t('Cloud Providers'),
submenu: await getCloudProvidersViewMenu(),
});
if (process.env.NODE_ENV === 'development') {
submenu.push({
label: '&Reload',
label: i18n.t('&Reload'),
accelerator: 'Ctrl+R',
click () {
mainWindow.webContents.reload();
}
});
submenu.push({
label: 'Toggle &Developer Tools',
label: i18n.t('Toggle &Developer Tools'),
accelerator: 'Alt+Ctrl+I',
click () {
mainWindow.toggleDevTools();
@@ -259,35 +286,56 @@ async function otherMenuView (mainWindow) {
}
return {
label: '&View',
label: i18n.t('&View'),
submenu,
};
}
const otherMenuHelp = {
label: 'Help',
submenu: [{
label: 'Learn More',
click () {
shell.openExternal('http://appium.io');
}
}, {
label: 'Documentation',
click () {
shell.openExternal('https://appium.io/documentation.html');
}
}, {
label: 'Search Issues',
click () {
shell.openExternal('https://github.com/appium/appium-desktop/issues');
}
}]
};
function otherMenuHelp () {
return {
label: i18n.t('Help'),
submenu: [{
label: 'Learn More',
click () {
shell.openExternal('http://appium.io');
}
}, {
label: i18n.t('Documentation'),
click () {
shell.openExternal('https://appium.io/documentation.html');
}
}, {
label: i18n.t('Search Issues'),
click () {
shell.openExternal('https://github.com/appium/appium-desktop/issues');
}
}]
};
}
menuTemplates.other = async (mainWindow) => [
menuTemplates.other = async () => [
otherMenuFile(mainWindow),
await otherMenuView(mainWindow),
otherMenuHelp
otherMenuHelp()
];
export default menuTemplates;
async function rebuildMenus (mainWin = null) {
if (mainWin) {
mainWindow = mainWin;
}
if (!mainWindow) {
return;
}
if (config.platform === 'darwin') {
const template = await menuTemplates.mac(mainWindow);
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
} else {
const template = await menuTemplates.other(mainWindow);
const menu = Menu.buildFromTemplate(template);
mainWindow.setMenu(menu);
}
}
export default rebuildMenus;
+30 -21
View File
@@ -9,18 +9,18 @@ const ENV_VARIABLE_NAMES = [
const {app, dialog, getCurrentWindow} = remote;
export default class Config extends Component {
class Config extends Component {
componentWillMount () {
this.props.getEnvironmentVariables();
}
saveAndRestart () {
const {environmentVariables} = this.props;
const { environmentVariables, t } = this.props;
ipcRenderer.send('appium-save-env', environmentVariables);
ipcRenderer.once('appium-save-env-done', () => {
const message = `Application must be restarted for changes to take effect`;
const dialogOptions = {type: 'info', buttons: ['Restart Now', 'Restart Later'], message};
const message = t(`Application must be restarted for changes to take effect`);
const dialogOptions = {type: 'info', buttons: [t('Restart Now'), t('Restart Later')], message};
ipcRenderer.removeAllListeners('appium-save-env-done');
dialog.showMessageBox(dialogOptions, (response) => {
if (response === 0) {
@@ -36,25 +36,34 @@ export default class Config extends Component {
}
render () {
const {setEnvironmentVariable, environmentVariables, defaultEnvironmentVariables} = this.props;
const {
setEnvironmentVariable,
environmentVariables,
defaultEnvironmentVariables,
t,
} = this.props;
return <div className={styles.container}>
<h3>Environment Variables</h3>
{ENV_VARIABLE_NAMES.map((ENV_NAME) => (
<Row key={ENV_NAME} className={styles.row} gutter={16}>
return (
<div className={styles.container}>
<h3>{t('Environment Variables')}</h3>
{ENV_VARIABLE_NAMES.map((ENV_NAME) => (
<Row key={ENV_NAME} className={styles.row} gutter={16}>
<Col span={24}>
<Input addonBefore={ENV_NAME}
placeholder={defaultEnvironmentVariables[ENV_NAME]}
onChange={(evt) => setEnvironmentVariable(ENV_NAME, evt.target.value)}
value={environmentVariables[ENV_NAME]} />
</Col>
</Row>
))}
<Row>
<Col span={24}>
<Input addonBefore={ENV_NAME}
placeholder={defaultEnvironmentVariables[ENV_NAME]}
onChange={(evt) => setEnvironmentVariable(ENV_NAME, evt.target.value)}
value={environmentVariables[ENV_NAME]} />
<Button onClick={() => this.saveAndRestart()}>{t('Save and Restart')}</Button>
</Col>
</Row>
))}
<Row>
<Col span={24}>
<Button onClick={() => this.saveAndRestart()}>Save and Restart</Button>
</Col>
</Row>
</div>;
</div>
);
}
}
export default Config;
@@ -0,0 +1,66 @@
.container {
position: absolute;
width: 100%;
height: 100%;
}
.loader,
.loader:before,
.loader:after {
background: #555;
-webkit-animation: load1 1s infinite ease-in-out;
animation: load1 1s infinite ease-in-out;
width: 1em;
height: 4em;
}
.loader {
color: #555;
text-indent: -9999em;
top: 45%;
margin: auto;
position: relative;
font-size: 11px;
-webkit-transform: translateZ(0);
-ms-transform: translateZ(0);
transform: translateZ(0);
-webkit-animation-delay: -0.16s;
animation-delay: -0.16s;
}
.loader:before,
.loader:after {
position: absolute;
top: 0;
content: '';
}
.loader:before {
left: -1.5em;
-webkit-animation-delay: -0.32s;
animation-delay: -0.32s;
}
.loader:after {
left: 1.5em;
}
@-webkit-keyframes load1 {
0%,
80%,
100% {
box-shadow: 0 0;
height: 4em;
}
40% {
box-shadow: 0 -2em;
height: 5em;
}
}
@keyframes load1 {
0%,
80%,
100% {
box-shadow: 0 0;
height: 4em;
}
40% {
box-shadow: 0 -2em;
height: 5em;
}
}
@@ -0,0 +1,10 @@
import React from 'react';
import styles from './Spinner.css';
const Spinner = () => (
<div className={styles.container}>
<div className={styles.loader}/>
</div>
);
export default Spinner;
@@ -1,6 +1,7 @@
import React, { Component } from 'react';
import { Modal, Input, Checkbox, notification } from 'antd';
import { withTranslation } from '../../util';
import { propTypes, updateArg } from './shared';
import StartButton from './StartButton';
import SavePresetButton from './SavePresetButton';
@@ -16,7 +17,7 @@ import styles from './AdvancedTab.css';
// isolateSimDevice, tmpDir, traceDir, debugLogSpacing,
// suppressAdbKillServer, webkitDebugProxyPort, defaultCapabilities
export default class AdvancedTab extends Component {
class AdvancedTab extends Component {
constructor (props) {
super(props);
@@ -27,7 +28,7 @@ export default class AdvancedTab extends Component {
}
buildInput (argName, type, label) {
const {serverArgs} = this.props;
const {serverArgs, t} = this.props;
if (type === 'text') {
return (
@@ -56,7 +57,7 @@ export default class AdvancedTab extends Component {
return '';
}
throw new Error(`Invalid type ${type}`);
throw new Error(t('invalidType', {type}));
}
openPresetModal () {
@@ -74,6 +75,7 @@ export default class AdvancedTab extends Component {
}
savePreset (evt) {
const {t} = this.props;
if (evt) {
// might get here from Modal binding (no event) or form submit binding
// (event)
@@ -85,14 +87,14 @@ export default class AdvancedTab extends Component {
}
this.props.savePreset(this.state.newPresetName, this.props.serverArgs);
notification.success({
message: 'Saved',
description: `Your preset '${this.state.newPresetName}' has been added ` +
`to the list in the Presets tab!`
message: t('Saved'),
description: t('presetAdded', {presetName: this.state.newPresetName})
});
this.setState({modalOpen: false});
}
modal () {
const {t} = this.props;
const form = (
<div>
<form className={styles.input}
@@ -113,11 +115,11 @@ export default class AdvancedTab extends Component {
<Modal
visible={this.state.modalOpen}
className={styles.presetModal}
title="Save Server Arguments Preset"
title={t('Save Server Arguments Preset')}
width={340}
okText="Save"
okText={t('Save')}
onOk={this.savePreset.bind(this)}
cancelText="Cancel"
cancelText={t('Cancel')}
onCancel={this.closePresetModal.bind(this)}
>
{form}
@@ -126,42 +128,42 @@ export default class AdvancedTab extends Component {
}
render () {
const {startServer, serverStarting, presetSaving, serverVersion} = this.props;
const {startServer, serverStarting, presetSaving, serverVersion, t} = this.props;
return (
<div className={styles.advancedForm}>
<form onSubmit={startServer}>
<div className={styles.inputSection}>
<div className={styles.secTitle}>General</div>
<div className={styles.secTitle}>{t('General')}</div>
<div className={styles.secBody}>
{this.buildInput('address', 'text', 'Server Address')}
{this.buildInput('port', 'text', 'Server Port')}
{this.buildInput('log', 'text', 'Logfile Path')}
{this.buildInput('loglevel', 'text', 'Log Level')}
{this.buildInput('tmpDir', 'text', 'Override Temp Path')}
{this.buildInput('nodeconfig', 'text', 'Node Config File Path')}
{this.buildInput('localTimezone', 'checkbox', 'Local Timezone')}
{this.buildInput('sessionOverride', 'checkbox', 'Allow Session Override')}
{this.buildInput('logTimestamp', 'checkbox', 'Log Timestamps')}
{this.buildInput('logNoColors', 'checkbox', 'Supress Log Color')}
{this.buildInput('enforceStrictCaps', 'checkbox', 'Strict Caps Mode')}
{this.buildInput('relaxedSecurityEnabled', 'checkbox', 'Relaxed Security')}
{this.buildInput('defaultCapabilities', 'textarea', 'Default Capabilities')}
{this.buildInput('address', 'text', t('Server Address'))}
{this.buildInput('port', 'text', t('Server Port'))}
{this.buildInput('log', 'text', t('Logfile Path'))}
{this.buildInput('loglevel', 'text', t('Log Level'))}
{this.buildInput('tmpDir', 'text', t('Override Temp Path'))}
{this.buildInput('nodeconfig', 'text', t('Node Config File Path'))}
{this.buildInput('localTimezone', 'checkbox', t('Local Timezone'))}
{this.buildInput('sessionOverride', 'checkbox', t('Allow Session Override'))}
{this.buildInput('logTimestamp', 'checkbox', t('Log Timestamps'))}
{this.buildInput('logNoColors', 'checkbox', t('Supress Log Color'))}
{this.buildInput('enforceStrictCaps', 'checkbox', t('Strict Caps Mode'))}
{this.buildInput('relaxedSecurityEnabled', 'checkbox', t('Relaxed Security'))}
{this.buildInput('defaultCapabilities', 'textarea', t('Default Capabilities'))}
</div>
<div className={styles.secTitle}>iOS</div>
<div className={styles.secBody}>
{this.buildInput('wdaLocalPort', 'text', 'WebDriverAgent Port')}
{this.buildInput('callbackHost', 'text', 'executeAsync Callback Host')}
{this.buildInput('callbackPort', 'text', 'executeAsync Callback Port')}
{this.buildInput('wdaLocalPort', 'text', t('WebDriverAgent Port'))}
{this.buildInput('callbackHost', 'text', t('executeAsync Callback Host'))}
{this.buildInput('callbackPort', 'text', t('executeAsync Callback Port'))}
</div>
<div className={styles.secTitle}>Android</div>
<div className={styles.secBody}>
{this.buildInput('bootstrapPort', 'text', 'Bootstrap Port')}
{this.buildInput('selendroidPort', 'text', 'Selendroid Port')}
{this.buildInput('chromeDriverPort', 'text', 'Chromedriver Port')}
{this.buildInput('chromedriverExecutable', 'text', 'Chromedriver Binary Path')}
{this.buildInput('bootstrapPort', 'text', t('Bootstrap Port'))}
{this.buildInput('selendroidPort', 'text', t('Selendroid Port'))}
{this.buildInput('chromeDriverPort', 'text', t('Chromedriver Port'))}
{this.buildInput('chromedriverExecutable', 'text', t('Chromedriver Binary Path'))}
</div>
</div>
<div className={styles.actions}>
@@ -176,3 +178,5 @@ export default class AdvancedTab extends Component {
}
AdvancedTab.propTypes = {...propTypes};
export default withTranslation(AdvancedTab);
@@ -1,19 +1,20 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Button } from 'antd';
import { withTranslation } from '../../util';
import styles from './StartButton.css';
export default class DeletePresetButton extends Component {
class DeletePresetButton extends Component {
render () {
const {presetDeleting, deletePreset} = this.props;
const {presetDeleting, deletePreset, t} = this.props;
return (
<div>
<Button className={styles.startButton}
type={presetDeleting ? 'disabled' : null}
onClick={deletePreset}
>{presetDeleting ? 'Deleting...' : 'Delete Preset'}</Button>
>{presetDeleting ? t('Deleting…') : t('Delete Preset')}</Button>
</div>
);
}
@@ -22,4 +23,6 @@ export default class DeletePresetButton extends Component {
DeletePresetButton.propTypes = {
presetDeleting: PropTypes.bool.isRequired,
deletePreset: PropTypes.func.isRequired,
};
};
export default withTranslation(DeletePresetButton);
@@ -2,6 +2,7 @@ import _ from 'lodash';
import moment from 'moment';
import React, { Component } from 'react';
import { notification, Table } from 'antd';
import { withTranslation } from '../../util';
import { propTypes } from './shared';
import StartButton from './StartButton';
@@ -9,7 +10,7 @@ import DeletePresetButton from './DeletePresetButton';
import advancedStyles from './AdvancedTab.css';
import styles from './PresetsTab.css';
export default class PresetsTab extends Component {
class PresetsTab extends Component {
constructor (props) {
super(props);
@@ -29,10 +30,11 @@ export default class PresetsTab extends Component {
}
emptyPresetList () {
const {t} = this.props;
return (
<div className={styles.noPresetsContainer}>
<div className={styles.noPresetsMessage}>
You don't yet have any presets. Create some on the Advanced tab.
{t('noPresets')}
</div>
</div>
);
@@ -63,19 +65,21 @@ export default class PresetsTab extends Component {
}
deletePreset (evt) {
const {t} = this.props;
evt.preventDefault();
if (window.confirm(`Are you sure you want to delete ${this.state.selectedPreset}?`)) {
if (window.confirm(t('deleteSelectedPresetConfirmation', {presetName: this.state.selectedPreset}))) {
this.props.deletePreset(this.state.selectedPreset);
this.setState({selectedPreset: null});
notification.success({
message: 'Deleted',
description: 'Preset successfully trashed'
message: t('Deleted'),
description: t('Preset successfully trashed')
});
}
}
presetList () {
const {presets} = this.props;
const {presets, t} = this.props;
return (
<ul className={styles.presetsList}>
{_.toPairs(presets).map((p) =>
@@ -86,7 +90,7 @@ export default class PresetsTab extends Component {
>
<li className={`${styles.preset} ${p[0] === this.state.selectedPreset ? styles.presetItemActive : ''}`}>
<div className={styles.presetItemTitle}>{p[0]}</div>
<div className={styles.presetItemDesc}>{`Saved ${moment(p[1]._modified).fromNow()}`}</div>
<div className={styles.presetItemDesc}>{t('savedTimestamp', {when: moment(p[1]._modified).fromNow()})}</div>
</li>
</a>
)}
@@ -95,14 +99,15 @@ export default class PresetsTab extends Component {
}
presetDetail () {
const {t} = this.props;
const preset = this.selectedPresetData();
if (preset) {
const columns = [{
title: 'Server Argument',
title: t('Server Argument'),
dataIndex: 'arg',
width: 200
}, {
title: 'Value',
title: t('Value'),
dataIndex: 'val',
}];
let data = [];
@@ -137,7 +142,7 @@ export default class PresetsTab extends Component {
<div className={advancedStyles.actions}>
<StartButton {...{serverStarting, startServer, serverVersion, disabledOverride: !this.presetIsSelected()}} />
{this.presetIsSelected() &&
<DeletePresetButton {...{presetDeleting, deletePreset: this.deletePreset.bind(this)}} />
<DeletePresetButton {...{presetDeleting, deletePreset: this.deletePreset.bind(this)}} />
}
</div>
</form>
@@ -147,3 +152,5 @@ export default class PresetsTab extends Component {
}
PresetsTab.propTypes = {...propTypes};
export default withTranslation(PresetsTab);
@@ -1,19 +1,19 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Button } from 'antd';
import { withTranslation } from '../../util';
import styles from './StartButton.css';
export default class SavePresetButton extends Component {
class SavePresetButton extends Component {
render () {
const {presetSaving, savePreset} = this.props;
const {presetSaving, savePreset, t} = this.props;
return (
<div>
<Button className={styles.startButton}
type={presetSaving ? 'disabled' : ''}
onClick={savePreset}
>{presetSaving ? 'Saving...' : 'Save As Preset...'}</Button>
>{presetSaving ? t('Saving…') : t('Save As Preset…')}</Button>
<input type="submit" hidden={true} />
</div>
);
@@ -24,3 +24,5 @@ SavePresetButton.propTypes = {
presetSaving: PropTypes.bool.isRequired,
savePreset: PropTypes.func.isRequired,
};
export default withTranslation(SavePresetButton);
@@ -1,11 +1,11 @@
import React, { Component } from 'react';
import { Input } from 'antd';
import { withTranslation } from '../../util';
import { propTypes, updateArg } from './shared';
import StartButton from './StartButton';
import styles from './SimpleTab.css';
export default class SimpleTab extends Component {
class SimpleTab extends Component {
render () {
const {startServer, serverArgs, serverStarting, serverVersion} = this.props;
@@ -32,3 +32,5 @@ export default class SimpleTab extends Component {
}
SimpleTab.propTypes = {...propTypes};
export default withTranslation(SimpleTab);
@@ -2,10 +2,11 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Button, Icon } from 'antd';
import { ipcRenderer } from 'electron';
import { withTranslation } from '../../util';
import styles from './StartButton.css';
export default class StartButton extends Component {
class StartButton extends Component {
isEnabled () {
return !(this.props.serverStarting || this.props.disabledOverride);
}
@@ -19,7 +20,7 @@ export default class StartButton extends Component {
}
render () {
const {startServer, serverStarting, serverVersion} = this.props;
const {startServer, serverStarting, serverVersion, t} = this.props;
const buttonProps = {};
if (!this.isEnabled()) {
buttonProps.disabled = true;
@@ -32,13 +33,13 @@ export default class StartButton extends Component {
type="primary"
onClick={this.isEnabled() ? startServer : this.noop}
>
{serverStarting ? 'Starting...' : `Start Server v${serverVersion}`}
{serverStarting ? t('Starting…') : t('startServer', {serverVersion})}
</Button>
<input type="submit" hidden={true} />
<Button id='configBtn'
className={styles.configButton}
onClick={() => this.openConfig()}>
Edit Configurations <Icon type="setting" />
{t('Edit Configurations')}<Icon type="setting" />
</Button>
</div>
);
@@ -50,3 +51,5 @@ StartButton.propTypes = {
startServer: PropTypes.func.isRequired,
disabledOverride: PropTypes.bool,
};
export default withTranslation(StartButton);
@@ -15,6 +15,8 @@ const TAB_SIMPLE = 0, TAB_ADVANCED = 1, TAB_PRESETS = 2;
export default class StartServer extends Component {
displayTab () {
const {t} = this.props;
switch (this.props.tabId) {
case TAB_SIMPLE:
return <SimpleTab {...this.props} />;
@@ -23,12 +25,12 @@ export default class StartServer extends Component {
case TAB_PRESETS:
return <PresetsTab {...this.props} />;
default:
throw new Error('Invalid tab id');
throw new Error(t('Invalid tab id'));
}
}
render () {
const {tabId, switchTab} = this.props;
const {tabId, switchTab, t} = this.props;
return (
<div className={styles.container}>
<div className={styles.formAndLogo}>
@@ -37,13 +39,13 @@ export default class StartServer extends Component {
<Button.Group className={styles.tabButtons}>
<Button type={tabId === TAB_SIMPLE ? 'primary' : null }
onClick={() => switchTab(TAB_SIMPLE)}
>Simple</Button>
>{t('Simple')}</Button>
<Button type={tabId === TAB_ADVANCED ? 'primary' : null }
onClick={() => switchTab(TAB_ADVANCED)}
>Advanced</Button>
>{t('Advanced')}</Button>
<Button type={tabId === TAB_PRESETS ? 'primary' : null }
onClick={() => switchTab(TAB_PRESETS)}
>Presets</Button>
>{t('Presets')}</Button>
</Button.Group>
</div>
{this.displayTab()}
+9 -4
View File
@@ -1,13 +1,18 @@
import * as React from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { withTranslation } from '../util';
export default class App extends React.Component {
class App extends React.Component {
render () {
const { children } = this.props;
return <React.Fragment>{children}</React.Fragment>;
return (
<React.Fragment>{children}</React.Fragment>
);
}
}
App.propTypes = {
children: PropTypes.element.isRequired
};
};
export default withTranslation(App);
+2 -1
View File
@@ -2,6 +2,7 @@ import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as ConfigActions from '../actions/Config';
import ConfigPage from '../components/Config/Config';
import { withTranslation } from '../util';
function mapStateToProps (state) {
return state.config;
@@ -11,4 +12,4 @@ function mapDispatchToProps (dispatch) {
return bindActionCreators(ConfigActions, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(ConfigPage);
export default withTranslation(ConfigPage, connect(mapStateToProps, mapDispatchToProps));
+2 -1
View File
@@ -1,5 +1,6 @@
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { withTranslation } from '../util';
import * as InspectorActions from '../actions/Inspector';
import InspectorPage from '../components/Inspector/Inspector';
@@ -11,4 +12,4 @@ function mapDispatchToProps (dispatch) {
return bindActionCreators(InspectorActions, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(InspectorPage);
export default withTranslation(InspectorPage, connect(mapStateToProps, mapDispatchToProps));
+2 -1
View File
@@ -1,5 +1,6 @@
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { withTranslation } from '../util';
import * as ServerActions from '../actions/ServerMonitor';
import ServerMonitor from '../components/ServerMonitor/ServerMonitor';
@@ -11,4 +12,4 @@ function mapDispatchToProps (dispatch) {
return bindActionCreators(ServerActions, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(ServerMonitor);
export default withTranslation(ServerMonitor, connect(mapStateToProps, mapDispatchToProps));
+2 -1
View File
@@ -1,5 +1,6 @@
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { withTranslation } from '../util';
import * as SessionActions from '../actions/Session';
import Session from '../components/Session/Session';
@@ -11,4 +12,4 @@ function mapDispatchToProps (dispatch) {
return bindActionCreators(SessionActions, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(Session);
export default withTranslation(Session, connect(mapStateToProps, mapDispatchToProps));
+2 -1
View File
@@ -1,5 +1,6 @@
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { withTranslation } from '../util';
import * as StartServerActions from '../actions/StartServer';
import StartServer from '../components/StartServer/StartServer';
@@ -11,4 +12,4 @@ function mapDispatchToProps (dispatch) {
return bindActionCreators(StartServerActions, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(StartServer);
export default withTranslation(StartServer, connect(mapStateToProps, mapDispatchToProps));
+21 -10
View File
@@ -1,4 +1,4 @@
import React from 'react';
import React, { Suspense } from 'react';
import { Switch, Route } from 'react-router';
import App from './containers/App';
import StartServerPage from './containers/StartServerPage';
@@ -6,15 +6,26 @@ import ServerMonitorPage from './containers/ServerMonitorPage';
import SessionPage from './containers/SessionPage';
import InspectorPage from './containers/InspectorPage';
import ConfigPage from './containers/ConfigPage';
import Spinner from './components/Spinner/Spinner';
import { ipcRenderer } from 'electron';
import i18n from '../configs/i18next.config.renderer';
ipcRenderer.on('appium-language-changed', (event, message) => {
if (i18n.language !== message.language) {
i18n.changeLanguage(message.language);
}
});
export default () => (
<App>
<Switch>
<Route exact path="/" component={StartServerPage} />
<Route path="/monitor" component={ServerMonitorPage} />
<Route path="/session" component={SessionPage} />
<Route path="/inspector" component={InspectorPage} />
<Route path="/config" component={ConfigPage} />
</Switch>
</App>
<Suspense fallback={<Spinner />}>
<App>
<Switch>
<Route exact path="/" component={StartServerPage} />
<Route path="/monitor" component={ServerMonitorPage} />
<Route path="/session" component={SessionPage} />
<Route path="/inspector" component={InspectorPage} />
<Route path="/config" component={ConfigPage} />
</Switch>
</App>
</Suspense>
);
+11 -1
View File
@@ -1,4 +1,7 @@
import XPath from 'xpath';
import { withTranslation as wt } from 'react-i18next';
import config from '../configs/app.config';
import _ from 'lodash';
/**
* Get an optimal XPath for a DOMNode
@@ -59,4 +62,11 @@ export function getOptimalXPath (doc, domNode, uniqueAttributes = ['id']) {
// If there's an unexpected exception, abort and don't get an XPath
return null;
}
}
}
export function withTranslation (componentCls, ...hocs) {
return _.flow(
...hocs,
wt(config.namespace),
)(componentCls);
}
+88
View File
@@ -0,0 +1,88 @@
{
"en": "English",
"de": "Deutsch",
"Edit": "Bearbeiten",
"Redo": "Wiederholen",
"Cut": "Ausschneiden",
"Copy": "Kopieren",
"Paste": "Einsetzen",
"Select All": "Alles auswählen",
"Window": "Fenster",
"Minimize": "Im Dock ablegen",
"Close": "Schließen",
"Bring All to Front": "Alle nach vorne bringen",
"Help": "Hilfe",
"Documentation": "Dokumentation",
"Search Issues": "Nach Probleme suchen",
"About Appium": "Über Appium",
"Check for updates": "Nach Updates suchen",
"New Session Window…": "Neues Sessionfenster…",
"Configurations…": "Konfigurationen…",
"Hide Appium": "Appium ausblenden",
"Hide Others": "Andere ausblenden",
"Show All": "Alle einblenden",
"Quit": "Beenden",
"Toggle Full Screen": "Vollbildmodus ein-\/ausschalten",
"Languages": "Sprachen",
"View": "Darstellung",
"Reload": "Neu laden",
"Toggle Developer Tools": "Entwickler-Werkzeuge ein-\/ausblenden",
"Learn More": "Weitere Infos",
"Undo": "Widerrufen",
"Cloud Providers": "Cloudanbieter…",
"Environment Variables": "Umgebungsvariablen",
"Save and Restart": "Sichern und neustarten",
"Application must be restarted for changes to take effect": "Die Änderungen werden erst beim nächsten Start übernommen",
"Restart Now": "Jetzt neustarten",
"Restart Later": "Später neustarten",
"Simple": "Einfach",
"Advanced": "Erweitert",
"Presets": "Voreinstellungen",
"Edit Configurations": "Konfiguration bearbeiten",
"Server Address": "Serveradresse",
"Server Port": "Server-Port",
"Logfile Path": "Protokolldateipfad",
"Log Level": "Protokollstufe",
"Override Temp Path": "Temporärer Pfad überschreiben",
"Node Config File Path": "Konfiguration-Dateipfad von Node",
"Local Timezone": "Lokale Zeitzone",
"Allow Session Override": "Sessionüberschreibung erlauben",
"Log Timestamps": "Protokoll-Zeitmarken",
"Supress Log Color": "Protokollfarbe deaktivieren",
"Strict Caps Mode": "Beschränkter Fähigkeitenmodus",
"Relaxed Security": "Entspannte Sicherheit",
"Default Capabilities": "Standardfähigkeiten",
"WebDriverAgent Port": "WebDriverAgent-Port",
"executeAsync Callback Host": "executeAsync Callback Host",
"executeAsync Callback Port": "executeAsync Callback Port",
"Bootstrap Port": "Bootstrap-Port",
"Selendroid Port": "Selendroid-Port",
"Chromedriver Port": "Chromedriver-Port",
"Chromedriver Binary Path": "Chromedriver Binärdateipfad",
"Save Server Arguments Preset": "Voreinstellungen von Serverargumente sichern",
"Save": "Sichern",
"Cancel": "Abbrechen",
"Save As Preset…": "Als Voreinstellung sichern…",
"noPresets": "Sie haben noch keine Voreinstellungen. Erstellen Sie bitte einige Voreinstellungen auf dem Tab \"Erweitert\".",
"startServer": "Server v{{serverVersion}} starten",
"deleteSelectedPresetConfirmation": "Möchten Sie {{presetName}} wirklich löschen?",
"savedTimestamp": "Wurde um {{when}} gesichert",
"General": "Allgemein",
"invalidType": "Ungültiger Typ {{type}}",
"presetAdded": "Your preset '{{presetName}}' has been added to the list in the Presets tab!",
"datetimeFormat": "MMM Do YYYY, h:mma",
"updateDetails": "Release Date: {{pubDate}}\n\nRelease Notes: {{notes}}",
"appiumIsAvailable": "Appium Desktop {{name}} ist verfügbar",
"updateIsBeingDownloaded": "Update is being downloaded now. You will be notified again when it is complete",
"updateIsDownloaded": "Appium Desktop {{releaseName}} has been downloaded. Must restart to apply the updates (note: it may take several minutes for Appium Desktop to install and restart)",
"updateDownloadFailed": "Failed to download update. Reason: {{message}}",
"OK": "OK",
"Install Now": "Jetzt installieren",
"Install Later": "Später installieren",
"Update Download Started": "Update Download Started",
"No update available": "No update available",
"Appium Desktop is up-to-date": "Appium Desktop is up-to-date",
"Later": "Später",
"Update Downloaded": "Update Downloaded",
"Could not download update": "Could not download update"
}
+88
View File
@@ -0,0 +1,88 @@
{
"en": "English",
"de": "Deutsch",
"Edit": "Edit",
"Redo": "Redo",
"Cut": "Cut",
"Copy": "Copy",
"Paste": "Paste",
"Select All": "Select All",
"Window": "Window",
"Minimize": "Minimize",
"Close": "Close",
"Bring All to Front": "Bring All to Front",
"Help": "Help",
"Documentation": "Documentation",
"Search Issues": "Search Issues",
"About Appium": "About Appium",
"Check for updates": "Check for updates",
"New Session Window…": "New Session Window…",
"Configurations…": "Configurations…",
"Hide Appium": "Hide Appium",
"Hide Others": "Hide Others",
"Show All": "Show All",
"Quit": "Quit",
"Toggle Full Screen": "Toggle Full Screen",
"Languages": "Languages",
"View": "View",
"Reload": "Reload",
"Toggle Developer Tools": "Toggle Developer Tools",
"Learn More": "Learn More",
"Undo": "Undo",
"Cloud Providers": "Cloud Providers",
"Environment Variables": "Environment Variables",
"Save and Restart": "Save and Restart",
"Application must be restarted for changes to take effect": "Application must be restarted for changes to take effect",
"Restart Now": "Restart Now",
"Restart Later": "Restart Later",
"Simple": "Simple",
"Advanced": "Advanced",
"Presets": "Presets",
"Edit Configurations": "Edit Configurations",
"Server Address": "Server Address",
"Server Port": "Server Port",
"Logfile Path": "Logfile Path",
"Log Level": "Log Level",
"Override Temp Path": "Override Temp Path",
"Node Config File Path": "Node Config File Path",
"Local Timezone": "Local Timezone",
"Allow Session Override": "Allow Session Override",
"Log Timestamps": "Log Timestamps",
"Supress Log Color": "Supress Log Color",
"Strict Caps Mode": "Strict Caps Mode",
"Relaxed Security": "Relaxed Security",
"Default Capabilities": "Default Capabilities",
"WebDriverAgent Port": "WebDriverAgent Port",
"executeAsync Callback Host": "executeAsync Callback Host",
"executeAsync Callback Port": "executeAsync Callback Port",
"Bootstrap Port": "Bootstrap Port",
"Selendroid Port": "Selendroid Port",
"Chromedriver Port": "Chromedriver Port",
"Chromedriver Binary Path": "Chromedriver Binary Path",
"Save Server Arguments Preset": "Save Server Arguments Preset",
"Save": "Save",
"Cancel": "Cancel",
"Save As Preset…": "Save As Preset…",
"noPresets": "You don't yet have any presets. Create some on the Advanced tab.",
"startServer": "Start Server v{{serverVersion}}",
"deleteSelectedPresetConfirmation": "Are you sure you want to delete {{presetName}}?",
"savedTimestamp": "Saved {{when}}",
"General": "General",
"invalidType": "Invalid type {{type}}",
"presetAdded": "Your preset '{{presetName}}' has been added to the list in the Presets tab!",
"datetimeFormat": "MMM Do YYYY, h:mma",
"updateDetails": "Release Date: {{pubDate}}\n\nRelease Notes: {{notes}}",
"appiumIsAvailable": "Appium Desktop {{name}} is available",
"updateIsBeingDownloaded": "Update is being downloaded now. You will be notified again when it is complete",
"updateIsDownloaded": "Appium Desktop {{releaseName}} has been downloaded. Must restart to apply the updates (note: it may take several minutes for Appium Desktop to install and restart)",
"updateDownloadFailed": "Failed to download update. Reason: {{message}}",
"OK": "OK",
"Install Now": "Install Now",
"Install Later": "Install Later",
"Update Download Started": "Update Download Started",
"No update available": "No update available",
"Appium Desktop is up-to-date": "Appium Desktop is up-to-date",
"Later": "Later",
"Update Downloaded": "Update Downloaded",
"Could not download update": "Could not download update"
}
+96
View File
@@ -0,0 +1,96 @@
const { logger, tempDir, fs, zip } = require('appium-support');
const request = require('request');
const path = require('path');
const { createWriteStream } = require('fs');
const B = require('bluebird');
const { asyncify } = require('asyncbox');
const log = logger.getLogger('CROWDIN');
const PROJECT_ID = process.env.CROWDIN_PROJECT_ID;
const PROJECT_KEY = process.env.CROWDIN_PROJECT_KEY;
if (!PROJECT_ID || !PROJECT_KEY) {
throw new Error(`Both CROWDIN_PROJECT_ID and CROWDIN_PROJECT_KEY environment ` +
`variables must be set`);
}
const RESOURCES_ROOT = path.resolve('assets', 'locales');
const ORIGINAL_LANGUAGE = 'en';
const USER_AGENT = 'Appium Desktop';
async function exportTranslations () {
const options = {
url: `https://api.crowdin.com/api/project/${PROJECT_ID}/export?key=${PROJECT_KEY}`,
port: 443,
method: 'GET',
headers: {
'User-Agent': USER_AGENT,
},
};
return await new B((resolve, reject) => {
request(options)
.on('error', reject)
.on('response', (res) => {
if (res.statusCode >= 400) {
return reject(`Cannot export the translated resources in Crowdin. Error code: ${res.statusCode}`);
}
log.info(`Successfully exported Crowdin translations`);
})
.on('close', resolve);
});
}
async function downloadTranslations (dstPath) {
const options = {
url: `https://api.crowdin.com/api/project/${PROJECT_ID}/download/all.zip?key=${PROJECT_KEY}`,
port: 443,
method: 'GET',
headers: {
'User-Agent': USER_AGENT,
},
};
return await new B((resolve, reject) => {
request(options)
.on('error', reject)
.on('response', (res) => {
if (res.statusCode >= 400) {
return reject(`Cannot download the translated resources from Crowdin. Error code: ${res.statusCode}`);
}
log.info(`Successfully downloaded Crowdin translations`);
})
.pipe(createWriteStream(dstPath))
.on('close', resolve);
});
}
async function main () {
await exportTranslations();
const zipPath = await tempDir.path({prefix: 'translations', suffix: '.zip'});
try {
await downloadTranslations(zipPath);
const tmpRoot = await tempDir.openDir();
try {
await zip.extractAllTo(zipPath, tmpRoot);
for (const name of await fs.readdir(tmpRoot)) {
const currentPath = path.join(tmpRoot, name);
if (!(await fs.stat(currentPath)).isDirectory() || name === ORIGINAL_LANGUAGE) {
continue;
}
const dstPath = path.resolve(RESOURCES_ROOT, name);
if (await fs.exists(dstPath)) {
await fs.rimraf(dstPath);
}
await fs.mv(currentPath, path.resolve(RESOURCES_ROOT, name), {
mkdirp: true
});
log.info(`Successfully updated resources for the '${name}' language`);
}
} finally {
await fs.rimraf(tmpRoot);
}
} finally {
await fs.rimraf(zipPath);
}
}
asyncify(main);
+39
View File
@@ -0,0 +1,39 @@
const { logger } = require('appium-support');
const request = require('request');
const path = require('path');
const { createReadStream } = require('fs');
const log = logger.getLogger('CROWDIN');
const PROJECT_ID = process.env.CROWDIN_PROJECT_ID;
const PROJECT_KEY = process.env.CROWDIN_PROJECT_KEY;
if (!PROJECT_ID || !PROJECT_KEY) {
throw new Error(`Both CROWDIN_PROJECT_ID and CROWDIN_PROJECT_KEY environment ` +
`variables must be set`);
}
const resourcePath = path.resolve('assets', 'locales', 'en', 'translation.json');
const fieldName = `files[/${path.basename(resourcePath)}]`;
const options = {
url: `https://api.crowdin.com/api/project/${PROJECT_ID}/update-file?key=${PROJECT_KEY}`,
port: 443,
method: 'POST',
headers: {
'User-Agent': 'Appium Desktop',
},
formData: {
[fieldName]: createReadStream(resourcePath)
}
};
// eslint-disable-next-line promise/prefer-await-to-callbacks
request(options, (err, res, body) => {
if (err) {
throw err;
}
log.debug(`Response code: ${res.statusCode}`);
if (res.statusCode >= 400) {
throw new Error(JSON.stringify(body));
}
});
+314 -73
View File
@@ -2598,6 +2598,12 @@
"integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
"dev": true
},
"array-differ": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz",
"integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=",
"dev": true
},
"array-equal": {
"version": "1.0.0",
"resolved": "http://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz",
@@ -2651,6 +2657,21 @@
"resolved": "https://registry.npmjs.org/array-tree-filter/-/array-tree-filter-2.1.0.tgz",
"integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw=="
},
"array-union": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
"integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
"dev": true,
"requires": {
"array-uniq": "^1.0.1"
}
},
"array-uniq": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
"integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
"dev": true
},
"array-unique": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
@@ -3765,19 +3786,10 @@
}
}
},
"caller-path": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz",
"integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=",
"dev": true,
"requires": {
"callsites": "^0.2.0"
}
},
"callsites": {
"version": "0.2.0",
"resolved": "http://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz",
"integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz",
"integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==",
"dev": true
},
"camelcase": {
@@ -4022,7 +4034,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@@ -4437,7 +4450,8 @@
"safe-buffer": {
"version": "5.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -4493,6 +4507,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -4536,12 +4551,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
}
}
}
@@ -5885,6 +5902,29 @@
"integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=",
"dev": true
},
"del": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz",
"integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=",
"dev": true,
"requires": {
"globby": "^5.0.0",
"is-path-cwd": "^1.0.0",
"is-path-in-cwd": "^1.0.0",
"object-assign": "^4.0.1",
"pify": "^2.0.0",
"pinkie-promise": "^2.0.0",
"rimraf": "^2.2.8"
},
"dependencies": {
"pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
}
}
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -6822,6 +6862,12 @@
"shimmer": "^1.2.0"
}
},
"emitter-mixin": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/emitter-mixin/-/emitter-mixin-0.0.3.tgz",
"integrity": "sha1-WUjLKG8uSO3DslGnz8H3iDOW1lw=",
"dev": true
},
"emoji-regex": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz",
@@ -6888,7 +6934,6 @@
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
"integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
"dev": true,
"optional": true,
"requires": {
"prr": "~1.0.1"
}
@@ -7008,9 +7053,9 @@
}
},
"eslint": {
"version": "5.10.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-5.10.0.tgz",
"integrity": "sha512-HpqzC+BHULKlnPwWae9MaVZ5AXJKpkxCVXQHrFaRw3hbDj26V/9ArYM4Rr/SQ8pi6qUPLXSSXC4RBJlyq2Z2OQ==",
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-5.13.0.tgz",
"integrity": "sha512-nqD5WQMisciZC5EHZowejLKQjWGuFS5c70fxqSKlnDME+oz9zmE8KTlX+lHSg+/5wsC/kf9Q9eMkC8qS3oM2fg==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
@@ -7030,6 +7075,7 @@
"glob": "^7.1.2",
"globals": "^11.7.0",
"ignore": "^4.0.6",
"import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4",
"inquirer": "^6.1.0",
"js-yaml": "^3.12.0",
@@ -7041,10 +7087,8 @@
"natural-compare": "^1.4.0",
"optionator": "^0.8.2",
"path-is-inside": "^1.0.2",
"pluralize": "^7.0.0",
"progress": "^2.0.0",
"regexpp": "^2.0.1",
"require-uncached": "^1.0.3",
"semver": "^5.5.1",
"strip-ansi": "^4.0.0",
"strip-json-comments": "^2.0.1",
@@ -7059,9 +7103,9 @@
"dev": true
},
"debug": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz",
"integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"dev": true,
"requires": {
"ms": "^2.1.1"
@@ -7077,6 +7121,16 @@
"estraverse": "^4.1.1"
}
},
"import-fresh": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz",
"integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==",
"dev": true,
"requires": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
}
},
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
@@ -8472,6 +8526,28 @@
"integrity": "sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg==",
"dev": true
},
"globby": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz",
"integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=",
"dev": true,
"requires": {
"array-union": "^1.0.1",
"arrify": "^1.0.0",
"glob": "^7.0.3",
"object-assign": "^4.0.1",
"pify": "^2.0.0",
"pinkie-promise": "^2.0.0"
},
"dependencies": {
"pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
}
}
},
"globule": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz",
@@ -8745,7 +8821,8 @@
"version": "2.16.3",
"resolved": "http://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
"integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=",
"dev": true
"dev": true,
"optional": true
},
"hoist-non-react-statics": {
"version": "2.5.5",
@@ -8800,6 +8877,14 @@
"whatwg-encoding": "^1.0.1"
}
},
"html-parse-stringify2": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/html-parse-stringify2/-/html-parse-stringify2-2.0.1.tgz",
"integrity": "sha1-3FZwtyksoVi3vJFsmmc1rIhyg0o=",
"requires": {
"void-elements": "^2.0.1"
}
},
"htmlnano": {
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-0.1.10.tgz",
@@ -9347,6 +9432,35 @@
"integrity": "sha1-pls0RZrWNnrbs3B6gqPJ+RYWcDA=",
"dev": true
},
"i18next": {
"version": "14.1.1",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-14.1.1.tgz",
"integrity": "sha512-HItn9RHLyrDqe6pw6qXMYHGPHNc3y1FZndJfBlD6k4sRS0FAlYLvqCDVIWFc1XultBgsv348TtvL/lleG6JgBg=="
},
"i18next-node-fs-backend": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/i18next-node-fs-backend/-/i18next-node-fs-backend-2.1.1.tgz",
"integrity": "sha512-OW3wv6/b93L1Ks7IFm0bZ1ljRnOS3FAwU7JcGlznT1o0SKVPO5W13KoNFQa6QAArYm+ksQezgIZCda+CrNtE6g==",
"requires": {
"js-yaml": "3.12.0",
"json5": "2.0.0"
},
"dependencies": {
"json5": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.0.0.tgz",
"integrity": "sha512-0EdQvHuLm7yJ7lyG5dp7Q3X2ku++BG5ZHaJ5FTnaXpKqDrw4pMxel5Bt3oAYMthnrthFBdnZ1FcsXTPyrQlV0w==",
"requires": {
"minimist": "^1.2.0"
}
},
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
}
}
},
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -9485,38 +9599,64 @@
"dev": true
},
"inquirer": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.1.tgz",
"integrity": "sha512-088kl3DRT2dLU5riVMKKr1DlImd6X7smDhpXUCkJDCKvTEJeRiXh0G132HG9u5a+6Ylw9plFRY7RuTnwohYSpg==",
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.2.tgz",
"integrity": "sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA==",
"dev": true,
"requires": {
"ansi-escapes": "^3.0.0",
"chalk": "^2.0.0",
"ansi-escapes": "^3.2.0",
"chalk": "^2.4.2",
"cli-cursor": "^2.1.0",
"cli-width": "^2.0.0",
"external-editor": "^3.0.0",
"external-editor": "^3.0.3",
"figures": "^2.0.0",
"lodash": "^4.17.10",
"lodash": "^4.17.11",
"mute-stream": "0.0.7",
"run-async": "^2.2.0",
"rxjs": "^6.1.0",
"rxjs": "^6.4.0",
"string-width": "^2.1.0",
"strip-ansi": "^5.0.0",
"through": "^2.3.6"
},
"dependencies": {
"ansi-escapes": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
"integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
"dev": true
},
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"dev": true
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
},
"rxjs": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz",
"integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==",
"dev": true,
"requires": {
"tslib": "^1.9.0"
}
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
@@ -9834,6 +9974,21 @@
"integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=",
"dev": true
},
"is-path-cwd": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz",
"integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=",
"dev": true
},
"is-path-in-cwd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz",
"integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==",
"dev": true,
"requires": {
"is-path-inside": "^1.0.0"
}
},
"is-path-inside": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz",
@@ -10283,6 +10438,12 @@
}
}
},
"junk": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/junk/-/junk-1.0.3.tgz",
"integrity": "sha1-h75jSIZJy9ym9Tqzm+yczSNH9ZI=",
"dev": true
},
"just-extend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz",
@@ -10888,6 +11049,18 @@
"integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw=",
"dev": true
},
"maximatch": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/maximatch/-/maximatch-0.1.0.tgz",
"integrity": "sha1-hs2NawTJ8wfAWmuUGZBtA2D7E6I=",
"dev": true,
"requires": {
"array-differ": "^1.0.0",
"array-union": "^1.0.1",
"arrify": "^1.0.0",
"minimatch": "^3.0.0"
}
},
"md5": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz",
@@ -12160,6 +12333,24 @@
}
}
},
"parcel-plugin-asset-copier": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/parcel-plugin-asset-copier/-/parcel-plugin-asset-copier-1.0.0.tgz",
"integrity": "sha512-PQ/Hyc99EvksHi5ypiDkeFF4WLRXdSqTUWHMIf0MBke0Rf4zCih6l6QJgvJM+u9g1OYEgilCBE/0hI6+U1ZQWg==",
"dev": true,
"requires": {
"recursive-copy": "^2.0.9"
}
},
"parent-module": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.0.tgz",
"integrity": "sha512-8Mf5juOMmiE4FcmzYc4IaiS9L3+9paz2KOiXzkRviCP6aDmN49Hz6EMWz0lGNp9pX80GvvAuLADtyGfW/Em3TA==",
"dev": true,
"requires": {
"callsites": "^3.0.0"
}
},
"parse-asn1": {
"version": "5.1.1",
"resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz",
@@ -12550,12 +12741,6 @@
"xmldom": "0.1.x"
}
},
"pluralize": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz",
"integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==",
"dev": true
},
"pn": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz",
@@ -13700,8 +13885,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
"integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
"dev": true,
"optional": true
"dev": true
},
"ps-list": {
"version": "4.1.0",
@@ -14422,14 +14606,25 @@
}
},
"react": {
"version": "16.7.0",
"resolved": "https://registry.npmjs.org/react/-/react-16.7.0.tgz",
"integrity": "sha512-StCz3QY8lxTb5cl2HJxjwLFOXPIFQp+p+hxQfc8WE0QiLfCtIlKj8/+5tjjKm8uSTlAW+fCPaavGFS06V9Ar3A==",
"version": "16.8.1",
"resolved": "https://registry.npmjs.org/react/-/react-16.8.1.tgz",
"integrity": "sha512-wLw5CFGPdo7p/AgteFz7GblI2JPOos0+biSoxf1FPsGxWQZdN/pj6oToJs1crn61DL3Ln7mN86uZ4j74p31ELQ==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.12.0"
"scheduler": "^0.13.1"
},
"dependencies": {
"scheduler": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.1.tgz",
"integrity": "sha512-VJKOkiKIN2/6NOoexuypwSrybx13MY7NSy9RNt8wPvZDMRT1CW6qlpF5jXRToXNHz3uWzbm2elNpZfXfGPqP9A==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
}
}
}
},
"react-addons-test-utils": {
@@ -14439,14 +14634,14 @@
"dev": true
},
"react-dom": {
"version": "16.7.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.7.0.tgz",
"integrity": "sha512-D0Ufv1ExCAmF38P2Uh1lwpminZFRXEINJe53zRAbm4KPwSyd6DY/uDoS0Blj9jvPpn1+wivKpZYc8aAAN/nAkg==",
"version": "16.8.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.1.tgz",
"integrity": "sha512-N74IZUrPt6UiDjXaO7UbDDFXeUXnVhZzeRLy/6iqqN1ipfjrhR60Bp5NuBK+rv3GMdqdIuwIl22u1SYwf330bg==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.12.0"
"scheduler": "^0.13.1"
}
},
"react-hot-loader": {
@@ -14482,6 +14677,25 @@
}
}
},
"react-i18next": {
"version": "10.0.4",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-10.0.4.tgz",
"integrity": "sha512-jwlll/+ntPATCIodcrZGxt7LD52Cldts4+kxw2omtm2sDhA649H78/nvG/z36WTeH+rpZdIfypAUR362E9tHKg==",
"requires": {
"@babel/runtime": "^7.3.1",
"html-parse-stringify2": "2.0.1"
},
"dependencies": {
"@babel/runtime": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.3.1.tgz",
"integrity": "sha512-7jGW8ppV0ant637pIqAcFfQDDH1orEPGJb8aXfUozuCU3QqX7rX4DA8iwrbPrR1hcH0FTTHz47yQnk+bl5xHQA==",
"requires": {
"regenerator-runtime": "^0.12.0"
}
}
}
},
"react-is": {
"version": "16.7.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.7.0.tgz",
@@ -14688,6 +14902,32 @@
"resolve": "^1.1.6"
}
},
"recursive-copy": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/recursive-copy/-/recursive-copy-2.0.10.tgz",
"integrity": "sha512-S9J9XJUnfZ2NUS3lK6lx6HWLl2nWui+f7AKuu+qoFs4ikEPYgZ3qKk1T6tmBnr7PzhtKnawE+6TREy9XQKmxCA==",
"dev": true,
"requires": {
"del": "^2.2.0",
"emitter-mixin": "0.0.3",
"errno": "^0.1.2",
"graceful-fs": "^4.1.4",
"junk": "^1.0.1",
"maximatch": "^0.1.0",
"mkdirp": "^0.5.1",
"pify": "^2.3.0",
"promise": "^7.0.1",
"slash": "^1.0.0"
},
"dependencies": {
"pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
}
}
},
"redent": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
@@ -15280,16 +15520,6 @@
"integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=",
"dev": true
},
"require-uncached": {
"version": "1.0.3",
"resolved": "http://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz",
"integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=",
"dev": true,
"requires": {
"caller-path": "^0.1.0",
"resolve-from": "^1.0.0"
}
},
"resize-observer-polyfill": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
@@ -15314,9 +15544,9 @@
}
},
"resolve-from": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz",
"integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true
},
"resolve-pathname": {
@@ -15486,9 +15716,9 @@
}
},
"scheduler": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.12.0.tgz",
"integrity": "sha512-t7MBR28Akcp4Jm+QoR63XgAi9YgCUmgvDHqf5otgAj4QvdoBE4ImCX0ffehefePPG+aitiYHp0g/mW6s4Tp+dw==",
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.1.tgz",
"integrity": "sha512-VJKOkiKIN2/6NOoexuypwSrybx13MY7NSy9RNt8wPvZDMRT1CW6qlpF5jXRToXNHz3uWzbm2elNpZfXfGPqP9A==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
@@ -15879,10 +16109,16 @@
"supports-color": "^5.5.0"
}
},
"slash": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
"integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=",
"dev": true
},
"slice-ansi": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.0.0.tgz",
"integrity": "sha512-4j2WTWjp3GsZ+AOagyzVbzp4vWGtZ0hEZ/gDY/uTvm6MTxUfTUIsnMIFb1bn8o0RuXiqUw15H1bue8f22Vw2oQ==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
"integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.0",
@@ -16540,14 +16776,14 @@
"dev": true
},
"table": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/table/-/table-5.1.1.tgz",
"integrity": "sha512-NUjapYb/qd4PeFW03HnAuOJ7OMcBkJlqeClWxeNlQ0lXGSb52oZXGzkO0/I0ARegQ2eUT1g2VDJH0eUxDRcHmw==",
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/table/-/table-5.2.2.tgz",
"integrity": "sha512-f8mJmuu9beQEDkKHLzOv4VxVYlU68NpdzjbGPl69i4Hx0sTopJuNxuzJd17iV2h24dAfa93u794OnDA5jqXvfQ==",
"dev": true,
"requires": {
"ajv": "^6.6.1",
"lodash": "^4.17.11",
"slice-ansi": "2.0.0",
"slice-ansi": "^2.0.0",
"string-width": "^2.1.1"
},
"dependencies": {
@@ -17463,6 +17699,11 @@
"indexof": "0.0.1"
}
},
"void-elements": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz",
"integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w="
},
"w3c-hr-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz",
+11 -4
View File
@@ -18,6 +18,7 @@
"keywords": [
"appium"
],
"assetsPath": "assets",
"homepage": "https://github.com/appium/appium-desktop",
"main": "dist/main.js",
"scripts": {
@@ -47,7 +48,9 @@
"postinstall": "node ./check-engines package.json package.json && rimraf node_modules/jimp/browser && node ./sentry-set-version",
"postversion": "git pull --tags && git push && git push --tags",
"pre-commit": "npm run lint && mocha --reporter dot ./test/unit",
"sentry-release": "node ./sentry-release-script"
"sentry-release": "node ./sentry-release-script",
"crowdin-update": "node crowdin-update-resources.js",
"crowdin-sync": "node crowdin-sync-translations.js"
},
"pre-commit": [
"pre-commit"
@@ -120,6 +123,8 @@
"electron-log": "^2.2.17",
"electron-settings": "^2.2.4",
"electron-updater": "^4.0.1",
"i18next": "^14.1.1",
"i18next-node-fs-backend": "^2.1.1",
"fix-path": "^2.1.0",
"font-awesome": "4.x",
"format-json": "1.x",
@@ -129,8 +134,9 @@
"moment": "^2.20.1",
"postcss": "^7.0.4",
"prop-types": "^15.6.2",
"react": "^16.6.3",
"react-dom": "^16.6.3",
"react": "^16.8.1",
"react-dom": "^16.8.1",
"react-i18next": "^10.0.4",
"react-hot-loader": "^4.3.12",
"react-redux": "^6.0.0",
"react-router": "^4.3.1",
@@ -168,7 +174,7 @@
"electron-builder": "^20.38.2",
"electron-builder-lib": "^20.15.3",
"electron-devtools-installer": "^2.2.3",
"eslint": "^5.9.0",
"eslint": "^5.13.0",
"eslint-config-appium": "^4.0.1",
"eslint-plugin-babel": "^5.2.1",
"eslint-plugin-import": "^2.8.0",
@@ -186,6 +192,7 @@
"mocha-multi-reporters": "^1.1.7",
"node-libs-browser": "^2.1.0",
"parcel-bundler": "^1.10.3",
"parcel-plugin-asset-copier": "^1.0.0",
"postcss-modules": "^1.4.1",
"pre-commit": "^1.2.2",
"react-addons-test-utils": "^15.6.2",