3

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
HansCz
  • 284
  • 1
  • 6
  • 11

3 Answers3

13

Here's a more complete answer based on Dwaynemac's solution:

  1. Add a key with the locale name to each yml file. For example in en.yml:

    en:
      language: English
    

    and in es.yml:

    es:
      language: Español
    
  2. Add a helper method (for example in /app/helpers/application_helper.rb) which creates an array of locale and locale name pairs:

      def locale_name_pairs
        I18n.available_locales.map do |locale|
          [I18n.t('language', locale: locale), locale.to_s]
        end
      end
    
  3. In your form use locale_name_pairs to create your selection dropdown:

    f.select :locale, options_for_select(locale_name_pairs, @user.locale)
    

Note for the lazy: If you prefer to skip step 2 then you can use the following one-liner in step 3: f.select :locale, options_for_select(I18n.available_locales.map{ |locale| [I18n.t('language', locale: locale), locale.to_s] } , @user.locale)

Ben Maraney
  • 141
  • 1
  • 6
13

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
  • 1,076
  • 1
  • 13
  • 24
5

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
  • 4,976
  • 22
  • 26
  • Thanks Peter. The Language table does not map directly, so I'll try out Language.all(:conditions => { :code => I18n.available_locales }) – HansCz Oct 05 '09 at 14:12
  • 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 Oct 05 '09 at 14:32