views:

4301

answers:

5

How can I go about making my routes recognise an optional prefix parameter as follows:

/*lang/controller/id

In that the lang part is optional, and has a default value if it's not specified in the URL:

/en/posts/1   => lang = en
/fr/posts/1   => lang = fr
/posts/1      => lang = en

EDIT

Ideally, I'm looking to do this across many controllers and actions by mapping a namespace:

map.namespace "*lang" do |lang|
  lang.resources :posts
  lang.resources :stories
end
A: 

I'm guessing (no time to test right now) that this might work:

map.connect ':language/posts/:id', :controller => 'posts', :action => 'show'
map.connect 'posts/:id', :controller => 'posts', :action => 'show'

OK, tried it - it works, on Rails 2.1.1 at least. So that's good. Can't get the :defaults idea to work, though, which is a shame, because it's DRYer.

Mike Woodhouse
+2  A: 

You can define defaults in a route using the :defaults key. Try this:

map.connect ':lang/posts/:id', :controller => 'posts', :action => 'show',
                               :defaults => { :lang => 'en' }
John Topley
Doesn't work in 2.1.1 on my machine. http://localhost:3000/posts/1 gives No route matches "/posts/1" with {:method=>:get}Looks like :defaults only provides parameters not in the route? I need to investigate: I didn't know about :defaults at all, so thanks!
Mike Woodhouse
I think this is on the right track - is it possible to integrate this option with namespaces and resources?
Mr. Matt
IIRC, defaults work like default values in method parameters -- i.e. defaults can only be used on the last values when listed in order, not the first values
Ian Terrell
+1  A: 

Thought you could at one time use a [key] => nil to specify an optional parameter. Something like:

map.connect ':lang/posts/:id', :controller => 'posts', :action => 'show', :lang => nil
Collin
+9  A: 

OK, I've managed to sort out this problem:

THere is no way of doing this in Rails by default (at least, not yet). Instead of using namespaces and default values, I needed to install Sven Fuchs' routing filter.

Once the plugin is installed, I added the following file to my lib directory:

require 'routing_filter/base'

module RoutingFilter
  class Locale < Base

    # remove the locale from the beginning of the path, pass the path
    # to the given block and set it to the resulting params hash
    def around_recognize(path, env, &block)
      locale = nil
      path.sub! %r(^/([a-zA-Z]{2})(?=/|$)) do locale = $1; '' end
      returning yield do |params|
        params[:locale] = locale || 'en'
      end
    end

    def around_generate(*args, &block)
      locale = args.extract_options!.delete(:locale) || 'en'
      returning yield do |result|
        if locale != 'en'
          result.sub!(%r(^(http.?://[^/]*)?(.*))){ "#{$1}/#{locale}#{$2}" }
        end 
      end
    end

  end
end

I added this line to routes.rb:

map.filter 'locale'

This basically fills out a before and after hook, generated by the plugin, that wraps the rails routing.

When a url is recognised, and before Rails gets to do anything with it, the around_recognize method is called. This will extract a two-letter code representing the locale, and pass it through in the params, defaulting to 'en' if no locale is specified.

Likewise, when a url is generated, the locale parameter will be pushed into the URL on the left side.

This gives me the following urls and mappings:

/   => :locale => 'en'
/en => :locale => 'en'
/fr => :locale => 'fr'

All existing url helpers work as before, with the only difference being that unless the locale is specified, it is preserved:

home_path                  => /
home_path(:locale => 'en') => /
home_path(:locale => 'fr') => /fr
Mr. Matt
Thanks for the tip, this worked great for some translation/localization/internationalization work I'm doing. FYI, you don't have to add that file to your library -- the 'locale' filter is already included in the plugin. All you should have to do is:1. Install the plugin.2. Add `map.filter 'locale'` to your routes.rb.After that it just starts working! Nice. I also added `RoutingFilter::Locale.include_default_locale = false` to my environment.rb to avoid `/en` being in my links.Works great, I hope Sven's plugin gets pulled into the Rails I18n codebase.
Jordan Brough
Here's another option I ran across today, haven't looked into it (routing-filter is working great) but might be of interest to others: http://github.com/raul/translate_routes
Jordan Brough
A: 

I understand that this old question, but it may be useful.

  map.with_options(:path_prefix => ":locale") do |m|
    m.resources :posts
    m.resources :stories        
  end

And you have to add before filter to application controller to define locale, such as

before_filter :define_locale

def define_locale
  if params[:locale] == nil
    I18n.locale = 'en'
  else
    I18n.locale = params[:locale]
  end
end
ADeryabin
The path prefix is not optional, so you always have to supply a locale unless you duplicate the routes outside the block.
dseifert