switch languages without an app restart (#1555)

switch languages without an app restart
10.7.0-preview
Michael Keller 2019-08-10 18:16:40 +12:00 committed by GitHub
commit 7e371ee49e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 189 additions and 119 deletions

View File

@ -69,75 +69,81 @@
"analyticsOptOut": {
"message": "Opt out of the anonymised collection of statistics data"
},
"userLanguageSelect": {
"message": "Language (need to restart the application for the changes to take effect)"
"language_changed": {
"message": "Language change saved"
},
"language_choice_message": {
"message": "Change language:",
"description": "Try and be brief"
},
"language_default": {
"message": "Default"
"message": "System Default"
},
"language_default_pretty": {
"message": "System Default ($t(detectedLanguage))"
},
"language_ca": {
"message": "Catal\u00e0 (ca)",
"message": "Catal\u00e0",
"description": "Don't translate!!!"
},
"language_de": {
"message": "Deutsch (de)",
"message": "Deutsch",
"description": "Don't translate!!!"
},
"language_en": {
"message": "English (en)",
"message": "English",
"description": "Don't translate!!!"
},
"language_es": {
"message": "Espa\u00f1ol (es)",
"message": "Espa\u00f1ol",
"description": "Don't translate!!!"
},
"language_fr": {
"message": "Fran\u00e7ais (fr)",
"message": "Fran\u00e7ais",
"description": "Don't translate!!!"
},
"language_gl": {
"message": "Galego (gl)",
"message": "Galego",
"description": "Don't translate!!!"
},
"language_hr": {
"message": "Hrvatski (hr)",
"message": "Hrvatski",
"description": "Don't translate!!!"
},
"language_id": {
"message": "Bahasa Indonesia (id)",
"message": "Bahasa Indonesia",
"description": "Don't translate!!!"
},
"language_it": {
"message": "Italiano (it)",
"message": "Italiano",
"description": "Don't translate!!!"
},
"language_ja": {
"message": "\u65E5\u672C\u8A9E (ja)",
"message": "\u65E5\u672C\u8A9E",
"description": "Don't translate!!!"
},
"language_ko": {
"message": "\ud55c\uad6d\uc5b4 (ko)",
"message": "\ud55c\uad6d\uc5b4",
"description": "Don't translate!!!"
},
"language_lv": {
"message": "Latvie\u0161u (lv)",
"message": "Latvie\u0161u",
"description": "Don't translate!!!"
},
"language_pt": {
"message": "Portugu\u00EAs (pt)",
"message": "Portugu\u00EAs",
"description": "Don't translate!!!"
},
"language_ru": {
"message": "\u0420\u0443\u0441\u0441\u043A\u0438\u0439 \u044F\u0437\u044B\u043A (ru)",
"message": "\u0420\u0443\u0441\u0441\u043A\u0438\u0439 \u044F\u0437\u044B\u043A",
"description": "Don't translate!!!"
},
"language_sv": {
"message": "Svenska (sv)",
"message": "Svenska",
"description": "Don't translate!!!"
},
"language_zh_CN": {
"message": "\u7b80\u4f53\u4e2d\u6587 (zh_CN)",
"message": "\u7b80\u4f53\u4e2d\u6587",
"description": "Don't translate!!!"
},
@ -231,6 +237,12 @@
"tabLanding": {
"message": "Welcome"
},
"tabChangelog": {
"message": "Changelog"
},
"tabPrivacyPolicy": {
"message": "Privacy Policy"
},
"tabHelp": {
"message": "Documentation & Support"
},
@ -573,15 +585,9 @@
"defaultFacebookText": {
"message": "We also have a <a href=\"https://www.facebook.com/groups/betaflightgroup/\" target=\"_blank\">Facebook Group</a>.<br />Join us to get a place to talk about Betaflight, ask configuration questions, or just hang out with fellow pilots."
},
"defaultChangelogAction": {
"message": "Changelog"
},
"defaultChangelogHead": {
"message": "Configurator - Changelog"
},
"defaultPrivacyPolicyAction": {
"message": "Privacy Policy"
},
"defaultButtonFirmwareFlasher": {
"message": "Firmware Flasher"
},

View File

@ -40,7 +40,7 @@ body {
a {
text-decoration: none;
color: var(--defaultText);
font-family: 'open_sanssemibold', Arial;
font-weight: bold;
}
a:hover {
@ -513,7 +513,7 @@ input[type="number"]::-webkit-inner-spin-button {
}
#log a {
font-weight: regular;
font-weight: normal;
color: #ffbb00;
}
@ -590,6 +590,7 @@ input[type="number"]::-webkit-inner-spin-button {
#tabs li a {
font-family: 'open_sansregular', Arial;
font-weight: normal;
padding-left: 33px;
padding-top: 5px;
padding-bottom: 3px;
@ -1654,6 +1655,7 @@ dialog {
color: #fff;
font-size: 12px;
font-family: 'open_sansregular', Arial;
font-weight: normal;
text-shadow: 0px 1px rgba(0, 0, 0, 0.25);
margin-top: -1px;
}
@ -1666,6 +1668,7 @@ dialog {
color: #fff;
font-size: 12px;
font-family: 'open_sansregular', Arial;
font-weight: normal;
text-shadow: 0px 1px rgba(0, 0, 0, 0.25);
margin-top: -1px;
}

View File

@ -180,3 +180,22 @@
display: block;
float: left;
}
.tab-landing .languageSwitcher .selected_language {
font-weight: bold;
}
.tab-landing .languageSwitcher {
margin-left: auto;
margin-right: auto;
text-align: center;
color: silver;
}
.tab-landing .languageSwitcher a {
color: silver;
font-weight: normal;
white-space: nowrap;
}
.tab-landing .languageSwitcher a:not(:last-child):after {
content: ", ";
font-weight: normal;
}

View File

@ -30,14 +30,30 @@ i18n.init = function(cb) {
console.error('Error loading i18n ' + err);
} else {
console.log('i18n system loaded');
var detectedLanguage = i18n.getMessage('language_' + getValidLocale("DEFAULT"));
i18n.addResources({"detectedLanguage": detectedLanguage });
}
if (cb !== undefined) {
cb();
}
});
});
// This function should do the same things that the i18n.localizePage function below does.
i18next.on('languageChanged', function (newLang) {
var translate = function(messageID) {
return i18n.getMessage(messageID);
};
i18n.localizePage(true);
updateStatusBarVersion();
});
}
i18n.changeLanguage = function(languageSelected) {
ConfigStorage.set({'userLanguageSelect': languageSelected});
i18next.changeLanguage(getValidLocale(languageSelected));
i18n.selectedLanguage = languageSelected;
GUI.log(i18n.getMessage('language_changed'));
}
i18n.getMessage = function(messageID, parameters) {
var translatedString;
@ -79,44 +95,58 @@ i18n.existsMessage = function(key) {
* Helper functions, don't depend of the i18n framework
*/
i18n.localizePage = function() {
i18n.localizePage = function(forceReTranslate) {
var localized = 0;
var translate = function(messageID) {
localized++;
return i18n.getMessage(messageID);
};
$('[i18n]:not(.i18n-replaced)').each(function() {
var element = $(this);
if (forceReTranslate) {
$('[i18n]').each(function() {
var element = $(this);
element.html(translate(element.attr('i18n')));
});
$('[i18n_title]').each(function() {
var element = $(this);
element.attr('title', translate(element.attr('i18n_title')));
});
$('[i18n_value]').each(function() {
var element = $(this);
element.val(translate(element.attr('i18n_value')));
});
$('[i18n_placeholder]').each(function() {
var element = $(this);
element.attr('placeholder', translate(element.attr('i18n_placeholder')));
});
} else {
element.html(translate(element.attr('i18n')));
element.addClass('i18n-replaced');
});
$('[i18n]:not(.i18n-replaced)').each(function() {
var element = $(this);
element.html(translate(element.attr('i18n')));
element.addClass('i18n-replaced');
});
$('[i18n_title]:not(.i18n_title-replaced)').each(function() {
var element = $(this);
$('[i18n_title]:not(.i18n_title-replaced)').each(function() {
var element = $(this);
element.attr('title', translate(element.attr('i18n_title')));
element.addClass('i18n_title-replaced');
});
element.attr('title', translate(element.attr('i18n_title')));
element.addClass('i18n_title-replaced');
});
$('[i18n_value]:not(.i18n_value-replaced)').each(function() {
var element = $(this);
element.val(translate(element.attr('i18n_value')));
element.addClass('i18n_value-replaced');
});
$('[i18n_placeholder]:not(.i18n_placeholder-replaced)').each(function() {
var element = $(this);
element.attr('placeholder', translate(element.attr('i18n_placeholder')));
element.addClass('i18n_placeholder-replaced');
});
$('[i18n_value]:not(.i18n_value-replaced)').each(function() {
var element = $(this);
element.val(translate(element.attr('i18n_value')));
element.addClass('i18n_value-replaced');
});
$('[i18n_placeholder]:not(.i18n_placeholder-replaced)').each(function() {
var element = $(this);
element.attr('placeholder', translate(element.attr('i18n_placeholder')));
element.addClass('i18n_placeholder-replaced');
});
}
return localized;
}
@ -129,7 +159,8 @@ function getStoredUserLocale(cb) {
var userLanguage = 'DEFAULT';
if (result.userLanguageSelect) {
userLanguage = result.userLanguageSelect
}
}
i18n.selectedLanguage = userLanguage;
userLanguage = getValidLocale(userLanguage);

View File

@ -121,12 +121,6 @@ function startProcess() {
checkForConfiguratorUpdates();
}
ConfigStorage.get('logopen', function (result) {
if (result.logopen) {
$("#showlog").trigger('click');
}
});
// log webgl capability
// it would seem the webgl "enabling" through advanced settings will be ignored in the future
// and webgl will be supported if gpu supports it by default (canary 40.0.2175.0), keep an eye on this one
@ -385,29 +379,6 @@ function startProcess() {
DarkTheme.setConfig(checked);
}).change();
ConfigStorage.get('userLanguageSelect', function (result) {
var userLanguage_e = $('div.userLanguage select');
var languagesAvailables = i18n.getLanguagesAvailables();
userLanguage_e.append('<option value="DEFAULT">' + i18n.getMessage('language_default') + '</option>');
userLanguage_e.append('<option disabled>------</option>');
languagesAvailables.forEach(function(element) {
var languageName = i18n.getMessage('language_' + element);
userLanguage_e.append('<option value="' + element + '">' + languageName + '</option>');
});
if (result.userLanguageSelect) {
userLanguage_e.val(result.userLanguageSelect);
}
userLanguage_e.change(function () {
var languageSelected = $(this).val();
// Select the new language, a restart is required
ConfigStorage.set({'userLanguageSelect': languageSelected});
});
});
function close_and_cleanup(e) {
if (e.type == 'click' && !$.contains($('div#options-window')[0], e.target) || e.type == 'keyup' && e.keyCode == 27) {
$(document).unbind('click keyup', close_and_cleanup);
@ -529,6 +500,12 @@ function startProcess() {
$(this).data('state', state);
});
ConfigStorage.get('logopen', function (result) {
if (result.logopen) {
$("#showlog").trigger('click');
}
});
ConfigStorage.get('permanentExpertMode', function (result) {
if (result.permanentExpertMode) {
$('input[name="expertModeCheckbox"]').prop('checked', true);

View File

@ -174,7 +174,9 @@ PortHandler.update_port_select = function (ports) {
$('div#port-picker #port').append($("<option/>", {value: ports[i], text: ports[i], data: {isManual: false}}));
}
$('div#port-picker #port').append($("<option/>", {value: 'manual', text: i18n.getMessage('portsSelectManual'), data: {isManual: true}}));
$('div#port-picker #port').append($("<option/>", {value: 'manual', i18n: 'portsSelectManual', data: {isManual: true}}));
i18n.localizePage();
};
PortHandler.port_detected = function(name, code, timeout, ignore_timeout) {

View File

@ -84,7 +84,7 @@ TABS.firmware_flasher.initialize = function (callback) {
$('div.release_info').slideDown();
} else {
self.flashingMessage(i18n.getMessage('firmwareFlasherHexCorrupted'), self.FLASH_MESSAGE_TYPES.INVALID);
self.flashingMessage('firmwareFlasherHexCorrupted', self.FLASH_MESSAGE_TYPES.INVALID);
}
});
}
@ -252,7 +252,7 @@ TABS.firmware_flasher.initialize = function (callback) {
function buildBuildTypeOptionsList() {
buildType_e.empty();
buildTypesToShow.forEach((build, index) => {
buildType_e.append($("<option value='{0}' selected>{1}</option>".format(index, build.tag ? i18n.getMessage(build.tag) : build.title)))
buildType_e.append($("<option value='{0}' {1}>{2}</option>".format(index,build.tag ? 'i18n="' + build.tag + '" ' : '', build.tag ? i18n.getMessage(build.tag) : build.title)))
});
$('select[name="build_type"]').val($('select[name="build_type"] option:first').val());
}
@ -300,16 +300,17 @@ TABS.firmware_flasher.initialize = function (callback) {
var build_type = $(this).val();
$('select[name="board"]').empty()
.append($("<option value='0'>{0}</option>".format(i18n.getMessage('firmwareFlasherOptionLabelSelectBoard'))));
.append($("<option value='0' i18n='firmwareFlasherOptionLabelSelectBoard'></option>"));
$('select[name="firmware_version"]').empty()
.append($("<option value='0'>{0}</option>".format(i18n.getMessage('firmwareFlasherOptionLabelSelectFirmwareVersion'))));
.append($("<option value='0' i18n='firmwareFlasherOptionLabelSelectFirmwareVersion'></option>"));
if (!GUI.connect_lock) {
buildTypesToShow[build_type].loader();
}
chrome.storage.local.set({'selected_build_type': build_type});
i18n.localizePage();
});
$('select[name="board"]').change(function() {
@ -317,7 +318,7 @@ TABS.firmware_flasher.initialize = function (callback) {
var target = $(this).val();
if (!GUI.connect_lock) {
self.flashingMessage(i18n.getMessage('firmwareFlasherLoadFirmwareFile'), self.FLASH_MESSAGE_TYPES.NEUTRAL)
self.flashingMessage('firmwareFlasherLoadFirmwareFile', self.FLASH_MESSAGE_TYPES.NEUTRAL)
.flashProgress(0);
$('div.git_info').slideUp();
@ -401,7 +402,7 @@ TABS.firmware_flasher.initialize = function (callback) {
self.flashingMessage(i18n.getMessage('firmwareFlasherFirmwareLocalLoaded', parsed_hex.bytes_total), self.FLASH_MESSAGE_TYPES.NEUTRAL);
} else {
self.flashingMessage(i18n.getMessage('firmwareFlasherHexCorrupted'), self.FLASH_MESSAGE_TYPES.INVALID);
self.flashingMessage('firmwareFlasherHexCorrupted', self.FLASH_MESSAGE_TYPES.INVALID);
}
});
}
@ -454,9 +455,10 @@ TABS.firmware_flasher.initialize = function (callback) {
}
function failed_to_load() {
$('span.progressLabel').text(i18n.getMessage('firmwareFlasherFailedToLoadOnlineFirmware'));
$('span.progressLabel').attr('i18n','firmwareFlasherFailedToLoadOnlineFirmware').removeClass('i18n-replaced');
$("a.load_remote_file").removeClass('disabled');
$("a.load_remote_file").text(i18n.getMessage('firmwareFlasherButtonLoadOnline'));
i18n.localizePage();
}
var summary = $('select[name="firmware_version"] option:selected').data('summary');
@ -466,7 +468,8 @@ TABS.firmware_flasher.initialize = function (callback) {
$("a.load_remote_file").addClass('disabled');
$.get(summary.url, onLoadSuccess).fail(failed_to_load);
} else {
$('span.progressLabel').text(i18n.getMessage('firmwareFlasherFailedToLoadOnlineFirmware'));
$('span.progressLabel').attr('i18n','firmwareFlasherFailedToLoadOnlineFirmware').removeClass('i18n-replaced');
i18n.localizePage();
}
});
@ -512,7 +515,8 @@ TABS.firmware_flasher.initialize = function (callback) {
STM32DFU.connect(usbDevices, parsed_hex, options);
}
} else {
$('span.progressLabel').text(i18n.getMessage('firmwareFlasherFirmwareNotLoaded'));
$('span.progressLabel').attr('i18n','firmwareFlasherFirmwareNotLoaded').removeClass('i18n-replaced');
i18n.localizePage();
}
}
}
@ -740,7 +744,13 @@ TABS.firmware_flasher.flashingMessage = function(message, type) {
break;
}
if (message != null) {
progressLabel_e.html(message);
if (i18next.exists(message)) {
progressLabel_e.attr('i18n',message).removeClass('i18n-replaced');
i18n.localizePage();
} else {
progressLabel_e.removeAttr('i18n');
progressLabel_e.html(message);
}
}
return self;

