Report abuse

Storing the locale in the URL without having to pass it into resource helpers

Goal:   article_path(@article)  #=>  /en/article/1

Overriding define_url_helper

All I did was add one line to this method.  I have placed this code in my environment.rb file. I know there is a cleaner way, but I'm just testing this for now. This should get some ideas going.

module ActionController
  module Routing
    class RouteSet
      class NamedRouteCollection
        def define_url_helper(route, name, kind, options)
          selector = url_helper_name(name, kind)

          # The segment keys used for positional paramters
          segment_keys = route.segments.collect do |segment|
            segment.key if segment.respond_to? :key
          end.compact
          hash_access_method = hash_access_name(name, kind)
          @module.send :module_eval, <<-end_eval # We use module_eval to avoid leaks
            def #{selector}(*args)
!!             #{'args << { :locale => @current_locale }' if segment_keys.include?(:locale)}
             opts = if args.empty? || Hash === args.first
                args.first || {}
              else
                args.zip(#{segment_keys.inspect}).inject({}) do |h, (v, k)|
                  h[k] = v
                  h
                end
              end
              url_for(#{hash_access_method}(opts))
            end
          end_eval
          @module.send(:protected, selector)
          helpers << selector
        end
      end
    end
  end
end

The Application Controller - Setting the @current_locale

before_filter :set_locale

def set_locale
  default_locale = 'en-US'
  request_language = request.env['HTTP_ACCEPT_LANGUAGE']
  request_language = request_language.nil? ? nil : 
    request_language[/[^,;]+/]

  @current_locale = params[:locale] || session[:locale] ||
            request_language || default_locale
  session[:locale] = @current_locale
  begin
    Locale.set @current_locale
  rescue
    Locale.set default_locale
  end
end
private :set_locale

The routes

map.index ':locale', :controller => 'general', :locale => 'en-US'  

map.hello ':locale/admin', :controller => 'general', :action => 'admin'

map.connect 'test', :controller => 'general', :action => 'test'

map.resources :features
map.resources :articles, :path_prefix => ":locale"

# Install the default route as the lowest priority.
map.connect ':locale/:controller/:action/:id.:format'
map.connect ':locale/:controller/:action/:id'

# You can also define another set of default routes for when the locale is not in the params
map.connect ':controller/:action/:id.:format'
map.connect ':controller/:action/:id'

The Result

Assuming params[:locale] = 'en' and params[:controller] => 'general'

link_to 'Articles', articles_path                                #=> /en/articles
link_to 'Articles', articles_path(:locale => 'fr')         #=> /fr/articles

link_to 'Features', features_path                             #=> /features

link_to 'Home', index_url                                        #=> /
link_to 'Home', index_url(:locale => 'fr')                 #=> /fr


link_to 'Test', :action => 'test'                                #=> /en/general/test

# The only caveat that I've found so far is that you need to specify the controller when trying to change the locale without a named route
link_to 'Always French', :controller => 'general', :action => 'french', :locale => 'fr'

# This returns an error that I haven't had time to investigate. Be my guest. :)
link_to 'Always French', :action => 'french', :locale => 'fr'