From 005ac589cca2b385ccab9f453f9bb488c7fbb943 Mon Sep 17 00:00:00 2001 From: Jorys_Paulin Date: Thu, 14 Jun 2018 13:14:15 +0200 Subject: [PATCH 01/14] Rewired bitrate logic to only one function --- static/js/index.js | 48 ++++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/static/js/index.js b/static/js/index.js index 6d6c649b..0a5d3d09 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -12,8 +12,7 @@ function attachListeners() { $('.resolutionMenu li').on('click', saveResolution); $('.framerateMenu li').on('click', saveFramerate); - $('#bitrateSlider').on('input', updateBitrateField); // input occurs every notch you slide - //$('#bitrateSlider').on('change', saveBitrate); //FIXME: it seems not working + $('#bitrateSlider').on('input', e => changeBitrate(e.target.value)); // input occurs every notch you slide $("#remoteAudioEnabledSwitch").on('click', saveRemoteAudio); $('#optimizeGamesSwitch').on('click', saveOptimize); $('#addHostCell').on('click', addHost); @@ -65,6 +64,25 @@ function onBoundsChanged() { } } +/** + * changeBitrate - Changes the bitrate value + * + * @param {Number} value The bitrate value + * @return {Number} The bitrate value + */ +function changeBitrate(value) { + // First, change UI + var slider = document.querySelector('#bitrateSlider') + slider.MaterialSlider.change(value) + var field = document.querySelector('#bitrateField') + field.innerText = value + "Mbps" + // Second, save to storage + storeData('bitrate', value, null) + window.currentBitrate = value; // DEBUG: This is meant for debugging + // Third, return value + return value +} + function changeUiModeForNaClLoad() { $('#main-navigation').children().hide(); $("#main-content").children().not("#listener, #naclSpinner").hide(); @@ -183,11 +201,6 @@ function snackbarLogLong(givenMessage) { document.querySelector('#snackbar').MaterialSnackbar.showSnackbar(data); } -function updateBitrateField() { - $('#bitrateField').html($('#bitrateSlider').val() + " Mbps"); - saveBitrate(); -} - function moduleDidLoad() { // load the HTTP cert and unique ID if we have one. chrome.storage.sync.get('cert', function(savedCert) { @@ -857,10 +870,6 @@ function saveHosts() { storeData('hosts', hosts, null); } -function saveBitrate() { - storeData('bitrate', $('#bitrateSlider').val(), null); -} - function saveRemoteAudio() { // MaterialDesignLight uses the mouseup trigger, so we give it some time to change the class name before // checking the new state @@ -877,28 +886,25 @@ function updateDefaultBitrate() { if (res === "1920:1080") { if (frameRate === "30") { // 1080p, 30fps - $('#bitrateSlider')[0].MaterialSlider.change('10'); + changeBitrate(10) } else { // 1080p, 60fps - $('#bitrateSlider')[0].MaterialSlider.change('20'); + changeBitrate(20) } } else if (res === "1280:720") { if (frameRate === "30") { // 720, 30fps - $('#bitrateSlider')[0].MaterialSlider.change('5'); + changeBitrate(5) } else { // 720, 60fps - $('#bitrateSlider')[0].MaterialSlider.change('10'); + changeBitrate(10) } } else if (res === "3840:2160") { if (frameRate === "30") { // 2160p, 30fps - $('#bitrateSlider')[0].MaterialSlider.change('40'); + changeBitrate(40) } else { // 2160p, 60fps - $('#bitrateSlider')[0].MaterialSlider.change('80'); + changeBitrate(80) } } else { // unrecognized option. In case someone screws with the JS to add custom resolutions - $('#bitrateSlider')[0].MaterialSlider.change('10'); + changeBitrate(10) } - - updateBitrateField(); - saveBitrate(); } function onWindowLoad() { From c851c3eb41863f6f5289f8d8c31de853821024d5 Mon Sep 17 00:00:00 2001 From: Jorys_Paulin Date: Thu, 14 Jun 2018 13:28:02 +0200 Subject: [PATCH 02/14] Added input to better finetune bitrate --- index.html | 1 + static/css/style.css | 9 ++++++++- static/js/index.js | 7 +++++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index abe56317..d437e8d1 100644 --- a/index.html +++ b/index.html @@ -62,6 +62,7 @@
+
diff --git a/static/css/style.css b/static/css/style.css index 387a5082..7b79f743 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -91,7 +91,13 @@ main { text-transform: none; } .bitrateMenu { - width: 170px; + width: auto; + display: flex; + align-items: center; + padding: 8px; +} +#bitrateInput { + width: 50%; } .mdl-button { @@ -128,6 +134,7 @@ main { } .mdl-textfield__input { border-bottom: 1px solid rgba(255, 255, 255, 0.90); + width: 25%; } .mdl-textfield__label { color: rgba(255, 255, 255, 0.90); diff --git a/static/js/index.js b/static/js/index.js index 0a5d3d09..5232948b 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -13,6 +13,7 @@ function attachListeners() { $('.resolutionMenu li').on('click', saveResolution); $('.framerateMenu li').on('click', saveFramerate); $('#bitrateSlider').on('input', e => changeBitrate(e.target.value)); // input occurs every notch you slide + $('#bitrateInput').on('input', e => changeBitrate(e.target.value)); $("#remoteAudioEnabledSwitch").on('click', saveRemoteAudio); $('#optimizeGamesSwitch').on('click', saveOptimize); $('#addHostCell').on('click', addHost); @@ -76,6 +77,8 @@ function changeBitrate(value) { slider.MaterialSlider.change(value) var field = document.querySelector('#bitrateField') field.innerText = value + "Mbps" + var input = document.querySelector('#bitrateInput') + input.value = value; // Second, save to storage storeData('bitrate', value, null) window.currentBitrate = value; // DEBUG: This is meant for debugging @@ -961,8 +964,8 @@ function onWindowLoad() { // load stored bitrate prefs chrome.storage.sync.get('bitrate', function(previousValue) { - $('#bitrateSlider')[0].MaterialSlider.change(previousValue.bitrate != null ? previousValue.bitrate : '10'); - updateBitrateField(); + var value = previousValue.bitrate != null ? previousValue.bitrate : '10' + changeBitrate(value) }); } } From 6b683d02de3f1dae5ec639a62f5c8bce12d3cf24 Mon Sep 17 00:00:00 2001 From: Jorys Paulin Date: Sat, 21 Jul 2018 11:49:24 +0200 Subject: [PATCH 03/14] Added Resolution class --- static/js/index.js | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/static/js/index.js b/static/js/index.js index 5232948b..2c5994de 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -86,6 +86,51 @@ function changeBitrate(value) { return value } +/** + * Resolution - Represents a resolution + */ +class Resolution { + + /** + * constructor - Creates a new Resolution object + * + * @param {Number} width Resolution width + * @param {Number} height Resolution height + * @param {String} name Resolution name + * @return {Object} The corresponding object + */ + constructor(width, height, name = null) { + this.width = width + this.height = height + if(name === null) + this.name = this.height + 'p' + else + this.name = name + } + + /** + * getObject - Returns the corresponding object + * + * @return {type} description + */ + getObject() { + return { width, height, name } = this + } + + /** + * getChildren - Returns an item + * + * @return {HTMLElement} The chooser + */ + getChildren() { + var child = document.createElement('li') + child.className = 'mdl-menu__item' + child.dataset.value = this.width + ':' + thids.height + child.innerText = this.name + return child + } +} + function changeUiModeForNaClLoad() { $('#main-navigation').children().hide(); $("#main-content").children().not("#listener, #naclSpinner").hide(); From a628043a47c557b6b3b08f3b307a5ba9d513e03e Mon Sep 17 00:00:00 2001 From: Jorys Paulin Date: Sat, 21 Jul 2018 12:09:06 +0200 Subject: [PATCH 04/14] Added customResolutions --- static/js/index.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/static/js/index.js b/static/js/index.js index 2c5994de..73d3c71d 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -963,6 +963,23 @@ function onWindowLoad() { loadWindowState(); if (chrome.storage) { + // Load stored customResolutions prefs + chrome.storage.sync.get('customResolutions', function(previousValue) { + if (previousValue.customResolutions != null) { + previousValue.customResolutions.forEach(item => { + console.log(item); + var child = document.createElement('li') + child.className = 'mdl-menu__item' + child.dataset.value = item.width + ':' + item.height + child.innerText = item.name||item.height + 'p' + $(child).on('click', saveResolution); + document.querySelector('.resolutionMenu').appendChild(child) + }) + } else { + console.info('No custom resolutions found') + } + }); + // load stored resolution prefs chrome.storage.sync.get('resolution', function(previousValue) { if (previousValue.resolution != null) { From ea4488304e5e150227088fe88e3b70ee92398557 Mon Sep 17 00:00:00 2001 From: Jorys Paulin Date: Sat, 21 Jul 2018 13:23:55 +0200 Subject: [PATCH 05/14] Added JSDoc to ALL functions --- static/js/index.js | 133 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 121 insertions(+), 12 deletions(-) diff --git a/static/js/index.js b/static/js/index.js index 73d3c71d..3c2da105 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -6,7 +6,9 @@ var api; // `api` should only be set if we're in a host-specific screen. on the var isInGame = false; // flag indicating whether the game stream started var windowState = 'normal'; // chrome's windowState, possible values: 'normal' or 'fullscreen' -// Called by the common.js module. +/** + * attachListeners - Gets called to attach listeners. Called by the common.js module. + */ function attachListeners() { changeUiModeForNaClLoad(); @@ -23,6 +25,9 @@ function attachListeners() { chrome.app.window.current().onMaximized.addListener(fullscreenChromeWindow); } +/** + * fullscreenChromeWindow - Fullscreens the chrome window + */ function fullscreenChromeWindow() { // when the user clicks the maximize button on the window, // FIRST restore it to the previous size, then fullscreen it to the whole screen @@ -34,6 +39,11 @@ function fullscreenChromeWindow() { chrome.app.window.current().fullscreen(); } +/** + * loadWindowState - Loads the window state: windowed, fullscreen... + * + * @return {Void} If chrome.storage is not avaliable + */ function loadWindowState() { if (!chrome.storage) { return; @@ -51,6 +61,9 @@ function loadWindowState() { }); } +/** + * onFullscreened - Gets called when window is fullscreened + */ function onFullscreened() { if (!isInGame && windowState == 'normal') { storeData('windowState', 'fullscreen', null); @@ -58,6 +71,9 @@ function onFullscreened() { } } +/** + * onBoundsChanged - Gets called when window's bounds changed + */ function onBoundsChanged() { if (!isInGame && windowState == 'fullscreen') { storeData('windowState', 'normal', null); @@ -97,7 +113,7 @@ class Resolution { * @param {Number} width Resolution width * @param {Number} height Resolution height * @param {String} name Resolution name - * @return {Object} The corresponding object + * @return {NvHTTP} The corresponding object */ constructor(width, height, name = null) { this.width = width @@ -131,6 +147,11 @@ class Resolution { } } +/** + * changeUiModeForNaClLoad - Changes the view to NaCL loading + * + * @return {type} description + */ function changeUiModeForNaClLoad() { $('#main-navigation').children().hide(); $("#main-content").children().not("#listener, #naclSpinner").hide(); @@ -138,12 +159,18 @@ function changeUiModeForNaClLoad() { $('#naclSpinner').css('display', 'inline-block'); } +/** + * startPollingHosts - Start polling the hosts + */ function startPollingHosts() { for (var hostUID in hosts) { beginBackgroundPollingOfHost(hosts[hostUID]); } } +/** + * stopPollingHosts - Stops polling the hosts + */ function stopPollingHosts() { for (var hostUID in hosts) { stopBackgroundPollingOfHost(hosts[hostUID]); @@ -185,6 +212,11 @@ function restoreUiAfterNaClLoad() { }); } +/** + * beginBackgroundPollingOfHost - Starts the polling of the host + * + * @param {NvHTTP} host The host object + */ function beginBackgroundPollingOfHost(host) { var el = document.querySelector('#hostgrid-' + host.serverUid) if (host.online) { @@ -225,6 +257,11 @@ function beginBackgroundPollingOfHost(host) { } } +/** + * stopBackgroundPollingOfHost - Stops the background polling of the host + * + * @param {NvHTTP} host The host object + */ function stopBackgroundPollingOfHost(host) { console.log('%c[index.js, backgroundPolling]', 'color: green;', 'Stopping background polling of host ' + host.serverUid + '\n', host, host.toString()); //Logging both object (for console) and toString-ed object (for text logs) window.clearInterval(activePolls[host.serverUid]); @@ -249,6 +286,11 @@ function snackbarLogLong(givenMessage) { document.querySelector('#snackbar').MaterialSnackbar.showSnackbar(data); } +/** + * moduleDidLoad - Gets called when the NaCL module is loaded + * + * @return {type} description + */ function moduleDidLoad() { // load the HTTP cert and unique ID if we have one. chrome.storage.sync.get('cert', function(savedCert) { @@ -303,7 +345,14 @@ function moduleDidLoad() { }); } -// pair to the given NvHTTP host object. Returns whether pairing was successful. +/** + * pairTo - description + * + * @param {NvHTTP} nvhttpHost The host object + * @param {Function} onSuccess Callback on success + * @param {Function} onFailure Callback on faillure + * @return {Void} + */ function pairTo(nvhttpHost, onSuccess, onFailure) { if (!pairingCert) { snackbarLog('ERROR: cert has not been generated yet. Is NaCl initialized?'); @@ -360,6 +409,11 @@ function pairTo(nvhttpHost, onSuccess, onFailure) { }); } +/** + * hostChosen - Gets called when a host is chosen + * + * @param {NvHTTP} host The host object + */ function hostChosen(host) { if (!host.online) { @@ -420,8 +474,12 @@ function addHost() { }); } - -// host is an NvHTTP object +/** + * addHostToGrid - description + * + * @param {NvHTTP} host The host object + * @param {Boolean} ismDNSDiscovered Whether or not host was mDNS discovered + */ function addHostToGrid(host, ismDNSDiscovered) { var outerDiv = $("
", { @@ -538,7 +596,11 @@ function sortTitles(list, sortOrder) { }); } -// show the app list +/** + * showApps - Shows the app list view + * + * @param {NvHTTP} host The host object + */ function showApps(host) { if (!host || !host.paired) { // safety checking. shouldn't happen. console.log('%c[index.js, showApps]', 'color: green;', 'Moved into showApps, but `host` did not initialize properly! Failing.'); @@ -632,7 +694,9 @@ function showApps(host) { showAppsMode(); } -// set the layout to the initial mode you see when you open moonlight +/** + * showHostsAndSettingsMode - Changes the view to list hosts and settings (initial view) + */ function showHostsAndSettingsMode() { console.log('%c[index.js]', 'color: green;', 'Entering "Show apps and hosts" mode'); $("#main-navigation").show(); @@ -648,6 +712,9 @@ function showHostsAndSettingsMode() { startPollingHosts(); } +/** + * showAppsMode - Changes the view to list apps + */ function showAppsMode() { console.log('%c[index.js]', 'color: green;', 'Entering "Show apps" mode'); $('#backIcon').show(); @@ -676,9 +743,13 @@ function showAppsMode() { stopPollingHosts(); } - -// start the given appID. if another app is running, offer to quit it. -// if the given app is already running, just resume it. +/** + * startGame - Start the given appID. If another app is running, offer to quit it. If the given app is already running, just resume it. + * + * @param {NvHTTP} host description + * @param {Number} appID The ID of the app to start + * @return {type} description + */ function startGame(host, appID) { if (!host || !host.paired) { console.error('%c[index.js, startGame]', 'color: green;', 'Attempted to start a game, but `host` did not initialize properly. Host object: ', host); @@ -787,6 +858,9 @@ function startGame(host, appID) { }); } +/** + * playGameMode - Change the view to streaming mode + */ function playGameMode() { console.log('%c[index.js, playGameMode]', 'color:green;', 'Entering play game mode'); isInGame = true; @@ -802,6 +876,9 @@ function playGameMode() { } // Maximize the size of the nacl module by scaling and resizing appropriately +/** + * fullscreenNaclModule - Fullscreens the NaCL module + */ function fullscreenNaclModule() { var streamWidth = $('#selectResolution').data('value').split(':')[0]; var streamHeight = $('#selectResolution').data('value').split(':')[1]; @@ -819,6 +896,9 @@ function fullscreenNaclModule() { module.style.paddingTop = ((screenHeight - module.height) / 2) + "px"; } +/** + * stopGameWithConfirmation - Stops the game after user confirmation + */ function stopGameWithConfirmation() { if (api.currentGame === 0) { snackbarLog('Nothing was running'); @@ -845,6 +925,12 @@ function stopGameWithConfirmation() { } } +/** + * stopGame - Stops the currently running game + * + * @param {NvHTTP} host The host object + * @param {Function} callbackFunction The callback function + */ function stopGame(host, callbackFunction) { isInGame = false; @@ -885,6 +971,9 @@ function storeData(key, data, callbackFunction) { chrome.storage.sync.set(obj, callbackFunction); } +/** + * saveResolution - Saves the option for resolution to storage + */ function saveResolution() { var chosenResolution = $(this).data('value'); $('#selectResolution').text($(this).text()).data('value', chosenResolution); @@ -892,6 +981,9 @@ function saveResolution() { updateDefaultBitrate(); } +/** + * saveOptimize - Saves the option for optimisations to storage + */ function saveOptimize() { // MaterialDesignLight uses the mouseup trigger, so we give it some time to change the class name before // checking the new state @@ -902,6 +994,9 @@ function saveOptimize() { }, 100); } +/** + * saveFramerate - Saves the option for framerate to storage + */ function saveFramerate() { var chosenFramerate = $(this).data('value'); $('#selectFramerate').text($(this).text()).data('value', chosenFramerate); @@ -909,15 +1004,19 @@ function saveFramerate() { updateDefaultBitrate(); } - - // storing data in chrome.storage takes the data as an object, and shoves it into JSON to store // unfortunately, objects with function instances (classes) are stripped of their function instances when converted to a raw object // so we cannot forget to revive the object after we load it. +/** + * saveHosts - Saves the hosts to storage + */ function saveHosts() { storeData('hosts', hosts, null); } +/** + * saveRemoteAudio - Saves the option for remote audio to storage + */ function saveRemoteAudio() { // MaterialDesignLight uses the mouseup trigger, so we give it some time to change the class name before // checking the new state @@ -928,6 +1027,11 @@ function saveRemoteAudio() { }, 100); } +/** + * updateDefaultBitrate - Updates the bitrate according to the resolution and framerate + * + * @return {Void} + */ function updateDefaultBitrate() { var res = $('#selectResolution').data('value'); var frameRate = $('#selectFramerate').data('value').toString(); @@ -955,6 +1059,11 @@ function updateDefaultBitrate() { } } +/** + * onWindowLoad - Gets clalled when the main window loads + * + * @return {type} description + */ function onWindowLoad() { console.log('%c[index.js]', 'color: green;', 'Moonlight\'s main window loaded'); // don't show the game selection div From b377813789e7af379e25df8b0ad1a9008937a592 Mon Sep 17 00:00:00 2001 From: Jorys Paulin Date: Sat, 21 Jul 2018 13:53:00 +0200 Subject: [PATCH 06/14] Removed some Jquery --- static/js/index.js | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/static/js/index.js b/static/js/index.js index 3c2da105..064dcd83 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -11,17 +11,20 @@ var windowState = 'normal'; // chrome's windowState, possible values: 'normal' o */ function attachListeners() { changeUiModeForNaClLoad(); - + // Streaming controls handlers $('.resolutionMenu li').on('click', saveResolution); $('.framerateMenu li').on('click', saveFramerate); - $('#bitrateSlider').on('input', e => changeBitrate(e.target.value)); // input occurs every notch you slide - $('#bitrateInput').on('input', e => changeBitrate(e.target.value)); $("#remoteAudioEnabledSwitch").on('click', saveRemoteAudio); $('#optimizeGamesSwitch').on('click', saveOptimize); - $('#addHostCell').on('click', addHost); - $('#backIcon').on('click', showHostsAndSettingsMode); - $('#quitCurrentApp').on('click', stopGameWithConfirmation); - $(window).resize(fullscreenNaclModule); + // Inputs handlers + document.querySelector('#bitrateSlider').oninput = e => changeBitrate(e.target.value) + document.querySelector('#bitrateInput').oninput = e => changeBitrate(e.target.value) + // Controls handler + document.querySelector('#addHostCell').onclick = () => addHost() + document.querySelector('#backIcon').onclick = () => showHostsAndSettingsMode() + document.querySelector('#quitCurrentApp').onclick = () => stopGameWithConfirmation() + // Resize handlers + window.onresize = () => fullscreenNaclModule() chrome.app.window.current().onMaximized.addListener(fullscreenChromeWindow); } @@ -964,6 +967,13 @@ function stopGame(host, callbackFunction) { }); } +/** + * storeData - Stores data into chrome.storage.sync + * + * @param {String} key The key for the storage + * @param {Object|String|Number} data The data to store + * @param {Function} callbackFunction The callback function + */ function storeData(key, data, callbackFunction) { var obj = {}; obj[key] = data; @@ -1076,7 +1086,6 @@ function onWindowLoad() { chrome.storage.sync.get('customResolutions', function(previousValue) { if (previousValue.customResolutions != null) { previousValue.customResolutions.forEach(item => { - console.log(item); var child = document.createElement('li') child.className = 'mdl-menu__item' child.dataset.value = item.width + ':' + item.height From 61c43cdf20cbdb53b5ae954fa643c5e2afd2d765 Mon Sep 17 00:00:00 2001 From: Jorys Paulin Date: Sat, 21 Jul 2018 16:38:36 +0200 Subject: [PATCH 07/14] Started localizing --- _locales/en/messages.json | 38 ++++++++++++++++++++++++++++ _locales/fr/messages.json | 38 ++++++++++++++++++++++++++++ manifest.json | 9 ++++--- static/js/index.js | 52 +++++++++++++++++---------------------- 4 files changed, 104 insertions(+), 33 deletions(-) create mode 100644 _locales/en/messages.json create mode 100644 _locales/fr/messages.json diff --git a/_locales/en/messages.json b/_locales/en/messages.json new file mode 100644 index 00000000..6627e3e9 --- /dev/null +++ b/_locales/en/messages.json @@ -0,0 +1,38 @@ +{ + "loading_plugin": { + "message": "Loading Moonlight plugin..." + }, + "loading_apps": { + "message": "Loading apps..." + }, + "stopping_game": { + "message": "Stopping $1" + }, + "error": { + "message": "Error" + }, + "cert_error": { + "message": "Cert has not been generated yet. Is NaCl initialized?" + }, + "busy_error": { + "message": "Your computer is busy. Stop streaming to pair" + }, + "pair_error": { + "message": "Failed to pair with $1" + }, + "remove_host": { + "message": "Remove host" + }, + "delete_host": { + "message": "Are you sure you want to delete $1?" + }, + "add_host": { + "message": "Add host" + }, + "gamelist_empty": { + "message": "Your game list is empty" + }, + "game_running": { + "message": "$1 is already running. Would you like to quit? All unsaved progress will be lost" + } +} diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json new file mode 100644 index 00000000..41664b15 --- /dev/null +++ b/_locales/fr/messages.json @@ -0,0 +1,38 @@ +{ + "loading_plugin": { + "message": "Chargement du plugin..." + }, + "loading_apps": { + "message": "Chargement des applications..." + }, + "stopping_game": { + "message": "Arrêt de $1" + }, + "error": { + "message": "Erreur" + }, + "cert_error": { + "message": "Le certificat n'a pas été généré. Est-ce que NaCL est activé?" + }, + "busy_error": { + "message": "Votre ordinateur est occupé. Arrêter de streamer pour appareiller" + }, + "pair_error": { + "message": "Impossible d'appareiller $1" + }, + "remove_host": { + "message": "Supprimer l'ordinateur" + }, + "delete_host": { + "message": "Voulez-vous vraiement enlever $1?" + }, + "add_host": { + "message": "Ajouter un ordinateur" + }, + "gamelist_empty": { + "message": "Votre liste d'applications est vide" + }, + "game_running": { + "message": "$1 est en cours d'execution. Voulez-vous quitter? Toute progression non enregistrée sera perdue" + } +} diff --git a/manifest.json b/manifest.json index 30eebd5c..16c5175f 100644 --- a/manifest.json +++ b/manifest.json @@ -4,11 +4,12 @@ "short_name": "Moonlight", "version": "0.9.0", "description": "Open-source client for NVIDIA GameStream", + "default_locale": "en", "icons": { "128": "icons/icon128.png", "48": "icons/icon48.png", "32": "icons/icon32.png", - "16": "icons/icon16.png" + "16": "icons/icon16.png" }, "app": { "background": { @@ -29,9 +30,9 @@ "power", "overrideEscFullscreen", { "socket": [ - "tcp-connect", - "resolve-host", - "udp-bind:*:*", + "tcp-connect", + "resolve-host", + "udp-bind:*:*", "udp-send-to:*:*" ] } ], diff --git a/static/js/index.js b/static/js/index.js index 064dcd83..856eaa4a 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -158,7 +158,7 @@ class Resolution { function changeUiModeForNaClLoad() { $('#main-navigation').children().hide(); $("#main-content").children().not("#listener, #naclSpinner").hide(); - $('#naclSpinnerMessage').text('Loading Moonlight plugin...'); + $('#naclSpinnerMessage').text(chrome.i18n.getMessage('loading_plugin')); $('#naclSpinner').css('display', 'inline-block'); } @@ -358,7 +358,7 @@ function moduleDidLoad() { */ function pairTo(nvhttpHost, onSuccess, onFailure) { if (!pairingCert) { - snackbarLog('ERROR: cert has not been generated yet. Is NaCl initialized?'); + snackbarLog(chrome.i18n.getMessage('cert_error')); console.warn('%c[index.js]', 'color: green;', 'User wants to pair, and we still have no cert. Problem = very yes.'); onFailure(); return; @@ -379,7 +379,7 @@ function pairTo(nvhttpHost, onSuccess, onFailure) { var randomNumber = String("0000" + (Math.random() * 10000 | 0)).slice(-4); var pairingDialog = document.querySelector('#pairingDialog'); - $('#pairingDialogText').html('Please enter the number ' + randomNumber + ' on the GFE dialog on the computer. This dialog will be dismissed once complete'); + pairingDialog.innerText = 'Please enter the number ' + randomNumber + ' on the GFE dialog on the computer. This dialog will be dismissed once complete' pairingDialog.showModal(); $('#cancelPairingDialog').off('click'); @@ -390,10 +390,11 @@ function pairTo(nvhttpHost, onSuccess, onFailure) { console.log('%c[index.js]', 'color: green;', 'Sending pairing request to ' + nvhttpHost.hostname + ' with random number' + randomNumber); nvhttpHost.pair(randomNumber).then(function(paired) { if (!paired) { + var pairingDialogText = document.querySelector('#pairingDialogText') if (nvhttpHost.currentGame != 0) { - $('#pairingDialogText').html('Error: ' + nvhttpHost.hostname + ' is busy. Stop streaming to pair.'); + pairingDialogText.innerText = chrome.i18n.getMessage('busy_error') } else { - $('#pairingDialogText').html('Error: failed to pair with ' + nvhttpHost.hostname + '.'); + pairingDialogText.innerText = chrome.i18n.getMessage('pair_error', nvhttpHost.hostname) } console.log('%c[index.js]', 'color: green;', 'Failed API object:', nvhttpHost, nvhttpHost.toString()); //Logging both the object and the toString version for text logs onFailure(); @@ -404,7 +405,7 @@ function pairTo(nvhttpHost, onSuccess, onFailure) { pairingDialog.close(); onSuccess(); }, function(failedPairing) { - snackbarLog('Failed pairing to: ' + nvhttpHost.hostname); + snackbarLog(chrome.i18n.getMessage('pair_error', nvhttpHost.hostname)); console.error('%c[index.js]', 'color: green;', 'Pairing failed, and returned:', failedPairing); console.error('%c[index.js]', 'color: green;', 'Failed API object:', nvhttpHost, nvhttpHost.toString()); //Logging both the object and the toString version for text logs onFailure(); @@ -457,7 +458,7 @@ function addHost() { // try to pair if they continue $('#continueAddHost').off('click'); $('#continueAddHost').on('click', function() { - var inputHost = $('#dialogInputHost').val(); + var inputHost = document.querySelector('#dialogInputHost').value; var _nvhttpHost = new NvHTTP(inputHost, myUniqueid, inputHost); pairTo(_nvhttpHost, function() { @@ -483,15 +484,13 @@ function addHost() { * @param {NvHTTP} host The host object * @param {Boolean} ismDNSDiscovered Whether or not host was mDNS discovered */ -function addHostToGrid(host, ismDNSDiscovered) { - - var outerDiv = $("
", { - class: 'host-container mdl-card mdl-shadow--4dp', - id: 'host-container-' + host.serverUid, - role: 'link', - tabindex: 0, - 'aria-label': host.hostname - }); +function addHostToGrid(host, ismDNSDiscovered = false) { + var outerDiv = document.createElement('div') + outerDiv.className = 'host-container mdl-card mdl-shadow--4dp' + outerDiv.id = 'host-container-' + host.serverUid + outerDiv.setAttribute('role', 'link') + outerDiv.tabIndex = 0 + outerDiv.setAttribute('aria-label', host.hostname) var cell = $("
", { class: 'mdl-card__title mdl-card--expand', id: 'hostgrid-' + host.serverUid @@ -505,7 +504,7 @@ function addHostToGrid(host, ismDNSDiscovered) { id: "removeHostButton-" + host.serverUid, role: 'button', tabindex: 0, - 'aria-label': 'Remove host ' + host.hostname + 'aria-label': chrome.i18n.getMessage('remove_error') }); removalButton.off('click'); removalButton.click(function() { @@ -515,7 +514,7 @@ function addHostToGrid(host, ismDNSDiscovered) { cell.click(function() { hostChosen(host); }); - outerDiv.keypress(function(e) { + $(outerDiv).keypress(function(e) { if (e.keyCode == 13) { hostChosen(host); } @@ -531,8 +530,7 @@ function addHostToGrid(host, ismDNSDiscovered) { function removeClicked(host) { var deleteHostDialog = document.querySelector('#deleteHostDialog'); - document.getElementById('deleteHostDialogText').innerHTML = - ' Are you sure you want to delete ' + host.hostname + '?'; + document.getElementById('deleteHostDialogText').innerHTML = chrome.i18n.getMessage('delete_host', host.hostname); deleteHostDialog.showModal(); $('#cancelDeleteHost').off('click'); @@ -614,7 +612,7 @@ function showApps(host) { $("#gameList .game-container").remove(); // Show a spinner while the applist loads - $('#naclSpinnerMessage').text('Loading apps...'); + $('#naclSpinnerMessage').text(chrome.i18n.getMessage('loading_apps')); $('#naclSpinner').css('display', 'inline-block'); $("div.game-container").remove(); @@ -628,7 +626,7 @@ function showApps(host) { var img = new Image() img.src = 'static/res/applist_empty.svg' $('#game-grid').html(img) - snackbarLog('Your game list is empty') + snackbarLog(chrome.i18n.getMessage('gamelist_empty')) return; // We stop the function right here } // if game grid is populated, empty it @@ -766,9 +764,7 @@ function startGame(host, appID) { if (host.currentGame != 0 && host.currentGame != appID) { host.getAppById(host.currentGame).then(function(currentApp) { var quitAppDialog = document.querySelector('#quitAppDialog'); - document.getElementById('quitAppDialogText').innerHTML = - currentApp.title + ' is already running. Would you like to quit ' + - currentApp.title + '?'; + document.getElementById('quitAppDialogText').innerHTML = chrome.i18n.getMessage('game_running', currentApp.title); quitAppDialog.showModal(); $('#cancelQuitApp').off('click'); $('#cancelQuitApp').on('click', function() { @@ -908,9 +904,7 @@ function stopGameWithConfirmation() { } else { api.getAppById(api.currentGame).then(function(currentGame) { var quitAppDialog = document.querySelector('#quitAppDialog'); - document.getElementById('quitAppDialogText').innerHTML = - ' Are you sure you would like to quit ' + - currentGame.title + '? Unsaved progress will be lost.'; + document.getElementById('quitAppDialogText').innerHTML = chrome.i18n.getMessage('game_running', currentGame.title) quitAppDialog.showModal(); $('#cancelQuitApp').off('click'); $('#cancelQuitApp').on('click', function() { @@ -948,7 +942,7 @@ function stopGame(host, callbackFunction) { return; } var appName = runningApp.title; - snackbarLog('Stopping ' + appName); + snackbarLog(chrome.i18n.getMessage('stopping_game', runningApp.title)); host.quitApp().then(function(ret2) { host.refreshServerInfo().then(function(ret3) { // refresh to show no app is currently running. showApps(host); From c9e49e6c35b9e130e84a481cdaf62406c30d267a Mon Sep 17 00:00:00 2001 From: Jorys Paulin Date: Sat, 21 Jul 2018 18:38:33 +0200 Subject: [PATCH 08/14] Added localization --- _locales/en/messages.json | 18 ++++++++++++++++++ _locales/fr/messages.json | 18 ++++++++++++++++++ index.html | 12 ++++++------ static/js/index.js | 6 ++++++ 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 6627e3e9..7bb69e00 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -34,5 +34,23 @@ }, "game_running": { "message": "$1 is already running. Would you like to quit? All unsaved progress will be lost" + }, + "quit_current_app": { + "message": "Quit current app" + }, + "option_resolution": { + "message": "Resolution" + }, + "option_framerate": { + "message": "Framerate" + }, + "option_bandwidth": { + "message": "Bandwidth" + }, + "option_audio": { + "message": "Play audio on host" + }, + "option_optimisations": { + "message": "Enable optimisations" } } diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index 41664b15..441fed74 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -34,5 +34,23 @@ }, "game_running": { "message": "$1 est en cours d'execution. Voulez-vous quitter? Toute progression non enregistrée sera perdue" + }, + "quit_current_app": { + "message": "Quitter l'application en cours" + }, + "option_resolution": { + "message": "Resolution" + }, + "option_framerate": { + "message": "Taux de rafraichissement" + }, + "option_bandwidth": { + "message": "Bande passante" + }, + "option_audio": { + "message": "Jouer l'audio sur l'ordinateur" + }, + "option_optimisations": { + "message": "Activer les optimisations" } } diff --git a/index.html b/index.html index d437e8d1..f588f21c 100644 --- a/index.html +++ b/index.html @@ -35,7 +35,7 @@
- Resolution + Resolution
@@ -66,7 +66,7 @@
- Bandwidth + Bandwidth
@@ -76,7 +76,7 @@ volume_up
- Play audio on the host + Play audio on the host
@@ -86,13 +86,13 @@ timeline
- Allow game optimisations + Allow game optimisations
diff --git a/static/js/index.js b/static/js/index.js index 856eaa4a..37d46142 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -1141,6 +1141,12 @@ function onWindowLoad() { var value = previousValue.bitrate != null ? previousValue.bitrate : '10' changeBitrate(value) }); + + document.querySelectorAll('.localize').forEach(item => { + var message = item.dataset.message + var localized = chrome.i18n.getMessage(message) + item.innerText = localized + }) } } From a12dcdc5079272c7872191a767ac8de49e5ced81 Mon Sep 17 00:00:00 2001 From: Jorys Paulin Date: Sat, 21 Jul 2018 18:54:38 +0200 Subject: [PATCH 09/14] Added german translation --- _locales/de/messages.json | 56 +++++++++++++++++++++++++++++++++++++++ _locales/en/messages.json | 2 +- 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 _locales/de/messages.json diff --git a/_locales/de/messages.json b/_locales/de/messages.json new file mode 100644 index 00000000..4785d698 --- /dev/null +++ b/_locales/de/messages.json @@ -0,0 +1,56 @@ +{ + "loading_plugin": { + "message": "L�dt Moonlight Plugin..." + }, + "loading_apps": { + "message": "L�dt Anwendungen..." + }, + "stopping_game": { + "message": "Stoppe $1" + }, + "error": { + "message": "Fehler" + }, + "cert_error": { + "message": "Zertifikat ist noch nicht generiert worden. Ist NaCl aktiviert?" + }, + "busy_error": { + "message": "Dein Computer ist besch�ftigt. Halte den Stream an, um dich zu verbinden." + }, + "pair_error": { + "message": "Verbindung mit $1 fehlgeschlagen." + }, + "remove_host": { + "message": "Host entfernen" + }, + "delete_host": { + "message": "Bist du sicher, dass du $1 entfernen m�chtest?" + }, + "add_host": { + "message": "Host hinzuf�gen" + }, + "gamelist_empty": { + "message": "Deine Spieleliste ist leer." + }, + "game_running": { + "message": "$1 l�uft bereits. M�chtest du es beenden? Jeder ungespeicherte Fortschritt geht verloren." + }, + "quit_current_app": { + "message": "Beende die aktuelle Anwendung" + }, + "option_resolution": { + "message": "Auflösung" + }, + "option_framerate": { + "message": "Framerate" + }, + "option_bandwidth": { + "message": "Bandbreite" + }, + "option_audio": { + "message": "Audio auf Host-PC abspielen" + }, + "option_optimisations": { + "message": "Optimierungen aktivieren" + } +} diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 7bb69e00..9594d0c7 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -51,6 +51,6 @@ "message": "Play audio on host" }, "option_optimisations": { - "message": "Enable optimisations" + "message": "Enable optimizations" } } From 9f7672a228cfaf83ad4bd586a9e19dd6407ddca6 Mon Sep 17 00:00:00 2001 From: Jorys Paulin Date: Wed, 25 Jul 2018 17:40:19 +0200 Subject: [PATCH 10/14] Added snackbar when host offline --- _locales/de/messages.json | 3 +++ _locales/en/messages.json | 3 +++ _locales/fr/messages.json | 3 +++ static/js/index.js | 1 + 4 files changed, 10 insertions(+) diff --git a/_locales/de/messages.json b/_locales/de/messages.json index 4785d698..bc000f72 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -29,6 +29,9 @@ "add_host": { "message": "Host hinzuf�gen" }, + "offline_host": { + "message": "Der Computer ist offline" + }, "gamelist_empty": { "message": "Deine Spieleliste ist leer." }, diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 9594d0c7..f50f426b 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -29,6 +29,9 @@ "add_host": { "message": "Add host" }, + "offline_host": { + "message": "Host is offline" + }, "gamelist_empty": { "message": "Your game list is empty" }, diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index 441fed74..1ad2b63e 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -29,6 +29,9 @@ "add_host": { "message": "Ajouter un ordinateur" }, + "offline_host": { + "message": "L'ordinateur est hors-ligne" + }, "gamelist_empty": { "message": "Votre liste d'applications est vide" }, diff --git a/static/js/index.js b/static/js/index.js index 37d46142..a7e799bf 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -421,6 +421,7 @@ function pairTo(nvhttpHost, onSuccess, onFailure) { function hostChosen(host) { if (!host.online) { + snackbarLog(chrome.i18n.getMessage('offline_host')) return; } From 00bf63331a1467e8cef9432254dd74b56f8786e6 Mon Sep 17 00:00:00 2001 From: Jorys Paulin Date: Wed, 25 Jul 2018 17:41:22 +0200 Subject: [PATCH 11/14] Added chrome.runtime options It allows to launch the app from a verified website using chrome.runtime .sendMessageExternal(extension_id, message) --- manifest.json | 3 +++ static/js/background.js | 20 ++++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index 16c5175f..a5e23c84 100644 --- a/manifest.json +++ b/manifest.json @@ -18,6 +18,9 @@ "scripts": ["static/js/jquery-2.2.0.min.js", "static/js/material.min.js", "static/js/common.js", "static/js/background.js"] } }, + "externally_connectable": { + "matches": ["*://localhost:*/*", "*://moonlight-stream.com:*/*", "*://moonlight-stream.github.io:*/*"] + }, "sockets": { "udp": { "bind": "*", "send": "*" } }, diff --git a/static/js/background.js b/static/js/background.js index 664eb98e..9cde1c89 100644 --- a/static/js/background.js +++ b/static/js/background.js @@ -17,8 +17,7 @@ function createWindow(state) { }); } -chrome.app.runtime.onLaunched.addListener(function() { - console.log('Chrome app runtime launched.'); +function launchApp() { var windowState = 'normal'; if (chrome.storage) { @@ -32,4 +31,21 @@ chrome.app.runtime.onLaunched.addListener(function() { } else { createWindow(windowState); } +} + +chrome.app.runtime.onLaunched.addListener(function() { + console.log('Chrome app runtime launched.'); + launchApp() }); + +chrome.runtime.onMessageExternal.addListener(function(request, sender, sendResponse) { + if(request && request.message) { + if(request.message == 'VERSION') { + var manifestData = chrome.runtime.getManifest(); + sendResponse({name: 'moonlight-chrome', version: manifestData.version}) + } + if(request.message == 'LAUNCH') { + launchApp() + } + } +}) From fec14bbcbcf8323b43dc0d1e6e2435d74fb80ad7 Mon Sep 17 00:00:00 2001 From: Jorys Paulin Date: Thu, 26 Jul 2018 09:47:03 +0200 Subject: [PATCH 12/14] Localized add host dialog --- _locales/de/messages.json | 6 ++++++ _locales/en/messages.json | 6 ++++++ _locales/fr/messages.json | 6 ++++++ index.html | 4 ++-- static/js/index.js | 2 +- 5 files changed, 21 insertions(+), 3 deletions(-) diff --git a/_locales/de/messages.json b/_locales/de/messages.json index bc000f72..3b469b70 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -55,5 +55,11 @@ }, "option_optimisations": { "message": "Optimierungen aktivieren" + }, + "pairing": { + "message": "Paarung" + }, + "pairing_dialog": { + "message": "Bitte geben Sie $1 auf Ihrem Computer ein. Dieser Dialog wird einmal beendet" } } diff --git a/_locales/en/messages.json b/_locales/en/messages.json index f50f426b..46eca912 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -55,5 +55,11 @@ }, "option_optimisations": { "message": "Enable optimizations" + }, + "pairing": { + "message": "Pairing" + }, + "pairing_dialog": { + "message": "Please enter $1 on your computer. This dialog will dismiss once complete" } } diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index 1ad2b63e..f0519687 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -55,5 +55,11 @@ }, "option_optimisations": { "message": "Activer les optimisations" + }, + "pairing": { + "message": "Appareillage" + }, + "pairing_dialog": { + "message": "Veuillez entrer $1 sur votre ordinateur. Cette fenètre se fermera ensuite" } } diff --git a/index.html b/index.html index f588f21c..85f8b9e7 100644 --- a/index.html +++ b/index.html @@ -101,7 +101,7 @@
-

