Rails link to current page and passing parameters to it

Ruby on-RailsInternationalization

Ruby on-Rails Problem Overview


I am adding I18N to my rails application by passing the locale using url params. My urls are looking like http://example.com/en/users and http://example.com/ar/users (for the english and arabic locales respectively).

In my routes file, I have defined my routes with a :path_prefix option:

map.resources :users, :path_prefix => '/:locale'

And locale is being set using a before_filter defined in ApplicationController

def set_locale
    I18n.locale = params[:locale]
end

I also defined ApplicationController#default_url_options, to add locale to all urls generated by the application:

def default_url_options(options={})
    {:locale => I18n.locale}
end

What I want is to add a link in the layout header (displayed in all pages) that would link to the same page but with the other locale.

For instance, if I am browsing the arabic locale, I want a "English" link in the header, that will redirect me back to my current page, and set the locale to english. Is there a way to do this in rails?

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

Took me a while to find this but here is my solution:

link_to 'English', url_for( :locale => 'en' )
link_to 'Deutch', url_for( :locale => 'de' ) 

From the docs here: http://api.rubyonrails.org/classes/ActionController/Base.html#M000649

> When generating a new URL, missing > values may be filled in from the > current request‘s parameters. For > example, url_for :action => > ‘some_action‘ will retain the current > controller, as expected. This behavior > extends to other parameters, including > :controller, :id, and any other > parameters that are placed into a > Route‘s path.

So using url_for will default to the current request's parameters, just change the one's you want in your code. In this case all I changed was :locale, so everything else stays the same.

Note this also works for "hidden" :parameters. So if you have:

map.my_map ':locale/my_map', :controller => 'home', :action => 'my_map'

using the above url_for in the page /en/my_map will not have 'home' in the url (ie /en/home/my_map). Bonus.

Solution 2 - Ruby on-Rails

So I found a way to more explicitly do this with out relying on (as much) rails magic.

url_for(params.merge({:your_new_parameter => value}))

This should work in any link_to.

All its doing is taking the current request's parameters and merging your new desired hash into them and then creating a new url for that.

Solution 3 - Ruby on-Rails

Link to current page with different locales

Tested on Rails 4

Hello all. After some time of research I decide to write my own solution for this.

link_to 'English', url_for( :locale => 'en' )
link_to 'Deutch', url_for( :locale => 'de' ) 

This works perfect, but it allows XSS Vulnerability just passing parameters in your URL like below:

http://localhost:3000/en/about?host=www.fishingsiteorbadurl.com/%23&port=80

Or worst case:

http://localhost:3000/en/about?host=%D0%BE%D1%87%D0%B5%D0%BD%D1%8C%D0%BF%D0%BB%D0%BE%D1%85%D0%BE%D0%B9%D1%81%D0%B0%D0%B9%D1%82.%D1%80%D1%84

Check out what URLs you will get after going through this link in your application.

My production solution. Method "change language" redirects to any page with proper locale just using HTTP_REFERER in request object. Please note: URI.path method for get only path, not whole url

> Make "change language" method in any controller:

        def change_lang
 
        if request.referer.nil?
                 refer = root_url
        else
                 uri = URI(request.referer)
                 refer = uri.path
        end
        lang = params[:lang]
        cookies[:locale] = lang
        redirect_to refer
 
        end

> application_controller.rb

before_action :set_locale

def set_locale

# -- Get lang from cookies or url parameter locale

user_locale = cookies[:locale] || params[:locale]

# -- If present

if user_locale.present? 
	
	# -- If it is has 2 symbols

	user_locale = user_locale.scan(/[a-zA-Z]{2}/) 
else
	
	# -- If no - use default en locale

	user_locale = 'en'
end


# -- Check, is this locale available for using.
# Please note: this needed for disable invalid locale warning.

if I18n.available_locales.include?(user_locale[0].to_sym)

	I18n.locale =  user_locale[0]
else
	I18n.locale =   "en"
end

end

> add this to your layout

<%= link_to 'English', change_lang_path('en') %> <%= link_to 'Russian', change_lang_path('ru') %>

> config/routes.rb

scope "(:locale)", locale: /[a-zA-Z]{2}/ do
get "change_lang/:lang" => "users#change_lang", :as => "change_lang"
end

There is no need to use params.merge or any monkey-patch solution.

I hope this helps, because I personally spent a lot of time to solve it.

Solution 4 - Ruby on-Rails

A much quicker avenue - and convenient if you have many parameters that change in different places... avoid the clutter with an anchor tag that just merges the new locale param to the existing ones (and actually killing the old locale param).

