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.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);
|
||||
}
|
||||
window.habitrpgShared.i18n.strings = window.env.translations;
|
||||
window.env.t = window.habitrpgShared.i18n.t;
|
||||
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 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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user