diff --git a/public/js/env.js b/public/js/env.js index 8a6611cd90..bcae78640c 100644 --- a/public/js/env.js +++ b/public/js/env.js @@ -12,9 +12,5 @@ if(window.moment && window.env.language && window.env.language.momentLang && win window.moment.lang(window.env.language.momentLangCode); } -window.env.t = function(stringName, vars){ - var string = window.env.translations[stringName]; - if(!string) return window._.template(window.env.translations.stringNotFound, {string: stringName}); - - return vars === undefined ? string : window._.template(string, vars); -} \ No newline at end of file +window.habitrpgShared.i18n.strings = window.env.translations; +window.env.t = window.habitrpgShared.i18n.t; \ No newline at end of file diff --git a/src/i18n.js b/src/i18n.js new file mode 100644 index 0000000000..9b1cf27998 --- /dev/null +++ b/src/i18n.js @@ -0,0 +1,95 @@ +var fs = require('fs'), + path = require('path'), + _ = require('lodash'), + User = require('./models/user').model, + shared = require('habitrpg-shared'), + translations = {}; + +var loadTranslations = function(locale){ + var files = fs.readdirSync(path.join(__dirname, "/../node_modules/habitrpg-shared/locales/", locale)); + translations[locale] = {}; + _.each(files, function(file){ + _.merge(translations[locale], require(path.join(__dirname, "/../node_modules/habitrpg-shared/locales/", locale, file))); + }); +}; + +// First fetch english so we can merge with missing strings in other languages +loadTranslations('en'); + +fs.readdirSync(path.join(__dirname, "/../node_modules/habitrpg-shared/locales/")).forEach(function(file) { + if(file === 'en' || file === 'README.md') return; + loadTranslations(file); + // Merge missing strings from english + _.defaults(translations[file], translations.en); +}); + +var langCodes = Object.keys(translations); + +var avalaibleLanguages = _.map(langCodes, function(langCode){ + return { + code: langCode, + name: translations[langCode].languageName + } +}); + +// Load MomentJS localization files +var momentLangs = {}; + +// Handle different language codes from MomentJS and /locales +var momentLangsMapping = { + 'en': 'en-gb', + 'no': 'nn' +}; + +var momentLangs = {}; + +_.each(langCodes, function(code){ + var lang = _.find(avalaibleLanguages, {code: code}); + lang.momentLangCode = (momentLangsMapping[code] || code); + try{ + // MomentJS lang files are JS files that has to be executed in the browser so we load them as plain text files + var f = fs.readFileSync(path.join(__dirname, '/../node_modules/moment/lang/' + lang.momentLangCode + '.js'), 'utf8'); + momentLangs[code] = f; + }catch (e){} +}); + +var getUserLanguage = function(req, callback){ + var getFromBrowser = function(){ + var acceptable = _(req.acceptedLanguages).map(function(lang){ + return lang.slice(0, 2); + }).uniq().value(); + var matches = _.intersection(acceptable, langCodes); + return matches.length > 0 ? matches[0] : 'en'; + }; + + if(req.session && req.session.userId){ + User.findOne({_id: req.session.userId}, function(err, user){ + if(err) return callback(err); + if(user && user.preferences.language && translations[user.preferences.language]){ + return callback(null, _.find(avalaibleLanguages, {code: user.preferences.language})); + }else{ + var langCode = getFromBrowser(); + // Because english is usually always avalaible as an acceptable language for the browser, + // if the user visit the page when his own language is not avalaible yet + // he'll have english set in his preferences, which is not good. + //if(user && translations[langCode]){ + //user.preferences.language = langCode; + //user.save(); //callback? + //} + return callback(null, _.find(avalaibleLanguages, {code: langCode})) + } + }); + }else{ + return callback(null, _.find(avalaibleLanguages, {code: getFromBrowser()})); + } +}; + +shared.i18n.translations = translations; + +module.exports = { + translations: translations, + avalaibleLanguages: avalaibleLanguages, + langCodes: langCodes, + getUserLanguage: getUserLanguage, + momentLangs: momentLangs +}; \ No newline at end of file diff --git a/src/middleware.js b/src/middleware.js index bb40be8334..232c0ab355 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -7,6 +7,8 @@ var limiter = require('connect-ratelimit'); var logging = require('./logging'); var domainMiddleware = require('domain-middleware'); var cluster = require('cluster'); +var i18n = require('./i18n.js'); +var shared = require('habitrpg-shared'); module.exports.apiThrottle = function(app) { if (nconf.get('NODE_ENV') !== 'production') return; @@ -139,97 +141,14 @@ var getManifestFiles = function(page){ return code; } -// Translations - -var translations = {}; - -var loadTranslations = function(locale){ - var files = fs.readdirSync(path.join(__dirname, "/../node_modules/habitrpg-shared/locales/", locale)); - translations[locale] = {}; - _.each(files, function(file){ - _.merge(translations[locale], require(path.join(__dirname, "/../node_modules/habitrpg-shared/locales/", locale, file))); - }); -}; - -// First fetch english so we can merge with missing strings in other languages -loadTranslations('en'); - -fs.readdirSync(path.join(__dirname, "/../node_modules/habitrpg-shared/locales/")).forEach(function(file) { - if(file === 'en') return; - loadTranslations(file); - // Merge missing strings from english - _.defaults(translations[file], translations.en); -}); - -var langCodes = Object.keys(translations); - -var avalaibleLanguages = _.map(langCodes, function(langCode){ - return { - code: langCode, - name: translations[langCode].languageName - } -}); - -// Load MomentJS localization files -var momentLangs = {}; - -// Handle different language codes from MomentJS and /locales -var momentLangsMapping = { - 'en': 'en-gb', - 'no': 'nn' -}; - -var momentLangs = {}; - -_.each(langCodes, function(code){ - var lang = _.find(avalaibleLanguages, {code: code}); - lang.momentLangCode = (momentLangsMapping[code] || code); - try{ - // MomentJS lang files are JS files that has to be executed in the browser so we load them as plain text files - var f = fs.readFileSync(path.join(__dirname, '/../node_modules/moment/lang/' + lang.momentLangCode + '.js'), 'utf8'); - momentLangs[code] = f; - }catch (e){} -}); - -var getUserLanguage = function(req, callback){ - var getFromBrowser = function(){ - var acceptable = _(req.acceptedLanguages).map(function(lang){ - return lang.slice(0, 2); - }).uniq().value(); - var matches = _.intersection(acceptable, langCodes); - return matches.length > 0 ? matches[0] : 'en'; - }; - - if(req.session && req.session.userId){ - User.findOne({_id: req.session.userId}, function(err, user){ - if(err) return callback(err); - if(user && user.preferences.language && translations[user.preferences.language]){ - return callback(null, _.find(avalaibleLanguages, {code: user.preferences.language})); - }else{ - var langCode = getFromBrowser(); - // Because english is usually always avalaible as an acceptable language for the browser, - // if the user visit the page when his own language is not avalaible yet - // he'll have english set in his preferences, which is not good. - //if(user && translations[langCode]){ - //user.preferences.language = langCode; - //user.save(); //callback? - //} - return callback(null, _.find(avalaibleLanguages, {code: langCode})) - } - }); - }else{ - return callback(null, _.find(avalaibleLanguages, {code: getFromBrowser()})); - } -} - module.exports.locals = function(req, res, next) { - getUserLanguage(req, function(err, language){ + i18n.getUserLanguage(req, function(err, language){ if(err) return res.json(500, {err: err}); var isStaticPage = req.url.split('/')[1] === 'static'; // If url contains '/static/' // Load moment.js language file only when not on static pages - language.momentLang = ((!isStaticPage && momentLangs[language.code])|| undefined); + language.momentLang = ((!isStaticPage && i18n.momentLangs[language.code])|| undefined); res.locals.habitrpg = { NODE_ENV: nconf.get('NODE_ENV'), @@ -239,15 +158,14 @@ module.exports.locals = function(req, res, next) { STRIPE_PUB_KEY: nconf.get('STRIPE_PUB_KEY'), getManifestFiles: getManifestFiles, getBuildUrl: getBuildUrl, - avalaibleLanguages: avalaibleLanguages, + avalaibleLanguages: i18n.avalaibleLanguages, language: language, isStaticPage: isStaticPage, - translations: translations[language.code], - t: function(stringName, vars){ - var string = translations[language.code][stringName]; - if(!string) return _.template(translations[language.code].stringNotFound, {string: stringName}); - - return vars === undefined ? string : _.template(string, vars); + translations: i18n.translations[language.code], + t: function(){ // stringName and vars are the allowed parameters + var args = Array.prototype.slice.call(arguments, 0); + args.push(language.code); + return shared.i18n.t.apply(null, args); }, siteVersion: siteVersion } diff --git a/src/server.js b/src/server.js index 205e379d6b..c494641a5f 100644 --- a/src/server.js +++ b/src/server.js @@ -27,6 +27,9 @@ if (cluster.isMaster && (isDev || isProd)) { var swagger = require("swagger-node-express"); var autoinc = require('mongoose-id-autoinc'); + // Setup translations + var i18n = require('./i18n'); + var middleware = require('./middleware'); var TWO_WEEKS = 1000 * 60 * 60 * 24 * 14; diff --git a/test/spec/rootCtrlSpec.js b/test/spec/rootCtrlSpec.js index 20bb41857f..52d31a1375 100644 --- a/test/spec/rootCtrlSpec.js +++ b/test/spec/rootCtrlSpec.js @@ -28,30 +28,4 @@ describe('Root Controller', function() { expect(scope.contribText({level: 8, text: 'Blacksmith'}, {npc: 'NPC'})).to.eql('NPC'); }); - describe('i18n', function(){ - var t = env.t; - beforeEach(function(){ - env.translations = { - "text1": 'translatedText1', - "text2": '<%= number %> eggs', - "stringNotFound" : "String '<%= string %>' not found.", - }; - }); - - it('translates strings', function(){ - expect(t('')).to.eql("String '' not found."); - expect(t('text1')).to.eql('translatedText1'); - expect(t(' text1 ')).to.eql("String ' text1 ' not found."); - expect(t('text3')).to.eql("String 'text3' not found."); - }); - - it('interpolates strings', function(){ - expect(t('text2', {number: 2})).to.eql('2 eggs'); - expect(t('text2', {number: 'ten'})).to.eql('ten eggs'); - expect(t('text2', {n: 10, number: 'ten'})).to.eql('ten eggs'); - expect(t('text2')).to.eql('<%= number %> eggs'); - }); - }); - - }); \ No newline at end of file