diff --git a/public/language/en_GB/global.json b/public/language/en_GB/global.json index a1f23f3193..c935f1c5d8 100644 --- a/public/language/en_GB/global.json +++ b/public/language/en_GB/global.json @@ -69,5 +69,6 @@ "invisible": "Invisible", "offline": "Offline", - "privacy": "Privacy" + "privacy": "Privacy", + "language": "Language" } diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index d190dbe8fb..885e1bcb6c 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -178,7 +178,7 @@ var ajaxify = ajaxify || {}; } data.relative_path = RELATIVE_PATH; - + templates.parse(tpl_url, data, function(template) { translator.translate(template, function(translatedTemplate) { $('#content').html(translatedTemplate); diff --git a/public/src/forum/accountsettings.js b/public/src/forum/accountsettings.js index fcb4c52272..bb18cacb2c 100644 --- a/public/src/forum/accountsettings.js +++ b/public/src/forum/accountsettings.js @@ -7,16 +7,21 @@ define(['forum/accountheader'], function(header) { $('#submitBtn').on('click', function() { var settings = {}; - $('.account input, .account textarea').each(function(id, input) { + $('.account').find('input, textarea, select').each(function(id, input) { input = $(input); + var setting = input.attr('data-property'); + if (input.is('select')) { + settings[setting] = input.val(); + return; + } switch (input.attr('type')) { case 'text' : case 'textarea' : - settings[input.attr('data-property')] = input.val(); + settings[setting] = input.val(); break; case 'checkbox' : - settings[input.attr('data-property')] = input.is(':checked') ? 1 : 0; + settings[setting] = input.is(':checked') ? 1 : 0; break; } }); @@ -32,9 +37,16 @@ define(['forum/accountheader'], function(header) { }); socket.emit('user.getSettings', function(err, settings) { - for (var setting in settings) { - if (settings.hasOwnProperty(setting)) { - var input = $('.account input[data-property="' + setting + '"]'); + var inputs = $('.account').find('input, textarea, select'); + + inputs.each(function(index, input) { + input = $(input); + var setting = input.attr('data-property'); + if (setting) { + if (input.is('select')) { + input.val(settings[setting]); + return; + } switch (input.attr('type')) { case 'text' : @@ -46,7 +58,7 @@ define(['forum/accountheader'], function(header) { break; } } - } + }); }); }; diff --git a/public/src/translator.js b/public/src/translator.js index f795131f38..edfdf6730e 100644 --- a/public/src/translator.js +++ b/public/src/translator.js @@ -4,17 +4,15 @@ var translator = {}, - files = { - loaded: {}, - loading: {}, - callbacks: {} // could be combined with "loading" in future. - }; + languages = {}; module.exports = translator; // Use this in plugins to add your own translation files. - translator.addTranslation = function(filename, translations) { - files.loaded[filename] = translations; + translator.addTranslation = function(language, filename, translations) { + languages[language] = languages[language] || {}; + languages[language].loaded = languages[language].loaded || {}; + languages[language].loaded[filename] = translations; }; translator.getLanguage = function() { @@ -67,11 +65,21 @@ } }; - translator.translate = function (data, callback) { + translator.translate = function (data, language, callback) { if (!data) { return callback(data); } + if (typeof language === 'function') { + callback = language; + if ('undefined' !== typeof window && config) { + language = config.defaultLang || 'en_GB'; + } else { + var meta = require('../../src/meta'); + language = meta.config.defaultLang || 'en_GB'; + } + } + function insertLanguage(text, key, value, variables) { if (value) { for (var i = 1, ii = variables.length; i < ii; i++) { @@ -109,12 +117,12 @@ var languageFile = parsedKey[0]; parsedKey = ('' + parsedKey[1]).split(',')[0]; - if (files.loaded[languageFile]) { - data = insertLanguage(data, key, files.loaded[languageFile][parsedKey], variables); + if (isLanguageFileLoaded(language, languageFile)) { + data = insertLanguage(data, key, languages[language].loaded[languageFile][parsedKey], variables); } else { loading++; (function (languageKey, parsedKey, languageFile, variables) { - translator.load(languageFile, function (languageData) { + translator.load(language, languageFile, function (languageData) { data = insertLanguage(data, languageKey, languageData[parsedKey], variables); loading--; checkComplete(); @@ -132,61 +140,73 @@ } }; - translator.clearLoadedFiles = function() { - files.loaded = {}; - files.loading = {}; - }; - - translator.load = function (filename, callback) { + translator.load = function (language, filename, callback) { - if (files.loaded[filename] && !files.loading[filename]) { + if (isLanguageFileLoaded(language, filename)) { if (callback) { - callback(files.loaded[filename]); + callback(languages[language].loaded[filename]); } - } else if (files.loading[filename]) { + } else if (isLanguageFileLoading(language, filename)) { if (callback) { - files.callbacks[filename] = files.callbacks[filename] || []; - files.callbacks[filename].push(callback); + addLanguageFileCallback(language, filename, callback); } } else { - files.loading[filename] = true; + languages[language] = languages[language] || {loading: {}, loaded: {}, callbacks: []}; + + languages[language].loading[filename] = true; + + load(language, filename, function(translations) { - load(filename, function(language) { - files.loaded[filename] = language; + languages[language].loaded[filename] = translations; if (callback) { - callback(language); + callback(translations); } - while (files.callbacks[filename] && files.callbacks[filename].length) { - files.callbacks[filename].pop()(language); + while (languages[language].callbacks[filename] && languages[language].callbacks[filename].length) { + languages[language].callbacks[filename].pop()(translations); } - files.loading[filename] = false; + languages[language].loading[filename] = false; }); } }; - function load(filename, callback) { + function isLanguageFileLoaded(language, filename) { + var languageObj = languages[language]; + return languageObj && languageObj.loaded && languageObj.loaded[filename] && !languageObj.loading[filename]; + } + + function isLanguageFileLoading(language, filename) { + return languages[language] && languages[language].loading && languages[language].loading[filename]; + } + + function addLanguageFileCallback(language, filename, callback) { + languages[language].callbacks = languages[language].callbacks || {}; + + languages[language].callbacks[filename] = languages[language].callbacks[filename] || []; + languages[language].callbacks[filename].push(callback); + } + + function load(language, filename, callback) { if ('undefined' !== typeof window) { - loadClient(filename, callback); + loadClient(language, filename, callback); } else { - loadServer(filename, callback); + loadServer(language, filename, callback); } } - function loadClient(filename, callback) { + function loadClient(language, filename, callback) { var timestamp = new Date().getTime(); - $.getJSON(config.relative_path + '/language/' + config.defaultLang + '/' + filename + '.json?v=' + timestamp, callback); + $.getJSON(config.relative_path + '/language/' + language + '/' + filename + '.json?v=' + timestamp, callback); } - function loadServer(filename, callback) { + function loadServer(language, filename, callback) { var fs = require('fs'), path = require('path'), winston = require('winston'), - meta = require('../../src/meta'), - language = meta.config.defaultLang || 'en_GB'; + meta = require('../../src/meta'); if (!fs.existsSync(path.join(__dirname, '../language', language))) { winston.warn('[translator] Language \'' + meta.config.defaultLang + '\' not found. Defaulting to \'en_GB\''); diff --git a/src/controllers/accounts.js b/src/controllers/accounts.js index 518c170445..3be58c7b62 100644 --- a/src/controllers/accounts.js +++ b/src/controllers/accounts.js @@ -15,6 +15,7 @@ var fs = require('fs'), utils = require('./../../public/src/utils'), meta = require('./../meta'), plugins = require('./../plugins'), + languages = require('./../languages'), image = require('./../image'), file = require('./../file'); @@ -331,22 +332,29 @@ accountsController.accountSettings = function(req, res, next) { return next(err); } - user.getUserFields(uid, ['username', 'userslug'], function(err, userData) { + async.parallel({ + user: function(next) { + user.getUserFields(uid, ['username', 'userslug'], next); + }, + languages: function(next) { + languages.list(next); + } + }, function(err, results) { if (err) { return next(err); } - if(!userData) { + if(!results.user) { return userNotFound(); } - userData.yourid = req.user.uid; - userData.theirid = uid; - userData.settings = settings; - res.render('accountsettings', userData); + results.user.yourid = req.user.uid; + results.user.theirid = uid; + results.user.settings = settings; + + res.render('accountsettings', results); }); }); - }); }; diff --git a/src/controllers/api.js b/src/controllers/api.js index d136bb5e84..6f1f7042a6 100644 --- a/src/controllers/api.js +++ b/src/controllers/api.js @@ -58,6 +58,7 @@ apiController.getConfig = function(req, res, next) { config.topicsPerPage = settings.topicsPerPage; config.postsPerPage = settings.postsPerPage; config.notificationSounds = settings.notificationSounds; + config.defaultLang = settings.language || config.defaultLang; if (res.locals.isAPI) { res.json(200, config); diff --git a/src/meta.js b/src/meta.js index 63dfb6629e..c27e318664 100644 --- a/src/meta.js +++ b/src/meta.js @@ -63,11 +63,6 @@ var fs = require('fs'), callback(err, res); } - - // this might be a good spot to add a hook - if (field === 'defaultLang') { - translator.clearLoadedFiles(); - } }); }, setOnEmpty: function (field, value, callback) { @@ -173,10 +168,8 @@ var fs = require('fs'), isTopic: /^topic\/\d+\/?/, isUserPage: /^user\/[^\/]+(\/[\w]+)?/ }, - build: function (urlFragment, callback) { - var user = require('./user'); - - Meta.title.parseFragment(decodeURIComponent(urlFragment), function(err, title) { + build: function (urlFragment, language, callback) { + Meta.title.parseFragment(decodeURIComponent(urlFragment), language, function(err, title) { if (err) { title = Meta.config.browserTitle || 'NodeBB'; } else { @@ -186,14 +179,14 @@ var fs = require('fs'), callback(null, title); }); }, - parseFragment: function (urlFragment, callback) { + parseFragment: function (urlFragment, language, callback) { var translated = ['', 'recent', 'unread', 'users', 'notifications']; if (translated.indexOf(urlFragment) !== -1) { if (!urlFragment.length) { urlFragment = 'home'; } - translator.translate('[[pages:' + urlFragment + ']]', function(translated) { + translator.translate('[[pages:' + urlFragment + ']]', language, function(translated) { callback(null, translated); }); } else if (this.tests.isCategory.test(urlFragment)) { @@ -215,7 +208,7 @@ var fs = require('fs'), User.getUsernameByUserslug(userslug, function(err, username) { if (subpage) { - translator.translate('[[pages:user.' + subpage + ', ' + username + ']]', function(translated) { + translator.translate('[[pages:user.' + subpage + ', ' + username + ']]', language, function(translated) { callback(null, translated); }); } else { diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index f56cc9404d..948806572e 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -143,30 +143,31 @@ middleware.checkAccountPermissions = function(req, res, next) { }; middleware.buildHeader = function(req, res, next) { - async.parallel([ - function(next) { - res.locals.renderHeader = true; - next(); - }, - function(next) { - controllers.api.getConfig(req, res, function(err, config) { - res.locals.config = config; - next(err); - }); + res.locals.renderHeader = true; + async.parallel({ + config: function(next) { + controllers.api.getConfig(req, res, next); }, - function(next) { - // consider caching this, since no user specific information is loaded here - app.render('footer', {}, function(err, template) { - translator.translate(template, function(parsedTemplate) { - res.locals.footer = parsedTemplate; - next(err); - }); - }); + footer: function(next) { + app.render('footer', {}, next); + } + }, function(err, results) { + if (err) { + return next(err); } - ], next); + + res.locals.config = results.config; + + translator.translate(results.footer, results.config.defaultLang, function(parsedTemplate) { + res.locals.footer = parsedTemplate; + next(); + }); + }); }; middleware.renderHeader = function(req, res, callback) { + var uid = req.user ? parseInt(req.user.uid, 10) : 0; + var custom_header = { 'navigation': [] }; @@ -218,8 +219,6 @@ middleware.renderHeader = function(req, res, callback) { } } - var uid = '0'; - templateValues.metaTags = defaultMetaTags.concat(res.locals.metaTags || []).map(function(tag) { if(!tag || typeof tag.content !== 'string') { winston.warn('Invalid meta tag. ', tag); @@ -239,9 +238,6 @@ middleware.renderHeader = function(req, res, callback) { href: nconf.get('relative_path') + '/favicon.ico' }); - if(req.user && req.user.uid) { - uid = req.user.uid; - } templateValues.useCustomCSS = false; if (meta.config.useCustomCSS === '1') { @@ -249,34 +245,30 @@ middleware.renderHeader = function(req, res, callback) { templateValues.customCSS = meta.config.customCSS; } - async.parallel([ - function(next) { - translator.translate('[[pages:' + path.basename(req.url) + ']]', function(translated) { - var metaTitle = templateValues.metaTags.filter(function(tag) { - return tag.name === 'title'; - }); - - if (translated) { - templateValues.browserTitle = translated; - } else if (metaTitle.length > 0 && metaTitle[0].content) { - templateValues.browserTitle = metaTitle[0].content; - } else { - templateValues.browserTitle = meta.config.browserTitle || 'NodeBB'; - } - - next(); - }); + async.parallel({ + title: function(next) { + if (uid) { + user.getSettings(uid, function(err, settings) { + if (err) { + return next(err); + } + meta.title.build(req.url.slice(1), settings.language, next); + }); + } else { + meta.title.build(req.url.slice(1), meta.config.defaultLang, next); + } }, - function(next) { - user.isAdministrator(uid, function(err, isAdmin) { - templateValues.isAdmin = isAdmin || false; - next(); - }); + isAdmin: function(next) { + user.isAdministrator(uid, next); } - ], function() { - app.render('header', templateValues, function(err, template) { - callback(null, template); - }); + }, function(err, results) { + if (err) { + return next(err); + } + templateValues.browserTitle = results.title; + templateValues.isAdmin = results.isAdmin || false; + + app.render('header', templateValues, callback); }); }); }; @@ -322,7 +314,7 @@ middleware.processRender = function(req, res, next) { middleware.renderHeader(req, res, function(err, template) { str = template + str; - translator.translate(str, function(translated) { + translator.translate(str, res.locals.config.defaultLang, function(translated) { fn(err, translated); }); }); diff --git a/src/socket.io/meta.js b/src/socket.io/meta.js index e2733e33a7..e583b9ad21 100644 --- a/src/socket.io/meta.js +++ b/src/socket.io/meta.js @@ -32,7 +32,16 @@ SocketMeta.reconnected = function(socket) { }; SocketMeta.buildTitle = function(socket, text, callback) { - meta.title.build(text, callback); + if (socket.uid) { + user.getSettings(socket.uid, function(err, settings) { + if (err) { + return callback(err); + } + meta.title.build(text, settings.language, callback); + }); + } else { + meta.title.build(text, meta.config.defaultLang, callback); + } }; SocketMeta.updateHeader = function(socket, data, callback) { diff --git a/src/user/settings.js b/src/user/settings.js index 7e9665d9f0..35ca90717d 100644 --- a/src/user/settings.js +++ b/src/user/settings.js @@ -29,6 +29,7 @@ module.exports = function(User) { settings.topicsPerPage = settings.topicsPerPage ? parseInt(settings.topicsPerPage, 10) : parseInt(meta.config.topicsPerPage, 10) || 20; settings.postsPerPage = settings.postsPerPage ? parseInt(settings.postsPerPage, 10) : parseInt(meta.config.postsPerPage, 10) || 10; settings.notificationSounds = settings.notificationSounds ? parseInt(settings.notificationSounds, 10) === 1 : true; + settings.language = settings.language || meta.config.defaultLang || 'en_GB'; callback(null, settings); }); }); @@ -47,7 +48,8 @@ module.exports = function(User) { usePagination: data.usePagination, topicsPerPage: data.topicsPerPage, postsPerPage: data.postsPerPage, - notificationSounds: data.notificationSounds + notificationSounds: data.notificationSounds, + language: data.language || meta.config.defaultLang }, callback); }; }; \ No newline at end of file