port i18n lib to es6 and extract middleware

This commit is contained in:
Matteo Pagliazzi
2015-11-12 15:28:43 +01:00
parent 40fab075e2
commit a07d4dad12
2 changed files with 183 additions and 0 deletions
+98
View File
@@ -0,0 +1,98 @@
import fs from 'fs';
import path from 'path';
import _ from 'lodash';
import shared from '../../../../common';
const localePath = path.join(__dirname, '/../../../../common/locales/');
// Store translations
export let translations = {};
// Store MomentJS localization files
export let momentLangs = {};
// Handle differencies in language codes between MomentJS and /locales
let momentLangsMapping = {
en: 'en-gb',
en_GB: 'en-gb', // eslint-disable-line camelcase
no: 'nn',
zh: 'zh-cn',
es_419: 'es', // eslint-disable-line camelcase
};
function _loadTranslations (locale) {
let files = fs.readdirSync(path.join(localePath, locale));
translations[locale] = {};
files.forEach((file) => {
if (path.extname(file) !== '.json') return;
// We use require to load and parse a JSON file
_.merge(translations[locale], require(path.join(localePath, locale, file))); // eslint-disable-line global-require
});
}
// First fetch English strings so we can merge them with missing strings in other languages
_loadTranslations('en');
// Then load all other languages
fs.readdirSync(localePath).forEach((file) => {
if (file === 'en' || fs.statSync(path.join(localePath, file)).isDirectory() === false) return;
_loadTranslations(file);
// Merge missing strings from english
_.defaults(translations[file], translations.en);
});
// Add translations to shared
shared.i18n.translations = translations;
export let langCodes = Object.keys(translations);
export let avalaibleLanguages = langCodes.map((langCode) => {
return {
code: langCode,
name: translations[langCode].languageName,
};
});
langCodes.forEach((code) => {
let lang = _.find(avalaibleLanguages, {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
// We wrap everything in a try catch because the file might not exist
let f = fs.readFileSync(path.join(__dirname, `/../../../node_modules/moment/locale/${lang.momentLangCode}.js`), 'utf8');
momentLangs[code] = f;
} catch (e) { // eslint-disable-lint no-empty
// TODO implement some type of error loggin?
// The catch block is mandatory so can't be removed
}
});
// Remove en_GB from langCodes checked by browser to avoid it being
// used in place of plain original 'en' (it's an optional language that can be enabled only in setting)
export let defaultLangCodes = _.without(langCodes, 'en_GB');
// A map of languages that have different versions and the relative versions
export let multipleVersionsLanguages = {
es: ['es-419', 'es-mx', 'es-gt', 'es-cr', 'es-pa', 'es-do', 'es-ve', 'es-co', 'es-pe',
'es-ar', 'es-ec', 'es-cl', 'es-uy', 'es-py', 'es-bo', 'es-sv', 'es-hn',
'es-ni', 'es-pr'],
zh: ['zh-tw'],
};
// Export en strings only, temporary solution for mobile
// This is copied from middlewares/locals#t()
// TODO review if this can be removed since the old mobile app is no longer active
// stringName and vars are the allowed parameters
export function enTranslations (...args) {
let language = _.find(avalaibleLanguages, {code: 'en'});
// language.momentLang = ((!isStaticPage && i18n.momentLangs[language.code]) || undefined);
args.push(language.code);
return shared.i18n.t(...args);
}
@@ -0,0 +1,85 @@
import { model as User } from '../../models/user';
import accepts from 'accepts';
import _ from 'lodash';
import {
translations,
defaultLangCodes,
multipleVersionsLanguages,
} from '../../libs/api-v3/i18n';
function _getFromBrowser (req) {
let acceptedLanguages = accepts(req).languages();
let acceptable = _(acceptedLanguages).map((lang) => {
return lang.slice(0, 2);
}).uniq().value();
let matches = _.intersection(acceptable, defaultLangCodes);
let iAcceptedCompleteLang = matches.length > 0 ? multipleVersionsLanguages.indexOf(matches[0].toLowerCase()) : -1;
if (iAcceptedCompleteLang !== -1) {
let acceptedCompleteLang = _.find(acceptedLanguages, (accepted) => {
return accepted.slice(0, 2) === multipleVersionsLanguages[iAcceptedCompleteLang];
});
if (acceptedCompleteLang) {
acceptedCompleteLang = acceptedCompleteLang.toLowerCase();
} else {
return 'en';
}
if (matches[0] === 'es') {
// In case of a Latin American version of Spanish use 'es_419'
return multipleVersionsLanguages.es.indexOf(acceptedCompleteLang !== -1) ? 'es_419' : 'es';
} else if (matches[0] === 'zh') {
let iChinese = multipleVersionsLanguages.zh.indexOf(acceptedCompleteLang.toLowerCase());
return iChinese !== -1 ? multipleVersionsLanguages.zh[iChinese] : 'zh';
} else {
return 'en';
}
} else if (matches.length > 0) {
return matches[0].toLowerCase();
} else {
return 'en';
}
}
function _getFromUser (user, req) {
let lang;
if (user && user.preferences.language && translations[user.preferences.language]) {
lang = user.preferences.language;
} else {
let preferred = _getFromBrowser(req);
lang = translations[preferred] ? preferred : 'en';
}
return lang;
}
export default function getUserLanguage (req, res, next) {
if (req.query.lang) { // In case the language is specified in the request url, use it
req.language = translations[req.query.lang] ? req.query.lang : 'en';
return next();
} else if (req.locals && req.locals.user) { // If the request is authenticated, use the user's preferred language
req.language = _getFromUser(req.locals.user, req);
return next();
} else if (req.session && req.session.userId) { // Same thing if the user has a valid session
User
.findOne({
_id: req.session.userId,
}, 'preferences.language')
.exec()
.then((user) => {
req.language = _getFromUser(user, req);
return next();
})
.catch(next);
} else { // Otherwise get from browser
req.language = _getFromUser(null, req);
return next();
}
}