mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
Reimplement UI translation JS code
Add a new Translator class that handles the loading of translation data and provides a simpler translate() API. Also refactor l10n.js to export a single function for translating static text, splitting it up into separate function calls for each different area of the UI with strings to translate.
This commit is contained in:
@@ -315,9 +315,10 @@
|
||||
<script src="../../../bower_components/marked/lib/marked.js"></script>
|
||||
<script src="../../../bower_components/Jed/jed.js"></script>
|
||||
<script src="../../../bower_components/jed-gettext-parser/jedGettextParser.js"></script>
|
||||
<script src="js/l10n.js"></script>
|
||||
<script src="js/helpers.js"></script>
|
||||
<script src="js/loot.js"></script>
|
||||
<script src="js/translator.js"></script>
|
||||
<script src="js/l10n.js"></script>
|
||||
<script src="js/plugin.js"></script>
|
||||
<script src="js/filters.js"></script>
|
||||
<script src="js/events.js"></script>
|
||||
|
||||
+16
-16
@@ -53,7 +53,7 @@ function onChangeGame(evt) {
|
||||
}
|
||||
|
||||
/* Send off a CEF query with the folder name of the new game. */
|
||||
showProgress(l10n.jed.translate('Loading game data...').fetch());
|
||||
showProgress(loot.l10n.translate('Loading game data...'));
|
||||
var request = JSON.stringify({
|
||||
name: 'changeGame',
|
||||
args: [
|
||||
@@ -123,14 +123,14 @@ function updateMasterlistNoProgress() {
|
||||
/* Hack to stop cards overlapping. */
|
||||
document.getElementById('main').lastElementChild.updateSize();
|
||||
|
||||
toast(l10n.jed.translate('Masterlist updated to revision %s.').fetch(loot.game.masterlist.revision));
|
||||
toast(loot.l10n.translate('Masterlist updated to revision %s.', loot.game.masterlist.revision));
|
||||
} else {
|
||||
toast(l10n.jed.translate('No masterlist update was necessary.').fetch());
|
||||
toast(loot.l10n.translate('No masterlist update was necessary.'));
|
||||
}
|
||||
}).catch(processCefError);
|
||||
}
|
||||
function onUpdateMasterlist(evt) {
|
||||
showProgress(l10n.jed.translate('Updating masterlist...').fetch());
|
||||
showProgress(loot.l10n.translate('Updating masterlist...'));
|
||||
updateMasterlistNoProgress().then(function(result){
|
||||
closeProgressDialog();
|
||||
}).catch(processCefError);
|
||||
@@ -154,7 +154,7 @@ function onSortPlugins(evt) {
|
||||
promise = promise.then(updateMasterlistNoProgress());
|
||||
}
|
||||
promise.then(function(){
|
||||
showProgress(l10n.jed.translate('Sorting plugins...').fetch());
|
||||
showProgress(loot.l10n.translate('Sorting plugins...'));
|
||||
loot.query('sortPlugins').then(JSON.parse).then(function(result){
|
||||
if (result) {
|
||||
loot.game.oldLoadOrder = loot.game.plugins;
|
||||
@@ -256,7 +256,7 @@ function onRedatePlugins(evt) {
|
||||
return;
|
||||
}
|
||||
|
||||
showMessageDialog(l10n.jed.translate('Redate Plugins?').fetch(), l10n.jed.translate('This feature is provided so that modders using the Creation Kit may set the load order it uses. A side-effect is that any subscribed Steam Workshop mods will be re-downloaded by Steam. Do you wish to continue?').fetch(), l10n.jed.translate('Redate').fetch(), function(result){
|
||||
showMessageDialog(loot.l10n.translate('Redate Plugins?'), loot.l10n.translate('This feature is provided so that modders using the Creation Kit may set the load order it uses. A side-effect is that any subscribed Steam Workshop mods will be re-downloaded by Steam. Do you wish to continue?'), loot.l10n.translate('Redate'), function(result){
|
||||
if (result) {
|
||||
loot.query('redatePlugins').then(function(response){
|
||||
toast('Plugins were successfully redated.');
|
||||
@@ -265,7 +265,7 @@ function onRedatePlugins(evt) {
|
||||
});
|
||||
}
|
||||
function onClearAllMetadata(evt) {
|
||||
showMessageDialog('', l10n.jed.translate('Are you sure you want to clear all existing user-added metadata from all plugins?').fetch(), l10n.jed.translate('Clear').fetch(), function(result){
|
||||
showMessageDialog('', loot.l10n.translate('Are you sure you want to clear all existing user-added metadata from all plugins?'), loot.l10n.translate('Clear'), function(result){
|
||||
if (result) {
|
||||
loot.query('clearAllMetadata').then(JSON.parse).then(function(result){
|
||||
if (result) {
|
||||
@@ -287,7 +287,7 @@ function onClearAllMetadata(evt) {
|
||||
}
|
||||
});
|
||||
|
||||
toast(l10n.jed.translate('All user-added metadata has been cleared.').fetch());
|
||||
toast(loot.l10n.translate('All user-added metadata has been cleared.'));
|
||||
}
|
||||
}).catch(processCefError);
|
||||
}
|
||||
@@ -344,7 +344,7 @@ function onCopyContent(evt) {
|
||||
});
|
||||
|
||||
loot.query(request).then(function(){
|
||||
toast(l10n.jed.translate("LOOT's content has been copied to the clipboard.").fetch());
|
||||
toast(loot.l10n.translate("LOOT's content has been copied to the clipboard."));
|
||||
}).catch(processCefError);
|
||||
}
|
||||
function onCopyLoadOrder(evt) {
|
||||
@@ -366,7 +366,7 @@ function onCopyLoadOrder(evt) {
|
||||
});
|
||||
|
||||
loot.query(request).then(function(){
|
||||
toast(l10n.jed.translate("The load order has been copied to the clipboard.").fetch());
|
||||
toast(loot.l10n.translate("The load order has been copied to the clipboard."));
|
||||
}).catch(processCefError);
|
||||
}
|
||||
function onSwitchSidebarTab(evt) {
|
||||
@@ -577,11 +577,11 @@ function onCopyMetadata(evt) {
|
||||
});
|
||||
|
||||
loot.query(request).then(function(){
|
||||
toast(l10n.jed.translate('The metadata for "%s" has been copied to the clipboard.').fetch(evt.target.getName()));
|
||||
toast(loot.l10n.translate('The metadata for "%s" has been copied to the clipboard.', evt.target.getName()));
|
||||
}).catch(processCefError);
|
||||
}
|
||||
function onClearMetadata(evt) {
|
||||
showMessageDialog('', l10n.jed.translate('Are you sure you want to clear all existing user-added metadata from "%s"?').fetch(evt.target.getName()), l10n.jed.translate('Clear').fetch(), function(result){
|
||||
showMessageDialog('', loot.l10n.translate('Are you sure you want to clear all existing user-added metadata from "%s"?', evt.target.getName()), loot.l10n.translate('Clear'), function(result){
|
||||
if (result) {
|
||||
var request = JSON.stringify({
|
||||
name: 'clearPluginMetadata',
|
||||
@@ -607,7 +607,7 @@ function onClearMetadata(evt) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
toast(l10n.jed.translate('The user-added metadata for "%s" has been cleared.').fetch(evt.target.getName()));
|
||||
toast(loot.l10n.translate('The user-added metadata for "%s" has been cleared.', evt.target.getName()));
|
||||
/* Now perform search again. If there is no current search, this won't
|
||||
do anything. */
|
||||
document.getElementById('searchBar').search();
|
||||
@@ -630,9 +630,9 @@ function onSidebarClick(evt) {
|
||||
}
|
||||
function onQuit(evt) {
|
||||
if (!document.getElementById('applySortButton').classList.contains('hidden')) {
|
||||
handleUnappliedChangesClose(l10n.jed.translate('sorted load order').fetch());
|
||||
handleUnappliedChangesClose(loot.l10n.translate('sorted load order'));
|
||||
} else if (document.body.hasAttribute('data-editors')) {
|
||||
handleUnappliedChangesClose(l10n.jed.translate('metadata edits').fetch());
|
||||
handleUnappliedChangesClose(loot.l10n.translate('metadata edits'));
|
||||
} else {
|
||||
window.close();
|
||||
}
|
||||
@@ -643,7 +643,7 @@ function onJumpToGeneralInfo(evt) {
|
||||
}
|
||||
function onContentRefresh(evt) {
|
||||
/* Send a query for updated load order and plugin header info. */
|
||||
showProgress(l10n.jed.translate('Refreshing data...').fetch());
|
||||
showProgress(loot.l10n.translate('Refreshing data...'));
|
||||
loot.query('getGameData').then(function(result){
|
||||
/* Parse the data sent from C++. */
|
||||
try {
|
||||
|
||||
@@ -118,7 +118,7 @@ var filters = {
|
||||
|
||||
doNotCleanFilter: function(message) {
|
||||
if (document.getElementById('hideDoNotCleanMessages').checked) {
|
||||
return message.content[0].str.indexOf(l10n.jed.translate("Do not clean").fetch()) == -1;
|
||||
return message.content[0].str.indexOf(loot.l10n.translate("Do not clean")) == -1;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
@@ -157,7 +157,7 @@ function getConflictingPluginsFromFilter() {
|
||||
]
|
||||
});
|
||||
|
||||
showProgress(l10n.jed.translate('Checking if plugins have been loaded...').fetch());
|
||||
showProgress(loot.l10n.translate('Checking if plugins have been loaded...'));
|
||||
|
||||
return loot.query(request).then(JSON.parse).then(function(result){
|
||||
if (result) {
|
||||
|
||||
@@ -4,7 +4,7 @@ function processCefError(err) {
|
||||
promise errors, not just CEF errors. */
|
||||
console.log(err.stack);
|
||||
closeProgressDialog();
|
||||
showMessageBox(l10n.jed.translate('Error').fetch(), err.message);
|
||||
showMessageBox(loot.l10n.translate('Error'), err.message);
|
||||
}
|
||||
|
||||
function showElement(element) {
|
||||
@@ -24,13 +24,13 @@ function toast(text) {
|
||||
}
|
||||
function showMessageDialog(title, text, positiveText, closeCallback) {
|
||||
var dialog = document.createElement('loot-message-dialog');
|
||||
dialog.setButtonText(positiveText, l10n.jed.translate('Cancel').fetch());
|
||||
dialog.setButtonText(positiveText, loot.l10n.translate('Cancel'));
|
||||
dialog.showModal(title, text, closeCallback);
|
||||
document.body.appendChild(dialog);
|
||||
}
|
||||
function showMessageBox(title, text) {
|
||||
var dialog = document.createElement('loot-message-dialog');
|
||||
dialog.setButtonText(l10n.jed.translate('OK').fetch());
|
||||
dialog.setButtonText(loot.l10n.translate('OK'));
|
||||
dialog.showModal(title, text);
|
||||
document.body.appendChild(dialog);
|
||||
}
|
||||
@@ -51,7 +51,7 @@ function closeProgressDialog() {
|
||||
}
|
||||
}
|
||||
function handleUnappliedChangesClose(change) {
|
||||
showMessageDialog('', l10n.jed.translate('You have not yet applied or cancelled your %s. Are you sure you want to quit?').fetch(change), l10n.jed.translate('Quit').fetch(), function(result){
|
||||
showMessageDialog('', loot.l10n.translate('You have not yet applied or cancelled your %s. Are you sure you want to quit?', change), loot.l10n.translate('Quit'), function(result){
|
||||
if (result) {
|
||||
/* Cancel any sorting and close any editors. Cheat by sending a
|
||||
cancelSort query for as many times as necessary. */
|
||||
@@ -70,4 +70,3 @@ function handleUnappliedChangesClose(change) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ function initVars() {
|
||||
if (loot.version.length > pos + 1) {
|
||||
document.getElementById('LOOTBuild').textContent = loot.version.substring(pos + 1);
|
||||
} else {
|
||||
document.getElementById('LOOTBuild').textContent = l10n.jed.translate('unknown').fetch();
|
||||
document.getElementById('LOOTBuild').textContent = loot.l10n.translate('unknown');
|
||||
}
|
||||
|
||||
loot.version = loot.version.substring(0, pos);
|
||||
@@ -139,12 +139,12 @@ function initVars() {
|
||||
console.log('getSettings response: ' + results[2]);
|
||||
}
|
||||
}).then(function(){
|
||||
return l10n.getJedInstance(loot.settings.language).then(function(jed){
|
||||
l10n.translateStaticText(jed);
|
||||
l10n.jed = jed;
|
||||
|
||||
/* Also need to update the settings UI. */
|
||||
loot.updateSettingsUI();
|
||||
/* Translate static text. */
|
||||
loot.l10n = new loot.Translator(loot.settings.language);
|
||||
loot.l10n.load().then(() => {
|
||||
loot.translateStaticText(loot.l10n);
|
||||
/* Also need to update the settings UI. */
|
||||
loot.updateSettingsUI();
|
||||
}).catch(processCefError);
|
||||
}).then(function(){
|
||||
if (result) {
|
||||
|
||||
+282
-285
File diff suppressed because it is too large
Load Diff
@@ -180,12 +180,12 @@ var loot = {
|
||||
if (change.object[change.name] && change.object[change.name].revision) {
|
||||
document.getElementById('masterlistRevision').textContent = change.object[change.name].revision;
|
||||
} else {
|
||||
document.getElementById('masterlistRevision').textContent = l10n.jed.translate("N/A").fetch();
|
||||
document.getElementById('masterlistRevision').textContent = loot.l10n.translate("N/A");
|
||||
}
|
||||
if (change.object[change.name] && change.object[change.name].date) {
|
||||
document.getElementById('masterlistDate').textContent = change.object[change.name].date;
|
||||
} else {
|
||||
document.getElementById('masterlistDate').textContent = l10n.jed.translate("N/A").fetch();
|
||||
document.getElementById('masterlistDate').textContent = loot.l10n.translate("N/A");
|
||||
}
|
||||
} else if (change.name == 'globalMessages') {
|
||||
/* For the messages, they don't have a JS 'class' so need to everything
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
'use strict';
|
||||
|
||||
(function (root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['bower_components/Jed/jed', 'bower_components/jed-gettext-parser/jedGettextParser'], factory);
|
||||
} else {
|
||||
// Browser globals
|
||||
root.loot = root.loot || {};
|
||||
root.loot.Translator = factory(root.Jed, root.jedGettextParser);
|
||||
}
|
||||
}(this, (Jed, jedGettextParser) => {
|
||||
return class Translator {
|
||||
/* Returns a Promise */
|
||||
constructor(locale) {
|
||||
this.locale = locale || 'en';
|
||||
this.jed = undefined;
|
||||
}
|
||||
|
||||
load() {
|
||||
const defaultTranslationData = {
|
||||
'messages': {
|
||||
'': {
|
||||
'domain': 'messages',
|
||||
'lang': 'en',
|
||||
'plural_forms': 'nplurals=2; plural=(n != 1);',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let translationDataPromise;
|
||||
if (this.locale === 'en') {
|
||||
/* Just resolve to an empty data set. */
|
||||
translationDataPromise = Promise.resolve(defaultTranslationData);
|
||||
} else {
|
||||
translationDataPromise = new Promise((resolve, reject) => {
|
||||
const url = 'loot://l10n/' + this.locale + '/LC_MESSAGES/loot.mo';
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url);
|
||||
xhr.responseType = 'arraybuffer';
|
||||
xhr.addEventListener('readystatechange', (evt) => {
|
||||
if (evt.target.readyState === 4) {
|
||||
/* Status is 0 for local file URL loading. */
|
||||
if (evt.target.status >= 200 && evt.target.status < 400) {
|
||||
resolve(jedGettextParser.mo.parse(evt.target.response));
|
||||
} else {
|
||||
reject(new Error(evt.target.statusText));
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
|
||||
return translationDataPromise.catch((error) => {
|
||||
console.log('Error loading translation data: ' + error.message);
|
||||
return defaultTranslationData;
|
||||
}).then((result) => {
|
||||
this.jed = new Jed({
|
||||
'locale_data': result,
|
||||
'domain': 'messages',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
translate(text, ...substitutions) {
|
||||
if (text === undefined) {
|
||||
return '';
|
||||
}
|
||||
const func = this.jed.translate(text);
|
||||
return func.fetch.apply(func, substitutions);
|
||||
}
|
||||
};
|
||||
}));
|
||||
@@ -7,9 +7,14 @@
|
||||
<script src="../../../../../node_modules/should/should.js"></script>
|
||||
<script src="../../../../../node_modules/mocha/mocha.js"></script>
|
||||
|
||||
<script src="../../../../../bower_components/Jed/jed.js"></script>
|
||||
<script src="../../../../../bower_components/jed-gettext-parser/jedGettextParser.js"></script>
|
||||
|
||||
<script>mocha.setup('bdd')</script>
|
||||
<script src="../../../../gui/html/js/plugin.js"></script>
|
||||
<script src="../../../../gui/html/js/translator.js"></script>
|
||||
<script src="test_plugin.js"></script>
|
||||
<script src="test_translator.js"></script>
|
||||
<script>
|
||||
onload = function(){
|
||||
mocha.checkLeaks();
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
'use strict';
|
||||
|
||||
describe('Translator', () => {
|
||||
describe('#Tranlsator()', () => {
|
||||
it('should set locale to "en" if no locale is given', () => {
|
||||
const l10n = new loot.Translator();
|
||||
l10n.locale.should.equal('en');
|
||||
});
|
||||
|
||||
it('should set the locale to the given value', () => {
|
||||
const l10n = new loot.Translator('de');
|
||||
l10n.locale.should.equal('de');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#load()', () => {
|
||||
it('should return a Promise', () => {
|
||||
const l10n = new loot.Translator();
|
||||
|
||||
l10n.load().should.be.a.Promise();
|
||||
});
|
||||
|
||||
it('should be fulfilled for a locale of "en"', () => {
|
||||
const l10n = new loot.Translator('en');
|
||||
|
||||
return l10n.load().should.be.fulfilled();
|
||||
});
|
||||
|
||||
/* Cannot test rejection or other locales as the URL uses is invalid in the
|
||||
browser, causing a CORS error that cannot be handled in JavaScript. */
|
||||
});
|
||||
|
||||
describe('#translate()', () => {
|
||||
let l10n;
|
||||
|
||||
beforeEach(() => {
|
||||
l10n = new loot.Translator();
|
||||
});
|
||||
|
||||
it('should throw if the translator has not been loaded', () => {
|
||||
(() => { l10n.translate('foo'); }).should.throw();
|
||||
});
|
||||
|
||||
it('should return an empty string if nothing is passed', () => {
|
||||
return l10n.load().then(() => {
|
||||
l10n.translate().should.equal('');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the input string if the current locale is "en"', () => {
|
||||
return l10n.load().then(() => {
|
||||
l10n.translate('foo').should.equal('foo');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the translated string if locale data has been loaded', () => {
|
||||
/* Since loading data doesn't work in the browser, hack it by setting some
|
||||
data manually. */
|
||||
l10n.jed = new window.Jed({
|
||||
'locale_data': {
|
||||
'messages': {
|
||||
'': {
|
||||
'domain': 'messages',
|
||||
'lang': 'en',
|
||||
'plural_forms': 'nplurals=2; plural=(n != 1);',
|
||||
},
|
||||
'foo': ['bar'],
|
||||
},
|
||||
},
|
||||
'domain': 'messages',
|
||||
});
|
||||
|
||||
l10n.translate('foo').should.equal('bar');
|
||||
});
|
||||
|
||||
it('should subsitute additional arguments into string', () => {
|
||||
/* Since loading data doesn't work in the browser, hack it by setting some
|
||||
data manually. */
|
||||
l10n.jed = new window.Jed({
|
||||
'locale_data': {
|
||||
'messages': {
|
||||
'': {
|
||||
'domain': 'messages',
|
||||
'lang': 'en',
|
||||
'plural_forms': 'nplurals=2; plural=(n != 1);',
|
||||
},
|
||||
'foo %1$s %2$s': ['%2$s is bar'],
|
||||
},
|
||||
},
|
||||
'domain': 'messages',
|
||||
});
|
||||
|
||||
l10n.translate('foo %1$s %2$s', 'is not', 'bar').should.equal('bar is bar');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user