improve(i18n): move translations to their own module, use i18n from habitrpg-shared, @djuretic i have remove the tests because the code is now handled in habitrpg-shared, we may add them back there

This commit is contained in:
Matteo Pagliazzi
2014-03-05 16:43:00 +01:00
parent 4990a6d0e1
commit 8ec69b4f83
5 changed files with 110 additions and 124 deletions

View File

@@ -12,9 +12,5 @@ if(window.moment && window.env.language && window.env.language.momentLang && win
window.moment.lang(window.env.language.momentLangCode); window.moment.lang(window.env.language.momentLangCode);
} }
window.env.t = function(stringName, vars){ window.habitrpgShared.i18n.strings = window.env.translations;
var string = window.env.translations[stringName]; window.env.t = window.habitrpgShared.i18n.t;
if(!string) return window._.template(window.env.translations.stringNotFound, {string: stringName});
return vars === undefined ? string : window._.template(string, vars);
}

95
src/i18n.js Normal file
View File

@@ -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
};

View File

@@ -7,6 +7,8 @@ var limiter = require('connect-ratelimit');
var logging = require('./logging'); var logging = require('./logging');
var domainMiddleware = require('domain-middleware'); var domainMiddleware = require('domain-middleware');
var cluster = require('cluster'); var cluster = require('cluster');
var i18n = require('./i18n.js');
var shared = require('habitrpg-shared');
module.exports.apiThrottle = function(app) { module.exports.apiThrottle = function(app) {
if (nconf.get('NODE_ENV') !== 'production') return; if (nconf.get('NODE_ENV') !== 'production') return;
@@ -139,97 +141,14 @@ var getManifestFiles = function(page){
return code; 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) { 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}); if(err) return res.json(500, {err: err});
var isStaticPage = req.url.split('/')[1] === 'static'; // If url contains '/static/' var isStaticPage = req.url.split('/')[1] === 'static'; // If url contains '/static/'
// Load moment.js language file only when not on static pages // 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 = { res.locals.habitrpg = {
NODE_ENV: nconf.get('NODE_ENV'), 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'), STRIPE_PUB_KEY: nconf.get('STRIPE_PUB_KEY'),
getManifestFiles: getManifestFiles, getManifestFiles: getManifestFiles,
getBuildUrl: getBuildUrl, getBuildUrl: getBuildUrl,
avalaibleLanguages: avalaibleLanguages, avalaibleLanguages: i18n.avalaibleLanguages,
language: language, language: language,
isStaticPage: isStaticPage, isStaticPage: isStaticPage,
translations: translations[language.code], translations: i18n.translations[language.code],
t: function(stringName, vars){ t: function(){ // stringName and vars are the allowed parameters
var string = translations[language.code][stringName]; var args = Array.prototype.slice.call(arguments, 0);
if(!string) return _.template(translations[language.code].stringNotFound, {string: stringName}); args.push(language.code);
return shared.i18n.t.apply(null, args);
return vars === undefined ? string : _.template(string, vars);
}, },
siteVersion: siteVersion siteVersion: siteVersion
} }

View File

@@ -27,6 +27,9 @@ if (cluster.isMaster && (isDev || isProd)) {
var swagger = require("swagger-node-express"); var swagger = require("swagger-node-express");
var autoinc = require('mongoose-id-autoinc'); var autoinc = require('mongoose-id-autoinc');
// Setup translations
var i18n = require('./i18n');
var middleware = require('./middleware'); var middleware = require('./middleware');
var TWO_WEEKS = 1000 * 60 * 60 * 24 * 14; var TWO_WEEKS = 1000 * 60 * 60 * 24 * 14;

View File

@@ -28,30 +28,4 @@ describe('Root Controller', function() {
expect(scope.contribText({level: 8, text: 'Blacksmith'}, {npc: 'NPC'})).to.eql('NPC'); 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');
});
});
}); });