From 413bb4736cabb8df203d17fe503a098d719b8d67 Mon Sep 17 00:00:00 2001
From: Peter Jaszkowiak
Date: Thu, 20 Apr 2017 00:16:06 -0600
Subject: [PATCH 1/5] Save language metadata on build to avoid readdirs later
---
src/languages.js | 81 ++++++++++++++++++++++++++-----------------
src/meta/languages.js | 19 ++++++++++
2 files changed, 69 insertions(+), 31 deletions(-)
diff --git a/src/languages.js b/src/languages.js
index c4c3d5ae0e..520ae8bba1 100644
--- a/src/languages.js
+++ b/src/languages.js
@@ -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) {
+ var parsed;
+ try {
+ parsed = JSON.parse(buffer.toString());
+ } catch (e) {
+ return callback(e);
+ }
+
+ var langs = parsed.languages;
+ codeCache = langs;
+ callback(null, langs);
+ });
+};
+
+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);
}
-
- if (!stat.isDirectory()) {
- return next();
+ try {
+ var lang = JSON.parse(buffer.toString());
+ next(null, lang);
+ } catch (e) {
+ next(e);
}
-
- 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) {
+ }, function (err, languages) {
if (err) {
return callback(err);
}
- // Sort alphabetically
- languages = languages.sort(function (a, b) {
- return a.code > b.code ? 1 : -1;
+
+ // filter out invalid ones
+ languages = languages.filter(function (lang) {
+ return lang.code && lang.name && lang.dir;
});
- callback(err, languages);
+ listCache = languages;
+ callback(null, languages);
});
});
};
-
-module.exports = Languages;
diff --git a/src/meta/languages.js b/src/meta/languages.js
index b420445f16..395711cba7 100644
--- a/src/meta/languages.js
+++ b/src/meta/languages.js
@@ -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
From 2476221b792da3ec926fdf35b24acc1c6d358709 Mon Sep 17 00:00:00 2001
From: Peter Jaszkowiak
Date: Thu, 20 Apr 2017 00:16:10 -0600
Subject: [PATCH 2/5] Close #3462, automatically detect user language based on
browser accepts header
---
src/webserver.js | 48 +++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 41 insertions(+), 7 deletions(-)
diff --git a/src/webserver.js b/src/webserver.js
index 94684d8e24..3911846b67 100644
--- a/src/webserver.js
+++ b/src/webserver.js
@@ -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) {
+ 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);
From c7929ec7d83383c99a4847a156b0e1bfab053c33 Mon Sep 17 00:00:00 2001
From: Peter Jaszkowiak
Date: Fri, 21 Apr 2017 22:10:25 -0600
Subject: [PATCH 3/5] Add option for disabling language autodetection
---
install/data/defaults.json | 3 ++-
public/language/en-GB/admin/general/languages.json | 3 ++-
src/controllers/admin/languages.js | 1 +
src/views/admin/general/languages.tpl | 11 +++++++++++
src/webserver.js | 2 +-
5 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/install/data/defaults.json b/install/data/defaults.json
index c471db6b89..86e63882cd 100644
--- a/install/data/defaults.json
+++ b/install/data/defaults.json
@@ -35,5 +35,6 @@
"allowPrivateGroups": 1,
"unreadCutoff": 2,
"bookmarkThreshold": 5,
- "topicsPerList": 20
+ "topicsPerList": 20,
+ "autoDetectLang": 1
}
diff --git a/public/language/en-GB/admin/general/languages.json b/public/language/en-GB/admin/general/languages.json
index da45cade2c..bdd57849b3 100644
--- a/public/language/en-GB/admin/general/languages.json
+++ b/public/language/en-GB/admin/general/languages.json
@@ -1,5 +1,6 @@
{
"language-settings": "Language Settings",
"description": "The default language determines the language settings for all users who are visiting your forum.
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"
}
\ No newline at end of file
diff --git a/src/controllers/admin/languages.js b/src/controllers/admin/languages.js
index 0ac4e98e99..e2d848ddae 100644
--- a/src/controllers/admin/languages.js
+++ b/src/controllers/admin/languages.js
@@ -18,6 +18,7 @@ languagesController.get = function (req, res, next) {
res.render('admin/general/languages', {
languages: languages,
+ autoDetectLang: parseInt(meta.config.autoDetectLang, 10) === 1,
});
});
};
diff --git a/src/views/admin/general/languages.tpl b/src/views/admin/general/languages.tpl
index 310d1a366d..747c5d43af 100644
--- a/src/views/admin/general/languages.tpl
+++ b/src/views/admin/general/languages.tpl
@@ -16,6 +16,17 @@
+
+
diff --git a/src/webserver.js b/src/webserver.js
index 3911846b67..412378883c 100644
--- a/src/webserver.js
+++ b/src/webserver.js
@@ -206,7 +206,7 @@ function setupAutoLocale(app, callback) {
});
app.use(function (req, res, next) {
- if (parseInt(req.uid, 10) > 0) {
+ if (parseInt(req.uid, 10) > 0 || parseInt(meta.config.autoDetectLang, 10) !== 1) {
return next();
}
From 6432c02ab80f0832539c02cbc1843a6d755a0a01 Mon Sep 17 00:00:00 2001
From: Peter Jaszkowiak
Date: Fri, 21 Apr 2017 23:54:59 -0600
Subject: [PATCH 4/5] Add tests
---
test/locale-detect.js | 41 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
create mode 100644 test/locale-detect.js
diff --git a/test/locale-detect.js b/test/locale-detect.js
new file mode 100644
index 0000000000..84ea5af59c
--- /dev/null
+++ b/test/locale-detect.js
@@ -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();
+ });
+ });
+ });
+});
From 64bf542d1705eb3cb78e6a9a573ab8eb2466d1a0 Mon Sep 17 00:00:00 2001
From: Peter Jaszkowiak
Date: Fri, 21 Apr 2017 23:55:58 -0600
Subject: [PATCH 5/5] Fix HTML `lang` attribute using the `defaultLang`
Themes need `lang="{function.localeToHTML, userLang, defaultLang}"` in
their header.tpl file
---
public/src/modules/helpers.js | 3 ++-
src/middleware/header.js | 1 +
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/public/src/modules/helpers.js b/public/src/modules/helpers.js
index 3f02a86758..e1cf620607 100644
--- a/public/src/modules/helpers.js
+++ b/public/src/modules/helpers.js
@@ -176,7 +176,8 @@
}).join('');
};
- helpers.localeToHTML = function (locale) {
+ helpers.localeToHTML = function (locale, fallback) {
+ locale = locale || fallback || 'en-GB';
return locale.replace('_', '-');
};
diff --git a/src/middleware/header.js b/src/middleware/header.js
index 70c0755def..0eb9cc9a1f 100644
--- a/src/middleware/header.js
+++ b/src/middleware/header.js
@@ -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;