Last active
December 11, 2015 16:39
-
-
Save toolness/4629385 to your computer and use it in GitHub Desktop.
Simple express server that exposes a Transifex project's resources as a requirejs i18n bundle.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const PORT = process.env['PORT'] || 3000; | |
const PROJECT = process.env['TRANSIFEX_PROJECT'] || 'friendlycode'; | |
const USERPASS = process.env['TRANSIFEX_USERPASS'] || ''; | |
const ROOT_LOCALE = process.env['TRANSIFEX_ROOT_LOCALE'] || 'en'; | |
const AUTH_HEADER = 'Basic ' + new Buffer(USERPASS).toString('base64'); | |
const BASE_TRANSIFEX_URL = 'https://www.transifex.com/api/2/project/' + | |
PROJECT; | |
const REVIEWED_RE = /^\/reviewed\/(.*)$/; | |
const TOPLEVEL_RE = /^\/(.*)\/nls\/([A-Za-z0-9\-_]+)\.js$/; | |
const LOCALE_RE = /^\/(.*)\/nls\/([a-z\-]+)\/([A-Za-z0-9\-_]+)\.js$/; | |
const TRANSIFEX_CACHE_TIME = 30000; | |
var express = require('express'); | |
var request = require('request'); | |
var app = express(); | |
var transifexCache = {}; // Not 12-factor compliant, but it'll do. | |
var toTransifexLocale = exports.toTransifexLocale = function(locale) { | |
var parts = locale.split(/[-_]/); | |
if (parts.length >= 2) | |
return parts[0].toLowerCase() + "_" + parts[1].toUpperCase(); | |
return parts[0].toLowerCase(); | |
}; | |
var toBundleLocale = exports.toBundleLocale = function(locale) { | |
return locale.toLowerCase().replace(/_/g, '-'); | |
}; | |
var toBundleMetadata = exports.toBundleMetadata = function(resource) { | |
var metadata = {}; | |
resource.available_languages.forEach(function(language) { | |
if (language.code == resource.source_language_code) | |
return metadata.root = true; | |
metadata[toBundleLocale(language.code)] = true; | |
}); | |
return metadata; | |
}; | |
var toBundleDict = exports.toBundleDict = function(options) { | |
var dict = {}; | |
options.strings.forEach(function(info) { | |
if (!info.translation) return; | |
if (options.reviewedOnly && !info.reviewed) return; | |
dict[info.key] = info.translation; | |
}); | |
return dict; | |
}; | |
function slugify(name) { | |
var slug = []; | |
for (var i = 0; i < name.length; i++) | |
if (name[i].match(/[A-Za-z\-_0-9]/)) | |
slug.push(name[i]); | |
return slug.join(''); | |
} | |
function sendExportsAsModule(exports, res) { | |
var js = "define(" + JSON.stringify(exports, null, 2) + ");"; | |
res.set('Content-Type', 'application/javascript; charset=utf-8'); | |
return res.send(js); | |
} | |
function transifexRequest(toplevelModule, path, res, cb) { | |
var url = '/resource/' + slugify(toplevelModule) + path; | |
if (url in transifexCache && transifexCache[url].expiry > Date.now()) | |
cb(JSON.parse(transifexCache[url].body)); | |
else | |
request.get({ | |
url: BASE_TRANSIFEX_URL + url, | |
headers: {'Authorization': AUTH_HEADER} | |
}, function(error, response, body) { | |
if (error) { | |
console.log("ERROR", error); | |
return res.send(500); | |
} | |
if (response.statusCode == 404) | |
return res.send(404); | |
if (response.statusCode != 200) { | |
console.log("transifex returned " + response.statusCode); | |
return res.send(502); | |
} | |
transifexCache[url] = { | |
expiry: Date.now() + TRANSIFEX_CACHE_TIME, | |
body: body | |
}; | |
cb(JSON.parse(body)); | |
}); | |
} | |
function main(app) { | |
if (!USERPASS) | |
throw new Error('user:pass not in TRANSIFEX_USERPASS env var'); | |
app.listen(PORT, function() { | |
console.log("listening on port " + PORT); | |
}); | |
} | |
app.use(function parseReviewedOnlyPrefix(req, res, next) { | |
var reviewedMatch = req.url.match(REVIEWED_RE); | |
var reviewedOnly = false; | |
var path; | |
if (reviewedMatch) { | |
path = '/' + reviewedMatch[1]; | |
reviewedOnly = true; | |
} else { | |
path = req.url; | |
} | |
req.i18n = { | |
path: path, | |
reviewedOnly: reviewedOnly | |
}; | |
next(); | |
}); | |
app.use(function handleI18nToplevelModule(req, res, next) { | |
var toplevelMatch = req.i18n.path.match(TOPLEVEL_RE); | |
if (toplevelMatch) { | |
var toplevelModule = toplevelMatch[1] + '/nls/' + toplevelMatch[2]; | |
return transifexRequest(toplevelModule, '/?details', res, function(obj) { | |
return sendExportsAsModule(toBundleMetadata(obj), res); | |
}); | |
} | |
next(); | |
}); | |
app.use(function handleI18nLocaleModule(req, res, next) { | |
var localeMatch = req.i18n.path.match(LOCALE_RE); | |
if (localeMatch) { | |
var locale = localeMatch[2]; | |
var toplevelModule = localeMatch[1] + '/nls/' + localeMatch[3]; | |
var path; | |
if (locale == 'root') | |
locale = ROOT_LOCALE; | |
path = '/translation/' + toTransifexLocale(locale) + '/strings/'; | |
return transifexRequest(toplevelModule, path, res, function(obj) { | |
return sendExportsAsModule(toBundleDict({ | |
strings: obj, | |
reviewedOnly: req.i18n.reviewedOnly | |
}), res); | |
}); | |
} | |
next(); | |
}); | |
if (!module.parent) main(app); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "transifex-to-requirejs-i18n-server", | |
"version": "0.0.1", | |
"description": "Server that converts a Transifex project to requirejs i18n bundles", | |
"main": "app.js", | |
"dependencies": { | |
"express": "3.0.6", | |
"request": "2.12.0" | |
}, | |
"devDependencies": {}, | |
"scripts": { | |
"test": "echo \"Error: no test specified\" && exit 1" | |
}, | |
"repository": "", | |
"author": "Atul Varma", | |
"license": "BSD" | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For more information on what this is used for, see mozilla/friendlycode#175.