diff options
Diffstat (limited to 'docs/developers')
-rw-r--r-- | docs/developers/i18n.md | 143 |
1 files changed, 143 insertions, 0 deletions
diff --git a/docs/developers/i18n.md b/docs/developers/i18n.md new file mode 100644 index 000000000..480d0d954 --- /dev/null +++ b/docs/developers/i18n.md @@ -0,0 +1,143 @@ +--- +layout: page +title: Internationalisation (for devs) +--- + +# Internationalisation in the code + +<p class="lead"> + This page describes some technical aspects of internationalising the + Alaveteli code. It's mostly aimed at devs who are working on the + codebase — if you just want to translate Alaveteli into your + own langauge, see this page on + <a href="{{ site.baseurl }}customising/translation">Translating Alaveteli</a> + instead. +</p> + + +## Deployment notes + +Deployed translations for the project live in ``locale/``. + +Translations live in the project page at +[Transifex](https://www.transifex.net/projects/p/alaveteli/) and should +be submitted there. + +To deploy, say, English and Spanish translations at once: + + * Ensure their PO files are at ```locale/en/app.po``` and ```locale/es/app.po``` + (for example, by downloading them from Transifex) + * Set <code><a href="{{ site.baseurl }}customising/config/#AVAILABLE_LOCALES">AVAILABLE_LOCALES</a></code> + to <code>en es</code> + +The ``pot``-file at ``locale/app.pot`` acts as the template for PO files. When +new translation strings have been added to the source, this ``pot``-file can be +updated using the script at ``script/generate_pot.sh``. This looks for new +translatable strings in the source and creates entries in the ``pot``-file. + +For more details about the translations, see the page about +[translating Alaveteli]({{ site.baseurl }}customising/translation). + + +## Technical implementation details + +### Getting the current locale + +This is complicated by the fact that there are two competing ways to define a +locale+territory combination. The POSIX (and `gettext` and Transifex) way is +like `en_GB`; the Rails way is like `en-US`. Because we are using gettext and +Transifex for translations, we must deal with both. Wherever you need to know +the Rails version of the currently selected locale, use `I18n.locale`; wherever +you want to know the POSIX version of the locale, use `FastGettext.locale`. + +## I18n in templates + +Before you add i18n strings to the source, you should read +[internationalisation guidelines](http://mysociety.github.io/internationalization.html) +that apply to all our projects. + +Some hints for adding the strings into the Alaveteli code: + +* Simple strings: ```<% = _("String to translate") %>``` +* Strings that include variables: give the translator a hand by inserting + strings that can be interpolated, so the variable has meaning. For example, + ```<%= "Nothing found for '" + h(@query) + "'" %>``` might become ```<%= + _("Nothing found for '{{search_terms}}'", :search_terms => h(@query)) %>``` +* Strings containing numbers: ```<%= n_('%d request', '%d requests', @quantity) % @quantity %>``` +* We allow some inline HTML where it helps with meaningful context, for example: + +``` +_('<a href="{{browse_url}}">Browse all</a> or <a href="{{add_url}}">ask us to add it</a>.', + :browse_url => @browse_url, :add_url => @add_url) +``` + +Similar rules can apply to strings in the python source code, as long as you +import ```_```, ```n_```, etc. + +## Programmatic access of translated PublicBodies + +Apart from the templates, the only other area of i18n currently implemented is +in the PublicBodies. + +The implementation allows for getting different locales of a PublicBody like so: + +```ruby + PublicBody.with_locale("es") do + puts PublicBody.find(230).name + end +``` + +Usually, that's all the code you need to know about. There's a method +```self.locale_from_params()``` available on all models which returns a locale +specified as ```locale=xx``` in the query string, and which falls back to the +default locale, that you can use in conjunction with the ```with_locale``` +method above. All the joining on internal translation tables should usually be +handled automagically -- but there are some exceptions, that follow below. + +### Overriding model field setters + +Internally, we use the [Globalize plugin](https://rubygems.org/gems/globalize) +to localize model fields. Where column "foo" has been marked in the model as +```:translates```, globalize overrides ```foo.baz = 12``` to actually set the +value in column ```baz``` of table ```foo_translations```. + +A side effect of the way it does this is that if you wish to override a +specific attribute setter, you will need to explicitly call the Globalize +machinery; something like: + +```ruby + def name=(name) + globalize.write(self.class.locale || I18n.locale, "name", name) + self["name"] = short_name + # your other stuff here + end +``` + +### Searching + +The ```find_first_by_<attr>``` and ```find_all_by_<attr>``` magic methods +should work. If you want to do a more programmatic search, you will need to +join on the translation table. For example: + +```ruby + query = "#{translated_attr_name(someattr) = ? AND #{translated_attr_name('locale')} IN (?)" + locales = Globalize.fallbacks(locale || I18n.locale).map(&:to_s) + find( + :first, + :joins => :translations, + :conditions => [query, value, locales], + :readonly => false + ) +``` + +You may also need to do some lower-level SQL joins or conditions. See +```PublicBodyController.list``` for an example of a query that has a condition +that is explicitly locale-aware (look for the ```locale_condition``` variable) + + +## I18n in URLs + +We have tried using the [translate_routes plugin](https://github.com/raul/translate_routes) +to localize URLs. This looks up URL segments as translation strings in a YAML +file at```config/i18n-routes.yml```. However, we no longer use it because we +found it was overly complex. |