How do you structure i18n yaml files in Rails?

2019-01-16 05:28发布

问题:

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?

回答1:

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 :)



回答2:

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?



回答3:

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.



回答4:

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.



回答5:

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.



回答6:

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