From ea0c8e8a9fdc1cbcd33ab344c02df490f13f840e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Smodi=C5=A1?= Date: Sat, 26 Sep 2015 15:23:26 +0200 Subject: Reimplemented translation support: leave the DOM structure intact, upon display language switch only replace language-dependent content. --- js/codeq/core.js | 2 +- js/codeq/problem.js | 275 +++++++++++++++++++++++------------------------- js/codeq/translation.js | 71 +++++++++++++ 3 files changed, 201 insertions(+), 147 deletions(-) create mode 100644 js/codeq/translation.js (limited to 'js') 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('


'); + html.push('
'); + // content: problem directory + html.push(''); + 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('\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('\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 -- cgit v1.2.1