mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 19:15:58 +01:00 
			
		
		
		
	Merge branch 'master' into develop
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -55,3 +55,5 @@ tx.exe | |||||||
|  |  | ||||||
| ##Coverage output | ##Coverage output | ||||||
| coverage | coverage | ||||||
|  |  | ||||||
|  | build | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								Gruntfile.js
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								Gruntfile.js
									
									
									
									
									
								
							| @@ -27,6 +27,8 @@ module.exports = function (grunt) { | |||||||
| 			compiling = 'js'; | 			compiling = 'js'; | ||||||
| 		} else if (target === 'templatesUpdated') { | 		} else if (target === 'templatesUpdated') { | ||||||
| 			compiling = 'tpl'; | 			compiling = 'tpl'; | ||||||
|  | 		} else if (target === 'langUpdated') { | ||||||
|  | 			compiling = 'lang'; | ||||||
| 		} else if (target === 'serverUpdated') { | 		} else if (target === 'serverUpdated') { | ||||||
| 			// Do nothing, just restart | 			// Do nothing, just restart | ||||||
| 		} | 		} | ||||||
| @@ -93,7 +95,18 @@ module.exports = function (grunt) { | |||||||
| 					'!node_modules/nodebb-*/node_modules/**', | 					'!node_modules/nodebb-*/node_modules/**', | ||||||
| 					'!node_modules/nodebb-*/.git/**' | 					'!node_modules/nodebb-*/.git/**' | ||||||
| 				] | 				] | ||||||
| 			} | 			}, | ||||||
|  | 			langUpdated: { | ||||||
|  | 				files: [ | ||||||
|  | 					'public/language/**/*.json', | ||||||
|  | 					'node_modules/nodebb-*/**/*.json', | ||||||
|  | 					'!node_modules/nodebb-*/node_modules/**', | ||||||
|  | 					'!node_modules/nodebb-*/.git/**', | ||||||
|  | 					'!node_modules/nodebb-*/plugin.json', | ||||||
|  | 					'!node_modules/nodebb-*/package.json', | ||||||
|  | 					'!node_modules/nodebb-*/theme.json', | ||||||
|  | 				], | ||||||
|  | 			}, | ||||||
| 		} | 		} | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								build.js
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								build.js
									
									
									
									
									
								
							| @@ -5,7 +5,7 @@ var winston = require('winston'); | |||||||
