diff options
Diffstat (limited to 'docs/developers/i18n.md')
-rw-r--r-- | docs/developers/i18n.md | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/docs/developers/i18n.md b/docs/developers/i18n.md new file mode 100644 index 000000000..deabc99a1 --- /dev/null +++ b/docs/developers/i18n.md @@ -0,0 +1,161 @@ +--- +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 language, see + <a href="{{ site.baseurl }}docs/customising/translation">translating Alaveteli</a> + instead. +</p> + +## Deployment notes + +Deployed translations for the project live in ``locale/``. + +We encourage translations to be done on +[Transifex](https://www.transifex.net/projects/p/alaveteli/) +because translators can work through its web interface rather than needing to edit the +<a href="{{ site.baseurl }}docs/glossary/#po" class="glossary__link">`.po` and `.pot` files</a> +directly. Ultimately, Transifex just captures translators' +work and turns it into the files that Alaveteli needs (using gettext). + +### How to get the latest translations onto your site + +For example, to deploy 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 }}docs/customising/config/#available_locales">AVAILABLE_LOCALES</a></code> + to <code>en es</code> + +### How to add new strings to the translations + +You need to do this if you've added any new strings to the code that need +translations (or if you change an existing one). + +To update the +<a href="{{ site.baseurl }}docs/glossary/#po" class="glossary__link">`.po` or `.pot` files</a> +for each language, run: + + bundle exec rake gettext:store_model_attributes + +followed by: + + bundle exec rake gettext:find + +If `gettext:find` only creates the file `locale/im-config.pot` then you need to +unset the `TEXTDOMAIN` environment variable and try again. + +For more details about the translations, see the page about +[translating Alaveteli]({{ site.baseurl }}docs/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. + + * for the Rails version of the currently selected locale, use `I18n.locale` + * for 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 Ruby source code. + +## 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://github.com/globalize/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) + +## Translation and releases + +The release manager will enforce a translation freeze just before a new release +is cut. During such time, you must not introduce new strings to the code if +your work is due for inclusion in this release. This is necessary to allow +translators time to complete and check their translations against all the known +strings. See more about [translating Alaveteli]({{ site.baseurl }}docs/customising/translation/). + |