Add Host

+

Add Host

@@ -125,7 +125,7 @@
-

Pairing

+

Pairing

Please enter the number XXXX on the GFE dialog on the computer. This dialog will be dismissed once complete diff --git a/static/js/index.js b/static/js/index.js index a7e799bf..51248620 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -379,8 +379,8 @@ function pairTo(nvhttpHost, onSuccess, onFailure) { var randomNumber = String("0000" + (Math.random() * 10000 | 0)).slice(-4); var pairingDialog = document.querySelector('#pairingDialog'); - pairingDialog.innerText = 'Please enter the number ' + randomNumber + ' on the GFE dialog on the computer. This dialog will be dismissed once complete' pairingDialog.showModal(); + pairingDialog.querySelector('#pairingDialogText').innerText = chrome.i18n.getMessage('pairing_dialog', randomNumber) $('#cancelPairingDialog').off('click'); $('#cancelPairingDialog').on('click', function() { From 33ce25503a7859d413b9f8b59dbaae175e07f6bb Mon Sep 17 00:00:00 2001 From: Jorys Paulin Date: Thu, 26 Jul 2018 09:55:07 +0200 Subject: [PATCH 13/14] Localized actions --- _locales/de/messages.json | 12 ++++++++++++ _locales/en/messages.json | 12 ++++++++++++ _locales/fr/messages.json | 12 ++++++++++++ index.html | 12 ++++++------ 4 files changed, 42 insertions(+), 6 deletions(-) diff --git a/_locales/de/messages.json b/_locales/de/messages.json index 3b469b70..53646c58 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -61,5 +61,17 @@ }, "pairing_dialog": { "message": "Bitte geben Sie $1 auf Ihrem Computer ein. Dieser Dialog wird einmal beendet" + }, + "no": { + "message": "Nein" + }, + "yes": { + "message": "Ja" + }, + "cancel": { + "message": "Stornieren" + }, + "continue": { + "message": "Weiter" } } diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 46eca912..8b7f5622 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -61,5 +61,17 @@ }, "pairing_dialog": { "message": "Please enter $1 on your computer. This dialog will dismiss once complete" + }, + "no": { + "message": "Non" + }, + "yes": { + "message": "Yes" + }, + "cancel": { + "message": "Cancel" + }, + "continue": { + "message": "Continue" } } diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index f0519687..f00ce763 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -61,5 +61,17 @@ }, "pairing_dialog": { "message": "Veuillez entrer $1 sur votre ordinateur. Cette fenètre se fermera ensuite" + }, + "no": { + "message": "Non" + }, + "yes": { + "message": "Oui" + }, + "cancel": { + "message": "Anuler" + }, + "continue": { + "message": "Continuer" } } diff --git a/index.html b/index.html index 85f8b9e7..ae9df204 100644 --- a/index.html +++ b/index.html @@ -143,8 +143,8 @@