|  |  | ||||||
| var buildStart; | var buildStart; | ||||||
|  |  | ||||||
| var valid = ['js', 'clientCSS', 'acpCSS', 'tpl']; | var valid = ['js', 'clientCSS', 'acpCSS', 'tpl', 'lang']; | ||||||
|  |  | ||||||
| exports.buildAll = function (callback) { | exports.buildAll = function (callback) { | ||||||
| 	exports.build(valid.join(','), callback); | 	exports.build(valid.join(','), callback); | ||||||
| @@ -89,6 +89,12 @@ exports.buildTargets = function (targets, callback) { | |||||||
| 						meta.templates.compile(step.bind(this, startTime, target, next)); | 						meta.templates.compile(step.bind(this, startTime, target, next)); | ||||||
| 						break; | 						break; | ||||||
| 					 | 					 | ||||||
|  | 					case 'lang': | ||||||
|  | 						winston.info('[build] Building language files'); | ||||||
|  | 						startTime = Date.now(); | ||||||
|  | 						meta.languages.build(step.bind(this, startTime, target, next)); | ||||||
|  | 						break; | ||||||
|  |  | ||||||
| 					default: | 					default: | ||||||
| 						winston.warn('[build] Unknown build target: \'' + target + '\''); | 						winston.warn('[build] Unknown build target: \'' + target + '\''); | ||||||
| 						setImmediate(next); | 						setImmediate(next); | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								nodebb
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								nodebb
									
									
									
									
									
								
							| @@ -375,7 +375,7 @@ switch(process.argv[2]) { | |||||||
| 		async.series([ | 		async.series([ | ||||||
| 			function (next) { | 			function (next) { | ||||||
| 				process.stdout.write('1. '.bold + 'Bringing base dependencies up to date... '.yellow); | 				process.stdout.write('1. '.bold + 'Bringing base dependencies up to date... '.yellow); | ||||||
| 				require('child_process').execFile('/usr/bin/env', ['npm', 'i', '--production'], { stdio: 'ignore' }, next); | 				cproc.exec('npm i --production', { cwd: __dirname, stdio: 'ignore' }, next); | ||||||
| 			}, | 			}, | ||||||
| 			function (next) { | 			function (next) { | ||||||
| 				process.stdout.write('OK\n'.green); | 				process.stdout.write('OK\n'.green); | ||||||
|   | |||||||
| @@ -103,12 +103,9 @@ define('forum/category', [ | |||||||
| 		return bottomIndex; | 		return bottomIndex; | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	$(window).on('action:popstate', function (ev, data) { | 	$(window).on('action:ajaxify.contentLoaded', function (ev, data) { | ||||||
| 		if (data.url.startsWith('category/')) { | 		if (ajaxify.data.template.category) { | ||||||
| 			var cid = data.url.match(/^category\/(\d+)/); | 			var cid = ajaxify.data.cid; | ||||||
| 			if (cid && cid[1]) { |  | ||||||
| 				cid = cid[1]; |  | ||||||
| 			} |  | ||||||
| 			if (!cid) { | 			if (!cid) { | ||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
| @@ -140,8 +137,10 @@ define('forum/category', [ | |||||||
| 				$('[component="category"]').empty(); | 				$('[component="category"]').empty(); | ||||||
|  |  | ||||||
| 				loadTopicsAfter(Math.max(0, bookmarkIndex - 1) + 1, 1, function () { | 				loadTopicsAfter(Math.max(0, bookmarkIndex - 1) + 1, 1, function () { | ||||||
|  | 					$(window).one('action:topics.loaded', function () { | ||||||
| 						Category.scrollToTopic(bookmarkIndex, clickedIndex, 0); | 						Category.scrollToTopic(bookmarkIndex, clickedIndex, 0); | ||||||
| 					}); | 					}); | ||||||
|  | 				}); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	}); | 	}); | ||||||
| @@ -167,9 +166,8 @@ define('forum/category', [ | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		var scrollTo = components.get('category/topic', 'index', bookmarkIndex); | 		var scrollTo = components.get('category/topic', 'index', bookmarkIndex); | ||||||
| 		var	cid = ajaxify.data.cid; |  | ||||||
|  |  | ||||||
| 		if (scrollTo.length && cid) { | 		if (scrollTo.length) { | ||||||
| 			$('html, body').animate({ | 			$('html, body').animate({ | ||||||
| 				scrollTop: (scrollTo.offset().top - offset) + 'px' | 				scrollTop: (scrollTo.offset().top - offset) + 'px' | ||||||
| 			}, duration !== undefined ? duration : 400, function () { | 			}, duration !== undefined ? duration : 400, function () { | ||||||
| @@ -272,7 +270,7 @@ define('forum/category', [ | |||||||
| 			return callback(); | 			return callback(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		$(window).trigger('action:categories.loading'); | 		$(window).trigger('action:category.loading'); | ||||||
| 		var params = utils.params(); | 		var params = utils.params(); | ||||||
| 		infinitescroll.loadMore('categories.loadMore', { | 		infinitescroll.loadMore('categories.loadMore', { | ||||||
| 			cid: ajaxify.data.cid, | 			cid: ajaxify.data.cid, | ||||||
| @@ -288,7 +286,7 @@ define('forum/category', [ | |||||||
| 				done(); | 				done(); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			$(window).trigger('action:categories.loaded'); | 			$(window).trigger('action:category.loaded'); | ||||||
| 			callback(); | 			callback(); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| (function (factory) { | (function (factory) { | ||||||
| 	'use strict'; | 	'use strict'; | ||||||
| 	function loadClient(language, namespace) { | 	function loadClient(language, namespace) { | ||||||
| 		return Promise.resolve(jQuery.getJSON(config.relative_path + '/api/language/' + language + '/' + namespace)); | 		return Promise.resolve(jQuery.getJSON(config.relative_path + '/assets/language/' + language + '/' + namespace + '.json?' + config['cache-buster'])); | ||||||
| 	} | 	} | ||||||
| 	var warn = function () {}; | 	var warn = function () {}; | ||||||
| 	if (typeof config === 'object' && config.environment === 'development') { | 	if (typeof config === 'object' && config.environment === 'development') { | ||||||
| @@ -17,7 +17,6 @@ | |||||||
| 	} else if (typeof module === 'object' && module.exports) { | 	} else if (typeof module === 'object' && module.exports) { | ||||||
| 		// Node | 		// Node | ||||||
| 		(function () { | 		(function () { | ||||||
| 			require('promise-polyfill'); |  | ||||||
| 			var languages = require('../../../src/languages'); | 			var languages = require('../../../src/languages'); | ||||||
|  |  | ||||||
| 			if (global.env === 'development') { | 			if (global.env === 'development') { | ||||||
| @@ -292,7 +291,7 @@ | |||||||
| 				warn('[translator] Parameter `namespace` is ' + namespace + (namespace === '' ? '(empty string)' : '')); | 				warn('[translator] Parameter `namespace` is ' + namespace + (namespace === '' ? '(empty string)' : '')); | ||||||
| 				translation = Promise.resolve({}); | 				translation = Promise.resolve({}); | ||||||
| 			} else { | 			} else { | ||||||
| 				translation = this.translations[namespace] = this.translations[namespace] || this.load(this.lang, namespace); | 				translation = this.translations[namespace] = this.translations[namespace] || this.load(this.lang, namespace).catch(function () { return {}; }); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if (key) { | 			if (key) { | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| require.config({ | require.config({ | ||||||
| 	baseUrl: config.relative_path + "/src/modules", | 	baseUrl: config.relative_path + "/src/modules", | ||||||
| 	waitSeconds: 7, | 	waitSeconds: 7, | ||||||
| 	urlArgs: "v=" + config['cache-buster'], | 	urlArgs: config['cache-buster'], | ||||||
| 	paths: { | 	paths: { | ||||||
| 		'forum': '../client', | 		'forum': '../client', | ||||||
| 		'admin': '../admin', | 		'admin': '../admin', | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ var path = require('path'); | |||||||
| var async = require('async'); | var async = require('async'); | ||||||
| var sanitizeHTML = require('sanitize-html'); | var sanitizeHTML = require('sanitize-html'); | ||||||
|  |  | ||||||
| var languages = require('../languages'); |  | ||||||
| var utils = require('../../public/src/utils'); | var utils = require('../../public/src/utils'); | ||||||
| var Translator = require('../../public/src/modules/translator').Translator; | var Translator = require('../../public/src/modules/translator').Translator; | ||||||
|  |  | ||||||
| @@ -19,7 +18,7 @@ function filterDirectories(directories) { | |||||||
| 		// exclude category.tpl, group.tpl, category-analytics.tpl | 		// exclude category.tpl, group.tpl, category-analytics.tpl | ||||||
| 		return !dir.includes('/partials/') && | 		return !dir.includes('/partials/') && | ||||||
| 			/\/.*\//.test(dir) && | 			/\/.*\//.test(dir) && | ||||||
| 			!/category|group|category\-analytics$/.test(dir); | 			!/manage\/(category|group|category\-analytics)$/.test(dir); | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -107,6 +106,8 @@ function fallback(namespace, callback) { | |||||||
| } | } | ||||||
|  |  | ||||||
| function initDict(language, callback) { | function initDict(language, callback) { | ||||||
|  | 	var translator = Translator.create(language); | ||||||
|  |  | ||||||
| 	getAdminNamespaces(function (err, namespaces) { | 	getAdminNamespaces(function (err, namespaces) { | ||||||
| 		if (err) { | 		if (err) { | ||||||
| 			return callback(err); | 			return callback(err); | ||||||
| @@ -115,7 +116,9 @@ function initDict(language, callback) { | |||||||
| 		async.map(namespaces, function (namespace, cb) { | 		async.map(namespaces, function (namespace, cb) { | ||||||
| 			async.waterfall([ | 			async.waterfall([ | ||||||
| 				function (next) { | 				function (next) { | ||||||
| 					languages.get(language, namespace, next); | 					translator.getTranslation(namespace).then(function (translations) { | ||||||
|  | 						next(null, translations); | ||||||
|  | 					}, next); | ||||||
| 				}, | 				}, | ||||||
| 				function (translations, next) { | 				function (translations, next) { | ||||||
| 					if (!translations || !Object.keys(translations).length) { | 					if (!translations || !Object.keys(translations).length) { | ||||||
| @@ -139,7 +142,7 @@ function initDict(language, callback) { | |||||||
| 							title[1] + '/' + title[2] + ']]') : ''); | 							title[1] + '/' + title[2] + ']]') : ''); | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					Translator.create(language).translate(title).then(function (title) { | 					translator.translate(title).then(function (title) { | ||||||
| 						next(null, { | 						next(null, { | ||||||
| 							namespace: namespace, | 							namespace: namespace, | ||||||
| 							translations: str + '\n' + title, | 							translations: str + '\n' + title, | ||||||
|   | |||||||
| @@ -353,7 +353,6 @@ Controllers.ping = function (req, res) { | |||||||
|  |  | ||||||
| Controllers.handle404 = function (req, res) { | Controllers.handle404 = function (req, res) { | ||||||
| 	var relativePath = nconf.get('relative_path'); | 	var relativePath = nconf.get('relative_path'); | ||||||
| 	var isLanguage = new RegExp('^' + relativePath + '/api/language/.*/.*'); |  | ||||||
| 	var isClientScript = new RegExp('^' + relativePath + '\\/src\\/.+\\.js'); | 	var isClientScript = new RegExp('^' + relativePath + '\\/src\\/.+\\.js'); | ||||||
|  |  | ||||||
| 	if (plugins.hasListeners('action:meta.override404')) { | 	if (plugins.hasListeners('action:meta.override404')) { | ||||||
| @@ -366,8 +365,6 @@ Controllers.handle404 = function (req, res) { | |||||||
|  |  | ||||||
| 	if (isClientScript.test(req.url)) { | 	if (isClientScript.test(req.url)) { | ||||||
| 		res.type('text/javascript').status(200).send(''); | 		res.type('text/javascript').status(200).send(''); | ||||||
| 	} else if (isLanguage.test(req.url)) { |  | ||||||
| 		res.status(200).json({}); |  | ||||||
| 	} else if (req.path.startsWith(relativePath + '/uploads') || (req.get('accept') && req.get('accept').indexOf('text/html') === -1) || req.path === '/favicon.ico') { | 	} else if (req.path.startsWith(relativePath + '/uploads') || (req.get('accept') && req.get('accept').indexOf('text/html') === -1) || req.path === '/favicon.ico') { | ||||||
| 		meta.errors.log404(req.path || ''); | 		meta.errors.log404(req.path || ''); | ||||||
| 		res.sendStatus(404); | 		res.sendStatus(404); | ||||||
|   | |||||||
| @@ -3,53 +3,27 @@ | |||||||
| var fs = require('fs'); | var fs = require('fs'); | ||||||
| var path = require('path'); | var path = require('path'); | ||||||
| var async = require('async'); | var async = require('async'); | ||||||
| var LRU = require('lru-cache'); |  | ||||||
|  |  | ||||||
| var plugins = require('./plugins'); |  | ||||||
|  |  | ||||||
| var Languages = {}; | var Languages = {}; | ||||||
| var	languagesPath = path.join(__dirname, '../public/language'); | var	languagesPath = path.join(__dirname, '../build/public/language'); | ||||||
|  |  | ||||||
| Languages.init = function (next) { | Languages.init = function (next) { | ||||||
| 	if (Languages.hasOwnProperty('_cache')) { |  | ||||||
| 		Languages._cache.reset(); |  | ||||||
| 	} else { |  | ||||||
| 		Languages._cache = LRU(100); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	next(); | 	next(); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| Languages.get = function (language, namespace, callback) { | Languages.get = function (language, namespace, callback) { | ||||||
| 	var langNamespace = language + '/' + namespace; |  | ||||||
|  |  | ||||||
| 	if (Languages._cache && Languages._cache.has(langNamespace)) { |  | ||||||
| 		return callback(null, Languages._cache.get(langNamespace)); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var languageData; |  | ||||||
|  |  | ||||||
| 	fs.readFile(path.join(languagesPath, language, namespace + '.json'), { encoding: 'utf-8' }, function (err, data) { | 	fs.readFile(path.join(languagesPath, language, namespace + '.json'), { encoding: 'utf-8' }, function (err, data) { | ||||||
| 		if (err && err.code !== 'ENOENT') { | 		if (err) { | ||||||
| 			return callback(err); | 			return callback(err); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// If language file in core cannot be read, then no language file present |  | ||||||
| 		try { | 		try { | ||||||
| 			languageData = JSON.parse(data) || {}; | 			data = JSON.parse(data) || {}; | ||||||
| 		} catch (e) { | 		} catch (e) { | ||||||
| 			languageData = {}; | 			return callback(e); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if (plugins.customLanguages.hasOwnProperty(langNamespace)) { | 		callback(null, data); | ||||||
| 			Object.assign(languageData, plugins.customLanguages[langNamespace]); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if (Languages._cache) { |  | ||||||
| 			Languages._cache.set(langNamespace, languageData); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		callback(null, languageData); |  | ||||||
| 	}); | 	}); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -73,11 +47,13 @@ Languages.list = function (callback) { | |||||||
|  |  | ||||||
| 				var configPath = path.join(languagesPath, folder, 'language.json'); | 				var configPath = path.join(languagesPath, folder, 'language.json'); | ||||||
|  |  | ||||||
| 				fs.readFile(configPath, function (err, stream) { | 				fs.readFile(configPath, function (err, buffer) { | ||||||
| 					if (err) { | 					if (err && err.code !== 'ENOENT') { | ||||||
| 						return next(err); | 						return next(err); | ||||||
| 					} | 					} | ||||||
| 					languages.push(JSON.parse(stream.toString())); | 					if (buffer) { | ||||||
|  | 						languages.push(JSON.parse(buffer.toString())); | ||||||
|  | 					} | ||||||
| 					next(); | 					next(); | ||||||
| 				}); | 				}); | ||||||
| 			}); | 			}); | ||||||
|   | |||||||
| @@ -23,6 +23,7 @@ var utils = require('../public/src/utils'); | |||||||
| 	require('./meta/dependencies')(Meta); | 	require('./meta/dependencies')(Meta); | ||||||
| 	Meta.templates = require('./meta/templates'); | 	Meta.templates = require('./meta/templates'); | ||||||
| 	Meta.blacklist = require('./meta/blacklist'); | 	Meta.blacklist = require('./meta/blacklist'); | ||||||
|  | 	Meta.languages = require('./meta/languages'); | ||||||
|  |  | ||||||
| 	/* Assorted */ | 	/* Assorted */ | ||||||
| 	Meta.userOrGroupExists = function (slug, callback) { | 	Meta.userOrGroupExists = function (slug, callback) { | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ module.exports = function (Meta) { | |||||||
| 				Meta.configs.list(next); | 				Meta.configs.list(next); | ||||||
| 			}, | 			}, | ||||||
| 			function (config, next) { | 			function (config, next) { | ||||||
| 				config['cache-buster'] = utils.generateUUID(); | 				config['cache-buster'] = 'v=' + utils.generateUUID(); | ||||||
|  |  | ||||||
| 				Meta.config = config; | 				Meta.config = config; | ||||||
| 				setImmediate(next); | 				setImmediate(next); | ||||||
|   | |||||||
							
								
								
									
										221
									
								
								src/meta/languages.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								src/meta/languages.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,221 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | var winston = require('winston'); | ||||||
|  | var path = require('path'); | ||||||
|  | var async = require('async'); | ||||||
|  | var fs = require('fs'); | ||||||
|  | var mkdirp = require('mkdirp'); | ||||||
|  |  | ||||||
|  | var file = require('../file'); | ||||||
|  | var utils = require('../../public/src/utils'); | ||||||
|  | var Plugins = require('../plugins'); | ||||||
|  | var db = require('../database'); | ||||||
|  |  | ||||||
|  | var buildLanguagesPath = path.join(__dirname, '../../build/public/language'); | ||||||
|  | var	coreLanguagesPath = path.join(__dirname, '../../public/language'); | ||||||
|  |  | ||||||
|  | function getTranslationTree(callback) { | ||||||
|  | 	async.waterfall([ | ||||||
|  | 		// get plugin data | ||||||
|  | 		function (next) { | ||||||
|  | 			db.getSortedSetRange('plugins:active', 0, -1, next); | ||||||
|  | 		}, | ||||||
|  | 		function (plugins, next) { | ||||||
|  | 			var pluginBasePath = path.join(__dirname, '../../node_modules'); | ||||||
|  | 			var paths = plugins.map(function (plugin) { | ||||||
|  | 				return path.join(pluginBasePath, plugin); | ||||||
|  | 			}); | ||||||
|  |  | ||||||
|  | 			// Filter out plugins with invalid paths | ||||||
|  | 			async.filter(paths, file.exists, function (paths) { | ||||||
|  | 				next(null, paths); | ||||||
|  | 			}); | ||||||
|  | 		}, | ||||||
|  | 		function (paths, next) { | ||||||
|  | 			async.map(paths, Plugins.loadPluginInfo, next); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		// generate list of languages and namespaces | ||||||
|  | 		function (plugins, next) { | ||||||
|  | 			var languages = [], namespaces = []; | ||||||
|  |  | ||||||
|  | 			// pull languages and namespaces from paths | ||||||
|  | 			function extrude(languageDir, paths) { | ||||||
|  | 				paths.forEach(function (p) { | ||||||
|  | 					var rel = p.split(languageDir)[1].split(/[\/\\]/).slice(1); | ||||||
|  | 					var language = rel.shift().replace('_', '-').replace('@', '-x-'); | ||||||
|  | 					var namespace = rel.join('/').replace(/\.json$/, ''); | ||||||
|  |  | ||||||
|  | 					if (!language || !namespace) { | ||||||
|  | 						return; | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					if (languages.indexOf(language) === -1) { | ||||||
|  | 						languages.push(language); | ||||||
|  | 					} | ||||||
|  | 					if (namespaces.indexOf(namespace) === -1) { | ||||||
|  | 						namespaces.push(namespace); | ||||||
|  | 					} | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			plugins = plugins.filter(function (pluginData) { | ||||||
|  | 				return (typeof pluginData.languages === 'string'); | ||||||
|  | 			}); | ||||||
|  | 			async.parallel([ | ||||||
|  | 				// get core languages and namespaces | ||||||
|  | 				function (nxt) { | ||||||
|  | 					utils.walk(coreLanguagesPath, function (err, paths) { | ||||||
|  | 						if (err) { | ||||||
|  | 							return nxt(err); | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						extrude(coreLanguagesPath, paths); | ||||||
|  | 						nxt(); | ||||||
|  | 					}); | ||||||
|  | 				}, | ||||||
|  | 				// get plugin languages and namespaces | ||||||
|  | 				function (nxt) { | ||||||
|  | 					async.each(plugins, function (pluginData, cb) { | ||||||
|  | 						var pathToFolder = path.join(__dirname, '../../node_modules/', pluginData.id, pluginData.languages); | ||||||
|  | 						utils.walk(pathToFolder, function (err, paths) { | ||||||
|  | 							if (err) { | ||||||
|  | 								return cb(err); | ||||||
|  | 							} | ||||||
|  |  | ||||||
|  | 							extrude(pathToFolder, paths); | ||||||
|  | 							cb(); | ||||||
|  | 						}); | ||||||
|  | 					}, nxt); | ||||||
|  | 				}, | ||||||
|  | 			], function (err) { | ||||||
|  | 				if (err) { | ||||||
|  | 					return next(err); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				next(null, { | ||||||
|  | 					languages: languages, | ||||||
|  | 					namespaces: namespaces, | ||||||
|  | 					plugins: plugins, | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		// for each language and namespace combination, | ||||||
|  | 		// run through core and all plugins to generate | ||||||
|  | 		// a full translation hash | ||||||
|  | 		function (ref, next) { | ||||||
|  | 			var languages = ref.languages; | ||||||
|  | 			var namespaces = ref.namespaces; | ||||||
|  | 			var plugins = ref.plugins; | ||||||
|  |  | ||||||
|  | 			var tree = {}; | ||||||
|  |  | ||||||
|  | 			async.eachLimit(languages, 10, function (lang, nxt) { | ||||||
|  | 				async.eachLimit(namespaces, 10, function (ns, cb) { | ||||||
|  | 					var translations = {}; | ||||||
|  |  | ||||||
|  | 					async.series([ | ||||||
|  | 						// core first | ||||||
|  | 						function (n) { | ||||||
|  | 							fs.readFile(path.join(coreLanguagesPath, lang, ns + '.json'), function (err, buffer) { | ||||||
|  | 								if (err) { | ||||||
|  | 									if (err.code === 'ENOENT') { | ||||||
|  | 										return n(); | ||||||
|  | 									} | ||||||
|  | 									return n(err); | ||||||
|  | 								} | ||||||
|  |  | ||||||
|  | 								try { | ||||||
|  | 									Object.assign(translations, JSON.parse(buffer.toString())); | ||||||
|  | 									n(); | ||||||
|  | 								} catch (err) { | ||||||
|  | 									n(err); | ||||||
|  | 								} | ||||||
|  | 							}); | ||||||
|  | 						}, | ||||||
|  | 						function (n) { | ||||||
|  | 							// for each plugin, fallback in this order: | ||||||
|  | 							//  1. correct language string (en-GB) | ||||||
|  | 							//  2. old language string (en_GB) | ||||||
|  | 							//  3. plugin defaultLang (en-US) | ||||||
|  | 							//  4. old plugin defaultLang (en_US) | ||||||
|  | 							async.eachLimit(plugins, 10, function (pluginData, call) { | ||||||
|  | 								var pluginLanguages = path.join(__dirname, '../../node_modules/', pluginData.id, pluginData.languages); | ||||||
|  | 								function tryLang(lang, onEnoent) { | ||||||
|  | 									fs.readFile(path.join(pluginLanguages, lang, ns + '.json'), function (err, buffer) { | ||||||
|  | 										if (err) { | ||||||
|  | 											if (err.code === 'ENOENT') { | ||||||
|  | 												return onEnoent(); | ||||||
|  | 											} | ||||||
|  | 											return call(err); | ||||||
|  | 										} | ||||||
|  |  | ||||||
|  | 										try { | ||||||
|  | 											Object.assign(translations, JSON.parse(buffer.toString())); | ||||||
|  | 											call(); | ||||||
|  | 										} catch (err) { | ||||||
|  | 											call(err); | ||||||
|  | 										} | ||||||
|  | 									}); | ||||||
|  | 								} | ||||||
|  |  | ||||||
|  | 								tryLang(lang, function () { | ||||||
|  | 									tryLang(lang.replace('-', '_').replace('-x-', '@'), function () { | ||||||
|  | 										tryLang(pluginData.defaultLang, function () { | ||||||
|  | 											tryLang(pluginData.defaultLang.replace('-', '_').replace('-x-', '@'), call); | ||||||
|  | 										}); | ||||||
|  | 									}); | ||||||
|  | 								}); | ||||||
|  | 							}, function (err) { | ||||||
|  | 								if (err) { | ||||||
|  | 									return n(err); | ||||||
|  | 								} | ||||||
|  |  | ||||||
|  | 								tree[lang] = tree[lang] || {}; | ||||||
|  | 								tree[lang][ns] = translations; | ||||||
|  | 								n(); | ||||||
|  | 							}); | ||||||
|  | 						}, | ||||||
|  | 					], cb); | ||||||
|  | 				}, nxt); | ||||||
|  | 			}, function (err) { | ||||||
|  | 				next(err, tree); | ||||||
|  | 			}); | ||||||
|  | 		}, | ||||||
|  | 	], callback); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // write translation hashes from the generated tree to language files | ||||||
|  | function writeLanguageFiles(tree, callback) { | ||||||
|  | 	// iterate over languages and namespaces | ||||||
|  | 	async.eachLimit(Object.keys(tree), 10, function (language, cb) { | ||||||
|  | 		var namespaces = tree[language]; | ||||||
|  | 		async.eachLimit(Object.keys(namespaces), 100, function (namespace, next) { | ||||||
|  | 			var translations = namespaces[namespace]; | ||||||
|  |  | ||||||
|  | 			var filePath = path.join(buildLanguagesPath, language, namespace + '.json'); | ||||||
|  |  | ||||||
|  | 			mkdirp(path.dirname(filePath), function (err) { | ||||||
|  | 				if (err) { | ||||||
|  | 					return next(err); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				fs.writeFile(filePath, JSON.stringify(translations), next); | ||||||
|  | 			}); | ||||||
|  | 		}, cb); | ||||||
|  | 	}, callback); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | exports.build = function buildLanguages(callback) { | ||||||
|  | 	async.waterfall([ | ||||||
|  | 		getTranslationTree, | ||||||
|  | 		writeLanguageFiles, | ||||||
|  | 	], function (err) { | ||||||
|  | 		if (err) { | ||||||
|  | 			winston.error('[build] Language build failed: ' + err.message); | ||||||
|  | 			throw err; | ||||||
|  | 		} | ||||||
|  | 		callback(); | ||||||
|  | 	}); | ||||||
|  | }; | ||||||
| @@ -207,23 +207,6 @@ middleware.applyBlacklist = function (req, res, next) { | |||||||
| 	}); | 	}); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| middleware.getTranslation = function (req, res, next) { |  | ||||||
| 	var language = req.params.language; |  | ||||||
| 	var namespace = req.params[0]; |  | ||||||
|  |  | ||||||
| 	if (language && namespace) { |  | ||||||
| 		languages.get(language, namespace, function (err, translations) { |  | ||||||
| 			if (err) { |  | ||||||
| 				return next(err); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			res.status(200).json(translations); |  | ||||||
| 		}); |  | ||||||
| 	} else { |  | ||||||
| 		res.status(404).json('{}'); |  | ||||||
| 	} |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| middleware.processTimeagoLocales = function (req, res, next) { | middleware.processTimeagoLocales = function (req, res, next) { | ||||||
| 	var fallback = req.path.indexOf('-short') === -1 ? 'jquery.timeago.en.js' : 'jquery.timeago.en-short.js', | 	var fallback = req.path.indexOf('-short') === -1 ? 'jquery.timeago.en.js' : 'jquery.timeago.en-short.js', | ||||||
| 		localPath = path.join(__dirname, '../../public/vendor/jquery/timeago/locales', req.path), | 		localPath = path.join(__dirname, '../../public/vendor/jquery/timeago/locales', req.path), | ||||||
|   | |||||||
| @@ -24,7 +24,7 @@ module.exports = function (middleware) { | |||||||
| 			'^/templates/[\\w/]+.tpl', | 			'^/templates/[\\w/]+.tpl', | ||||||
| 			'^/api/login', | 			'^/api/login', | ||||||
| 			'^/api/widgets/render', | 			'^/api/widgets/render', | ||||||
| 			'^/api/language/.+', | 			'^/public/language', | ||||||
| 			'^/uploads/system/site-logo.png' | 			'^/uploads/system/site-logo.png' | ||||||
| 		]; | 		]; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -29,8 +29,6 @@ var middleware; | |||||||
| 	Plugins.lessFiles = []; | 	Plugins.lessFiles = []; | ||||||
| 	Plugins.clientScripts = []; | 	Plugins.clientScripts = []; | ||||||
| 	Plugins.acpScripts = []; | 	Plugins.acpScripts = []; | ||||||
| 	Plugins.customLanguages = {}; |  | ||||||
| 	Plugins.customLanguageFallbacks = {}; |  | ||||||
| 	Plugins.libraryPaths = []; | 	Plugins.libraryPaths = []; | ||||||
| 	Plugins.versionWarning = []; | 	Plugins.versionWarning = []; | ||||||
| 	Plugins.languageCodes = []; | 	Plugins.languageCodes = []; | ||||||
|   | |||||||
| @@ -9,8 +9,6 @@ var winston = require('winston'); | |||||||
| var nconf = require('nconf'); | var nconf = require('nconf'); | ||||||
| var _ = require('underscore'); | var _ = require('underscore'); | ||||||
| var file = require('../file'); | var file = require('../file'); | ||||||
|  |  | ||||||
| var utils = require('../../public/src/utils'); |  | ||||||
| var meta = require('../meta'); | var meta = require('../meta'); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -91,9 +89,6 @@ module.exports = function (Plugins) { | |||||||
| 				function (next) { | 				function (next) { | ||||||
| 					mapClientModules(pluginData, next); | 					mapClientModules(pluginData, next); | ||||||
| 				}, | 				}, | ||||||
| 				function (next) { |  | ||||||
| 					loadLanguages(pluginData, next); |  | ||||||
| 				} |  | ||||||
| 			], function (err) { | 			], function (err) { | ||||||
| 				if (err) { | 				if (err) { | ||||||
| 					winston.verbose('[plugins] Could not load plugin : ' + pluginData.id); | 					winston.verbose('[plugins] Could not load plugin : ' + pluginData.id); | ||||||
| @@ -252,60 +247,6 @@ module.exports = function (Plugins) { | |||||||
| 		callback(); | 		callback(); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	function loadLanguages(pluginData, callback) { |  | ||||||
| 		if (typeof pluginData.languages !== 'string') { |  | ||||||
| 			return callback(); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		var pathToFolder = path.join(__dirname, '../../node_modules/', pluginData.id, pluginData.languages); |  | ||||||
| 		var defaultLang = (pluginData.defaultLang || 'en_GB').replace('_', '-').replace('@', '-x-'); |  | ||||||
|  |  | ||||||
| 		utils.walk(pathToFolder, function (err, languages) { |  | ||||||
| 			if (err) { |  | ||||||
| 				return callback(err); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			async.each(languages, function (pathToLang, next) { |  | ||||||
| 				fs.readFile(pathToLang, function (err, file) { |  | ||||||
| 					if (err) { |  | ||||||
| 						return next(err); |  | ||||||
| 					} |  | ||||||
| 					var data; |  | ||||||
| 					var language = path.dirname(pathToLang).split(/[\/\\]/).pop().replace('_', '-').replace('@', '-x-'); |  | ||||||
| 					var namespace = path.basename(pathToLang, '.json'); |  | ||||||
| 					var langNamespace = language + '/' + namespace; |  | ||||||
|  |  | ||||||
| 					try { |  | ||||||
| 						data = JSON.parse(file.toString()); |  | ||||||
| 					} catch (err) { |  | ||||||
| 						winston.error('[plugins] Unable to parse custom language file: ' + pathToLang + '\r\n' + err.stack); |  | ||||||
| 						return next(err); |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					Plugins.customLanguages[langNamespace] = Plugins.customLanguages[langNamespace] || {}; |  | ||||||
| 					Object.assign(Plugins.customLanguages[langNamespace], data); |  | ||||||
|  |  | ||||||
| 					if (defaultLang && defaultLang === language) { |  | ||||||
| 						Plugins.languageCodes.filter(function (lang) { |  | ||||||
| 							return defaultLang !== lang; |  | ||||||
| 						}).forEach(function (lang) { |  | ||||||
| 							var langNS = lang + '/' + namespace; |  | ||||||
| 							Plugins.customLanguages[langNS] = Object.assign(Plugins.customLanguages[langNS] || {}, data); |  | ||||||
| 						}); |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					next(); |  | ||||||
| 				}); |  | ||||||
| 			}, function (err) { |  | ||||||
| 				if (err) { |  | ||||||
| 					return callback(err); |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				callback(); |  | ||||||
| 			}); |  | ||||||
| 		}); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	function resolveModulePath(fullPath, relPath) { | 	function resolveModulePath(fullPath, relPath) { | ||||||
| 		/** | 		/** | ||||||
| 		  * With npm@3, dependencies can become flattened, and appear at the root level. | 		  * With npm@3, dependencies can become flattened, and appear at the root level. | ||||||
| @@ -363,6 +304,7 @@ module.exports = function (Plugins) { | |||||||
|  |  | ||||||
| 				return callback(new Error('[[error:parse-error]]')); | 				return callback(new Error('[[error:parse-error]]')); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			callback(null, pluginData); | 			callback(null, pluginData); | ||||||
| 		}); | 		}); | ||||||
| 	}; | 	}; | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ var nconf = require('nconf'); | |||||||
| var winston = require('winston'); | var winston = require('winston'); | ||||||
| var path = require('path'); | var path = require('path'); | ||||||
| var async = require('async'); | var async = require('async'); | ||||||
|  | var meta = require('../meta'); | ||||||
| var controllers = require('../controllers'); | var controllers = require('../controllers'); | ||||||
| var plugins = require('../plugins'); | var plugins = require('../plugins'); | ||||||
| var user = require('../user'); | var user = require('../user'); | ||||||
| @@ -145,7 +146,17 @@ module.exports = function (app, middleware, hotswapIds) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	app.use(middleware.privateUploads); | 	app.use(middleware.privateUploads); | ||||||
| 	app.use(relativePath + '/api/language/:language/(([a-zA-Z0-9\\-_.\\/]+))', middleware.getTranslation); | 	app.use(relativePath + '/assets', express.static(path.join(__dirname, '../../', 'build/public'), { | ||||||
|  | 		maxAge: app.enabled('cache') ? 5184000000 : 0 | ||||||
|  | 	})); | ||||||
|  |  | ||||||
|  | 	// DEPRECATED | ||||||
|  | 	app.use(relativePath + '/api/language', function (req, res) { | ||||||
|  | 		winston.warn('[deprecated] Accessing language files from `/api/language` is deprecated. ' +  | ||||||
|  | 			'Use `/assets/language/[langCode]/[namespace].json` for prefetch paths.'); | ||||||
|  | 		res.redirect(relativePath + '/assets/language' + req.path + '.json?' + meta.config['cache-buster']); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
| 	app.use(relativePath, express.static(path.join(__dirname, '../../', 'public'), { | 	app.use(relativePath, express.static(path.join(__dirname, '../../', 'public'), { | ||||||
| 		maxAge: app.enabled('cache') ? 5184000000 : 0 | 		maxAge: app.enabled('cache') ? 5184000000 : 0 | ||||||
| 	})); | 	})); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user