How to fix: i18n always translate to default locale - ruby-on-rails

I'm trying out the internationalization of a Rails app with i18n. I did some small tests with 2 languages: english and french.
The problem I have is that i18n always translate to the default locale. So if it's english, everything will be in english, and the same with french.
Here is what I tried:
config/initializers/locales.rb
# Permitted locales available for the application
I18n.available_locales = [:en, :fr]
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def default_url_options
{ locale: I18n.locale }
end
end
config/application.rb
module LanguageApp
class Application < Rails::Application
...
config.i18n.load_path += Dir["#{Rails.root.to_s}/config/locales/**/*.{rb,yml}"]
config.i18n.default_locale = :en
# I change the default locale here to :fr or :en
end
end
config/routes.rb
root to: "home#index"
get '/:locale/about' => 'about#index'
get '/:locale' => 'home#index'
I organized my yml files like this:
config/locales/views/about/en.yml
en:
about: "This page is about us."
config/locales/views/about/fr.yml
fr:
about: "Cette page est à propos de nous."
config/locales/views/home/en.yml
en:
welcome: "Hello world"
config/locales/views/home/fr.yml
fr:
welcome: "Bonjour le monde"
And finally my views:
app/views/about/index.html.erb
About us page. <%= t(:about) %>
app/views/home/index.html.erb
This is the homepage. <%= t(:welcome) %>
I think the problem may come from the way I organized my yml files but I don't understand why i18n only translate to the default locale and 'ignore' the other language.
EDIT:
To try this out in the browser with the rails server running, I tried to visit these URL:
localhost:3000
localhost:3000/en
localhost:3000/fr
These 3 URL give me the same content, so the :fr locale doesn't actually work (it returns the same translation as :en)
Same for
localhost:3000/en/about
localhost:3000/fr/about
I also tried it in the rails console:
> I18n.t(:welcome, :en)
"Hello world"
> I18n.t(:welcome, :fr)
"Hello world"

First set the locale for the request:
class ApplicationController < ActionController::Base
around_action :switch_locale
def switch_locale(&action)
I18n.with_locale(params[:locale] || I18n.default_locale, &action)
end
def default_url_options
{ locale: I18n.locale }
end
end
Don't use I18n.locale= as many older answers / tutorials do.
I18n.locale can leak into subsequent requests served by the same
thread/process if it is not consistently set in every controller. For
example executing I18n.locale = :es in one POST requests will have
effects for all later requests to controllers that don't set the
locale, but only in that particular thread/process. For that reason,
instead of I18n.locale = you can use I18n.with_locale which does not
have this leak issue.
Rails Guides
If you want to create translations for specific views you should nest the keys instead of just using flat hashes:
en:
home:
welcome: "Hello World"
fr:
home:
welcome: "Bonjour le monde"
And then use an implicit lookup in the view:
<h1><%= t('.welcome') %></h1>
This resolves the key to home.welcome.

Related

Crash in Rails ActionView related to I18n.fallbacks when invoked from delayed_job

I am trying to send an email from my rails 4 app like so (condensed version from the console):
> ActionMailer::Base.mail(from: 'mail#example.com', to: 'foo#example.com', subject: 'test', body: "Hello, you've got mail!").deliver_later
The mail would be sent by delayed_lob, in my local test setup I trigger it like so:
> Delayed::Job.last.invoke_job
However the job crashes with the following message:
Devise::Mailer#invitation_instructions: processed outbound mail in 56234.1ms
Performed ActionMailer::DeliveryJob from DelayedJob(mailers) in 56880.04ms
TypeError: no implicit conversion of nil into Array
from /Users/de/.rvm/gems/ruby-2.4.0/gems/actionview-4.2.10/lib/action_view/lookup_context.rb:51:in `concat'
from /Users/de/.rvm/gems/ruby-2.4.0/gems/actionview-4.2.10/lib/action_view/lookup_context.rb:51:in `block in <class:LookupContext>'
from /Users/de/.rvm/gems/ruby-2.4.0/gems/actionview-4.2.10/lib/action_view/lookup_context.rb:39:in `initialize_details'
from /Users/de/.rvm/gems/ruby-2.4.0/gems/actionview-4.2.10/lib/action_view/lookup_context.rb:205:in `initialize'
...
I have looked into the code of lookup_context.rb:51 GitHub, the problem is here:
register_detail(:locale) do
locales = [I18n.locale]
locales.concat(I18n.fallbacks[I18n.locale]) if I18n.respond_to? :fallbacks # < crashes here
# from the debugger I got:
# I18n.locale => :de
# I18n.fallbacks => {:en=>[]}
So obviously fallbacks does not contain my locale (:de) which results in a nil exception.
Apparently I18n.fallbacks is not configured correctly.
Question: How can I fix this?
I got the answer with the help of this blog post I found: https://sjoker.net/2013/12/30/delayed_job-and-localization/
It contains half of what I need. The proposed solution goes like this:
In order to propagate state from the time of creating a job, that state has to be transferred to the time the job is invoked by storing it on the job object in the database.
In the blog post, the author only stores the current locale to localize the mail at invitation time. However I also needed to store the fallbacks which required a little bit of serialization.
Here is my solution:
# Add a state attributes to delayed_jobs Table
class AddLocaleToDelayedJobs < ActiveRecord::Migration
def change
change_table :delayed_jobs do |t|
t.string :locale # will hold the current locale when the job is invoked
t.string :fallbacks # ...
end
end
end
# store the state when creating the job
Delayed::Worker.lifecycle.before(:enqueue) do |job|
# If Locale is not set
if(job.locale.nil? || job.locale.empty? && I18n.locale.to_s != I18n.default_locale.to_s)
job.locale = I18n.locale
job.fallbacks = I18n.fallbacks.to_json
end
end
# retrieve the state when invoking the job
Delayed::Worker.lifecycle.around(:invoke_job) do |job, &block|
# Store locale of worker
savedLocale = I18n.locale
savedFallbacks = I18n.fallbacks
begin
# Set locale from job or if not set use the default
if(job.locale.nil?)
I18n.locale = I18n.default_locale
else
h = JSON.parse(job.fallbacks, {:symbolize_names => true})
I18n.fallbacks = h.each { |k, v| h[k] = v.map(&:to_sym) } # any idea how parse this more elegantly?
I18n.locale = job.locale
end
# now really perform the job
block.call(job)
ensure
# Clean state from before setting locale
I18n.locale = savedLocale
I18n.fallbacks = savedFallbacks
end
end
You need to configure your default and fallback locale something like below
config.i18n.default_locale = :de
config.i18n.fallbacks = { de: :en }
Please try this. It will fallback to :en if :de is not found