<%= link_to "ру", request.params.merge( locale: 'ru' ) %>

But yes, one needs to whitelist parameters at that point, according to application's context.

Solution 5 - Ruby on-Rails

You can parse request_uri, and replace your locale in the path with regular expression

Ok, here is helper example. If I correctly understand the goal

def locale_url(url, locale)
  url.gsub(/\/\w*$/, "/#{locale}")
end

url = "http://www.domain.com/products/1/ru" # or request.request_uri
locale = "en"
locale_url(url, locale) #=> "http://www.domain.com/products/1/en"

This is a start point, so you can make some different stuff that you need

Solution 6 - Ruby on-Rails

You can safely use url_for to switch locales with url params if you set only_path: true:

<%= link_to I18n.t('language_name', locale: I18n.locale), url_for( params.clone.permit!.merge(locale: locale, only_path: true ) %>

We .clone the params before permitting them all (.permit!), to preserve strong parameters elsewhere. The only more secure solution I could find would be to time consumingly whitelist all params instead...


Robust I18n implementation:

Add a locales.rb initializer to define what I18n.available_locales you support:

# config/initializers/locales.rb

# Permitted locales available for the application
I18n.available_locales = [:en, :fr]

Set a language_name value in each language's locale file (e.g. fr.yml):

fr:
  language_name: "Français"

As you add more languages, this ERB will let you generically switch between them:

  // app/views/layouts/_languages.html.erb
  <span class="languages">
   <% I18n.available_locales.each do |locale| %>
      <% if I18n.locale == locale %>
        <%= link_to I18n.t('language_name', locale: locale), url_for( params.clone.permit!.merge(locale: locale, only_path: true ), {style: "display:none" } %>
      <% else %>
        <%= link_to I18n.t('language_name', locale: locale), url_for( params.clone.permit!.merge(locale: locale, only_path: true ) %>
      <% end %>
    <% end %>
  </span>

For the controller, we automatically find the correct language for the user by detecting their browser's Accept-Language HTTP header (using the http_accept_language gem).

Set a session cookie to preserve locale across requests.

Or optionally, use default_url_options to insert the ?locale= param into your app's url. I do both.

Controller:

class ApplicationController < ActionController::Base
  before_action :set_locale

  private

  def set_locale
    I18n.locale = begin
      extract_locale ||
        session[:locale] ||
          http_accept_language.compatible_language_from(I18n.available_locales) ||
            I18n.default_locale
    end
    session[:locale] = I18n.locale
  end

  def extract_locale
    parsed_locale = params[:locale].dup
    I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
  end

  def default_url_options
    { locale: I18n.locale }
  end
end

Solution 7 - Ruby on-Rails

This is what worked for me, which preserves params and protects against xss:

= link_to_unless_current "English", url_for( request.params.merge(locale: 'en'))

You could use link_to instead of link_to_unless_current

Solution 8 - Ruby on-Rails

Have a look at this, though it may not be DRY and proper one, but works perfectly for me. It reads all the parameters you supplied replacing only the locale EX urls : http://example.com:3000/us/users?t=123&m=343 etc

  def us_link           
        link_to "US", form_locale_url("/us")            
  end
  
  def jp_link           
    link_to "Japan",form_locale_url("/jp")           
  end              
  
  def form_locale_url(locale)            
    new_url = request.request_uri          
    new_locale_url = new_us_url = new_jp_url = new_url           
    if new_url == "/"          
      new_locale_url.sub!(/\//,locale)           
    elsif (new_url =~/\/us/) == 0        
      new_us_url.sub!(/\/us/,locale)        
    elsif (new_url =~/\/jp/) == 0          
      new_jp_url.sub!(/\/jp/,locale)       
    end     
  end

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionFaisalView Question on Stackoverflow
Solution 1 - Ruby on-RailspixelearthView Answer on Stackoverflow
Solution 2 - Ruby on-RailsWillView Answer on Stackoverflow
Solution 3 - Ruby on-RailsEvgeniy BView Answer on Stackoverflow
Solution 4 - Ruby on-RailsJeromeView Answer on Stackoverflow
Solution 5 - Ruby on-Railsfl00rView Answer on Stackoverflow
Solution 6 - Ruby on-RailsKelsey HannanView Answer on Stackoverflow
Solution 7 - Ruby on-RailsCloudMagickView Answer on Stackoverflow
Solution 8 - Ruby on-RailsSantosh BtView Answer on Stackoverflow