Skip to content

Instantly share code, notes, and snippets.

@ErikAndreas
Last active December 18, 2015 16:49
Show Gist options
  • Save ErikAndreas/5814231 to your computer and use it in GitHub Desktop.
Save ErikAndreas/5814231 to your computer and use it in GitHub Desktop.
Proof of concept of full stack and tooling for AngularJS i18n gettext-style using Jed, pybabel and po2json. UPDATE: will now be maintained at https://github.com/ErikAndreas/lingua and tooling at https://github.com/ErikAndreas/grunt-lingua

Been looking for a full stack including tools for gettext-style i18n with AngularJS.

  • Gettext-style support in markup (html and javascript) supporting singular, plural and interpolation/sprintf
  • Tooling for extraction of strings to be translated (to .pot) from html and javascript
  • Tooling for generating .json of .po files

Ended up (working proof of concept) with the following:

  • "lingua", an AngularJS module wrapping Jed
  • Some AngularJS bootstrapping
  • pybabel for xgettext style translations extraction (to .pot)
  • po2json for generating .json files (per translation) from .po files
<!-- sample usage html/partial/view markup -->
{{_("Your last.fm username")}}:<br/>
<input ng-model="lastFMuserName"/> <button ng-click="setLastFMuserName()">{{_("Set")}}</button>
<h3>{{_n("Suggestion","Suggestions",suggs.length)}}</h3>
// sample usage in Angular service
angular.module('swl').factory('artistAlbumModelService',['linguaService', ... ,function(linguaService, ...) {
addArtistAlbum:function(ar,al) {
if (artistAlbumModelService.containsArtistAlbum(ar,al)) {
statusService.add('error',linguaService._("Skipping duplicate, %1$s %2$s is already in the list",[ar,al]));
...
}]);
// sample usage in Angular controller
angular.module('swl').controller('SettingsCtrl',['$scope', ..., function($scope, ...) {
statusService.add('info',$scope._("import complete"));
}]);
// init
var Lingua = {
init:function(doc,cb) {
"use strict";
var locale = localStorage.getItem('locale');
console.log(locale);
if (locale) {
var s = doc.createElement('script');
s.setAttribute('src', "//code.angularjs.org/1.1.5/i18n/angular-locale_"+locale+".js");
doc.body.appendChild(s);
} else {
locale = "en-us";
}
microAjax('l_'+locale+'.json',function(data) {
data = JSON.parse(data);
var i18n = new Jed({
"domain" : locale,
"missing_key_callback" : function(key) {
console.error("Missing key " + key + " for " + locale);
},
locale_data : data
});
console.log(i18n);
window.i18n = i18n;
cb();
});
}
};
// the lingua module
angular.module('lingua',[]);
angular.module('lingua').factory('linguaService',function() {
var linguaService = {
_:function(singular, vars) {
return i18n.translate(singular).fetch(vars);
},
_n:function(singular, plural,n, vars) {
if (n) {
return i18n.translate(singular).ifPlural(n, plural).fetch(n);
} else {
return i18n.translate(singular).fetch(vars);
}
}
};
return linguaService;
});
// usage in view/partial: ng-click:changeLocale('sv-se);
angular.module('lingua').controller('linguaController',['$scope', '$window',function linguaController($scope,$window) {
$scope.changeLocale = function(locale) {
// so, only way to reload $locale is on full page reload
// and load angular-locale_xx-yy.js
localStorage.setItem('locale',locale);
$window.location.reload();
};
}]);
// Angular init stuff
angular.module('swl').run(['$rootScope',...,'linguaService',function($rootScope,...,linguaService) {
$rootScope._ = linguaService._;
$rootScope._n = linguaService._n;
...
}]);
<!doctype html>
<html lang="en" xmlns:ng="http://angularjs.org"> <!-- manual bootstrap so no ng-app -->
...
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers.js"></script>
<script src="js/services.js"></script>
<script src="js/directives.js"></script>
<script src="js/lingua.js"></script>
<script src="js/vendor/jed.js"></script>
<script src="js/vendor/microajax.js"></script>
<script>
angular.element(document).ready(function() {
console.log('angular doc ready');
Lingua.init(document, function() {
angular.bootstrap(document, ['swl']);
});
});
</script>
</body>
</html>

##Requirements:

  1. python + pybabel + jinja2
  2. nodejs + >npm install -g po2json

##Simple babel.cfg file: [javascript:*.js] encoding = utf-8

[jinja2: *.html] encoding = utf-8

##Workflow: ###Generate .pot file:

pybabel extract -F babel.cfg -k _n:1,2 -k _ -o translations/messages.pot . partials js

###Translations tool like poedit to create a catalog and make translations (outputs .po/.mo files)

###Generate .json

po2json translations/sv-se.po l_sv-se.json

@ErikAndreas
Copy link
Author

Glad if it helped!

I've yet not found any issues, however - the extraction (look at the pybabel command) and the functionality (wrapping Jed) is based on pure function calls within an expression (no filtering), i.e _("string to extract") or _n("singular", "plural", n)

I don't understand how (or why) you'd want to extract (for translation) what your example denotes.

But, it is problematic when/if pybabel fails (silently) and stops extraction for that entire file.

This is the most gettext-like workflow for Angular (with no server-side magic) I've been able to find/come up with, any suggestions for improvements will be most appreciated!

@0x-r4bbit
Copy link

Not gettext-style but maybe interesting for you guys @paumoreno @ErikAndreas: angular-translate -http://pascalprecht.github.io/angular-translate

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