summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleš Smodiš <aless@guru.si>2015-09-26 15:23:26 +0200
committerAleš Smodiš <aless@guru.si>2015-09-26 15:23:26 +0200
commitea0c8e8a9fdc1cbcd33ab344c02df490f13f840e (patch)
treebc7f0346da0dabd3b40b717afbd97ae23ff356c6
parent2d2efca92351293ab179956efee78ef8f697afd6 (diff)
Reimplemented translation support: leave the DOM structure intact, upon display language switch only replace language-dependent content.
-rw-r--r--index.html8
-rw-r--r--js/codeq/core.js2
-rw-r--r--js/codeq/problem.js275
-rw-r--r--js/codeq/translation.js71
4 files changed, 203 insertions, 153 deletions
diff --git a/index.html b/index.html
index 3613ad9..8b12721 100644
--- a/index.html
+++ b/index.html
@@ -101,12 +101,7 @@
</div>
<!-- problem selection screen for a specific language: groups of problems, with descriptions -->
- <div class="container" id="screen_problem" style="display: none;">
- <h1 class="language-title"></h1>
- <hr>
- <h3 class="language-description"></h3>
- <ul class="language-problems"></ul>
- </div>
+ <div class="container" id="screen_problem" style="display: none;"></div>
<!-- problem screen: prolog -->
<div class="container-fluid quadrants block1" id="screen_prolog" style="display: none;">
@@ -194,6 +189,7 @@
<script src="js/codemirror/show-hint.js"></script>
<!-- codeq app -->
<script src="js/codeq/core.js"></script>
+ <script src="js/codeq/translation.js"></script>
<script src="js/codeq/statusbar.js"></script>
<script src="js/codeq/navigation.js"></script>
<script src="js/codeq/comms.js"></script>
diff --git a/js/codeq/core.js b/js/codeq/core.js
index 441c257..1685dd7 100644
--- a/js/codeq/core.js
+++ b/js/codeq/core.js
@@ -385,8 +385,8 @@
codeq.availableLangs.push(key);
}
+ codeq.fire('init'); // tell any interested modules that we are not initialized, perhaps they want to initialize too
codeq.setLang(lang || 'en'); // initial language setting
-
// go to login
codeq.globalStateMachine.transition('login');
});
diff --git a/js/codeq/problem.js b/js/codeq/problem.js
index fcb5d8a..3e218d8 100644
--- a/js/codeq/problem.js
+++ b/js/codeq/problem.js
@@ -1,59 +1,119 @@
(function(){
var jqScreen = $('#screen_problem'),
- jqLanguageTitle = jqScreen.find('.language-title'),
- jqLanguageDescription = jqScreen.find('.language-description'),
- jqLanguageProblems = jqScreen.find('.language-problems'),
- languageCache = {}, // keyed by language identifier
- currentData = null, // currently active data
- createLanguageData = function (data, languageIdentifier, lang) { // data is the content of language.json
- var trLanguage = codeq.chooseTranslation(data.translations, lang),
+ languageCache = {}, // keyed by language identifier: processed data about languages
+ translationCache = [], // keys are autogenerated in ta(), a value is a dictionary of translations of a translation key for every language
+ langs, Nlangs, // constants, set on init
+
+ chooseTranslation = function (keyword, lang, currentDict, enDict, translations) {
+ var tr = currentDict[keyword],
+ otherLang;
+ if (tr) return tr; // if there is a translation in the current dictionary: return it
+ tr = enDict[keyword];
+ if (tr) { // if there is a translation in the english dictionary: return it
+ codeq.log.info('Translation for ' + keyword + ' not set for language ' + lang + ', using translation from en');
+ return tr;
+ }
+ for (otherLang in translations) {
+ if (!translations.hasOwnProperty(otherLang)) continue;
+ tr = (translations[otherLang] || {})[keyword];
+ if (tr) { // otherwise: return the first available translation in any language
+ codeq.log.info('Translation for ' + keyword + ' not set for language ' + lang + ', using translation from ' + otherLang);
+ return tr;
+ }
+ }
+ return keyword + ' not set for language ' + lang;
+ },
+
+ /**
+ * convert the input translations (arg0) for given keys (arg1..argN)
+ * so each keys holds all its translations for ever language
+ */
+ convertTranslations = function () {
+ var translations = arguments[0] || {},
+ result = {},
+ enDict = translations['en'] || {},
+ lang, dict, i, keyword, l;
+ // initialize result: one lang-dict per keyword
+ for (i = arguments.length - 1; i > 0; i--) result[arguments[i]] = {};
+ // convert translations: one keyword-dict per lang -> one lang-dict per keyword
+ for (l = Nlangs - 1; l >= 0; l--) {
+ lang = langs[l];
+ dict = translations[lang] || {};
+ for (i = arguments.length - 1; i > 0; i--) {
+ keyword = arguments[i];
+// result[keyword][lang] = dict[keyword] || keyword + ' not set for language ' + lang;
+ result[keyword][lang] = chooseTranslation(keyword, lang, dict, enDict, translations);
+ }
+ }
+ return result;
+ },
+
+ /**
+ * Connect the given translations of a key with the DOM, returning
+ * the string of arguments to use with a HTML tag which will contain
+ * a translation chosen from the given dictionary.
+ */
+ ta = function (trObj) { // an object of the form: {'en': 'english content', 'sl': 'slovenska vsebina'}
+ var result = ['data-dict="directory" data-tkey="', translationCache.length, '"'].join('');
+ translationCache.push(trObj);
+ return result;
+ },
+
+ /**
+ * Assemble the display structure and translations from the server's
+ * language.json for the given language identifier.
+ */
+ createLanguageData = function (data, languageIdentifier) { // data is the content of language.json
+ var li = languageIdentifier, // a shorthand
groups = data.groups || {},
- languageHints = trLanguage.hint || {},
html = [],
problemReferences = [],
- groupIdentifier, group, trGroup, problems, problemIdentifier, problem, trProblem;
+ groupIdentifier, group, problems, problemIdentifier, problem;
+ var langDict = convertTranslations(data.translations, 'name', 'description'), // this will be the resulting dictionary: multi-level keys that lead up to the lang-dict
+ groupDict, problemDict;
+ // title: HTML structure for "name" and "desc"
+ html.push('<h1 class="language-title translatable" ', ta(langDict.name), '></h1><hr>');
+ html.push('<div class="language-description translatable" ', ta(langDict.description), '></div>');
+ // content: problem directory
+ html.push('<ul class="language-problems">');
for (groupIdentifier in groups) {
if (!groups.hasOwnProperty(groupIdentifier)) continue;
group = groups[groupIdentifier] || {};
- trGroup = codeq.chooseTranslation(group.translations, lang);
- html.push('<li><div class="group-title">', codeq.escapeHtml(trGroup.name || 'Group name not set: ' + groupIdentifier), '</div>');
- html.push('<div class="group-description">', codeq.escapeHtml(trGroup.description || 'Group description not set: ' + groupIdentifier), '</div>');
+ groupDict = convertTranslations(group.translations, 'name', 'description'); // the group-level translations, added will
+ // group content
+ html.push('<li><div class="group-title translatable" ', ta(groupDict.name), '></div>');
+ html.push('<div class="group-description translatable" ', ta(groupDict.description), '></div>');
html.push('<ul class="group-problems">');
+ // problem content
problems = group.problems || {};
for (problemIdentifier in problems) {
if (!problems.hasOwnProperty(problemIdentifier)) continue;
- problem = problems[problemIdentifier];
- trProblem = codeq.chooseTranslation(problem.translations, lang);
- html.push('<li><a class="problem-', '' + problemReferences.length, '">', codeq.escapeHtml(trProblem.name || 'Problem name not set: ' + problemIdentifier), '</a></li>');
+ problem = problems[problemIdentifier] || {};
+ problemDict = convertTranslations(problem.translations, 'name');
+ html.push('<li><a class="problem-', '' + problemReferences.length, ' translatable" ', ta(problemDict.name), '></a></li>');
problemReferences.push({'g': groupIdentifier, 'p': problemIdentifier});
}
html.push('</ul></li>');
}
+ html.push('</ul>');
+
return {
- 'name': trLanguage.name || 'Language name not set: ' + languageIdentifier,
- 'description': trLanguage.description || 'Description not set: ' + languageIdentifier,
+ 'language': languageIdentifier,
'html': html.join(''),
'refs': problemReferences,
- 'hints': trLanguage.hint || {}
+ 'hints': {} // TODO: prepare common hints for the language
};
},
- onLangChange = function (args) {
- purgeDom();
- createDom(currentData);
- },
- createDom = function (languageData) {
- var lang = codeq.getLang(),
- language = languageData.identifier,
- data = languageData[lang];
- if (!data) {
- data = createLanguageData(languageData.raw, language, lang);
- languageData[lang] = data;
- }
- jqLanguageTitle.html(data.name);
- jqLanguageDescription.html(data.description);
- jqLanguageProblems.html(data.html);
- jqLanguageProblems.find('a').on('click', function () {
- var index = +$(this).attr('class').split('-')[1],
+
+ /**
+ * Instantiates the screen from the given processed data.
+ */
+ createDom = function (data) {
+ var language = data.language;
+ jqScreen.html(data.html);
+ codeq.tr.translateDom(jqScreen);
+ jqScreen.find('a').on('click', function () {
+ var index = +$(this).attr('class').split(' ')[0].split('-')[1],
ref = data.refs[index];
if (!ref) {
codeq.log.error('Clicked on a problem link having erroneous index: ' + index);
@@ -73,129 +133,52 @@
})
.done();
});
- codeq.on('langchange', onLangChange);
- },
- purgeDom = function () {
- jqLanguageProblems.find('a').off();
- jqLanguageProblems.empty();
- jqLanguageDescription.empty();
- jqLanguageTitle.empty();
- codeq.off('langchange', onLangChange);
},
- lastLanguage;
+ currentLanguage; // the currently active language
- codeq.globalStateMachine.register('problem',{
+ // ================================================================================
+ // Initialization, invoked from the boot sequence
+ // ================================================================================
+
+ codeq.on('init', function () {
+ codeq.tr.registerDictionary('directory', translationCache);
+ langs = codeq.availableLangs; // cache for easier access
+ Nlangs = langs.length;
+ });
+
+ // ================================================================================
+ // Register with the state machine
+ // ================================================================================
+
+ codeq.globalStateMachine.register('problem', {
'enter': function(language){
- if (language) lastLanguage = language;
- else language = lastLanguage; // This happens when we hit this with the back button
+ var data = null; // language data
$('#navigation-login').css('display', '');
$('#navigation-language').css('display', '');
$("#navigation-problem").addClass("active").css('display', '');
- jqScreen.css('display', '');
-
- currentData = languageCache[language];
- if (currentData) {
- createDom(currentData);
- }
- else {
- codeq.wait(codeq.comms.getLanguageDef(language).then(function (rawData) {
- currentData = {'raw': rawData, 'identifier': language};
- languageCache[language] = currentData;
- createDom(currentData);
- })).done();
- }
-/* codeq.comms.send({'action': 'list_problems', 'language':language})
- .then(
- function success(data) {
- var i, groups, group, problems, problem, first_group,
- jqGroup = $('#problem_group'),
- jqProblems = $('#problems'),
- html = [],
- mapping = {},
- onGroupChange = function () {
- var problems = mapping[jqGroup.val()],
- html = [],
- i, p;
- if (problems) {
- for (i = 0; i < problems.length; i++) {
- p = problems[i];
- html.push('<option value="', p.identifier, '">', p.name, '</option>\n')
- }
- }
- jqProblems.html(html.join(''));
- };
+ if (!language) language = currentLanguage; // This happens when we hit this with the back button
- if (data && (data.code === 0)) {
- $('#disabled').css('display', 'none');
- groups = data.problems;
- for (i = 0; i < groups.length; i++) {
- group = groups[i];
- var identifier = group.identifier.group;
- html.push('<option value="', identifier, '">', group.name.group, '</option>\n');
- mapping[identifier] = group.problems;
- }
- jqGroup.html(html.join(''));
- first_group = html[1];
- html = null;
-
- jqGroup.on('click', onGroupChange);
- jqGroup.val(first_group);
- onGroupChange();
-
- $("#submit_problem").on('click', function(){
- var identifier = $('#problem_group').val(),//.split('/'),
- problem = $('#problems').val();
- if(!identifier) alert('Choose a problem group');
- else if (!problem) alert('Choose a problem');
- else {
- $('#disabled').css('display', '');
- codeq.comms.getProblem(language, identifier, problem)
- .then(function (data) {
- if (data.code !== 0) throw new Error('Failed to obtain problem data, code: ' + data.code + ', message: ' + data.message);
- $('#disabled').css('display', 'none');
- switch (language) {//(identifier[0]) {
- case 'prolog':
- codeq.globalStateMachine.transition('prolog', data);
- break;
- case 'python':
- codeq.globalStateMachine.transition('python', data);
- break;
- default:
- alert('Unknown language: ' + language);
- break;
- }
- })
- .fail(function (reason) {
- $('#disabled').css('display', 'none');
- alert('Login request failed: ' + reason);
- })
- .done();
- }
- });
- $('#screen_problem').css('display', '');
- }
- else {
- $('#disabled').css('cursor', '');
- alert('Obtaining list of problems failed: code=' + data.code + ', reason=' + data.message);
- }
- },
-
- function failure(reason) {
- $('#disabled').css('cursor', '');
- alert('Request to obtain list of problems failed: ' + reason);
+ if (currentLanguage !== language) {
+ jqScreen.empty();
+ currentLanguage = language;
+ data = languageCache[language];
+ if (data) {
+ createDom(data);
+ }
+ else {
+ codeq.wait(codeq.comms.getLanguageDef(language).then(function (rawData) {
+ var data = createLanguageData(rawData, language);
+ languageCache[language] = data;
+ createDom(data);
+ })).done();
}
- ).done();*/
+ }
+
+ jqScreen.css('display', '');
},
'exit' : function(){
- purgeDom();
-/* $('#problem_group').off();
- $("#submit_problem").off();
- $("#problem_group option").remove();//empty the selects
- $("#problems option").remove();
- $("#screen_problem").css('display', 'none');*/
-
jqScreen.css('display', 'none');
$('#navigation-login').css('display', 'none');
$('#navigation-language').css('display', 'none');
diff --git a/js/codeq/translation.js b/js/codeq/translation.js
new file mode 100644
index 0000000..af39cdc
--- /dev/null
+++ b/js/codeq/translation.js
@@ -0,0 +1,71 @@
+(function () {
+
+ var dicts = {},
+ translateElement = function (jqElt, lang) {
+ var dictionaryKey = jqElt.data('dict'),
+ translationKey = jqElt.data('tkey'),
+ dict = dicts[dictionaryKey],
+ translations, html, key;
+ if (!dict) {
+ codeq.log.error('Cannot find translation dictionary ' + dictionaryKey);
+ return;
+ }
+ if (!(typeof translationKey === 'number' || typeof translationKey === 'string')) {
+ codeq.log.error('Cannot find the element\'s translation key, dictionary: ' + dictionaryKey);
+ return;
+ }
+ translations = dict[translationKey];
+ if (!translations) {
+ codeq.log.error('Translation key ' + translationKey + ' is missing from dictionary ' + dictionaryKey);
+ return;
+ }
+ html = translations[lang];
+ if (!html) {
+ html = translations['en'];
+ if (html) {
+ codeq.log.info('There is no translation in language ' + lang + ' for key ' + translationKey + ' in dictionary ' + dictionaryKey + ', defaulting to language en');
+ }
+ else {
+ for (key in translations) {
+ if (!translations.hasOwnProperty(key)) continue;
+ html = translations[key];
+ if (!html) continue;
+ codeq.log.info('There is no translation in languages ' + lang + ' and en for key ' + translationKey + ' in dictionary ' + dictionaryKey + ', defaulting to language ' + key);
+ break;
+ }
+ if (!html) {
+ codeq.log.warn('There is no translation in any language for key ' + translationKey + ' in dictionary ' + dictionaryKey + ', leaving empty');
+ return;
+ }
+ }
+ }
+ jqElt.html(html);
+ },
+ translateDocument = function (lang) {
+ $('.translatable').each(function () {
+ translateElement($(this), lang);
+ });
+ };
+
+ // Translate the whole document when the user switches the display language
+ codeq.on('langchange', function (args) {
+ translateDocument(args.lang);
+ });
+
+ // ================================================================================
+ // The module API
+ // ================================================================================
+
+ codeq.tr = {
+ 'registerDictionary': function (name, dict) {
+ dicts[name] = dict;
+ },
+
+ 'translateDom': function (jqTopElt) {
+ var lang = codeq.getLang();
+ jqTopElt.find('.translatable').each(function () {
+ translateElement($(this), lang);
+ });
+ }
+ };
+})(); \ No newline at end of file