From 51773c9162a2a3feae8459e01db20644431c8e6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Eixarch?= Date: Sun, 9 Dec 2012 09:39:00 +0100 Subject: [PATCH] Added translation plugins support --- src/node/hooks/i18n.js | 149 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 132 insertions(+), 17 deletions(-) diff --git a/src/node/hooks/i18n.js b/src/node/hooks/i18n.js index a45d46fee..f65986180 100644 --- a/src/node/hooks/i18n.js +++ b/src/node/hooks/i18n.js @@ -1,35 +1,150 @@ +/* + * REQUIRE MODULES + */ var languages = require('languages4translatewiki') , fs = require('fs') , path = require('path') , express = require('express') + , _ = require('underscore'); -var localesPath = __dirname+"/../../locales"; +/* + * PRIVATE + */ -// Serve English strings directly with /locales.ini -var localeIndex = fs.readFileSync(localesPath+'/en.ini')+'\r\n'; +// locales will store all locales ini files merged (core+plugins) in RAM +var locales = {}; -exports.availableLangs = {'en': {'nativeName': 'English', 'direction': 'ltr'}}; +// convert an ini file string (input) to JSON {key: value, key2: value2...} +var parseIni = function (input) { + var result = {}, + lines = input.split('\n'); + _.each (lines, function(line) { + if ((line.length > 0) && (line[0] != ';') && (line[0] != '[')) { + line = line.split('=', 2); + if (line.length == 2) result[line[0].trim()]=line[1].trim(); + } + }); + return result; +} -fs.readdir(localesPath, function(er, files) { - files.forEach(function(locale) { - var ext = path.extname(locale); - locale = path.basename(locale, ext).toLowerCase(); - if(locale == 'en' || ext != '.ini') return; +// convert JSON obj {key: value, key2: value2...} to ini file string +var generateIni = function (obj, section) { + //english by default + if (section == 'en') section = '*'; + var result = "["+section+"]\n"; + _.each (obj, function(value, key) { + result += key+" = "+value+"\n"; + }); + return result; +} - // build locale index - localeIndex += '['+locale+']\r\n@import url(locales/'+locale+'.ini)\r\n' - - // add info language {nativeName, direction} to availableLangs - exports.availableLangs[locale]=languages.getLanguageInfo(locale); - }) -}) +//explore recursive subdirectories from root and execute callback +//don't explore symbolic links +var exploreDir = function (root, callback) { + var stat = fs.lstatSync(root); + if (stat.isDirectory() && !stat.isSymbolicLink()) { + var names = fs.readdirSync(root), + subdirs = [], + files = []; + names.forEach (function(file) { + file = path.resolve(root,file); + stat = fs.lstatSync(file); + if (stat.isDirectory() && !stat.isSymbolicLink()) { + subdirs.push(file); + } else { + files.push(file); + } + }); + callback(root, subdirs, files); + subdirs.forEach(function (d) { + exploreDir(d, callback); + }); + } +}; +// return all files languages absolute path group by langcode +// {es: [pathcore, pathplugin1...], en:...} +var getAllLocalesPaths = function () { + var result = {}; + + //extract only files paths with name is a supported language code and have ini extension + var extractLangs = function (root, subdirs, files) { + _.each(files, function(file) { + var ext = path.extname(file), + locale = path.basename(file, ext).toLowerCase(); + if ((ext == '.ini') && languages.isValid(locale)) { + if (!(_.has(result, locale))) result[locale] = []; + result[locale].push(file); + } + }); + } + + //add core supported languages first + var root = path.resolve(__dirname+"/../../locales"); + exploreDir (root, extractLangs); + //add plugins languages (if any) + root = path.resolve(__dirname+"/../../../node_modules"); + exploreDir (root, extractLangs); + + return result; +} + +//save in locales all ini files merged by language code +var getAllLocales = function () { + _.each (getAllLocalesPaths(), function(files, langcode) { + locales[langcode]={} + _.each (files, function(file) { + _.extend(locales[langcode], parseIni(fs.readFileSync(file,'utf8'))); + }); + locales[langcode] = generateIni(locales[langcode], langcode); + }); +} + +//return all languages availables with nativeName and direction +//{es: {nativeName: "espaƱol", direction: "ltr"},...} +var getAvailableLangs = function () { + var result = {}; + if (_.isEmpty(locales)) getAllLocales(); + _.each(_.keys(locales), function(langcode) { + result[langcode]=languages.getLanguageInfo(langcode); + }); + return result; +} + +//return locale index that will be served in /locales.ini +var generateLocaleIndex = function () { + var result = ''; + if (_.isEmpty(locales)) getAllLocales(); + result += locales['en']+"\n"; + _.each(_.keys(locales), function(langcode) { + if (langcode != 'en') result+='['+langcode+']\n@import url(locales/'+langcode+'.ini)\n'; + }); + return result; +} + +/* + * PUBLIC + */ + +var localeIndex = generateLocaleIndex(); + +exports.availableLangs = getAvailableLangs(); exports.expressCreateServer = function(n, args) { - args.app.use('/locales', express.static(localesPath)); + args.app.get ('/locales/:locale', function(req, res) { + //works with /locale/en and /locale/en.ini requests + var locale = req.params.locale.split('.')[0]; + if (exports.availableLangs.hasOwnProperty(locale)) { + res.setHeader('Content-Type', 'text/plain; charset=utf8'); + res.send(locales[locale]); + } else { + res.send(404, 'Language not available'); + } + }) args.app.get('/locales.ini', function(req, res) { + res.setHeader('Content-Type', 'text/plain; charset=utf8'); res.send(localeIndex); })