-
-
Notifications
You must be signed in to change notification settings - Fork 196
I18n guide
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
andlocale/es/app.po
(e.g. by downloading them from Transifex, at least for the time being) - Add
define('OPTION_AVAILABLE_LOCALES', 'en es')
togeneral/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.
We use the translate_routes plugin to localize URLs. This looks up
URL segments as translation strings in a YAML file atconfig/i18n-routes.yml
.
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.
Only parts of the templates are i18n-aware (e.g. Home page, some search pages). Please help get them all that way! These notes may be of help in continuing to add i18n strings to the source:
- 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.
_('<a href="%s">Browse all</a> or <a href="%s">ask us to add it</a>.') % [url1, url2]
Similar rules can apply to strings in the python source code, as long as you import _
, n_
, etc.
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:
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.
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
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)