Skip to content

Instantly share code, notes, and snippets.

@toolness
Last active December 11, 2015 16:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save toolness/4629385 to your computer and use it in GitHub Desktop.
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.
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);
{
"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"
}
@toolness
Copy link
Author

For more information on what this is used for, see mozilla/friendlycode#175.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment