5

Writing a fully translated app can become tedious. Is there a way to set a default translation scope for the current context ?

Example : I am writing inside a partial _deadlines.html.erb in the show.html.erb action of my ProjectsController

Now because I am trying to be a good programmer, I am scoping all my translations. I would like to produce the following tree

projects:
  deadlines:
    now: "Hurry the deadline is today !"
    ....

How can I make it less tedious than writing each time the full scope ?

projects/show.html.erb

...
<%= render 'projects/deadlines', project: @project %>
...

projects/_deadlines.html.erb called from show.html.erb

<p>Deadline : <%= t(:now, scope: [:projects, :deadlines]) %></p>

Is there a way to set a default scope for the current context (here the whole _deadlines.html.erb file) ?

EDIT

Some people suggested to use Rails Lazy lookup, but this does not produce the scoping I'm looking for. In my case, I want to skip the action default scope (show, index, etc...) and add a scope for the current partial I am rendering (in my case _deadlines.html.erb)

Rails lazy lookup :

t('.now')
<=> t(:now, scope: [:projects, :show]

But I wanted :

t('.now')
<=> t(:now, scope: [:projects, :deadlines]
Cyril Duchon-Doris
  • 12,964
  • 9
  • 77
  • 164
  • 1
    This question is 5 years old. I would not recommend anymore "smart" translation scoping, but just to fall back to the recommended way (based on file path, compatible with Lazy lookup). When your application becomes bigger, it becomes harder to maintain "smart scopes" since it's harder to understand which translations are used where with static analysis. -> Better to make it easier to know which translations are used where. And if we need to change some key vocabulary, then it's up to the team to go through every available translation, using for example CTRL+F, to change everything. – Cyril Duchon-Doris Apr 20 '21 at 14:56

3 Answers3

5

Rails implements a convenient way to look up the locale inside views. When you have the following dictionary:

es:
  projects:
    index:  # in 'index.html.erb' template file
      title: "Título"
    deadlines:  # in '_deadlines.html.erb' partial file
      title: "Fecha límite"

you can look up these values as below:

# app/views/projects/index.html.erb
<%= t '.title' %>  # => "Título"

# app/views/projects/_deadlines.html.erb
<%= t '.title' %>  # => "Fecha límite"
shoji
  • 490
  • 2
  • 10
  • Someone else posted almost the same thing, but must have deleted his answer... The fact is that I'm looking for a way to declare a default scope different than the default `controller/action`. Here `t('.title')` would not output `scope: [:projects, :deadlines]`, but rather `scope: [:projects, :index]` as you wrote at the beginning – Cyril Duchon-Doris Feb 19 '15 at 16:20
  • 1
    Although I might be not understanding your question, I edited. I think lazy lookup is connected with `controller/action`. It's connected with **views** such as `views/projects/_deadlines.html.erb`. So, I think you don't need change scope for `_deadlines.html.erb`. I don't mind you'll delete my post if it's not correct answer. – shoji Feb 19 '15 at 17:16
  • Oh right, it actually produces the right output ! sorry – Cyril Duchon-Doris Feb 19 '15 at 17:24
  • While your solution is nice, it won't let me have different default scopes per partial, and I cannot map different pages to the same default scope. – Cyril Duchon-Doris May 17 '15 at 15:43
2

Okay I was actuall still not happy with this. This default 'lazy lookup' scope is totally krap when you want to translate the same thing at different places. Say I have two different partials that contain information dealing with the same model. Using lazy lookup, I would need to have the same translation twice in my yml file.

Here's a little piece of code that you can put in your application helper. It's basically an override of the default I18n.t that will set the scope to @t_scope when it is defined, and you don't need to worry about the scope anymore

My code addition

helpers/application_helper.rb

def t(*args)
  # If there is just one param and we have defined a translation scope in the view
  # only using symbols right now, need extended version to handle strings
  if args.size == 1 and args.first.is_a?(Symbol) and @t_scope
    super(args.shift, @t_scope)
  else
    super(*args)
  end
end

def set_t_scope(scope)
  push_t_scope(@t_scope ||= {})
  replace_t_scope(scope)
end
alias :t_scope :set_t_scope

def replace_t_scope(scope)
  @t_scope = {scope: scope}
end

def push_t_scope(scope)
  (@tscope_stack ||= []) << scope
end

def pop_t_scope
  @t_scope = @tscope_stack.pop
end

What you can do with it

projects/show.html.erb

<%= t_scope([:projects, :deadlines]) %>
<fieldset>
  <legend>Deadlines</legend>
  <% if Time.now > @project.deadline.expected_finish_date %>
  <p><%= t(:hurry) %></p>
  <% else %>
  <p><%= t(:you_have_time) %>
</fieldset>
<fieldset>
  <legend>Deadlines</legend>
  <%= render 'tasks', tasks: @project.tasks %>
...

views/projects/_tasks.html.erb

<%= t_scope([:projects, :tasks]) %>
  <% tasks.each do | task| %>
  <h2><%= t(:person_in_charge) %></h2>
  ...
<% pop_t_scope %>  

en.yml

en:
  projects:
    deadlines:
      hurry: "Hurry man !"
      you_have_time: "Relax, there's still time"
    tasks:
      person_in_charge: 'The Boss is %{name}'

Now the only problem that I see, is that when rendering multiple partials from a view, the @t_scope will be transferred and could potentiall cause problems. However wouldn't be a problem is @t_scope is set to nil at the beginning of each file

Cyril Duchon-Doris
  • 12,964
  • 9
  • 77
  • 164
  • I have further refined the code. Contact me if interested. – Cyril Duchon-Doris May 24 '15 at 23:38
  • 1
    This is a great solution EXCEPT it doesnt account for accessing other keys, such as t('footer.about'). It works well with t(:something) so you need to take into account strings versus symbols. Here is a suggested rewrite: https://gist.github.com/noctivityinc/5d4358b560fb92a1af70 – JoshL Aug 12 '15 at 18:15
0

Since t('.foo') is possible and might have options (for interpolation for example), here is my proposition to improve the previous one

def t(*args, **kwargs)
  if args.size == 1 and (args.first.is_a?(Symbol) || args.first.to_s.start_with?('.') ) and @t_scope
    I18n.t(args.shift, kwargs.reverse_merge!(@t_scope))
  else
    I18n.t(*args)
  end
end
brcebn
  • 1,571
  • 1
  • 23
  • 46