View File

@ -2,18 +2,51 @@
TABS.landing = {};
TABS.landing.initialize = function (callback) {
var self = this;
var self = this;
if (GUI.active_tab != 'landing') {
GUI.active_tab = 'landing';
if (GUI.active_tab != 'landing') {
GUI.active_tab = 'landing';
}
$('#content').load("./tabs/landing.html", function () {
function showLang(newLang) {
var bottomSection = $('.languageSwitcher');
bottomSection.find('a').each(function(index) {
var element = $(this);
var languageSelected = element.attr('lang');
if (newLang == languageSelected) {
element.removeClass('selected_language');
element.addClass('selected_language');
} else {
element.removeClass('selected_language');
}
});
}
$('#content').load("./tabs/landing.html", function () {
// translate to user-selected language
i18n.localizePage();
GUI.content_ready(callback);
var bottomSection = $('.languageSwitcher');
bottomSection.html(' <span i18n="language_choice_message"></span>');
bottomSection.append(' <a href="#" i18n="language_default_pretty" lang="DEFAULT"></a>');
var languagesAvailables = i18n.getLanguagesAvailables();
languagesAvailables.forEach(function(element) {
bottomSection.append(' <a href="#" lang="' + element + '" i18n="language_' + element + '"></a>');
});
bottomSection.find('a').each(function(index) {
var element = $(this);
element.click(function(){
var element = $(this);
var languageSelected = element.attr('lang');
if (!languageSelected) { return; }
if (i18n.selectedLanguage != languageSelected) {
i18n.changeLanguage(languageSelected);
showLang(languageSelected);
}
});
});
showLang(i18n.selectedLanguage);
// translate to user-selected language
i18n.localizePage();
GUI.content_ready(callback);
});
};

View File

@ -292,8 +292,8 @@
<ul class="mode-disconnected">
<li class="tab_landing" id="tab_landing"><a href="#" i18n="tabLanding" class="tabicon ic_welcome" i18n_title="tabLanding"></a>
</li>
<li class="tab_changelog"><a href="#" class="tabicon ic_help" i18n="defaultChangelogAction"></a></li>
<li class="tab_privacy_policy"><a href="#" class="tabicon ic_help" i18n="defaultPrivacyPolicyAction"></a></li>
<li class="tab_changelog"><a href="#" class="tabicon ic_help" i18n="tabChangelog"></a></li>
<li class="tab_privacy_policy"><a href="#" class="tabicon ic_help" i18n="tabPrivacyPolicy"></a></li>
<li class="tab_help" id="tab_help"><a href="#" i18n="tabHelp" class="tabicon ic_help"
i18n_title="tabHelp"></a></li>
<li class="tab_firmware_flasher" id="tabFirmware"><a href="#" i18n="tabFirmwareFlasher" class="tabicon ic_flasher"

View File

@ -40,8 +40,8 @@
</div>
</div>
<div class="content_foot">
<div class="sponsors">
</div>
<div class="languageSwitcher"></div>
<div class="sponsors"></div>
</div>
</div>
</div>

View File

@ -16,14 +16,3 @@
<div class="darkTheme">
<label><input type="checkbox" /><span i18n="darkTheme"></span></label>
</div>
<div class="separator"></div>
<div class="userLanguage">
<label>
<span class="dropdown">
<select class="dropdown-select" id="userLanguage" i18n_title="userLanguageSelect">
<!-- User languages generated here -->
</select>
</span>
<span i18n="userLanguageSelect"></span>
</label>
</div>