Skip to content
crowbot edited this page Jan 24, 2013 · 24 revisions

Deployment notes

Deployed translations for the project live in locale/.

Translations live in the project page at Transifex 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 (e.g. by downloading them from Transifex)
  • Add define('OPTION_AVAILABLE_LOCALES', 'en es') to general/config

The pot-file at locale/app.pot acts as the template for PO files. When new translation strings have been added to the source, it 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.

See TRANSLATE.md in the docs for more details.

Historical background for some of the rationale behind the current i18n implementation is discussed at i18n approach.

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

How to add i18n strings to the source. Please also see Marking up text for translation:

  • 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, e.g.
      :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 (q.v.) 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:

    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:

          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 to localize URLs. This looks up URL segments as translation strings in a YAML file atconfig/i18n-routes.yml. However, we ditched it on the grounds that it was overly complex. Notes retained here for reference:

For the plugin to work correctly, it needs a single, named route for each possible route, meaning that routes like help.help_general '/help/:action', :action => :action won't work -- because the URL portion dynamically selects a controller action.

To see how the plugin sets up translatable routes, run the rake routes tasks. You'll see that for each named route in the routing table, there is a new, named route corresponding to each active locale; for example, if you are using Spanish and English, the admin_user_update route becomes two routes, admin_user_update_es and admin_user_update_en.

To create a new YAML template when your routes have changed, run rake translate_routes:update_yaml["es"] and then update i18n-routes.yml correspondingly.

Note that this workflow needs improving! We should probably keep each locale's routes in a separate YAML file and only load the ones we need at runtime (right now everything in i18n-routes.yml is loaded). Ideally, we would also make the translations come from the locale's PO file.

Clone this wiki locally