How do you structure i18n yaml files in Rails?
Asked Answered
R

6

52

I started populating an en yaml file in Rails and I can already tell it will get messy and out of hand before too long. Is there a convention to keeping this file organized?

So far I have this structure:

language:
  resource:
    pages: # index, show, new, edit
      page html elements: # h1, title
  activerecord:
    attributes:
      model:
        property:

Now I have the following things that I want to fit into this structure but I'm unsure how to:

  1. Navigation
  2. Button text (save changes, create account, etc)
  3. Error messages from controller flash
  4. How to add multi-word keys. Do I use a space or an underscore? For exmaple, t(".update button")) or t(".update_button")

Is there a convention to locale file structure?

Ringhals answered 23/4, 2012 at 14:50 Comment(1)
You can now use lazy lookups with your own custom locale namespaces: github.com/abitdodgy/i18n_lazy_scopeRinghals
T
63

I've found that the best overall strategy is to somewhat reproduce the file structure so that given any translation, I can immediately find where it was called from. This gives me some kind of context for making the translation.

The majority of application translations are found in views, so my biggest top level namespace is usually views.

I create sub namespaces for the controller name and the action name or partial being used ex :

  • views.users.index.title
  • views.articles._sidebar.header

Both of these examples should make it obvious what part of my app we're translating and which file to look in to find it.

You mention navigation and buttons, if they are to be generic, then they belong in the views.application namespace just as do their view counterparts :

  • views.application._main_nav.links.about_us - a link in our app's main navigation partial
  • views.application.buttons.save
  • views.application.buttons.create - I have a bunch of these buttons ready to be used when needed

Flash messages are generated from the controller, so their top level namespace is... controllers! :)

We apply the same logic as we do to views :

  • controllers.users.create.flash.success|alert|notice

Again if you wanted to provide generic flash messages like "Operation successful", you would write something like this :

  • controllers.application.create.flash.notice

Finally, keys can be anything that is valid YAML, but please stick to using periods . as separators and underscores _ between words as a matter of convention.

The only thing left to sort out now, is getting rails' translations into its own namespace to clean up our top level :)

Trimetallic answered 28/4, 2012 at 14:20 Comment(3)
brilliant! Thank you. I sort fell into this pattern already, but was not sure if I was going about it the right way.Ringhals
I like this pattern but it's sad that nothing is built into Rails to help managing flash messages as it is done for active record errors (with cascading). We always have to type the same scope again and again...not DRY at all...Josi
@GauthierDelacroix I realised that, so I built a gem to let you use lazy lookup with your own custom namespaces: github.com/abitdodgy/i18n_lazy_scopeRinghals
E
51

I know that an answer has already been accepted, but this question provided me with some food for thought and I thought I'd share another structure for Rails i18n yml files for your consideration/criticism.

Given that I would like to

  1. keep the default app structure so I can use shorthand "lazy" lookups like t('.some_translation') in my views,
  2. avoid as much string repetition as possible, in particular with words that are not just the same, but also have identical contexts/meanings,
  3. only have to change a key once to have it reflected everywhere it's referenced,

for a config/locales/en.yml file that looks something like this:

activerecord:
  attributes:
    user:
      email: Email
      name: Name
      password: Password
      password_confirmation: Confirmation
  models:
    user: User
users:
  fields:
    email: Email
    name: Name
    password: Password
    confirmation: Confirmation
sessions:
  new:
    email: Email
    password: Password

I can see that there is significant repetition, and that the context of words like "Email" and "Password" are unambiguous and have the same meaning in their respective views. It would be a bit annoying to have to go and change them all if I decide to change "Email" to "e-mail", so I'd like to refactor the strings to reference a dictionary of some sort. So, how about adding a dictionary hash to the top of the file with some & anchors like this:

dictionary:
  email: &email Email
  name: &name Name
  password: &password Password
  confirmation: &confirmation Confirmation

activerecord:
  attributes:
    user:
      email: *email
      name: *name
      password: *password
      password_confirmation: *confirmation
  models:
    user: User
users:
  fields:  
    email: *email
    name: *name
    password: *password
    confirmation: *confirmation
sessions:
  new:
    email: *email
    password: *password

Whenever you get more than one instance of exactly the same word/phrase in your views, you could refactor it out to the dictionary. If the dictionary translation of a key in the base language doesn't make sense for a target language, then just change out the referenced value in the target language to a static string or add it as an extra entry to the target language's dictionary. I'm sure each language's dictionary could be refactored out into another file if they get too big and unwieldy.

This way of structuring i18n yaml files seemed to work well with some local test apps I tried it on. I'm hoping the wonderful Localeapp will provide support for this kind of anchoring/referencing in the future. But anyway, all this dictionary talk can't possibly be an original idea, so are there other issues with anchor referencing in YAML, or maybe just with the whole "dictionary" concept in general? Or is it just better to just rip out the default backend entirely and replace it with Redis or something?

