mirror of
https://github.com/appium/appium-desktop.git
synced 2026-10-16 20:20:47 +08:00
feat: Initialize internationalization with German translations (#819)
This commit is contained in:
committed by
Dan Graham
parent
17f78bd61d
commit
de9a38832e
+15
-3
@@ -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,4 +1,4 @@
|
||||
# Appium Desktop [](https://travis-ci.org/appium/appium-desktop) [](https://dev.azure.com/AppiumCI/Appium%20Desktop/_build/latest?definitionId=1) [](https://ci.appveyor.com/project/dpgraham/appium-desktop-2ur5j)
|
||||
# Appium Desktop [](https://travis-ci.org/appium/appium-desktop) [](https://dev.azure.com/AppiumCI/Appium%20Desktop/_build/latest?definitionId=1) [](https://ci.appveyor.com/project/dpgraham/appium-desktop-2ur5j) [](https://crowdin.com/project/appium-desktop)
|
||||
|
||||
[](https://greenkeeper.io/)
|
||||

|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
const config = {
|
||||
platform: process.platform,
|
||||
languages: ['en', 'de'],
|
||||
fallbackLng: 'en',
|
||||
namespace: 'translation',
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -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;
|
||||
@@ -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
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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()}
|
||||
|
||||
@@ -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,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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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));
|
||||
}
|
||||
});
|
||||
Generated
+314
-73
@@ -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
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user