views:

303

answers:

2

I want to make a language selection dropdown in a site user edit/create page.

For this purpose, I have of course translated the site to more than one language. Using I18n.available_languages, I can then get an array of locale codes, like so

development environment (Rails 2.3.4)
> I18n.available_locales
   => [:en, :da]

Furthermore, I have created a Language model and related it to User:

# app/models/language.rb
class Language < ActiveRecord::Base
  has_many :users  
end

# app/models/user.rb
class User < ActiveRecord::Base
  belongs_to :language  
end

# db/schema.rb
create_table "languages", :force => true do |t|
  t.string "name"
  t.string "code"
end

create_table "users", :force => true do |t|
  t.integer  "language_id"
end

The language table then contains a locale code and a language name in the native tongue, like so:

| id  | name                | code |
------------------------------------
| 28  | Dansk               | da   |
| 29  | Nederlands          | nl   |
| 30  | English             | en   |
| 31  | Esperanto           | eo   |

I then have the following assignment in the User new, create and edit actions:

# app/controllers/users_controller.rb (extract)
@available_languages = I18n.available_locales.collect {|language_code| Language.find_by_code(language_code.to_s)}

which I use in the view like so ('available_languages' is a local variable, since @available_languages from the controller has been passed to a partial):

# app/views/users/_form.haml (extract)
= f.collection_select(:language_id, available_languages, :id, :name, {:prompt => true})

The upshot of all this, is that the user will get a locale select dropdown to define the locale for the given user.

My question is: Is there a clean way to move the @available_languages assignment out of the UsersController and into the Language model, so I can shorten this:

@available_languages = I18n.available_locales.collect {|language_code| Language.find_by_code(language_code.to_s)}

to something like this:

@available_languages = Language.translations_available
+1  A: 

It seems to me that you're doing a couple of funny things. First off there are some problems with the following:

I18n.available_locales.collect {|language_code| Language.find_by_code(language_code.to_s)}

This setup causes you to generate one SQL query for every available locale. Furthermore if every locale in I18n.available_locales has a corresponding Language object and vice-versa, this code seems a bit unnecessary. You might as well just do:

Language.find(:all) # or even Language.all

If for some reason, they don't map directly, you could use this instead:

Language.all(:conditions => { :code => I18n.available_locales })

which in a more verbose form is equivalent to:

Language.find(:all, :conditions => ["code IN (?)", I18n.available_locales])

This will find all languages whose code is listed in I18n.available_locales. If you want a shortcut to this method, you can use named_scopes:

class Language < ActiveRecord::Base
  has_many :users

  named_scope :translation_available, :conditions => { :code => I18n.available_locales }
end

With this, you can then call:

Language.translation_available

I think this is what you wanted.

Peter Wagenet
Thanks Peter. The Language table does not map directly, so I'll try out Language.all(:conditions => { :code => I18n.available_locales })
HansCz
If you need an authoritative list of all two-letter language codes that map to I18n translation .yml files, my guess is this list is pretty authoritative: http://www.loc.gov/standards/iso639-2/php/English_list.php --- for a list of language names in their native tongues, you might use: http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
HansCz
A: 

I add a "locale_name" key to each yml with it's language name in it's own language as well. For example:

in es-AR.yml es-AR: locale_name: "Castellano"

in en.yml en: locale_name: "English"

dwaynemac