Middleman i18n not working

I'm newbie using ruby and middleman, I've created my project and all are working fine, but when I go to /es path I don't get any translation. I've searched for info without any results and tried to move code between folders testing configs and nothing.
My folder structure is:
|locales
|---en.yml
|---es.yml
|source
|es
|---index.html.haml
|layouts
|---layout.html.haml
|partials
|_header.html.haml
|_navigation.html.haml
|---index.html.haml
My YAML files
en.yml
en:
home: 'Home'
es.yml
es:
home: 'Inicio'
My HAML
%nav
= link_to t(:home), '/', class: "#{'active' if current_page.url == '/'}"
= link_to 'Portfolio', '/portfolio', class: "#{'active' if current_page.url == '/portfolio/'}"
= link_to t(:skills), '/skills', class: "#{'active' if current_page.url == '/skills/'}"
= link_to t(:about), '/about', class: "#{'active' if current_page.url == '/about/'}"
= link_to t(:contact), '/contact', class: "#{'active' if current_page.url == '/contact/'}"
My config
config.rb
###
# Page options, layouts, aliases and proxies
###
# Per-page layout changes:
#
# With no layout
page '/*.xml', layout: false
page '/*.json', layout: false
page '/*.txt', layout: false
# With alternative layout
# page "/path/to/file.html", layout: :otherlayout
# Proxy pages (http://middlemanapp.com/basics/dynamic-pages/)
# proxy "/this-page-has-no-template.html", "/template-file.html", locals: {
# which_fake_page: "Rendering a fake page with a local variable" }
# General configuration
set :partials_dir, 'partials'
activate :i18n, :templates_dir => 'partials'
activate :directory_indexes
# Reload the browser automatically whenever files change
configure :development do
activate :livereload
end
###
# Helpers
###
# Methods defined in the helpers block are available in templates
# helpers do
# def some_helper
# "Helping"
# end
# end
# Build-specific configuration
configure :build do
# Minify CSS on build
activate :minify_css
# Minify Javascript on build
activate :minify_javascript
end
I couldn't write a comment, but I think this might be the reason, your es.yml is wrong, since it starts en:
en:
home: 'Inicio'
Shouldn't it be
es:
home: 'Inicio'
I know this question is months old but I just had the same problem, looked all over the web for hours trying to find and answer and managed to fix the issue by adding these parameters after activating i18n:
config.rb
configure :build do
activate :i18n,
:mount_at_root => 'en',
:lang_map => { :'en' => 'en', :'es' => 'es' },
:path => '/'
end
Obviously, if you want "es" to be your default, change mount_at_root.
Hope this helps.
I achieved localized paths with separate URLs for English and Spanish by
adding index.es.html.erb in the root of the source directory
and setting activate :i18n, :path => "/:locale/" in the config.rb
In the browser, my language selector sends users to / or /es:
English
http://website.com/
Spanish
http://website.com/es
Folder structure
|data
|---home.json
|locales
|---en.yml
|---es.yml
|source
|---index.html.erb
|---index.es.html.erb
|---_slide.erb
|---config.rb
config.rb
configure :build do
activate :i18n, :path => "/:locale/"
activate :directory_indexes
...
end
slide.erb
Using t as a shortcut for I18n.t, I dynamically reference the translated value through the data.
<%= link_to t([data.link.text]),
data.link.href,
:id => data.link.id,
:class => 'btn btn-primary btn-lg btn-dark'
%>
home.json
The value of "text" correlates to the key in the .yml files.
{
"slides": [
{
"text": "slides.learnMore",
...
},
...
]
}
en.yml
en:
slides:
learnMore: "LEARN MORE"
...
es.yml
es:
slides:
learnMore: "APRENDE MÁS"
...
Move all your .erb.html that require to be duplicated per language to the folder: /source/localizable as explained in the docs:
https://middlemanapp.com/advanced/localization/#localizable-templates
You can change this folder name using the modifier: templates_dir:
# Look in `source/language_specific` instead
activate :i18n, :templates_dir => "language_specific"

