ELECTRON-519: support Japanese localization for menus, dialogs and windows (#401)

- add basic localisation implementation for menu items
- add more strings to support localisation on menus
- add more strings to support localisation on menus
- add all menu items for localisation
- refactor i18n code
- Add localization for screen picker, basic auth and notification settings child windows
- Add localization bridge
- add i18n support to more strings
- update translations
- add events to change language and redo menu template
- move config update logic to windowMgr
- fix linting issues and refactor
- add snipping tool messages
This commit is contained in:
Vishwas Shashidhar
2018-06-19 20:26:04 +05:30
committed by GitHub
parent fd39d680a0
commit a26a1d609c
23 changed files with 859 additions and 295 deletions

3
.gitignore vendored
View File

@@ -31,4 +31,5 @@ installer/win/Symphony-x86-cache
installer/win/Symphony-x86-SetupFiles
package-lock.json
library
*.log
*.log
js/translation/*_missing.json

View File

@@ -8,6 +8,7 @@ const log = require('../log.js');
const logLevels = require('../enums/logLevels.js');
const buildNumber = require('../../package.json').buildNumber;
const { initCrashReporterMain, initCrashReporterRenderer } = require('../crashReporter.js');
const i18n = require('../translation/i18n');
let aboutWindow;
@@ -90,8 +91,8 @@ function openAboutWindow(windowName) {
aboutWindow.webContents.on('crashed', function () {
const options = {
type: 'error',
title: 'Renderer Process Crashed',
message: 'Oops! Looks like we have had a crash.',
title: i18n.getMessageFor('Renderer Process Crashed'),
message: i18n.getMessageFor('Oops! Looks like we have had a crash.'),
buttons: ['Close']
};

View File

@@ -2,12 +2,13 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Authentication Request</title>
<title data-i18n-text="Authentication Request"></title>
<style>
html, body {
margin: 0;
height: 100%;
font-family: sans-serif;
font-size: 14px;
}
.container {
@@ -62,20 +63,20 @@
</head>
<body>
<div class="container">
<span>Please provide your login credentials for:</span>
<span id="hostname" class="hostname">hostname</span>
<span id="credentialsError" class="credentials-error">Invalid user name/password</span>
<span data-i18n-text="Please provide your login credentials for:"></span>
<span id="hostname" class="hostname" data-i18n-text="hostname"></span>
<span id="credentialsError" class="credentials-error" data-i18n-text="Invalid user name/password"></span>
<form id="basicAuth" name="Basic Auth" action="Login">
<table class="form">
<tbody>
<tr>
<td>User name:</td>
<td id="username-text" data-i18n-text="User name:"></td>
<td>
<input id="username" name="username" title="Username" required>
</td>
</tr>
<tr>
<td>Password:</td>
<td id="password-text" data-i18n-text="Password:"></td>
<td>
<input id="password" type="password" title="Password" required>
</td>
@@ -84,10 +85,10 @@
</table>
<div class="footer">
<div class="button-container">
<button type="submit" id="login">Log In</button>
<button type="submit" id="login" data-i18n-text="Log In"></button>
</div>
<div class="button-container">
<button type="button" id="cancel">Cancel</button>
<button type="button" id="cancel" data-i18n-text="Cancel"></button>
</div>
</div>
</form>

View File

@@ -9,6 +9,7 @@ const log = require('../log.js');
const logLevels = require('../enums/logLevels.js');
const { isMac } = require('../utils/misc');
const { initCrashReporterMain, initCrashReporterRenderer } = require('../crashReporter.js');
const i18n = require('../translation/i18n');
let basicAuthWindow;
@@ -96,6 +97,8 @@ function openBasicAuthWindow(windowName, hostname, isValidCredentials, clearSett
});
basicAuthWindow.webContents.on('did-finish-load', () => {
const basicAuthContent = i18n.getMessageFor('BasicAuth');
basicAuthWindow.webContents.send('i18n-basic-auth', basicAuthContent);
// initialize crash reporter
initCrashReporterMain({ process: 'basic auth window' });
initCrashReporterRenderer(basicAuthWindow, { process: 'render | basic auth window' });
@@ -106,8 +109,8 @@ function openBasicAuthWindow(windowName, hostname, isValidCredentials, clearSett
basicAuthWindow.webContents.on('crashed', function () {
const options = {
type: 'error',
title: 'Renderer Process Crashed',
message: 'Oops! Looks like we have had a crash.',
title: i18n.getMessageFor('Renderer Process Crashed'),
message: i18n.getMessageFor('Oops! Looks like we have had a crash.'),
buttons: ['Close']
};

View File

@@ -68,4 +68,16 @@ ipcRenderer.on('register-crash-reporter', (event, arg) => {
if (arg && typeof arg === 'object') {
crashReporter.start(arg);
}
});
ipcRenderer.on('i18n-basic-auth', (event, content) => {
if (content && typeof content === 'object') {
const i18nNodes = document.querySelectorAll('[data-i18n-text]');
for (let node of i18nNodes) {
if (node.attributes['data-i18n-text'] && node.attributes['data-i18n-text'].value) {
node.innerText = content[node.attributes['data-i18n-text'].value] || node.attributes['data-i18n-text'].value;
}
}
}
});

View File

@@ -9,6 +9,7 @@ const log = require('../log.js');
const logLevels = require('../enums/logLevels.js');
const { isMac, isWindowsOS } = require('./../utils/misc.js');
const { initCrashReporterMain, initCrashReporterRenderer } = require('../crashReporter.js');
const i18n = require('../translation/i18n');
let screenPickerWindow;
let preloadWindow;
@@ -92,6 +93,8 @@ function openScreenPickerWindow(eventSender, sources, id) {
});
screenPickerWindow.webContents.on('did-finish-load', () => {
const screenPickerContent = i18n.getMessageFor('ScreenPicker');
screenPickerWindow.webContents.send('i18n-screen-picker', screenPickerContent);
// initialize crash reporter
initCrashReporterMain({ process: 'desktop capture window' });
initCrashReporterRenderer(screenPickerWindow, { process: 'render | desktop capture window' });
@@ -101,8 +104,8 @@ function openScreenPickerWindow(eventSender, sources, id) {
screenPickerWindow.webContents.on('crashed', function () {
const options = {
type: 'error',
title: 'Renderer Process Crashed',
message: 'Oops! Looks like we have had a crash.',
title: i18n.getMessageFor('Renderer Process Crashed'),
message: i18n.getMessageFor('Oops! Looks like we have had a crash.'),
buttons: ['Close']
};

View File

@@ -20,6 +20,7 @@ const keyCodeEnum = Object.freeze({
let availableSources;
let selectedSource;
let currentIndex = -1;
let localizedContent;
document.addEventListener('DOMContentLoaded', () => {
renderDom();
@@ -49,11 +50,15 @@ function renderDom() {
}, false);
screenTab.addEventListener('click', () => {
updateShareButtonText('Select Screen');
updateShareButtonText(localizedContent && localizedContent[ 'Select Screen' ] ?
localizedContent[ 'Select Screen' ] :
'Select Screen');
}, false);
applicationTab.addEventListener('click', () => {
updateShareButtonText('Select Application');
updateShareButtonText(localizedContent && localizedContent[ 'Select Application' ] ?
localizedContent[ 'Select Application' ] :
'Select Application');
}, false);
document.addEventListener('keyup', handleKeyUpPress.bind(this), true);
@@ -164,7 +169,7 @@ function updateUI(source, itemContainer) {
highlightSelectedSource();
itemContainer.classList.add('selected');
shareButton.innerText = 'Share'
shareButton.innerText = localizedContent && localizedContent.Share ? localizedContent.Share : 'Share';
}
/**
@@ -271,4 +276,17 @@ ipcRenderer.on('register-crash-reporter', (event, arg) => {
if (arg && typeof arg === 'object') {
crashReporter.start(arg);
}
});
ipcRenderer.on('i18n-screen-picker', (event, content) => {
localizedContent = content;
if (content && typeof content === 'object') {
const i18nNodes = document.querySelectorAll('[data-i18n-text]');
for (let node of i18nNodes) {
if (node.attributes['data-i18n-text'] && node.attributes['data-i18n-text'].value) {
node.innerText = content[node.attributes['data-i18n-text'].value] || node.attributes['data-i18n-text'].value;
}
}
}
});

View File

@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Screen Picker</title>
<title data-i18n-text="Screen Picker"></title>
<style>
@font-face {
@@ -211,7 +211,7 @@
<body>
<div class="content">
<div class="custom-window-title">
<span>Choose what you&#39;d like to share</span>
<span data-i18n-text="Choose what you'd like to share"></span>
<div id="x-button">
<div class="content-button">
<i>
@@ -223,13 +223,13 @@
</div>
</div>
<div id="error-content">
<span id="error-message">No screens or applications are currently available.</span>
<span id="error-message" data-i18n-text="No screens or applications are currently available."></span>
</div>
<div id="main-content">
<input id="screen-tab" type="radio" name="tabs" checked>
<label id="screens" for="screen-tab" class="hidden">Screens</label>
<label id="screens" for="screen-tab" class="hidden" data-i18n-text="Screens"></label>
<input id="application-tab" type="radio" name="tabs">
<label id="applications" for="application-tab" class="hidden">Applications</label>
<label id="applications" for="application-tab" class="hidden" data-i18n-text="Applications"></label>
<section id="screen-contents">
</section>
<section id="application-contents">
@@ -237,8 +237,8 @@
</div>
<footer>
<button id="cancel" class="cancel-button">Cancel</button>
<button id="share" class="share-button-disable">Select Screen</button>
<button id="cancel" class="cancel-button" data-i18n-text="Cancel"></button>
<button id="share" class="share-button-disable" data-i18n-text="Select Screen"></button>
</footer>
</div>

View File

@@ -4,6 +4,7 @@ const electron = require('electron');
const log = require('../log.js');
const logLevels = require('../enums/logLevels.js');
const i18n = require('../translation/i18n');
let ignoreAllCertErrors = false;
@@ -33,8 +34,8 @@ electron.app.on('certificate-error', function(event, webContents, url, error,
defaultId: 1,
cancelId: 1,
noLink: true,
title: 'Certificate Error',
message: 'Certificate Error: ' + error + '\nURL: ' + url,
title: i18n.getMessageFor('Certificate Error'),
message: i18n.getMessageFor('Certificate Error') + `: ${error}\nURL: ${url}`,
});
event.preventDefault();

View File

@@ -4,6 +4,7 @@ const electron = require('electron');
const log = require('../log.js');
const logLevels = require('../enums/logLevels.js');
const i18n = require('../translation/i18n');
/**
* Show dialog pinned to given window when loading error occurs
@@ -17,9 +18,9 @@ const logLevels = require('../enums/logLevels.js');
function showLoadFailure(win, url, errorDesc, errorCode, retryCallback, showDialog) {
let msg;
if (url) {
msg = 'Error loading URL:\n' + url;
msg = i18n.getMessageFor('Error loading URL') + `:\n${url}`;
} else {
msg = 'Error loading window';
msg = i18n.getMessageFor('Error loading window');
}
if (errorDesc) {
msg += '\n\n' + errorDesc;
@@ -35,7 +36,7 @@ function showLoadFailure(win, url, errorDesc, errorCode, retryCallback, showDial
defaultId: 0,
cancelId: 1,
noLink: true,
title: 'Loading Error',
title: i18n.getMessageFor('Loading Error'),
message: msg
}, response);
}

View File

@@ -21,7 +21,8 @@ const cmds = keyMirror({
openScreenPickerWindow: null,
popupMenu: null,
optimizeMemoryConsumption: null,
setIsInMeeting: null
setIsInMeeting: null,
setLocale: null,
});
module.exports = {

View File

@@ -142,12 +142,13 @@ electron.ipcMain.on(apiName, (event, arg) => {
openScreenPickerWindow(event.sender, arg.sources, arg.id);
}
break;
case apiCmds.popupMenu:
var browserWin = electron.BrowserWindow.fromWebContents(event.sender);
case apiCmds.popupMenu: {
let browserWin = electron.BrowserWindow.fromWebContents(event.sender);
if (browserWin && !browserWin.isDestroyed()) {
windowMgr.getMenu().popup(browserWin, { x: 20, y: 15, async: true });
windowMgr.getMenu().popup(browserWin, {x: 20, y: 15, async: true});
}
break;
}
case apiCmds.optimizeMemoryConsumption:
if (typeof arg.memory === 'object' && typeof arg.cpuUsage === 'object' && typeof arg.memory.workingSetSize === 'number') {
optimizeMemory(arg.memory, arg.cpuUsage);
@@ -158,6 +159,11 @@ electron.ipcMain.on(apiName, (event, arg) => {
setIsInMeeting(arg.isInMeeting);
}
break;
case apiCmds.setLocale:
if (typeof arg.locale === 'string') {
eventEmitter.emit('language-changed', { language: arg.locale });
}
break;
default:
}

View File

@@ -1,9 +1,10 @@
'use strict';
const electron = require('electron');
const fs = require('fs');
const { updateConfigField, getMultipleConfigField } = require('../config.js');
const AutoLaunch = require('auto-launch');
const electron = require('electron');
const { updateConfigField, getMultipleConfigField } = require('../config.js');
const { isMac, isWindowsOS, isWindows10 } = require('../utils/misc.js');
const archiveHandler = require('../utils/archiveHandler');
const log = require('../log.js');
@@ -11,6 +12,7 @@ const logLevels = require('../enums/logLevels.js');
const eventEmitter = require('../eventEmitter');
const aboutApp = require('../aboutApp');
const titleBarStyles = require('../enums/titleBarStyles');
const i18n = require('../translation/i18n');
const configFields = [
'minimizeOnClose',
@@ -62,197 +64,228 @@ if (isMac) {
});
}
const template = [{
label: 'Edit',
submenu: [
buildMenuItem('undo'),
buildMenuItem('redo'),
{ type: 'separator' },
buildMenuItem('cut'),
buildMenuItem('copy'),
buildMenuItem('paste'),
buildMenuItem('pasteandmatchstyle'),
buildMenuItem('delete'),
buildMenuItem('selectall')
]
},
{
label: 'View',
submenu: [{
label: 'Reload',
accelerator: 'CmdOrCtrl+R',
click(item, focusedWindow) {
if (focusedWindow) {
focusedWindow.reload();
}
}
},
{ type: 'separator' },
buildMenuItem('resetzoom'),
buildMenuItem('zoomin'),
buildMenuItem('zoomout'),
{ type: 'separator' },
buildMenuItem('togglefullscreen'),
]
},
{
role: 'window',
submenu: [
buildMenuItem('minimize'),
buildMenuItem('close'),
]
},
{
role: 'help',
submenu:
[
{
label: 'Symphony Help',
click() { electron.shell.openExternal('https://support.symphony.com'); }
},
{
label: 'Learn More',
click() { electron.shell.openExternal('https://www.symphony.com'); }
},
{
label: 'Troubleshooting',
submenu: [
{
label: isMac ? 'Show Logs in Finder' : 'Show Logs in Explorer',
click(item, focusedWindow) {
const FILE_EXTENSIONS = [ '.log' ];
const MAC_LOGS_PATH = '/Library/Logs/Symphony/';
const WINDOWS_LOGS_PATH = '\\AppData\\Roaming\\Symphony\\logs';
let logsPath = isMac ? MAC_LOGS_PATH : WINDOWS_LOGS_PATH;
let source = electron.app.getPath('home') + logsPath;
if (!fs.existsSync(source) && focusedWindow && !focusedWindow.isDestroyed()) {
electron.dialog.showMessageBox(focusedWindow, {type: 'error', title: 'Failed!', message: 'No logs are available to share'});
return;
}
let destPath = isMac ? '/logs_symphony_' : '\\logs_symphony_';
let timestamp = new Date().getTime();
let destination = electron.app.getPath('downloads') + destPath + timestamp + '.zip';
archiveHandler.generateArchiveForDirectory(source, destination, FILE_EXTENSIONS)
.then(() => {
electron.shell.showItemInFolder(destination);
})
.catch((err) => {
if (focusedWindow && !focusedWindow.isDestroyed()) {
electron.dialog.showMessageBox(focusedWindow, {type: 'error', title: 'Failed!', message: `Unable to generate logs due to -> ${err}`});
}
})
}
},
{
label: isMac ? 'Show crash dump in Finder' : 'Show crash dump in Explorer',
click(item, focusedWindow) {
const FILE_EXTENSIONS = isMac ? [ '.dmp' ] : [ '.dmp', '.txt' ];
const crashesDirectory = electron.crashReporter.getCrashesDirectory();
let source = isMac ? crashesDirectory + '/completed' : crashesDirectory;
// TODO: Add support to get diagnostic reports from ~/Library/Logs/DiagnosticReports
if (!fs.existsSync(source) || fs.readdirSync(source).length === 0 && focusedWindow && !focusedWindow.isDestroyed()) {
electron.dialog.showMessageBox(focusedWindow, {type: 'error', title: 'Failed!', message: 'No crashes available to share'});
return;
}
let destPath = isMac ? '/crashes_symphony_' : '\\crashes_symphony_';
let timestamp = new Date().getTime();
let destination = electron.app.getPath('downloads') + destPath + timestamp + '.zip';
archiveHandler.generateArchiveForDirectory(source, destination, FILE_EXTENSIONS)
.then(() => {
electron.shell.showItemInFolder(destination);
})
.catch((err) => {
if (focusedWindow && !focusedWindow.isDestroyed()) {
electron.dialog.showMessageBox(focusedWindow, {type: 'error', title: 'Failed!', message: `Unable to generate crash reports due to -> ${err}`});
}
});
}
}
]
}
]
}
];
function getTemplate(app) {
const template = [{
label: i18n.getMessageFor('Edit'),
submenu: [
buildMenuItem('undo', i18n.getMessageFor('Undo')),
buildMenuItem('redo', i18n.getMessageFor('Redo')),
{ type: 'separator' },
buildMenuItem('cut', i18n.getMessageFor('Cut')),
buildMenuItem('copy', i18n.getMessageFor('Copy')),
buildMenuItem('paste', i18n.getMessageFor('Paste')),
buildMenuItem('pasteandmatchstyle', i18n.getMessageFor('Paste and Match Style')),
buildMenuItem('delete', i18n.getMessageFor('Delete')),
buildMenuItem('selectall', i18n.getMessageFor('Select All'))
]
},
{
label: i18n.getMessageFor('View'),
submenu: [{
label: i18n.getMessageFor('Reload'),
accelerator: 'CmdOrCtrl+R',
click(item, focusedWindow) {
if (focusedWindow) {
focusedWindow.reload();
}
}
},
{ type: 'separator' },
buildMenuItem('resetzoom', i18n.getMessageFor('Actual Size')),
buildMenuItem('zoomin', i18n.getMessageFor('Zoom In')),
buildMenuItem('zoomout', i18n.getMessageFor('Zoom Out')),
{ type: 'separator' },
buildMenuItem('togglefullscreen', i18n.getMessageFor('Toggle Full Screen')),
]
},
{
role: 'window',
label: i18n.getMessageFor('Window'),
submenu: [
buildMenuItem('minimize', i18n.getMessageFor('Minimize')),
buildMenuItem('close', i18n.getMessageFor('Close')),
]
},
{
role: 'help',
label: i18n.getMessageFor('Help'),
submenu:
[
{
label: i18n.getMessageFor('Symphony Help'),
click() {
electron.shell.openExternal('https://support.symphony.com');
}
},
{
label: i18n.getMessageFor('Learn More'),
click() {
electron.shell.openExternal('https://www.symphony.com');
}
},
{
label: i18n.getMessageFor('Troubleshooting'),
submenu: [
{
label: isMac ? i18n.getMessageFor('Show Logs in Finder') : i18n.getMessageFor('Show Logs in Explorer'),
click(item, focusedWindow) {
const FILE_EXTENSIONS = ['.log'];
const MAC_LOGS_PATH = '/Library/Logs/Symphony/';
const WINDOWS_LOGS_PATH = '\\AppData\\Roaming\\Symphony\\logs';
let logsPath = isMac ? MAC_LOGS_PATH : WINDOWS_LOGS_PATH;
let source = electron.app.getPath('home') + logsPath;
if (!fs.existsSync(source) && focusedWindow && !focusedWindow.isDestroyed()) {
electron.dialog.showMessageBox(focusedWindow, {
type: 'error',
title: i18n.getMessageFor('Failed!'),
message: i18n.getMessageFor('No logs are available to share')
});
return;
}
let destPath = isMac ? '/logs_symphony_' : '\\logs_symphony_';
let timestamp = new Date().getTime();
let destination = electron.app.getPath('downloads') + destPath + timestamp + '.zip';
archiveHandler.generateArchiveForDirectory(source, destination, FILE_EXTENSIONS)
.then(() => {
electron.shell.showItemInFolder(destination);
})
.catch((err) => {
if (focusedWindow && !focusedWindow.isDestroyed()) {
electron.dialog.showMessageBox(focusedWindow, {
type: 'error',
title: i18n.getMessageFor('Failed!'),
message: i18n.getMessageFor('Unable to generate logs due to ') + err
});
}
});
}
},
{
label: isMac ? i18n.getMessageFor('Show crash dump in Finder') : i18n.getMessageFor('Show crash dump in Explorer'),
click(item, focusedWindow) {
const FILE_EXTENSIONS = isMac ? ['.dmp'] : ['.dmp', '.txt'];
const crashesDirectory = electron.crashReporter.getCrashesDirectory();
let source = isMac ? crashesDirectory + '/completed' : crashesDirectory;
// TODO: Add support to get diagnostic reports from ~/Library/Logs/DiagnosticReports
if (!fs.existsSync(source) || fs.readdirSync(source).length === 0 && focusedWindow && !focusedWindow.isDestroyed()) {
electron.dialog.showMessageBox(focusedWindow, {
type: 'error',
title: i18n.getMessageFor('Failed!'),
message: i18n.getMessageFor('No crashes available to share')
});
return;
}
let destPath = isMac ? '/crashes_symphony_' : '\\crashes_symphony_';
let timestamp = new Date().getTime();
let destination = electron.app.getPath('downloads') + destPath + timestamp + '.zip';
archiveHandler.generateArchiveForDirectory(source, destination, FILE_EXTENSIONS)
.then(() => {
electron.shell.showItemInFolder(destination);
})
.catch((err) => {
if (focusedWindow && !focusedWindow.isDestroyed()) {
electron.dialog.showMessageBox(focusedWindow, {
type: 'error',
title: i18n.getMessageFor('Failed!'),
message: i18n.getMessageFor('Unable to generate crash reports due to ') + err
});
}
});
}
}
]
}
]
}
];
if (isMac && template[0].label !== app.getName()) {
template.unshift({
label: app.getName(),
submenu: [{
role: 'about'
role: 'about',
label: i18n.getMessageFor('About Symphony')
},
{
type: 'separator'
},
{
role: 'services',
label: i18n.getMessageFor('Services'),
submenu: []
},
{
type: 'separator'
},
{
role: 'hide'
role: 'hide',
label: i18n.getMessageFor('Hide Symphony')
},
{
role: 'hideothers'
role: 'hideothers',
label: i18n.getMessageFor('Hide Others')
},
{
role: 'unhide'
role: 'unhide',
label: i18n.getMessageFor('Show All')
},
{
type: 'separator'
},
{
role: 'quit'
role: 'quit',
label: i18n.getMessageFor('Quit Symphony')
}
]
});
// Edit menu.
// Edit menu.
template[1].submenu.push({
type: 'separator'
}, {
label: 'Speech',
label: i18n.getMessageFor('Speech'),
submenu: [{
role: 'startspeaking'
role: 'startspeaking',
label: i18n.getMessageFor('Start Speaking')
},
{
role: 'stopspeaking'
role: 'stopspeaking',
label: i18n.getMessageFor('Stop Speaking')
}
]
});
// Window menu.
// Window menu.
template[3].submenu = [{
label: 'Close',
accelerator: 'CmdOrCtrl+W',
role: 'close'
role: 'close',
label: i18n.getMessageFor('Close')
},
{
label: 'Minimize',
accelerator: 'CmdOrCtrl+M',
role: 'minimize'
role: 'minimize',
label: i18n.getMessageFor('Minimize')
},
{
label: 'Zoom',
role: 'zoom'
role: 'zoom',
label: i18n.getMessageFor('Zoom')
},
{
type: 'separator'
},
{
label: 'Bring All to Front',
role: 'front'
role: 'front',
label: i18n.getMessageFor('Bring All to Front')
}
];
}
@@ -266,39 +299,47 @@ function getTemplate(app) {
type: 'separator'
});
// Window menu -> launchOnStartup.
// Window menu -> launchOnStartup.
template[index].submenu.push({
label: 'Auto Launch On Startup',
label: i18n.getMessageFor('Auto Launch On Startup'),
type: 'checkbox',
checked: launchOnStartup,
click: function(item, focusedWindow) {
click: function (item, focusedWindow) {
if (item.checked) {
symphonyAutoLauncher.enable()
.catch(function(err) {
let title = 'Error setting AutoLaunch configuration';
log.send(logLevels.ERROR, 'MenuTemplate: ' + title + ': auto launch error ' + err);
if (focusedWindow && !focusedWindow.isDestroyed()) {
electron.dialog.showMessageBox(focusedWindow, {type: 'error', title, message: title + ': ' + err});
}
});
.catch(function (err) {
let title = 'Error setting AutoLaunch configuration';
log.send(logLevels.ERROR, 'MenuTemplate: ' + title + ': auto launch error ' + err);
if (focusedWindow && !focusedWindow.isDestroyed()) {
electron.dialog.showMessageBox(focusedWindow, {
type: 'error',
title: i18n.getMessageFor(title),
message: i18n.getMessageFor(title) + ': ' + err
});
}
});
} else {
symphonyAutoLauncher.disable()
.catch(function(err) {
let title = 'Error setting AutoLaunch configuration';
log.send(logLevels.ERROR, 'MenuTemplate: ' + title + ': auto launch error ' + err);
if (focusedWindow && !focusedWindow.isDestroyed()) {
electron.dialog.showMessageBox(focusedWindow, {type: 'error', title, message: title + ': ' + err});
}
});
.catch(function (err) {
let title = 'Error setting AutoLaunch configuration';
log.send(logLevels.ERROR, 'MenuTemplate: ' + title + ': auto launch error ' + err);
if (focusedWindow && !focusedWindow.isDestroyed()) {
electron.dialog.showMessageBox(focusedWindow, {
type: 'error',
title: i18n.getMessageFor(title),
message: i18n.getMessageFor(title) + ': ' + err
});
}
});
}
launchOnStartup = item.checked;
updateConfigField('launchOnStartup', launchOnStartup);
}
});
// Window menu -> alwaysOnTop.
// Window menu -> alwaysOnTop.
template[index].submenu.push({
label: 'Always on top',
label: i18n.getMessageFor('Always on Top'),
type: 'checkbox',
checked: isAlwaysOnTop,
click: (item) => {
@@ -311,40 +352,40 @@ function getTemplate(app) {
}
});
// Window menu -> minimizeOnClose.
// ToDo: Add behavior on Close.
// Window menu -> minimizeOnClose.
// ToDo: Add behavior on Close.
template[index].submenu.push({
label: 'Minimize on Close',
label: i18n.getMessageFor('Minimize on Close'),
type: 'checkbox',
checked: minimizeOnClose,
click: function(item) {
click: function (item) {
minimizeOnClose = item.checked;
updateConfigField('minimizeOnClose', minimizeOnClose);
}
});
// Window menu -> bringToFront
// Window menu -> bringToFront
template[index].submenu.push({
label: isWindowsOS ? 'Flash Notification in Taskbar' : 'Bring to Front on Notifications',
label: isWindowsOS ? i18n.getMessageFor('Flash Notification in Taskbar') : i18n.getMessageFor('Bring to Front on Notifications'),
type: 'checkbox',
checked: bringToFront,
click: function(item) {
click: function (item) {
bringToFront = item.checked;
updateConfigField('bringToFront', bringToFront);
}
});
// Window/View menu -> separator
// Window/View menu -> separator
template[index].submenu.push({
type: 'separator',
});
// Window - View menu -> memoryRefresh
// Window - View menu -> memoryRefresh
template[index].submenu.push({
label: 'Refresh app when idle',
label: i18n.getMessageFor('Refresh app when idle'),
type: 'checkbox',
checked: memoryRefresh,
click: function(item) {
click: function (item) {
memoryRefresh = item.checked;
updateConfigField('memoryRefresh', memoryRefresh);
}
@@ -353,12 +394,12 @@ function getTemplate(app) {
if (!isMac) {
if (isWindows10()) {
/* eslint-disable no-param-reassign */
/* eslint-disable no-param-reassign */
template[index].submenu.push({
label: 'Title Bar Style',
label: i18n.getMessageFor('Title Bar Style'),
submenu: [
{
label: 'Native With Custom',
label: i18n.getMessageFor('Native With Custom'),
type: 'checkbox',
checked: titleBarStyle === titleBarStyles.NATIVE_WITH_CUSTOM,
click: function (item) {
@@ -368,7 +409,7 @@ function getTemplate(app) {
}
},
{
label: 'Custom',
label: i18n.getMessageFor('Custom'),
type: 'checkbox',
checked: titleBarStyle === titleBarStyles.CUSTOM,
click: function (item) {
@@ -381,19 +422,19 @@ function getTemplate(app) {
}, {
type: 'separator'
});
/* eslint-enable no-param-reassign */
/* eslint-enable no-param-reassign */
}
template[index].submenu.push({
label: 'Quit Symphony',
click: function() {
label: i18n.getMessageFor('Quit Symphony'),
click: function () {
app.quit();
}
});
// This adds About Symphony under help menu for windows
// This adds About Symphony under help menu for windows
template[3].submenu.push({
label: 'About Symphony',
label: i18n.getMessageFor('About Symphony'),
click(focusedWindow) {
let windowName = focusedWindow ? focusedWindow.name : '';
aboutApp.openAboutWindow(windowName);
@@ -410,77 +451,82 @@ function getTemplate(app) {
*/
function setCheckboxValues() {
return new Promise((resolve) => {
/**
* Method that reads multiple config fields
*/
/**
* Method that reads multiple config fields
*/
getMultipleConfigField(configFields)
.then(function (configData) {
for (let key in configData) {
if (configData.hasOwnProperty(key)) { // eslint-disable-line no-prototype-builtins
switch (key) {
case 'minimizeOnClose':
minimizeOnClose = configData[key];
break;
case 'launchOnStartup':
launchOnStartup = configData[key];
break;
case 'alwaysOnTop':
isAlwaysOnTop = configData[key];
eventEmitter.emit('isAlwaysOnTop', {
isAlwaysOnTop: configData[key],
shouldActivateMainWindow: true
});
break;
case 'notificationSettings':
eventEmitter.emit('notificationSettings', configData[key]);
break;
case 'bringToFront':
bringToFront = configData[key];
break;
case 'isCustomTitleBar':
titleBarStyle = configData[key] ? titleBarStyles.CUSTOM : titleBarStyles.NATIVE_WITH_CUSTOM;
break;
case 'memoryRefresh':
memoryRefresh = configData[key];
break;
default:
break;
}
}
}
return resolve();
})
.catch((err) => {
let title = 'Error loading configuration';
log.send(logLevels.ERROR, 'MenuTemplate: error reading configuration fields, error: ' + err);
if (electron.BrowserWindow.getFocusedWindow() && !electron.BrowserWindow.getFocusedWindow().isDestroyed()) {
electron.dialog.showMessageBox(electron.BrowserWindow.getFocusedWindow(), {type: 'error', title, message: title + ': ' + err});
}
return resolve();
});
.then(function (configData) {
for (let key in configData) {
if (configData.hasOwnProperty(key)) { // eslint-disable-line no-prototype-builtins
switch (key) {
case 'minimizeOnClose':
minimizeOnClose = configData[key];
break;
case 'launchOnStartup':
launchOnStartup = configData[key];
break;
case 'alwaysOnTop':
isAlwaysOnTop = configData[key];
eventEmitter.emit('isAlwaysOnTop', {
isAlwaysOnTop: configData[key],
shouldActivateMainWindow: true
});
break;
case 'notificationSettings':
eventEmitter.emit('notificationSettings', configData[key]);
break;
case 'bringToFront':
bringToFront = configData[key];
break;
case 'isCustomTitleBar':
titleBarStyle = configData[key] ? titleBarStyles.CUSTOM : titleBarStyles.NATIVE_WITH_CUSTOM;
break;
case 'memoryRefresh':
memoryRefresh = configData[key];
break;
default:
break;
}
}
}
return resolve();
})
.catch((err) => {
let title = 'Error loading configuration';
log.send(logLevels.ERROR, 'MenuTemplate: error reading configuration fields, error: ' + err);
if (electron.BrowserWindow.getFocusedWindow() && !electron.BrowserWindow.getFocusedWindow().isDestroyed()) {
electron.dialog.showMessageBox(electron.BrowserWindow.getFocusedWindow(), {
type: 'error',
title: i18n.getMessageFor(title),
message: i18n.getMessageFor(title) + ': ' + err
});
}
return resolve();
});
});
}
/**
* Sets respective accelerators w.r.t roles for the menu template
*
* @param role {String} The action of the menu item
*
* @param role {String} The action of the menu item
* @param label {String} Menu item name
* @return {Object}
* @return {Object}.role The action of the menu item
* @return {Object}.accelerator keyboard shortcuts and modifiers
* @return {Object}.role The action of the menu item
* @return {Object}.accelerator keyboard shortcuts and modifiers
*/
function buildMenuItem(role) {
function buildMenuItem(role, label) {
if (isMac) {
return { role: role }
return label ? { role: role, label: label } : { role: role }
}
if (isWindowsOS) {
return { role: role, accelerator: windowsAccelerator[role] || '' }
return label ? { role: role, label: label, accelerator: windowsAccelerator[role] || '' }
: { role: role, accelerator: windowsAccelerator[role] || '' }
}
return { role: role }
return label ? { role: role, label: label } : { role: role }
}
function getMinimizeOnClose() {

View File

@@ -85,4 +85,16 @@ ipc.on('screens', (event, screens) => {
screenSelector.appendChild(option);
});
}
});
ipc.on('i18n-notification-settings', (event, content) => {
if (content && typeof content === 'object') {
const i18nNodes = document.querySelectorAll('[data-i18n-text]');
for (let node of i18nNodes) {
if (node.attributes['data-i18n-text'] && node.attributes['data-i18n-text'].value) {
node.innerText = content[node.attributes['data-i18n-text'].value] || node.attributes['data-i18n-text'].value;
}
}
}
});

View File

@@ -2,45 +2,45 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Symphony - Configure Notification Position</title>
<title data-i18n-text="Symphony - Configure Notification Position"></title>
<link rel="stylesheet" type="text/css" href="./configure-notification-position.css">
</head>
<body>
<div class="content">
<header class="header">
<span class="header__title">Notification Settings</span>
<span class="header__title" data-i18n-text="Notification Settings"></span>
</header>
<div class="form">
<form>
<label class="label">Monitor</label>
<label class="label" data-i18n-text="Monitor"></label>
<div id="screens" class="main">
<label>Notification shown on Monitor: </label>
<label data-i18n-text="Notification shown on Monitor: "></label>
<select class="selector" id="screen-selector" title="position">
</select>
</div>
<label class="label">Position</label>
<label class="label" data-i18n-text="Position"></label>
<div class="main">
<div class="first-set">
<div class="radio">
<label class="radio__label"><input id="upper-left" type="radio" name="position" value="upper-left">
Top Left
<span data-i18n-text="Top Left"></span>
</label>
</div>
<div class="radio">
<label class="radio__label">
<input id="lower-left" type="radio" name="position" value="lower-left">
Bottom Left
<span data-i18n-text="Bottom Left"></span>
</label>
</div>
</div>
<div class="second-set">
<div class="radio">
<label class="radio__label">Top Right
<label class="radio__label"><span data-i18n-text="Top Right"></span>
<input id="upper-right" type="radio" name="position" value="upper-right">
</label>
</div>
<div class="radio">
<label class="radio__label">Bottom Right
<label class="radio__label"><span data-i18n-text="Bottom Right"></span>
<input id="lower-right" type="radio" name="position" value="lower-right">
</label>
</div>
@@ -52,8 +52,8 @@
<footer class="footer">
<div class="buttonLayout">
<button id="cancel" class="buttonDismiss">CANCEL</button>
<button id="ok-button" class="button">OK</button>
<button id="cancel" class="buttonDismiss" data-i18n-text="CANCEL"></button>
<button id="ok-button" class="button" data-i18n-text="OK"></button>
</div>
</footer>
</body>

View File

@@ -10,8 +10,8 @@ const log = require('../../log.js');
const logLevels = require('../../enums/logLevels.js');
const notify = require('./../electron-notify');
const eventEmitter = require('./../../eventEmitter');
const { updateConfigField } = require('../../config');
const i18n = require('../../translation/i18n');
let configurationWindow;
let screens;
@@ -113,6 +113,8 @@ function openConfigurationWindow(windowName) {
});
configurationWindow.webContents.on('did-finish-load', () => {
const notificationSettingsContent = i18n.getMessageFor('NotificationSettings');
configurationWindow.webContents.send('i18n-notification-settings', notificationSettingsContent);
if (screens && screens.length >= 0) {
configurationWindow.webContents.send('screens', screens);
}

View File

@@ -303,6 +303,18 @@ function createAPI() {
*/
setIsInMeeting: function (isInMeeting) {
throttledSetIsInMeetingStatus(isInMeeting);
},
/**
* Sets the language which updates the application locale
* @param {string} locale - language identifier and a region identifier
* Ex: en-US, ja-JP
*/
setLocale: function (locale) {
local.ipcRenderer.send(apiName, {
cmd: apiCmds.setLocale,
locale,
});
}
};

22
js/translation/i18n.js Normal file
View File

@@ -0,0 +1,22 @@
const path = require("path");
const fs = require('fs');
let language;
let loadedTranslations = {};
const getMessageFor = function(phrase) {
let translation = loadedTranslations[phrase];
if(translation === undefined) {
translation = phrase;
}
return translation;
};
const setLanguage = function(lng) {
language = lng ? lng : 'en-US';
loadedTranslations = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'locale', language + '.json'), 'utf8'));
};
module.exports = {
setLanguage: setLanguage,
getMessageFor: getMessageFor
};

View File

@@ -25,6 +25,7 @@ const { isMac, isNodeEnv, isWindows10, isWindowsOS } = require('./utils/misc');
const { deleteIndexFolder } = require('./search/search.js');
const { isWhitelisted, parseDomain } = require('./utils/whitelistHandler');
const { initCrashReporterMain, initCrashReporterRenderer } = require('./crashReporter.js');
const i18n = require('./translation/i18n');
// show dialog when certificate errors occur
require('./dialogs/showCertError.js');
@@ -253,8 +254,8 @@ function doCreateMainWindow(initialUrl, initialBounds, isCustomTitleBar) {
mainWindow.webContents.on('crashed', function () {
const options = {
type: 'error',
title: 'Renderer Process Crashed',
message: 'Oops! Looks like we have had a crash. Please reload or close this window.',
title: i18n.getMessageFor('Renderer Process Crashed'),
message: i18n.getMessageFor('Oops! Looks like we have had a crash. Please reload or close this window.'),
buttons: ['Reload', 'Close']
};
@@ -274,10 +275,17 @@ function doCreateMainWindow(initialUrl, initialBounds, isCustomTitleBar) {
addWindowKey(key, mainWindow);
mainWindow.loadURL(url);
menu = electron.Menu.buildFromTemplate(getTemplate(app));
if (!isWindows10()) {
electron.Menu.setApplicationMenu(menu);
}
getConfigField('locale')
.then((language) => {
const lang = language || app.getLocale();
log.send(`setting app language to ${lang}`);
rebuildMenu(lang);
})
.catch((err) => {
const lang = app.getLocale();
log.send(`could not find language settings ${err}, defaulting to system language ${app.getLocale()}`);
rebuildMenu(lang);
});
mainWindow.on('close', function (e) {
if (willQuitApp) {
@@ -422,8 +430,8 @@ function doCreateMainWindow(initialUrl, initialBounds, isCustomTitleBar) {
let handleChildWindowCrashEvent = (e) => {
const options = {
type: 'error',
title: 'Renderer Process Crashed',
message: 'Oops! Looks like we have had a crash. Please reload or close this window.',
title: i18n.getMessageFor('Renderer Process Crashed'),
message: i18n.getMessageFor('Oops! Looks like we have had a crash. Please reload or close this window.'),
buttons: ['Reload', 'Close']
};
@@ -494,8 +502,8 @@ function doCreateMainWindow(initialUrl, initialBounds, isCustomTitleBar) {
electron.dialog.showMessageBox(mainWindow, {
type: 'warning',
buttons: ['Ok'],
title: 'Not Allowed',
message: `Sorry, you are not allowed to access this website (${navigatedURL}), please contact your administrator for more details`,
title: i18n.getMessageFor('Not Allowed'),
message: i18n.getMessageFor('Sorry, you are not allowed to access this website') + ' (' + navigatedURL + '), ' + i18n.getMessageFor('please contact your administrator for more details'),
});
});
});
@@ -538,10 +546,10 @@ function doCreateMainWindow(initialUrl, initialBounds, isCustomTitleBar) {
log.send(logLevels.INFO, 'permission is -> ' + userPermission);
if (!userPermission) {
let fullMessage = `Your administrator has disabled ${message}. Please contact your admin for help.`;
let fullMessage = i18n.getMessageFor('Your administrator has disabled') + message + '. ' + i18n.getMessageFor('Please contact your admin for help');
const browserWindow = BrowserWindow.getFocusedWindow();
if (browserWindow && !browserWindow.isDestroyed()) {
electron.dialog.showMessageBox(browserWindow, {type: 'error', title: 'Permission Denied!', message: fullMessage});
electron.dialog.showMessageBox(browserWindow, {type: 'error', title: i18n.getMessageFor('Permission Denied') + '!', message: fullMessage});
}
}
@@ -835,6 +843,25 @@ eventEmitter.on('notificationSettings', (notificationSettings) => {
display = notificationSettings.display;
});
eventEmitter.on('language-changed', (opts) => {
const lang = opts && opts.language || app.getLocale();
log.send(logLevels.INFO, `language changed to ${lang}. Updating menu and user config`);
rebuildMenu(lang);
updateConfigField('locale', lang);
});
function rebuildMenu(language) {
setLanguage(language);
menu = electron.Menu.buildFromTemplate(getTemplate(app));
if (!isWindows10()) {
electron.Menu.setApplicationMenu(menu);
}
}
function setLanguage(lang) {
i18n.setLanguage(lang);
}
/**
* Method that gets invoked when an external display
* is removed using electron 'display-removed' event.

97
locale/en-US.json Normal file
View File

@@ -0,0 +1,97 @@
{
"About Symphony": "About Symphony",
"Actual Size": "Actual Size",
"Always on Top": "Always on Top",
"Auto Launch On Startup": "Auto Launch On Startup",
"BasicAuth": {
"Authentication Request": "Authentication Request",
"Cancel": "Cancel",
"hostname": "hostname",
"Invalid user name/password": "Invalid user name/password",
"Log In": "Log In",
"Password:": "Password:",
"Please provide your login credentials for:": "Please provide your login credentials for:",
"User name:": "User name:"
},
"Bring All to Front": "Bring All to Front",
"Bring to Front on Notifications": "Bring to Front on Notifications",
"Close": "Close",
"Copy": "Copy",
"Cut": "Cut",
"Delete": "Delete",
"Edit": "Edit",
"Help": "Help",
"Hide Others": "Hide Others",
"Hide Symphony": "Hide Symphony",
"Learn More": "Learn More",
"Minimize": "Minimize",
"Minimize on Close": "Minimize on Close",
"NotificationSettings": {
"Bottom Left": "Bottom Left",
"Bottom Right": "Bottom Right",
"CANCEL": "CANCEL",
"Monitor": "Monitor",
"Notification Settings": "Notification Settings",
"Notification shown on Monitor: ": "Notification shown on Monitor: ",
"OK": "OK",
"Position": "Position",
"Symphony - Configure Notification Position": "Symphony - Configure Notification Position",
"Top Left": "Top Left",
"Top Right": "Top Right"
},
"Paste": "Paste",
"Paste and Match Style": "Paste and Match Style",
"Quit Symphony": "Quit Symphony",
"Redo": "Redo",
"Refresh app when idle": "Refresh app when idle",
"Reload": "Reload",
"ScreenPicker": {
"Applications": "Applications",
"Cancel": "Cancel",
"Choose what you'd like to share": "Choose what you'd like to share",
"No screens or applications are currently available.": "No screens or applications are currently available.",
"Screen Picker": "Screen Picker",
"Screens": "Screens",
"Select Application": "Select Application",
"Select Screen": "Select Screen",
"Share": "Share"
},
"Select All": "Select All",
"Services": "Services",
"Show All": "Show All",
"Show crash dump in Finder": "Show crash dump in Finder",
"Show Logs in Finder": "Show Logs in Finder",
"Speech": "Speech",
"Start Speaking": "Start Speaking",
"Stop Speaking": "Stop Speaking",
"Symphony Help": "Symphony Help",
"Toggle Full Screen": "Toggle Full Screen",
"Troubleshooting": "Troubleshooting",
"Undo": "Undo",
"View": "View",
"Window": "Window",
"Zoom": "Zoom",
"Zoom In": "Zoom In",
"Zoom Out": "Zoom Out",
"Renderer Process Crashed": "Renderer Process Crashed",
"Oops! Looks like we have had a crash.": "Oops! Looks like we have had a crash.",
"Failed!": "Failed!",
"No logs are available to share": "No logs are available to share",
"Unable to generate logs due to ": "Unable to generate logs due to ",
"Show crash dump in Explorer": "Show crash dump in Explorer",
"No crashes available to share": "No crashes available to share",
"Unable to generate crash reports due to ": "Unable to generate crash reports due to ",
"Error setting AutoLaunch configuration": "Error setting AutoLaunch configuration",
"Error loading configuration": "Error loading configuration",
"Certificate Error": "Certificate Error",
"Error loading URL": "Error loading URL",
"Error loading window": "Error loading window",
"Loading Error": "Loading Error",
"Oops! Looks like we have had a crash. Please reload or close this window.": "Oops! Looks like we have had a crash. Please reload or close this window.",
"Not Allowed": "Not Allowed",
"Sorry, you are not allowed to access this website": "Sorry, you are not allowed to access this website",
"please contact your administrator for more details": "please contact your administrator for more details",
"Your administrator has disabled": "Your administrator has disabled",
"Please contact your admin for help": "Please contact your admin for help",
"Permission Denied": "Permission Denied"
}

97
locale/en.json Normal file
View File

@@ -0,0 +1,97 @@
{
"About Symphony": "About Symphony",
"Actual Size": "Actual Size",
"Always on Top": "Always on Top",
"Auto Launch On Startup": "Auto Launch On Startup",
"BasicAuth": {
"Authentication Request": "Authentication Request",
"Cancel": "Cancel",
"hostname": "hostname",
"Invalid user name/password": "Invalid user name/password",
"Log In": "Log In",
"Password:": "Password:",
"Please provide your login credentials for:": "Please provide your login credentials for:",
"User name:": "User name:"
},
"Bring All to Front": "Bring All to Front",
"Bring to Front on Notifications": "Bring to Front on Notifications",
"Close": "Close",
"Copy": "Copy",
"Cut": "Cut",
"Delete": "Delete",
"Edit": "Edit",
"Help": "Help",
"Hide Others": "Hide Others",
"Hide Symphony": "Hide Symphony",
"Learn More": "Learn More",
"Minimize": "Minimize",
"Minimize on Close": "Minimize on Close",
"NotificationSettings": {
"Bottom Left": "Bottom Left",
"Bottom Right": "Bottom Right",
"CANCEL": "CANCEL",
"Monitor": "Monitor",
"Notification Settings": "Notification Settings",
"Notification shown on Monitor: ": "Notification shown on Monitor: ",
"OK": "OK",
"Position": "Position",
"Symphony - Configure Notification Position": "Symphony - Configure Notification Position",
"Top Left": "Top Left",
"Top Right": "Top Right"
},
"Paste": "Paste",
"Paste and Match Style": "Paste and Match Style",
"Quit Symphony": "Quit Symphony",
"Redo": "Redo",
"Refresh app when idle": "Refresh app when idle",
"Reload": "Reload",
"ScreenPicker": {
"Applications": "Applications",
"Cancel": "Cancel",
"Choose what you'd like to share": "Choose what you'd like to share",
"No screens or applications are currently available.": "No screens or applications are currently available.",
"Screen Picker": "Screen Picker",
"Screens": "Screens",
"Select Application": "Select Application",
"Select Screen": "Select Screen",
"Share": "Share"
},
"Select All": "Select All",
"Services": "Services",
"Show All": "Show All",
"Show crash dump in Finder": "Show crash dump in Finder",
"Show Logs in Finder": "Show Logs in Finder",
"Speech": "Speech",
"Start Speaking": "Start Speaking",
"Stop Speaking": "Stop Speaking",
"Symphony Help": "Symphony Help",
"Toggle Full Screen": "Toggle Full Screen",
"Troubleshooting": "Troubleshooting",
"Undo": "Undo",
"View": "View",
"Window": "Window",
"Zoom": "Zoom",
"Zoom In": "Zoom In",
"Zoom Out": "Zoom Out",
"Renderer Process Crashed": "Renderer Process Crashed",
"Oops! Looks like we have had a crash.": "Oops! Looks like we have had a crash.",
"Failed!": "Failed!",
"No logs are available to share": "No logs are available to share",
"Unable to generate logs due to ": "Unable to generate logs due to ",
"Show crash dump in Explorer": "Show crash dump in Explorer",
"No crashes available to share": "No crashes available to share",
"Unable to generate crash reports due to ": "Unable to generate crash reports due to ",
"Error setting AutoLaunch configuration": "Error setting AutoLaunch configuration",
"Error loading configuration": "Error loading configuration",
"Certificate Error": "Certificate Error",
"Error loading URL": "Error loading URL",
"Error loading window": "Error loading window",
"Loading Error": "Loading Error",
"Oops! Looks like we have had a crash. Please reload or close this window.": "Oops! Looks like we have had a crash. Please reload or close this window.",
"Not Allowed": "Not Allowed",
"Sorry, you are not allowed to access this website": "Sorry, you are not allowed to access this website",
"please contact your administrator for more details": "please contact your administrator for more details",
"Your administrator has disabled": "Your administrator has disabled",
"Please contact your admin for help": "Please contact your admin for help",
"Permission Denied": "Permission Denied"
}

104
locale/ja-JP.json Normal file
View File

@@ -0,0 +1,104 @@
{
"About Symphony": "Symphonyについて",
"Actual Size": "実際のサイズ",
"Always on Top": "常にトップに表示",
"Auto Launch On Startup": "スタートアップ時の自動起動",
"BasicAuth": {
"Authentication Request": "認証の要求",
"Cancel": "キャンセル",
"hostname": "ホスト名",
"Invalid user name/password": "ユーザー名またはパスワードが無効です",
"Log In": "ログイン",
"Password:": "パスワード:",
"Please provide your login credentials for:": "あなたのログイン資格情報を入力してください:",
"User name:": "ユーザー名:"
},
"Bring All to Front": "すべてを前面に表示",
"Bring to Front on Notifications": "通知の前面に表示",
"Close": "閉じる",
"Copy": "コピー",
"Cut": "切り取り",
"Delete": "削除",
"Edit": "編集",
"Help": "ヘルプ",
"Hide Others": "他を隠す",
"Hide Symphony": "Symphonyを隠す",
"Learn More": "詳細を表示",
"Minimize": "最小化",
"Minimize on Close": "閉じる時に最小化",
"NotificationSettings": {
"Bottom Left": "左下",
"Bottom Right": "右下",
"CANCEL": "キャンセル",
"Monitor": "モニター",
"Notification Settings": "通知設定",
"Notification shown on Monitor: ": "モニターに表示される通知:",
"OK": "OK",
"Position": "位置",
"Symphony - Configure Notification Position": "Symphony - 通知位置の設定",
"Top Left": "左上",
"Top Right": "右上"
},
"Paste": "ペースト",
"Paste and Match Style": "ペーストしてスタイルを合わせる",
"Quit Symphony": "Symphonyの終了",
"Redo": "やり直し",
"Refresh app when idle": "アイドル時にアプリを更新する",
"Reload": "再ロード",
"ScreenPicker": {
"Applications": "アプリケーション",
"Cancel": "キャンセル",
"Choose what you'd like to share": "共有するものを選択する",
"No screens or applications are currently available.": "現在利用可能な画面やアプリケーションはありません。",
"Screen Picker": "画面の選択",
"Screens": "画面",
"Select Application": "アプリケーションの選択",
"Select Screen": "画面の選択",
"Share": "共有"
},
"ScreenSnippet": {
"Pen": "ペン",
"Done": "完了",
"Snipping Tool": "スニッピングツール",
"Erase": "消去する",
"Highlight": "ハイライト"
},
"Select All": "すべてを選択",
"Services": "サービス",
"Show All": "すべてを表示",
"Show crash dump in Finder": "ファインダーにクラッシュダンプを表示",
"Show Logs in Finder": "ファインダーにログを表示",
"Speech": "スピーチ",
"Start Speaking": "スピーキングを始める",
"Stop Speaking": "スピーキングをやめる",
"Symphony Help": "Symphonyヘルプ",
"Toggle Full Screen": "フルスクリーン切り替え",
"Troubleshooting": "トラブルシューティング",
"Undo": "元に戻す",
"View": "ビュー",
"Window": "ウインドウ",
"Zoom": "ズーム",
"Zoom In": "ズームイン",
"Zoom Out": "ズームアウト",
"Renderer Process Crashed": "レンダラープロセスがクラッシュしました",
"Oops! Looks like we have had a crash.": "おっと!クラッシュしたようです。",
"Failed!": "失敗!",
"No logs are available to share": "共有できるログはありません",
"Unable to generate logs due to ": "以下が原因でログを生成できません ",
"Show crash dump in Explorer": "Explorerにクラッシュダンプを表示",
"No crashes available to share": "共有できるクラッシュはありません",
"Unable to generate crash reports due to ": "以下が原因でクラッシュレポートを生成できません ",
"Error setting AutoLaunch configuration": "自動起動設定の設定エラー",
"Error loading configuration": "設定を読み込む際のエラー",
"Certificate Error": "証明エラー",
"Error loading URL": "URLの読み込みエラー",
"Error loading window": "ウィンドウを読み込む際のエラー",
"Loading Error": "読み込みエラー",
"Oops! Looks like we have had a crash. Please reload or close this window.": "おっと!クラッシュしたようです。このウィンドウをリロードしてください。",
"Not Allowed": "未許可",
"Sorry, you are not allowed to access this website": "申し訳ありませんが、このウェブサイトへのアクセスは許可されていません",
"please contact your administrator for more details": "詳細については、管理者にお問い合わせください",
"Your administrator has disabled": "管理者が無効にしました",
"Please contact your admin for help": "ヘルプについては管理者にお問い合わせください",
"Permission Denied": "許可が拒否されました"
}

97
locale/ja.json Normal file
View File

@@ -0,0 +1,97 @@
{
"About Symphony": "Symphonyについて",
"Actual Size": "実際のサイズ",
"Always on Top": "常にトップに表示",
"Auto Launch On Startup": "スタートアップ時の自動起動",
"BasicAuth": {
"Authentication Request": "認証の要求",
"Cancel": "キャンセル",
"hostname": "ホスト名",
"Invalid user name/password": "ユーザー名またはパスワードが無効です",
"Log In": "ログイン",
"Password:": "パスワード:",
"Please provide your login credentials for:": "あなたのログイン資格情報を入力してください:",
"User name:": "ユーザー名:"
},
"Bring All to Front": "すべてを前面に表示",
"Bring to Front on Notifications": "通知の前面に表示",
"Close": "閉じる",
"Copy": "コピー",
"Cut": "切り取り",
"Delete": "削除",
"Edit": "編集",
"Help": "ヘルプ",
"Hide Others": "他を隠す",
"Hide Symphony": "Symphonyを隠す",
"Learn More": "詳細を表示",
"Minimize": "最小化",
"Minimize on Close": "閉じる時に最小化",
"NotificationSettings": {
"Bottom Left": "左下",
"Bottom Right": "右下",
"CANCEL": "キャンセル",
"Monitor": "モニター",
"Notification Settings": "通知設定",
"Notification shown on Monitor: ": "モニターに表示される通知:",
"OK": "OK",
"Position": "位置",
"Symphony - Configure Notification Position": "Symphony - 通知位置の設定",
"Top Left": "左上",
"Top Right": "右上"
},
"Paste": "ペースト",
"Paste and Match Style": "ペーストしてスタイルを合わせる",
"Quit Symphony": "Symphonyの終了",
"Redo": "やり直し",
"Refresh app when idle": "アイドル時にアプリを更新する",
"Reload": "再ロード",
"ScreenPicker": {
"Applications": "アプリケーション",
"Cancel": "キャンセル",
"Choose what you'd like to share": "共有するものを選択する",
"No screens or applications are currently available.": "現在利用可能な画面やアプリケーションはありません。",
"Screen Picker": "画面の選択",
"Screens": "画面",
"Select Application": "アプリケーションの選択",
"Select Screen": "画面の選択",
"Share": "共有"
},
"Select All": "すべてを選択",
"Services": "サービス",
"Show All": "すべてを表示",
"Show crash dump in Finder": "ファインダーにクラッシュダンプを表示",
"Show Logs in Finder": "ファインダーにログを表示",
"Speech": "スピーチ",
"Start Speaking": "スピーキングを始める",
"Stop Speaking": "スピーキングをやめる",
"Symphony Help": "Symphonyヘルプ",
"Toggle Full Screen": "フルスクリーン切り替え",
"Troubleshooting": "トラブルシューティング",
"Undo": "元に戻す",
"View": "ビュー",
"Window": "ウインドウ",
"Zoom": "ズーム",
"Zoom In": "ズームイン",
"Zoom Out": "ズームアウト",
"Renderer Process Crashed": "レンダラープロセスがクラッシュしました",
"Oops! Looks like we have had a crash.": "おっと!クラッシュしたようです。",
"Failed!": "失敗!",
"No logs are available to share": "共有できるログはありません",
"Unable to generate logs due to ": "以下が原因でログを生成できません ",
"Show crash dump in Explorer": "Explorerにクラッシュダンプを表示",
"No crashes available to share": "共有できるクラッシュはありません",
"Unable to generate crash reports due to ": "以下が原因でクラッシュレポートを生成できません ",
"Error setting AutoLaunch configuration": "自動起動設定の設定エラー",
"Error loading configuration": "設定を読み込む際のエラー",
"Certificate Error": "証明エラー",
"Error loading URL": "URLの読み込みエラー",
"Error loading window": "ウィンドウを読み込む際のエラー",
"Loading Error": "読み込みエラー",
"Oops! Looks like we have had a crash. Please reload or close this window.": "おっと!クラッシュしたようです。このウィンドウをリロードしてください。",
"Not Allowed": "未許可",
"Sorry, you are not allowed to access this website": "申し訳ありませんが、このウェブサイトへのアクセスは許可されていません",
"please contact your administrator for more details": "詳細については、管理者にお問い合わせください",
"Your administrator has disabled": "管理者が無効にしました",
"Please contact your admin for help": "ヘルプについては管理者にお問い合わせください",
"Permission Denied": "許可が拒否されました"
}