aboutsummaryrefslogtreecommitdiffstats
path: root/docs/developers/i18n.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/developers/i18n.md')
-rw-r--r--docs/developers/i18n.md161
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 &mdash; 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&nbsp;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/).
+