Guide to localizing a Rails application
Localizing a non-trivial application can be a huge undertaking. This card will give you an overview over the many components that are affected.
When you are asked to give an estimate for the effort involved, go through the list below and check which points are covered by your requirements. Work with a developer who has done a full-app localization before and assign an hour estimate to each of these points.
- Static strings and template text in
appmust be translated: Screens, mailer templates, PDF templates, helpers, sometimes models.
- Use the native Rails I18n API or Gettext.
- Native I18n has good integration with Rails (you already have it!), but you need to come up with consistent variable names for all translated strings. Use it if the number of translated strings is limited.
- Integrating Gettext is painful and cryptic, but it doesn't require you to name variables (the untranslated string serves as the translation key). Use Gettext if there are a million strings to translate. For integration, get help from someone who has gone through the pain before and choose wisely between the many available Gettext/Rails integration gems.
Strings generated by Rails
- All strings that are generated by the Rails framework can be overridden in locale dictionaries (e.g.
config/locales/de.yml). There's an awesome gem rails-i18n that gives you default dictionaries for many languages.
- Note that even though all strings can be translated through locale dictionaries, you might have grammar issues when Rails concatenates multiple strings. E.g. the
pluralizehelper or the default validation error messages (humanized attribute name + error message) make assumptions about sentence structure which are not true in all languages. In such cases you must intervene with custom error messages and additional dictionary entries.
- Tight layouts might have overflowing content boxes in verbose languages such as German. You might need to make the layout more spacious, or make language-specific customizations by giving your
langattribute and use that to scope language-specific style declarations.
- If you have any text in images (logos, buttons), you need to produce localized variants, e.g.
- Make sure you have access to the Gimp or Photoshop documents that were used to create these images.
- This might be a good moment to rewrite image buttons in pure CSS. You can make awesome buttons with CSS3 these days.
- If there is any localized content in your database (e.g. article names) you need to add additional fields to keep track of all localized versions, e.g.
Article#namereturns the variant in the current locale.
- You also need a migration strategy to fill those fields for existing records.
- You might need to make code that uses those fields locale-aware, e.g. a scope that finds a query by looking into
User generated content
- If users are creating content on your site, check if you need to know the language of that user generated content.
- Check if you need to shard your content. E.g. should Spanish users only see comments by Spanish users?
- Users will need a way to switch languages, probably through a language switcher that is always visible in the layout. You should store the user's choice in the database.
- Anonymous users who have not signed in also need a way to switch languages. You should store that choice in a cookie with faraway expiry.
- You will need to guess a default language when you haven't seen a session before, e.g. by looking at the requests's IP or
Accept-LanguageHTTP header. Note that even the best default never replaces a language switcher.
Routes and URLs
- You might want to localize route components so e.g.
/nachrichten/neu. You can do that with vanilla Rails router options. I recommend to only localize routes that are at the center of the user experience, e.g. URLs the user would print, bookmark or share with friends.
- If your routes have slugs like
/articles/1033-colorful-scarfyou might want this to localize that to e.g.
- Your localized pages should have a unique URL for each language, so Google can index each version separately. E.g. there should be a
/de/users/5003. The routing-filter gem comes with a filter that helps you with that. You must also handle the case where the user has switched to language
debut accesses a
/en/fooURL (redirect or let the URL win).
- Dates are formatted differently in different regions. Names for months and weekdays differ.
- Times are formatted differently in different regions. Some regions use a 12 hour clock instead of 24 hours.
- Numbers and money amounts have different fractional separators and thousands delimiters in different regions (although you might choose not to care).
- Phone numbers are formatted diffently in different regions (although you might choose not to care).
- If you are accepting dates/times/numbers/phone numbers in text inputs, you must also be locale-aware when rendering or parsing the input using hacks like this one.
- Format patterns should live in your locale dictionary (e.g.
config/locales/de.yml). They already do for dates, times, numbers and money amounts.
- There's a method
I18n.lthat can convert date and time values according to the rules of the current locale dictionary. Unfortunately you need to roll your own helpers for all other types of values (e.g. numbers, currencies).
- If different locales must be served different currencies or scales (e.g. length, speed), you must transparently convert back-and-forth under the hood.
- Depending on your requirements it might be a good idea to store consistent units in the database and convert values when rendering and parsing input. In other cases (e.g. currencies with changing exchange rates) this might not be possible.
- There's a gem that gives you your Rails locale dictionary and the
External services (e.g. Paypal)
- You might have to configure/skin external services like PayPal so its UI will show up in the correct language.
- Adding support for time zones is a lot of pain. Double-check if you need this. Maybe it's OK for your users to see times in another timezone, or maybe you don't need to show times and dates (sometimes "4 hours ago" can work).
- Rails has native support for time zones. You can set it up to store timestamps as UTC in the database, but return magic, zone-aware values when accessing ActiveRecord time or date attributes. In practice this solution is too clever and has many issues that can lead to stored timestamps being off by some hours (1, 2, 3, 4). You can control this, but you will shed tears.
- You must make time zones switchable in the UI, just like the language: Store the choice with user (or cookie), use a sensible default time zone, etc.
- Note that the time zone cannot be inferred from the language and vice versa.
- Check if you need to make any business logic aware of the locale. E.g. other countries might have different taxes, or you need additional form fields (like tax IDs) to bill customers in another language.
- Tests should continue to use the base language you had before starting localization. Don't rewrite all tests and don't test every screens with every language unless repeatedly broken translations becomes a pain point.
- Do test non-trivial language-specific customizations like the parsing of number formats and locale-aware business logic.
- Do test all localization artifacts in the UI like language switchers and localized URLs.
- If you are caching content with localized strings, make sure that the locale is part of the cache key.