Rails I18n: How to set fallback locale?

I'm on Rails 4, and I'm following how to use rails i18n fallback features
My website only has 2 lang, zh, and en, and I want other language such as de, fr fallback to en, so I set as suggested above:
config.i18n.fallbacks =[:en]
But when I visiting http://localhost:3000/?locale=de it still reports
"de" is not a valid locale. (locale for en and zh works fine)
How can I fix this?
UPDATE: this is how I receive locale
def set_locale
I18n.locale = params[:locale] || I18n.default_locale
end
Add this to your config/application.rb
I18n.config.enforce_available_locales = false

I18n locale disregarding fallbacks

I asked a previous question regarding locale-setting. I am trying to set up fallbacks of various Norwegian languages to Norwegian Bokmal (:nb). The desired behaviour is that if a browser passes nn or no as locale requests, the I18n.locale will be set to either :nn or :no, and then in the absence of translations for these locales, :nb will be served to the browser.
Based on the answer to my previous question, I have this line in my application initialiser:
config.i18n.default_locale = :en
config.i18n.fallbacks = {:nn => [:nb], :no => [:nb]}
In rails console, this gives me the following results:
> I18n.fallbacks
=> {:en=>[:en]}
> I18n.fallbacks[:nn]
=> [:nn, :nb, :en]
> I18n.fallbacks[:no]
=> [:no, :nb, :en]
Using a browser that has only nn & no in the language list, this does not work- it falls back to the default locale of :en instead. Here's the request headers:
Accept-Language: "nn,no;q=0.5"
If I add :nb to the browser language stack, I am correctly served Norwegian content.
Is there something I am missing in this process?
You need to set I18n.locale based on the browser setting.
def set_locale
I18n.locale = extract_locale_from_accept_language_header
end
private
def extract_locale_from_accept_language_header
request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first
end
Taken from: http://guides.rubyonrails.org/i18n.html#setting-the-locale-from-the-client-supplied-information

Rails: Model.human_attribute_name :field should raise an error when translation not found? (Maybe caused by state_machine?)

We often stumble over untranslated model attributes in our application. They most often come because an attribute was renamed or something like this.
It would be really helpful to have I18n raise an error when Model.human_attribute_name :field doesn't find a translation. Is there a way to achieve this?
Update:
It seems there's some other problem. here are my I18n settings:
I18n.enforce_available_locales = false
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]
config.i18n.default_locale = 'de-CH'
config.i18n.available_locales = ['de', 'de-CH', 'en']
config.i18n.locale = 'de-CH'
config.i18n.fallbacks = {'de-CH' => 'de', 'en-GB' => 'en'}
I can't set fallbacks = false because I want missing translations of de-CH to gracefully delegate to de, which in general seems to work fine. But for my state machine attribute human_to_state method it doesn't seem to work. Here's the view code causing the problem:
= f.input :state_event, collection: f.object.state_transitions,
label_method: :human_to_name # This causes the problem!
This is printed out as "State event" in the view, and when I add the following I18n key, it's translated successfully to "Status":
de:
mongoid:
attributes:
activity:
state_event: Status
So there really is a missing translation, but I18n doesn't complain in any way. I also tried to catch the exception using a custom exception handler, but this doesn't seem to be raised:
I18n.exception_handler = lambda do |exception, locale, key, options|
binding.pry # This is never reached!
end
Any idea what's going on? Is it a problem with state machine?
The problem lies in the fact that human_attribute_name falls back to
defaults << attribute.to_s.humanize
when nothing else found. In other words, human_attribute_name will never raise an error.
I "fixed" this by overriding human_attribute_name, patching the above mentioned line:
(put this in an initializer)
require 'active_support/core_ext/hash/reverse_merge'
module ActiveModel
module Translation
include ActiveModel::Naming
def human_attribute_name(attribute, options = {})
defaults = lookup_ancestors.map do |klass|
[:"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key}.#{attribute}",
:"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key.to_s.tr('.', '/')}.#{attribute}"]
end.flatten
defaults << :"attributes.#{attribute}"
defaults << options.delete(:default) if options[:default]
defaults << attribute.to_s.humanize if Rails.env.production? # Monkey patch
options.reverse_merge! :count => 1, :default => defaults
I18n.translate(defaults.shift, options)
end
end
end
Possible duplicate of : How to enable Rails I18n translation errors in views?
The accepted answer is:
config.i18n.fallbacks = false
#BettySt 's answer is pretty cool too, you should take a look at her solution.
Doc about how to handle I18n translation errors:
http://guides.rubyonrails.org/i18n.html#using-different-exception-handlers

Resources