mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-19 15:48:04 +01:00
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:
@@ -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
95
src/i18n.js
Normal 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
|
||||||
|
};
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user