Eupatorium answered 18/6, 2012 at 20:0 Comment(6)
That's a great question. Maybe you could post it and see what input you get?Ringhals
Thanks for the feedback! I posted this as its own question on SO here, so let's see what happens.Eupatorium
This is adding an extra layer of indirection (and increase knowledge debt), for a minimal benefit that could be alleviated using find and replace in the few occurences where it's duplicated. Besides, how often do you change wording for simple terms like email, password, confirmation. Once or twice during the lifetime of an app?Loar
You can now use lazy lookup with custom namespaces. This solves the biggest part of organising your locale file: github.com/abitdodgy/i18n_lazy_scopeRinghals
Disagree with @Magne. If you have built non-trivial Rails apps, you would find yourself repeating words related to the business domain pretty much everywhere. For me, the number of times I have to remember "a * in this file means it is a dictionary term" will always be less than the number of times I add/change translations for the same words. I think this is an elegant solution.Jolenejolenta
It is of course a cost/benefit decision in every special case. But don't do it just to do it. That would be prematurely optimising. Wait until it's a real problem (more than just a few instances), and then it will make sense to handle it like this. The question in @Emil's particular case is if whether or not it would make more sense to just reference one translation key with the mentioned sentence, and not have duplicate keys that refer to the same dictionary.Loar
N
9

Your question is not easy to answer, and there is not much material available on that topic. I have found the best resources are:

  • Rails Styleguide, section Internationalization
  • There are a lot of resources in the I18n wiki, but I don't have found there some that answer your questions.

So I will give it a try directly here:

  • Navigation

    I think you mean here the navigation elements like breadcrumbs, tabs, ... You have to define views for them, and stick then to the rule to move all view elements in separate files in the directory views (see the styleguide for the rule).

  • Button text (save changes, create account, etc)

    View elements, go into the same files as well. If you use the same buttons in different views, define a common file, and use that then.

  • Error messages from controller flash

    I would use the same rule as for views. Define a separate directory, include there the files for the controllers.

  • How to add multi-word keys. Do I use a space or an underscore? For exmaple, t(".update button")) or t(".update_button")

    I personally would prefer to use .update_button, not .update button, because it makes more explicit that this is one key.

Nicolnicola answered 28/4, 2012 at 10:35 Comment(0)
R
9

It's almost two years after I asked this question, and I want to share some insights. I believe the optimal structure is to namespace translations according to their MVC role (models, views, controllers). This keeps the locale file tidy, and prevents namespace collisions (for example, the scope en.users can represent a view or a controller).

en:
  controllers:
    users:
      show:
        welcome_flash: "Welcome back!"
  mailers:
    users_mailer:
      welcome_email:
        subject: "Good of you to join us"
  views:
    users:
      show:
        notice: "Oh no!

But using tidy namespaces like that breaks the lazy lookup feature in Rails. If you use lazy lookup, Rails will insert the namespace automatically for you, and it will not include the top level namespaces you created (views, controllers, etc...).

For example, the scope of t('.welcome_flash') resolves to en.users.show. Which stinks because users isn't clearly defined. What is it? A controller? A view? Something else?

To solve this problem I created the gem I18nLazyLookup. It allows you to use lazy lookup with your own custom namespaces.

Instead of using t, you can use t_scoped('welcome_flash'), and that would automatically resolve the scope to en.controllers.users.show. It also works for views and mailers, and you can customise the namespace the way you like.

Ringhals answered 28/1, 2015 at 10:58 Comment(3)
what about putting the flashes in the views where they are shown? So welcome_flash would be under the view.users.show.welcome_flash . That way you can eliminate the controllers/view namespace distinction, and just implicitly assume that users.show.notice refers to the view (which would have the controller flashes too, ofc). Seems to clutter the YAML file less.Loar
@Loar I find that I prefer to namespace the string in the location it was set. So I prefer to namespace controller flash messages in the controllers. Especially since most of the time the flash is universal to the entire app, and usually lives in layout, and doesn't belong to a specific action. But there's a case to be make for both; I just prefer to be more explicit.Ringhals
I've yet to find an approach that clearly surpasses other schemes. I ended up just creating my own system, which uses my own I18n backend that I desgined to allow me to reference keys from within strings. This allows me to organize my file how I like, but still provide the scopes Rails is looking by specifying them and pointing to their real key in the YAML file. This is OK for me because it's a personal project, but the lack of standardization on this front still makes it difficult for multiple people to contribute.Graziano
S
6

Editing directly the yaml files leads to messy and unreadable files.
Moreover, it'll be difficult for you to provide access to translators if, someday, you want a non-developer to add a new language.

I would recommend using localeapp, which generates a single yaml file.
But allows you to easily see and manage your translations in a web interface.
And to create additional access to translators.

Supreme answered 23/4, 2012 at 16:53 Comment(2)
thank you. While I appreciate your input, it does not answer the question. It's definitely an interesting service, though. I started using it.Ringhals
with localeapp, do you put all your translation keys for a language into that single yaml file that localeapp generates? or do you still use application.*.yml for something?Loar
N
0

Coming several years after the battle, but here is a (somewhat totally) different answer.

To begin with, I do not like the standard t('.xxx') style with the default namespace based on the file structure. I also don't really like the categorisation of translations depending on the DOM structure. While this is a nice approach for very structured translations, it is often repetitive, and not very intuitive.

I'd rather regroup my translations into more useful categories, so as to make it easier for my translators, because they can work on concrete themes, rather than some weird styles (some translators do not even know what MVC means)

so my translation file is structured like this

fr:
  theme1:
    theme11:
      translationxxx: blabla
  theme2:
    translationyyy: blabla

Depending on the needs, the "themes" can be a model, a more abstract context, etc. that is the most intuitive for the translators.

Because this would be a hassle to write everytime the scope in my views, I have added some convenience methods in my helpers to have a stack based translation context.

  • I push/pop translation scopes on a stack in my views by calling t_scope([new_scope] and pop_t
  • I override the t helper to use the last scope of the stack

The code for the translation scoping methods is available in that answer

Nutt answered 14/3, 2016 at 22:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.