mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-03-06 20:41:17 +01:00
Merge pull request #5616 from NodeBB/auto-lang
Automatically detect user language based on browser accepts header
This commit is contained in:
@@ -35,5 +35,6 @@
|
||||
"allowPrivateGroups": 1,
|
||||
"unreadCutoff": 2,
|
||||
"bookmarkThreshold": 5,
|
||||
"topicsPerList": 20
|
||||
"topicsPerList": 20,
|
||||
"autoDetectLang": 1
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"language-settings": "Language Settings",
|
||||
"description": "The default language determines the language settings for all users who are visiting your forum. <br />Individual users can override the default language on their account settings page.",
|
||||
"default-language": "Default Language"
|
||||
"default-language": "Default Language",
|
||||
"auto-detect": "Auto Detect Language Setting for Guests"
|
||||
}
|
||||
@@ -176,7 +176,8 @@
|
||||
}).join('');
|
||||
};
|
||||
|
||||
helpers.localeToHTML = function (locale) {
|
||||
helpers.localeToHTML = function (locale, fallback) {
|
||||
locale = locale || fallback || 'en-GB';
|
||||
return locale.replace('_', '-');
|
||||
};
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ languagesController.get = function (req, res, next) {
|
||||
|
||||
res.render('admin/general/languages', {
|
||||
languages: languages,
|
||||
autoDetectLang: parseInt(meta.config.autoDetectLang, 10) === 1,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ var fs = require('fs');
|
||||
var path = require('path');
|
||||
var async = require('async');
|
||||
|
||||
var Languages = {};
|
||||
var Languages = module.exports;
|
||||
var languagesPath = path.join(__dirname, '../build/public/language');
|
||||
|
||||
Languages.init = function (next) {
|
||||
@@ -27,10 +27,13 @@ Languages.get = function (language, namespace, callback) {
|
||||
});
|
||||
};
|
||||
|
||||
Languages.list = function (callback) {
|
||||
var languages = [];
|
||||
var codeCache = null;
|
||||
Languages.listCodes = function (callback) {
|
||||
if (codeCache && codeCache.length) {
|
||||
return callback(null, codeCache);
|
||||
}
|
||||
|
||||
fs.readdir(languagesPath, function (err, files) {
|
||||
fs.readFile(path.join(languagesPath, 'metadata.json'), function (err, buffer) {
|
||||
if (err && err.code === 'ENOENT') {
|
||||
return callback(null, []);
|
||||
}
|
||||
@@ -38,43 +41,59 @@ Languages.list = function (callback) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
async.each(files, function (folder, next) {
|
||||
fs.stat(path.join(languagesPath, folder), function (err, stat) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
var parsed;
|
||||
try {
|
||||
parsed = JSON.parse(buffer.toString());
|
||||
} catch (e) {
|
||||
return callback(e);
|
||||
}
|
||||
|
||||
if (!stat.isDirectory()) {
|
||||
return next();
|
||||
}
|
||||
|
||||
var configPath = path.join(languagesPath, folder, 'language.json');
|
||||
|
||||
fs.readFile(configPath, function (err, buffer) {
|
||||
if (err && err.code !== 'ENOENT') {
|
||||
return next(err);
|
||||
}
|
||||
if (buffer) {
|
||||
var lang = JSON.parse(buffer.toString());
|
||||
if (lang.name && lang.code && lang.dir) {
|
||||
languages.push(lang);
|
||||
}
|
||||
}
|
||||
next();
|
||||
});
|
||||
});
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
// Sort alphabetically
|
||||
languages = languages.sort(function (a, b) {
|
||||
return a.code > b.code ? 1 : -1;
|
||||
});
|
||||
|
||||
callback(err, languages);
|
||||
});
|
||||
var langs = parsed.languages;
|
||||
codeCache = langs;
|
||||
callback(null, langs);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Languages;
|
||||
var listCache = null;
|
||||
Languages.list = function (callback) {
|
||||
if (listCache && listCache.length) {
|
||||
return callback(null, listCache);
|
||||
}
|
||||
|
||||
Languages.listCodes(function (err, codes) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
async.map(codes, function (folder, next) {
|
||||
var configPath = path.join(languagesPath, folder, 'language.json');
|
||||
|
||||
fs.readFile(configPath, function (err, buffer) {
|
||||
if (err && err.code === 'ENOENT') {
|
||||
return next();
|
||||
}
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
try {
|
||||
var lang = JSON.parse(buffer.toString());
|
||||
next(null, lang);
|
||||
} catch (e) {
|
||||
next(e);
|
||||
}
|
||||
});
|
||||
}, function (err, languages) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
// filter out invalid ones
|
||||
languages = languages.filter(function (lang) {
|
||||
return lang.code && lang.name && lang.dir;
|
||||
});
|
||||
|
||||
listCache = languages;
|
||||
callback(null, languages);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -102,6 +102,25 @@ function getTranslationTree(callback) {
|
||||
});
|
||||
},
|
||||
|
||||
// save a list of languages to `${buildLanguagesPath}/metadata.json`
|
||||
// avoids readdirs later on
|
||||
function (ref, next) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
mkdirp(buildLanguagesPath, next);
|
||||
},
|
||||
function (x, next) {
|
||||
fs.writeFile(path.join(buildLanguagesPath, 'metadata.json'), JSON.stringify({
|
||||
languages: ref.languages.sort(),
|
||||
namespaces: ref.namespaces.sort(),
|
||||
}), next);
|
||||
},
|
||||
function (next) {
|
||||
next(null, ref);
|
||||
},
|
||||
], next);
|
||||
},
|
||||
|
||||
// for each language and namespace combination,
|
||||
// run through core and all plugins to generate
|
||||
// a full translation hash
|
||||
|
||||
@@ -133,6 +133,7 @@ module.exports = function (middleware) {
|
||||
templateValues.customJS = templateValues.useCustomJS ? meta.config.customJS : '';
|
||||
templateValues.maintenanceHeader = parseInt(meta.config.maintenanceMode, 10) === 1 && !results.isAdmin;
|
||||
templateValues.defaultLang = meta.config.defaultLang || 'en-GB';
|
||||
templateValues.userLang = res.locals.config.userLang;
|
||||
templateValues.privateUserInfo = parseInt(meta.config.privateUserInfo, 10) === 1;
|
||||
templateValues.privateTagListing = parseInt(meta.config.privateTagListing, 10) === 1;
|
||||
|
||||
|
||||
@@ -16,6 +16,17 @@
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form class="row">
|
||||
<div class="form-group col-sm-6">
|
||||
<div class="checkbox">
|
||||
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
|
||||
<input class="mdl-switch__input" type="checkbox" data-field="autoDetectLang" <!-- IF autoDetectLang -->checked<!-- ENDIF autoDetectLang -->/>
|
||||
<span class="mdl-switch__label">[[admin/general/languages:auto-detect]]</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -55,13 +55,16 @@ module.exports.listen = function (callback) {
|
||||
callback = callback || function () { };
|
||||
emailer.registerApp(app);
|
||||
|
||||
setupExpressApp(app);
|
||||
|
||||
helpers.register();
|
||||
|
||||
logger.init(app);
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
setupExpressApp(app, next);
|
||||
},
|
||||
function (next) {
|
||||
helpers.register();
|
||||
|
||||
logger.init(app);
|
||||
next();
|
||||
},
|
||||
initializeNodeBB,
|
||||
function (next) {
|
||||
winston.info('NodeBB Ready');
|
||||
@@ -110,7 +113,7 @@ function initializeNodeBB(callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function setupExpressApp(app) {
|
||||
function setupExpressApp(app, callback) {
|
||||
var middleware = require('./middleware');
|
||||
|
||||
var relativePath = nconf.get('relative_path');
|
||||
@@ -155,6 +158,8 @@ function setupExpressApp(app) {
|
||||
var toobusy = require('toobusy-js');
|
||||
toobusy.maxLag(parseInt(meta.config.eventLoopLagThreshold, 10) || 100);
|
||||
toobusy.interval(parseInt(meta.config.eventLoopInterval, 10) || 500);
|
||||
|
||||
setupAutoLocale(app, callback);
|
||||
}
|
||||
|
||||
function setupFavicon(app) {
|
||||
@@ -188,6 +193,35 @@ function setupCookie() {
|
||||
return cookie;
|
||||
}
|
||||
|
||||
function setupAutoLocale(app, callback) {
|
||||
languages.listCodes(function (err, codes) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var defaultLang = meta.config.defaultLang || 'en-GB';
|
||||
|
||||
var langs = [defaultLang].concat(codes).filter(function (el, i, arr) {
|
||||
return arr.indexOf(el) === i;
|
||||
});
|
||||
|
||||
app.use(function (req, res, next) {
|
||||
if (parseInt(req.uid, 10) > 0 || parseInt(meta.config.autoDetectLang, 10) !== 1) {
|
||||
return next();
|
||||
}
|
||||
|
||||
var lang = req.acceptsLanguages(langs);
|
||||
if (!lang) {
|
||||
return next();
|
||||
}
|
||||
req.query.lang = lang;
|
||||
next();
|
||||
});
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
function listen(callback) {
|
||||
callback = callback || function () { };
|
||||
var port = parseInt(nconf.get('port'), 10);
|
||||
|
||||
41
test/locale-detect.js
Normal file
41
test/locale-detect.js
Normal file
@@ -0,0 +1,41 @@
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var nconf = require('nconf');
|
||||
var request = require('request');
|
||||
|
||||
var meta = require('../src/meta');
|
||||
|
||||
describe('Language detection', function () {
|
||||
it('should detect the language for a guest', function (done) {
|
||||
request(nconf.get('url') + '/api/config', {
|
||||
headers: {
|
||||
'Accept-Language': 'de-DE,de;q=0.5',
|
||||
},
|
||||
}, function (err, res, body) {
|
||||
assert.ifError(err);
|
||||
assert.ok(body);
|
||||
|
||||
assert.strictEqual(JSON.parse(body).userLang, 'de');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should do nothing when disabled', function (done) {
|
||||
meta.configs.set('autoDetectLang', 0, function (err) {
|
||||
assert.ifError(err);
|
||||
|
||||
request(nconf.get('url') + '/api/config', {
|
||||
headers: {
|
||||
'Accept-Language': 'de-DE,de;q=0.5',
|
||||
},
|
||||
}, function (err, res, body) {
|
||||
assert.ifError(err);
|
||||
assert.ok(body);
|
||||
|
||||
assert.strictEqual(JSON.parse(body).userLang, 'en-GB');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user