Link to corresponding path in other locale (rails) - ruby-on-rails

I have a multi-lingual application in Ruby on Rails 4. For that I use the following domains:
Swedish: exempel.se
English: example.com
French: fr.example.com
Spanish: es.example.com
I want to be able to link to the corresponding path in another corresponding locale. For example if I am at www.exempel.se/denna-bloggpost I want to be able to easily link to (English/Spanish/French) to e.g. www.example.com/this-here-blog-post and es.example.com/este-posto-de-bloggo (sorry, can't speak Spanish :)).
I understand this is done with link_to 'Spanish', locale => :es but this gives me the url www.exempel.se/denna-bloggpost?locale=es which is not what I want. I expect to access es.example.com/esto-posto-de-bloggo
It seems like default_url_options has something to do with this but I can't make it work.
How do I solve this?
Edit (2017-03-30): I will also need this solution for hreflang in the section.
I am using the route_translator gem.
I have seen several solutions for this but they all include solutions with ?locale=en or are just to simplified (like, only working for one single controller). What I am hoping for is a solution that would do something like this:
<% I18n.available_locales.each |locale| do %>
<% I18n.t(this route e.g. /denna-bloggpost) #=> /este-posto-de-bloggo, /this-blog-post, /le-post-du-blog %>
<% end %>

You can use the locale-specific routes that route_translator generates for you. Run rake routes to see what these are. And then:
# Create links for every other locale
<% (I18n.available_locales - I18n.locale).each |locale| do %>
# Example URL helper here - replace with your own!
<%= Rails.application.routes.url_helpers.send("posts_#{locale}_url",
'denna-bloggpost') %>
<% end %>

In order to create such a dynamic mapping, you must presumably have something like this defined in your application config:
APP_CONFIG = {
# ...
base_urls: {
se: 'exempel.se',
en: 'example.com',
fr: 'fr.example.com',
es: 'es.example.com'
}
# ...
}
(It wouldn't necessarily need to be in this format, of course; this is just an example.)
In the locale switching menu, you can then define your links like this:
link_to("French", "#{APP_CONFIG[:base_urls][:fr]}#{request.env['PATH_INFO']}")
In order to actually set the locale in your application, you could then place something like this in your ApplicationController:
class ApplicationController
before_action :set_locale
def set_locale
I18n.locale = APP_CONFIG[:base_urls].key(request.host) || I18n.default_locale
end
end
For more information, see here: http://guides.rubyonrails.org/i18n.html#managing-the-locale-across-requests

This question is addressed a number of times and many ways. Yet the simplest approach in my opinion is
<%= link_to "ру", request.params.merge( locale: 'ru' ) %>

Related

Changing locale but staying on the current page

I did an internationalization of my website.
I used https://github.com/enriclluelles/route_translator
Every link are working depending on the current locale
I have 2 buttons like this : EN | FR
You can click on it to change the locale. It's working like this :
if I18n.locale == :fr
link_to('en', root_en_path)
else
link_to('fr', root_fr_path)
end
The only problem is that if I'm on a certain page in my website and I change the locale, it will comeback to the root_path
What I want is to stay on the same page.
For example: I have a how_it_works page
If I'm in /fr/how_it_works I want the EN button to link to /en/how_it_works
I've been told to pass the locale as a param but I don't want that because I don't want to have a big link like /fr/how_it_works?locale=en and I'm sure that's a bad design
The advice you were given would be the best approach and no, you wouldn't be using a query parameter as you thought.
Rails.application.routes.draw do
scope ':locale', constraints: { locale: /(en|fr)/ } do
#define all your scopeable routes within here
end
end
This way, you can visit /en/how-it-works or /fr/how-it-works. In your ApplicationController, you can do a before action :set_locale where you'd be setting the locale as params[:locale].
Hope that makes it clearer and resolves your issue.
UPDATE
Can't really figure out a better way of achieving this, but here's a way I could immediately work out:
url_params = params.to_h
if I18n.locale == :fr
link_to('en', url_params.merge(locale: :en))
else
link_to('fr', url_params.merge(locale: :fr)
end
Thank good lord for bringing the solution to me! I've just encountered you guys having the same issue so here it is what I have used.
<%= link_to(options = {controller: controller_name, action: action_name, locale: :en} , class: "navbar-item") do %>
<%= image_tag "gb.svg", size: "24x24" %>
<% end %>
For displaying a link to the same page, however changing locales and using an image as link.
After some research, I found a basic way to do it. I first thought this thing was already implemented in route_translator, but apparently no.
The solution :
if I18n.locale == :fr
en_uri = request.fullpath.gsub('/fr', '/en')
good_link = en_uri[0, 3] == '/en' ? en_uri : root_en_path
link_to('en', good_link)
else
fr_uri = request.fullpath.gsub('/en', '/fr')
good_link = fr_uri[0, 3] == '/fr' ? fr_uri : root_fr_path
link_to('fr', good_link)
end
I use a similar setup, and came across this (old) question. Matheus answer helped me to order my thoughts, and for me the solution was as simple as
<%= link_to "fr", controller: controller_name, action: action_name, locale: "fr" %>
Note that this works because I use "scope" in routes.rb:
scope "(:locale)", locale: /en|fr/ do
...
end
This way, the locale gets passed on every request, and fetching it is easy
I18n.locale = params[:locale]
I wanted to share this for others who might come by. Once again, built in rails comes with the power to do amazing things with a few lines.
Happy codin .
What about ? :
<%= link_to "English", current_page_params.merge(locale: :en) %>
See more about it : https://gorails.com/episodes/rails-link-to-current-page-with-params

Can I Set Up Routes That Include The Locale & Default the Locale In The Route When It Is Not Provided?

I currently have routing that requires the locale (i.e. /en, /fr, etc.). At some point I have used all of the statements below in routes.rb:
scope "/:locale", locale: /#{I18n.available_locales.join("|")}/ do
scope "/:locale", defaults: { :locale => "en" } do
scope "/:locale" do
I know that if I do the following if the route does not include the locale that it will point to the English version of the website. However it does not set the locale like I want once the page is displayed. If I go to the French version by clicking my locale logic the first link will display the French version of the page with /fr in the link. However if I click another link on the French page the locale goes back to English with the locale excluded from the link.
scope "(/:locale)", defaults: { :locale => "en" } do
Here is the code for my locale links in my application where a user can click on a flag image or text to change the locale:
<%= link_to_unless_current image_tag("english.jpg", alt: "#{t :english}"), locale: "en" %> <%= link_to_unless_current "#{t :english}", locale: "en" %>
<%= link_to_unless_current image_tag("french.jpg", alt: "#{t :french}"), locale: "fr" %> <%= link_to_unless_current "#{t :french}", locale: "fr" %>
What I would like to do is to prevent a 500 system error if by chance someone has an link saved before the website was localized. For example if they have http://mywebsite.com/video it would display the English version of the website and set the locale to "en".
Here is the code I have in application_controller.rb.
before_filter :set_locale
def default_url_options(options={})
{ :locale => I18n.locale }
end
private
def set_locale
I18n.locale = (params[:locale] if params[:locale].present?) || cookies[:locale] || 'en'
cookies[:locale] = I18n.locale if cookies[:locale] != I18n.locale.to_s
end
I'm not finding anything on this particular issue other than to use the routing-filter gem. I was using the gem but until there is a production version of the gem for Rails 4 I have no option but to figure this routing issue out.
Any help would be appreciated.
i find your question rather confusing... so my answer will refer to some parts of you code. maybe that gives you enough context to fix your problems.
do not use 3 routes for locale
one route is enough, please read the guides for internationlization.
i think that you will have to go with the optional approach as you want to support legacy urls:
scope "(/:locale)" {}
use the config.default_locale option
in the configuration you can configure fallbacks for localization.
don't mix default_url_options and cookies
if you are using cookies to keep track of your locale, you can can skip the default_url_options, you will have to keep the unlocalized versions anyways for backward compatibility.
if you MUST have urls like domain.com/en/something do it the other way around. avoid using cookies, use the URL everywhere and redirect people coming in from a legacy url.

Need To Add Locale To link_to Statement in a Mailer View

I have removed gem routing-filter from my Rails 3.2.13 application in preparation to upgrade it to Rails 4. The author(s) have a beta version for Rails 4 but I could never get it to work. I have implemented the normal Rails process for localization/internationalization. The last issue I need to resolve is a link_to statement that displays a thread in a mailer view.
When I was using routing-filter I had the link_to statement as below where I set #host in my mailer depending on the environment.
<%= link_to "#{t :view_thread}", micropost_url(#item), host: "#{#host}" %>
When I uninstalled routing-filter I started getting errors because of a missing locale when the attempt is made to send the email. It crashes and sends the application back to the landing page. The gem took care of this.
I have been searching for about an hour trying to find the correct syntax for this but I'm not finding any examples that include both the host and locale parameters. I have tried several guesses but I get syntax errors.
Any help would be appreciated. I will keep searching and trying.
Here is the solution I came up with. I was not sure where the locale clause should go related to the host clause. I decided to try the statement below and it worked.
<%= link_to "#{t :view_thread}", micropost_url(#item, locale: I18n.locale.to_s), host: "#{#host}" %>
Updated solution for Rails 6 (and probably 5).
First, make sure you followed Rails guides on localization, so it's all set up
Then, add this to your ApplicationMailer:
def default_url_options(options = {})
ActionMailer::Base.default_url_options
.merge(locale: I18n.locale)
.merge(options)
end
This will use your selected locale for links generation, so you don't have to pass locale: #locale every time.
And then, set current locale in your mailers with the following block.
I18n.with_locale(#locale) do
mail(to: #email,
subject: I18n.t("mailers.my_mailer.subject"))
end
The last piece of advice - don't forget to fallback with your #locale var, so it's smth along these lines: #locale = #user.locale || I18n.default_locale
Update:
Or you can simply monkeypatch mail method, but make sure you know what you're doing.
def mail(**)
I18n.with_locale(#locale || I18n.default_locale) { super }
end

Remove html tags from I18n "translation missing" messages

Consider the following code in a view:
<%= link_to 'Delete!', item , :confirm => t('action.item.confirm_deletion'), :method => :delete %>
It will normally come out as:
Delete!
But if the translation for action.item.confirm_deletion is missing for some reason (incomplete yml-file, typos, etc.) it comes out as:
Confirm Deletion</span>" data-method="delete" rel="nofollow">Delete!
which is invalid html, and user will see broken html tags on the homepage.
It may also be a security risk in some cases.
I know that I could use apply some escaping on every call to the I18n.t function, but that feels unnecessarily repetitive for the task.
So my question is:
Is there a way to make the "translation missing"-messages not contain html code.
There are multiple solutions for you.
You can alias the translation method to your own and call with the custom :default value (I would prefer this way):
module ActionView
module Helpers
module TranslationHelper
alias_method :translate_without_default :translate
def translate(key, options = {})
options.merge!(:default => "translation missing: #{key}") unless options.key?(:default)
translate_without_default(key, options)
end
end
end
end
Or you can overwrite the default value:
module I18n
class MissingTranslation
def html_message
"translation missing: #{keys.join('.')}"
end
end
end
In rails 4.2 you have to redefine the translate helper in the view:
https://github.com/rails/rails/blob/v4.2.5/actionview/lib/action_view/helpers/translation_helper.rb#L78
In rails 5 you can set in your application.rb:
config.action_view.debug_missing_translation = false

Rails / I18n: default scope

I'm using the default I18n module for Rails to translate my strings in views.
<%= t("registration.heading") %>
Now, when I'm in the registration-view, all my strings start with registration. I always have to write
<%= t("registration.heading.notice") %>
// or
<%= t(:heading, :scope => :registration) %>
It would be nice to define a default scope for that file (maybe even in the controller), so a call to t automatically adds the defined scope
// controller
set_i18n_default_scope :registration
// view
<%= t(:heading) %>
// --> looks in "registration.heading"
Is this possible?
If you organize your translations adding a view name, as in:
en:
registration:
index:
heading: "Registration heading"
then you may use this:
<%= t(".heading") %>
Notice that the first character is a dot.
You may read about it in Rails Internationalization (I18n) API Guide
If you have texts which are shared amongst numerous views, and you don't want to copy the same translation in each section for each view, you may use YAML references. They are nicely described on wikipedia: http://en.wikipedia.org/wiki/YAML#Repeated_nodes
It is possible. Check section 4.1.4 of the Rails i18n API
4.1.4 “Lazy” Lookup
Rails 2.3 implements a convenient way
to look up the locale inside views.
When you have the following
dictionary:
es: books:
index:
title: "Título"
you can look up the books.index.title value inside
app/views/books/index.html.erb
template like this (note the dot):
<%= t '.title' %>
Regarding Lazy Lookups:
Here's the general solution for this kind of problem
Common Problem: Where is Rails trying to look-up L10N / I18N Strings? - e.g. when doing Lazy Lookups
It's not easy to guess, because it's different for Views, Controllers, Models, Labels, Helpers, or Validations... etc... but...
It's easy to find out directly, using this:
http://www.unixgods.org/Rails/where_is_Rails_trying_to_lookup_L10N_strings.html
this helps figuring out what Rails thinks the current scope is (e.g. when using ".heading")
3 Simple Steps:
create a file ./config/initializers/i18n.rb , as described in the article above
put t('.heading') in your view
start "rails server" and look in the console output where Rails thinks the default location is for '.heading' for this view... e.g. what's the I18N-key
(4. then add the I18N string into the location identified by the key)
Works like a charm :-)
If you want to print out keys that I18n gem's lazy mode is looking for, you can add that in a i18n.rb file in your initializers folder:
module I18n
module Backend
class Simple
module Implementation
alias_method :lookup_orig, :lookup
# Give ability to check I18n looked up keys in lazy mode by setting env var I18N_DEBUG to true
#
def lookup(locale, key, scope = [], options = {})
puts "I18N keys: #{I18n.normalize_keys(locale, key, scope, options[:separator])}" if ENV['I18N_DEBUG']
lookup_orig(locale, key, scope, options)
end
end
end
end
end
(Gist: https://gist.github.com/capripot/6e6cf778ad2db0443280)
And then start your server like for instance:
I18N_DEBUG=true bundle exec rails server

Resources