10 Things You Might Not Know About Rails i18n
This is mostly a transcript of my recent lightning talk on i18n in Rails , go watch that if you prefer your content in video form. Otherwise read on to find out why I believe understanding i18n in Rails can be useful to you, a Rails developer, even if you’re not translating your applications into other languages. I’m going to share ten things I think are interesting in i18n, and explain how we can take advantage of them in our single language app code. But first a brief primer on i18n… i18n is shorthand for Internationalisation When we’re talking about i18n, it’s useful to understand the difference between Internationalisation and Localisation : The take away here is that although i18n is primarily a framework for adapting applications to support other languages, the core idea of separating content from applications code has useful properties that we can take advantage of it to improve our Rails applications, even if they only support a single language. Rails ships with the i18n gem , and there are two main components: Rails aliases these helpers in views and helper modules to the convenient shorthands of and respectively. So the most basic usage of i18n would look something like this: Primer over! On to the ten things… Even if you’re not localising your app you’re almost certainly still using i18n. That’s because it’s baked into the very fabric of the framework. For example Active Record uses i18n under the hood to generate error messages for its validations. It does so using a YAML file that ship with Active Model : Whenever you see something like “Title cannot be blank”, this is the i18n plumbing that was used to construct it. Rails i18n makes it easy to override these default errors messages. Just replicate the same key structure in your application’s locale file with your chosen phrasing: Rails will check your application’s locale file first, falling back to the defaults only if the message isn’t defined there. As well as overriding this global phrasing, it’s also possible to override the error messages for a given attribute name: Or you can get even more granular and override specific attributes on a given model. For example if we have the following model and validation: It’s possible to set a bespoke error message for this specific model and attribute: As well as overriding individual validation messages globally and for specific attributes, it’s also possible to change the actual format of error messages. By default, errors are constructed with the familiar format of . This is defined in the same YAML that ships with Active Model alongside the default error messages: This format works as a reasonable default to ship with the framework as it gives us mostly coherent error messages out of the box without us having to do anything, but it lacks fidelity and can result in some clunky and unfriendly sounding messages. For example, take this validation: If we go with the default format we end up with . Which is fine, but we can definitely do better. My personal preference on the apps I work with is to remove the attribute prefix entirely: This frees me from having to make my error messages work with attribute’s name upfront, allowing me to write more user-friendly and coherent error messages: Now changing the default format does come at a cost: without the attribute name prefix, the default messages that ship with Rails no longer form complete sentences. This forces me to write bespoke error messages for each validation I add to my app: Personally I’m happy to pay this cost for the benefit of improving the user experience. So whilst you may not want to do this on an existing app with thousands of error messages to backfill, it might be something you consider next time you run and are starting from a clean slate. Update : There’s actually a fairly neat way to avoid this cost, check out my follow-up post on how . As well using i18n to manage and override the error messages for the built-in validations, you can also use it to manage the messages for your custom validations. Let’s look at a custom model validation: Here the error message is inlined as part of the validation code. Instead of doing this, we can pass in a symbol identifying the error, and define the corresponding message in the locale file using the same key structure as the built-in validations: This results in more concise and compact model code, but also equally as nice is that it removes a presentational concern — the specific phrasing of the message — our the business logic. It’s also possible to interpolate both the attribute name and the value of the attribute directly into the error message, which is useful for custom validations that are used across multiple attributes and/or models: Another place where Rails makes use of i18n under the hood is the form helpers: specifically, the label helper methods. By default the label helper methods humanize the attribute name to arrive at the label text: One way to override the label text is to pass in a string to the helper call like so: I think a better way is to define label overrides in the application’s locale file: Not only does this keep the view code more concise and less noisy, but it has the added advantage of defining this label override in one place: now any other forms that display the same label will automatically get the overridden text without you having to manually copy the literal string to every template. And as with form errors, label overrides can be defined per-model as well as globally: i18n has sophisticated support for pluralisation that we can leverage to simplify our app code. As an example, we can display the stock availability of a product by calling out to i18n like so: And then define the phrasing based on the number of items in stock in our locale file: Much neater than a bunch of conditional logic in a helper method! As well as being available in Rails controllers and views, i18n is also integrated into Action Mailer, allowing you to organise content for mailer templates in local files. Mailer get one extra feature of controllers: the ability to specify email subject lines in locale files: Again, this enables us to move another presentational concern out of our application code and put it somewhere more appropriate. There is also a meta benefit to defining all these bits of content (mailer subject lines, form labels, validation error messages) in an application’s locale file: it puts them all in one centralised place, making them easier to change, check for consistency and generally manage. It also makes the content more accessible to non-developers such as designers and product folks. Right at the top I mentioned the method that is used to output localised times and dates. The way this works is by defining our own time and date formats in our locale file using the same format specification as Ruby’s : Here we’ve defined a short date format that will be familiar to my UK-based audience: day/month/year. To render a date using this format we simply pass in the format identifier to (or its alias): or with the shorthand: Now my American readers may be confused by this short format, as across the pond the preferred way to display dates is actually month/day/year. So whilst you may not need to localise your entire application, you might want to consider localising your date rendering, especially if you have customers both sides of the Atlantic. To do this you would define a US region specific English locale file: Then update your controller code to automatically switch locale so the user gets dates formatted appropriately for them. There are many ways to do actually do the locale switching, but at a high-level, you identify the user’s specific locale and then make sure your controller actions are executed with that locale. Here’s an example of doing that by sniffing the user’s locale from their browser headers: (The exact mechanics of the method is left as a separate exercise, but you could do worse than use the http_accept_language library) Little known fact: the i18n library supports locale files defined using Ruby! Now why would you do this, other than a burning hatred for YAML? Well one reason is that Ruby-based locale files allows you to make use of procs to dynamically generate content. Below is an excerpt from a Ruby-based locale file from one of my applications. It uses a proc to format dates as academic years, changing the output depending on which side of September the date is: Now this could just as easily be achieved using a standard Rails helper method. But for me I like the consistency and clarity of using a single mechanism ( ) for formatting dates across my application code, rather than with a mishmash of calls to bespoke helpers, , , etc. This is the part of the lightning talk where I make a meta point about what I’ve covered. I won’t write that up here, but if you’re interested go watch the video from minute 9. Internationalisation — the process of abstracting content and other locale-specific things away from application code itself Localisation — the process of adapting software to support different languages and regions (maybe using i18n) locale files where textual content is organised and stored per-language (normally as YAML) two helper methods: for outputting text content from the locale files for localising dates and times