Permalink
Browse files

Merge pull request #175 from toolness/transifex

Add support for Transifex localizations
  • Loading branch information...
2 parents e3f7a09 + 8e970e9 commit ad9c3e9d243c3296aa2f1695526cdefe2df58c1e @toolness toolness committed Jan 29, 2013
Showing with 614 additions and 38 deletions.
  1. +1 −0 .gitignore
  2. +54 −15 README.md
  3. +46 −17 bin/build-i18n.js
  4. +45 −3 bin/build-require.js
  5. +131 −0 bin/transifex.js
  6. +104 −0 examples/transifex.html
  7. +1 −2 js/require-config.js
  8. +7 −0 package.json
  9. +29 −1 test/node-tap/test-build-i18n.js
  10. +196 −0 test/node-tap/test-transifex.js
View
@@ -1,3 +1,4 @@
js/friendlycode-built.js
css/friendlycode-built.css
node_modules/
+transifex/
View
@@ -40,26 +40,62 @@ here:
## Localization
-Before localizing, please read the [requirejs i18n bundle][i18n]
+We currently use Transifex for localization. To localize Friendlycode
+in your language, please visit the
+[Transifex friendlycode project][transifex]. Any strings you don't
+translate will fall-back to English.
+
+### Trying out a Localization
+
+Run `node bin/server.js` and visit
+`http://localhost:8005/examples/transifex.html`. If this doesn't work,
+however—or if it runs too slowly for your tastes—you will have
+to take the following steps.
+
+1. Run `node bin/transifex.js -u user:pass`, where `user:pass` is your
+ Transifex username and password. You can run `node bin/transifex.js --help`
+ for information on more options, such as only importing strings that
+ have been reviewed. This will export all Transifex localizations as
+ requirejs i18n bundles in the `transifex` directory.
+
+2. Run `node bin/server.js` and then visit
+ `http://localhost:8005/examples/transifex.html?local=1` to see your
+ localizations.
+
+### Adding a new i18n bundle module
+
+Before adding a new i18n bundle, first read the [requirejs i18n bundle][i18n]
documentation.
-To create a localization, first run `node bin/build-i18n.js list`
-to display a list of i18n bundle modules that can be localized. You'll
-need to localize all of these to create a complete localization, but
-anything you don't localize will just fall-back to English.
+When creating an i18n bundle, you only need to provide the root localization.
+The following instructions assume that your new i18n bundle module is at
+`js/fc/nls/foo`.
+
+1. Run `node bin/build-i18n.js plist fc/nls/foo > foo.plist`. This will
+ output a [Property List][] file to `foo.plist`, which Transifex can
+ use as a template for localization.
+
+2. Under friendlycode's [resource management page][] on Transifex, add
+ a new resource with name `fc/nls/foo` and i18n type
+ `Apple PLIST files (.plist)`. Then upload the `foo.plist` file.
+
+### Updating an existing i18n bundle module
+
+If the root localization for an i18n bundle module has changed, follow the
+same steps for adding a new i18n bundle (above), but simply re-upload the
+plist file for the existing module instead of creating a new one.
-Suppose you decide you want to localize the `fc/nls/ui` module to `fr-fr`.
-Just do the following:
+### Deploying an internationalized widget
-1. Create the `js/fc/nls/fr-fr` directory if it doesn't already exist.
-2. Run `node bin/build-i18n.js template fc/nls/ui > js/fc/nls/fr-fr/ui.js`.
-3. Localize the strings in `js/fc/nls/fr-fr/ui.js`.
-4. Edit `js/fc/nls/ui.js` and add `"fr-fr": true` to the object being returned
- by the module.
+See the source code in `examples/transifex.html` for information
+on how to do this with unoptimized builds.
-You can test out your localization by setting your browser's language
-preference to `fr-fr` and then loading any embedding of your repository's
-friendlycode widget in your browser.
+For optimized builds, use the `--i18n-url` option to `bin/build-require.js`
+to create an optimized build that retrieves its localizations at run-time
+from a different URL. For instance, if you deploy your Transifex
+requirejs i18n bundles to `/locale`, running
+`node bin/build-require.js --i18n-url="/locale/"` will create an
+optimized build that loads localizations from that URL at runtime.
## Updating CodeMirror
@@ -71,3 +107,6 @@ can be updated with the `bin/update-codemirror.py` script.
[slowparse]: https://github.com/mozilla/slowparse
[hacktionary]: https://github.com/toolness/hacktionary
[CodeMirror]: http://codemirror.net/
+ [transifex]: https://www.transifex.com/projects/p/friendlycode/
+ [resource management page]: https://www.transifex.com/projects/p/friendlycode/resources/
+ [Property List]: http://help.transifex.com/features/formats.html#plist-format
View
@@ -1,28 +1,45 @@
var fs = require('fs');
var sys = require('sys');
-var resolve = require('path').resolve;
+var path = require('path');
var buildRequire = require('./build-require');
var rootDir = buildRequire.rootDir;
var requirejs = require('requirejs');
var config = buildRequire.generateConfig();
var bundles = exports.bundles = {};
+var packageJson = JSON.parse(fs.readFileSync(path.join(rootDir, "..",
+ "package.json"),
+ "utf-8"));
-function findNlsPaths(root, subdir) {
- var nlsPaths = [];
- subdir = subdir || '';
- fs.readdirSync(root + subdir).forEach(function(filename) {
- var relpath = subdir + '/' + filename;
- var stat = fs.statSync(root + relpath);
- if (stat.isDirectory()) {
- if (filename == 'nls') {
- nlsPaths.push(relpath.slice(1));
- } else
- nlsPaths = nlsPaths.concat(findNlsPaths(root, relpath));
+var makePlist = exports.makePlist = function(bundle) {
+ var escapeXML = function(str) {
+ return str.replace(/&/g, '&')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;')
+ .replace(/"/g, '&quot;');
+ };
+ var lines = [
+ '<?xml version="1.0" encoding="UTF-8"?>',
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" ' +
+ '"http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
+ '<plist version="1.0">',
+ ' <dict>'
+ ];
+
+ Object.keys(bundle.root).forEach(function(key) {
+ if (bundle.metadata && bundle.metadata[key]) {
+ var metadata = bundle.metadata[key];
+ var helpText = metadata.helpText || metadata.help;
+ if (helpText)
+ lines.push(' <!-- ' + escapeXML(helpText) + ' -->');
}
+ lines.push(' <key>' + escapeXML(key) + '</key>');
+ lines.push(' <string>' + escapeXML(bundle.root[key]) + '</string>');
});
- return nlsPaths;
-}
+ lines.push(' </dict>');
+ lines.push('</plist>');
+ return lines.join('\n');
+};
function loadModulesInNlsPath(path) {
fs.readdirSync(rootDir + '/' + path).forEach(function(filename) {
@@ -42,19 +59,24 @@ function loadInlineL10nStrings() {
var templateDir = requirejs.toUrl(templateCfg.htmlPath).replace(".js", "");
var root = bundles[templateCfg.i18nPath].root;
var metadata = bundles[templateCfg.i18nPath].metadata;
+ var relTemplateDir = path.relative(path.normalize(__dirname + '/..'),
+ templateDir);
fs.readdirSync(templateDir).forEach(function(filename) {
var content = fs.readFileSync(templateDir + '/' + filename, 'utf8');
var defaultValues = InlineL10n.parse(content);
for (var key in defaultValues) {
var value = defaultValues[key];
+ var githubUrl = packageJson.repository.url.replace(".git", "/blob/") +
+ packageJson.repository.defaultBranch +
+ '/' + relTemplateDir + '/' + filename;
if (key in root && root[key] != value)
throw new Error("conflicting definitions for key: " + key);
root[key] = value;
metadata[key] = {
+ helpText: 'This string appears in ' + githubUrl + '.',
help: 'This string appears in ' +
- '<a href="' + config.githubUrl + '/blob/gh-pages/templates/' +
- filename + '">' + filename + '</a>.'
+ '<a href="' + githubUrl + '">' + filename + '</a>.'
};
}
});
@@ -101,6 +123,13 @@ function main() {
.action(function() {
sys.puts(JSON.stringify(bundles, null, 2));
});
+ program
+ .command('plist [module-name]')
+ .description('output plist file for an i18n bundle module')
+ .action(function(moduleName) {
+ validateNlsModuleName(moduleName);
+ sys.puts(makePlist(bundles[moduleName]));
+ });
program.parse(process.argv);
if (process.argv.length == 2)
program.help();
@@ -109,7 +138,7 @@ function main() {
config.isBuild = true;
requirejs.config(config);
-findNlsPaths(rootDir).forEach(loadModulesInNlsPath);
+buildRequire.findNlsPaths(rootDir).forEach(loadModulesInNlsPath);
loadInlineL10nStrings();
if (!module.parent) main();
View
@@ -1,4 +1,6 @@
var requirejs = require('requirejs'),
+ fs = require('fs'),
+ url = require('url'),
jsdom = require('jsdom').jsdom,
resolve = require('path').resolve,
requireConfig = require('../js/require-config'),
@@ -13,7 +15,26 @@ var bailOnError = function(err) {
process.exit(1);
};
-var generateConfig = exports.generateConfig = function() {
+var findNlsPaths = exports.findNlsPaths = function(root, subdir) {
+ var nlsPaths = [];
+ subdir = subdir || '';
+ fs.readdirSync(root + subdir).forEach(function(filename) {
+ var relpath = subdir + '/' + filename;
+ var stat = fs.statSync(root + relpath);
+ if (stat.isDirectory()) {
+ if (filename == 'nls') {
+ nlsPaths.push(relpath.slice(1));
+ } else
+ nlsPaths = nlsPaths.concat(findNlsPaths(root, relpath));
+ }
+ });
+
+ return nlsPaths;
+};
+
+var generateConfig = exports.generateConfig = function(options) {
+ options = options || {};
+
var config = {
name: name,
out: jsOut,
@@ -42,15 +63,34 @@ var generateConfig = exports.generateConfig = function() {
config[name] = requireConfig[name];
});
config.baseUrl = rootDir;
+
+ if (options.i18nUrl) {
+ var runtimePathConfig = {paths: {}};
+ findNlsPaths(rootDir).forEach(function(path) {
+ config.paths[path] = "empty:";
+ runtimePathConfig.paths[path] = url.resolve(options.i18nUrl, path);
+ });
+ config.wrap = {
+ start: "require.config(" + JSON.stringify(runtimePathConfig) + ");",
+ end: ""
+ };
+ }
+
return config;
};
exports.rootDir = rootDir;
-if (!module.parent) {
+function main() {
+ var program = require('commander');
+
+ program
+ .option('--i18n-url [url]', "base URL to i18n bundles")
+ .parse(process.argv);
+
console.log("Generating", jsOut);
- requirejs.optimize(generateConfig(), function (buildResponse) {
+ requirejs.optimize(generateConfig(program), function (buildResponse) {
// buildResponse is just a text output of the modules
// included.
console.log("Done. About " + buildResponse.split('\n').length +
@@ -64,3 +104,5 @@ if (!module.parent) {
}, bailOnError);
}, bailOnError);
}
+
+if (!module.parent) main();
Oops, something went wrong.

0 comments on commit ad9c3e9

Please sign in to comment.