Quit Running App?

- - + +
@@ -155,8 +155,8 @@

Delete PC

- - + +
@@ -168,8 +168,8 @@

Add Host Manually

- - + +
From 49bde4fa496b751e723907b1249c541956655b53 Mon Sep 17 00:00:00 2001 From: Jorys Paulin Date: Thu, 26 Jul 2018 16:22:33 +0200 Subject: [PATCH 14/14] Localized "Add Host" dialog --- _locales/de/messages.json | 3 +++ _locales/en/messages.json | 3 +++ _locales/fr/messages.json | 3 +++ index.html | 2 +- 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/_locales/de/messages.json b/_locales/de/messages.json index 53646c58..fc83a138 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -73,5 +73,8 @@ }, "continue": { "message": "Weiter" + }, + "host_ip": { + "message": "IP-Adresse oder Hostname des PCs" } } diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 8b7f5622..e8389fb2 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -73,5 +73,8 @@ }, "continue": { "message": "Continue" + }, + "host_ip": { + "message": "IP address or hostname of the PC" } } diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index f00ce763..70c68c32 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -73,5 +73,8 @@ }, "continue": { "message": "Continuer" + }, + "host_ip": { + "message": "Addresse IP ou nom d'hote de l'ordinateur" } } diff --git a/index.html b/index.html index ae9df204..fe218d8b 100644 --- a/index.html +++ b/index.html @@ -164,7 +164,7 @@

Add Host Manually

- +