Table of Contents
If you modify the English translation file config/locales/en.yml, run:
bin/rake prepare_translations
before pushing to GitHub. This will normalize translation file formatting and check for missing or unused keys.
We're using translation.io to manage localization: translation.io/bikeindex/bike_index
To contribute, sign up for an account there and ask to be added to the project as a translator.
Non-English translation files should be treated as read-only. We sync these with our translation.io project.
In the view layer (templates and helper modules), externalize strings using the
I18n.t()
translation helper directly. Templates typically use the
"lazy" lookup form.
To aid translation, it's desirable to externalize strings in a form as close to semantically complete as possible.
In particular, use coarse conditional branching in templates. Duplication of user-facing copy is desirable whenever its alernative is to break up a string into units that, in isolation, might lose their context or encode an assumption about word order that doesn't hold in another language.
For example, instead of
- when = expedited ? "soon" : "eventually"
= "You'll #{when} receive your delivery"
prefer
- if expedited?
= "You'll soon receive your delivery"
- else
= "You'll eventually receive your delivery"
The former externalizes as
soon: spoedig
youll_receive_delivery: U ontvangt %{wanneer} uw levering ontvangt
But the translation we'd want is:
youll_soon_receive_delivery: U ontvangt binnenkort uw levering
Prefer URL helpers to hard-coded URLs. The former will forward the locale
query param when it's present.
Embedded links are typically translated separately and passed to an enclosing
html_safe
translation (note the _html
suffix in the
translation key):
- logout_link = link_to t(".log_out"), session_path(redirect_location: 'new_user'), method: :delete
= t(".if_you_dont_want_that_to_be_the_case_html", logout_link: logout_link)
Mailers are namespaced by mailer name, email name, and email format as follows
(note the .text
and .html
in the translation keys):
# config/locales/en.yml
organization_invitation:
html:
you_are_a_member: "%{sender_name} has indicated that you are a member of %{org_name}."
text:
you_are_a_member: "%{sender_name} has indicated that you are a member of %{org_name}."
-# app/views/organized_mailer/organization_invitation.html.haml
%p= t(".html.you_are_a_member", sender_name: @sender.display_name, org_name: @organization.name)
-# app/views/organized_mailer/organization_invitation.text.haml
= t(".text.you_are_a_member", sender_name: @sender.display_name, org_name: @organization.name)
Controllers use the translation
helper method defined in ControllerHelpers
.
This method wraps I18n.translate
and infers the scope in accordance with the
convention of scoping translations by their lexical location in the code base.
Both :scope
and :controller_method
can be overriden using the corresponding
keyword args. Note that base controllers should be passed :scope
or
:controller_method
explicitly. See the translation
method docstring for
implementation details
# app/controllers/concerns/controller_helpers.rb
def translation(key, scope: nil, controller_method: nil, **kwargs)
# . .
scope ||= [:controllers, controller_namespace, controller_name, controller_method.to_sym]
I18n.t(key, **kwargs, scope: scope.compact)
end
Client-side translations are defined under the :javascript
keyspace in en.yml
.
# config/locales/en.yml
javascript:
bikes_search:
The translation method can be invoked directly as I18n.t()
and passed a
complete scope:
<span className="attr-title">{I18n.t("javascript.bikes_search.registry")}</span>
Equivalently, a curried instance of I18n.t
can be initiated locally (by
convention, bound to t
) with the local keyspace set as needed:
// app/javascript/packs/external_registry_search/components/ExternalRegistrySearchResult.js
const t = BikeIndex.translator("bikes_search");
// . . .
<span className="attr-title">{t("registry")}</span>
A client-side JS translations file is generated when the prepare_translations
rake task is run. See PR #1353 for implementation details.
When building main, we check for un-synced translations and, if any are found, stop the build and open a PR to main with the translation updates.
You'll want to merge this PR (delete the description) to trigger a new build and retry deployment on main.
(See #1100 for details.)
To manually update the keys on translation.io, run
bin/rake translation:sync_and_purge
(requires having an active API key locally).