Adventures with Ruby

Plugin: translatable_columns

View Comments

It was just three days ago when I discussed how to translate columns. At the moment I was writing it, I was already thinking: “this should be a plugin”. So today, I took the liberty and created it.

Contents

  1. Installing
  2. Usage
  3. Validating
  4. Customizing
  5. Some extras

Installing

  1. First make sure you’re running Rails 2.2 or edge:
     
    rake rails:freeze:edge
     
  2. Install the plugin:
     
    ./script/plugin install git://github.com/iain/translatable_columns.git
     
  3. Create or modify a model to have multiple columns for one attribute:
     
    ./script/generate model Topic title_en:string title_nl:string title_de:string title_fr:string
     

Usage

Identify the columns you want to translate:

[sourcecode language="ruby"]
class Topic < ActiveRecord::Base
translatable_columns :title
end
[/sourcecode]

And you're done!

Create a form like this:

[sourcecode language="ruby"]
<% form_for(@topic) do |f| %>
<%= f.text_field :title %>
<% end %>
[/sourcecode]

And it will save to whatever locale is set in I18n. No hard feelings, nothing to worry about.

Validating

Validation is of course built in. If you want to validate the presence of at least one of the translations, just call validates_translation_of:

[sourcecode language="ruby"]
class Topic < ActiveRecord::Base
translatable_columns :title
validates_translation_of :title
end
[/sourcecode]

This will make your record invalid when none of the translated columns exist. It works exactly as validates_presence_of, including all its options!

Customizing

You can change the settings of translatable_columns on both a global level and at individual attribute level. There are two configuration options at the moment, called full_locale and use_default.

Set the global configuration in your environment file:

[sourcecode language="ruby"]
# These are the defaults of translatable_columns:
ActiveRecord::Base.translatable_columns_config.full_locale = false
ActiveRecord::Base.translatable_columns_config.use_default = true
[/sourcecode]

full_locale

With this option you can change which part of the locale is used in the columns. Default is false, so only the first part of the locale is expected in the column. So a title for en-US is called title_en and a title for en-GB is also called title_en. When you set full_locale to true, it uses the entire locale, substituting the hyphen with an underscore. This way a title for en-US is called title_en_us and a title for en-GB is called title_en_gb.

full_locale cannot be set per attribute just now.

use_default

With this option you can specify which value will be returned automatically if no proper value has been found. Default is true, so it will try harder to find a value. It might even be a value in another language.

You can set this option per attribute if you'd like, to override the global config.

[sourcecode language="ruby"]
class Topic < ActiveRecord::Base
translatable_columns :title, :use_default => false
end
[/sourcecode]

Some extras

What if the user has selected a locale which you don't have in the database? In this case it'll get the column belonging to the I18n.default_locale. Make sure you have a column for this locale, because you'll be serving a nasty error if even this one isn't present!

You might want to provide multiple languages for a user to fill in at once. This is one way to do it:

[sourcecode language="ruby"]
<% form_for(@topic) do |f| %>
<% Topic.available_translatable_columns_of(:title).each do |attribute| %>
<%= f.text_field attribute %>
<% end %>
<% end %>
[/sourcecode]

Happy devving!

Written by Iain Hecker

September 6th, 2008 at 8:19 pm

Posted in Uncategorized

  • http://www.icoretech.org kain

    Cool, I might have a solid use case for this.
    Thanks for sharing.

  • http://iain.nl/2008/09/translating-columns/ Translating Columns • iain.nl

    [...] Wednesday September 3rd • Howto’s and tips, Ruby on Rails Category I made this into a plugin: translatable_columns. [...]

  • Michael

    Have you checked out if it integrates well with Active Scaffold?

  • http://iain.nl/ iain

    Michael: No I haven’t, because I don’t use ActiveScaffold. You’ll have to test that yourself.

  • http://www.sivarg.com Gravis

    Hi, thanks for the new plugin, this looks very great.
    Anyway, I encounter some difficulties with migrations now. I have some migrations creating or updating values. Therefore, I can’t reproduce a clean dev environment since the old creations will fails agains validates_translation_of (and translatable_columns will certainly cause problems too). I’m not sure how to solve this (besides commenting all modifications in model files during migrations) :(

    Thanks

  • http://www.sivarg.com Gravis

    Maybe adding a boolean field in models, false by default, and set to true when the model has been migrated to a “localized” version ? validates_translation_of would check before everything this boolean value, and raise a warning (but still create the instance) if the model has not been localized yet.
    My 2c

  • http://iain.nl Iain Hecker

    @Gravis: If this one time only migration is the problem, it’s probably easier to temporarily comment out the validation, run the migration and then turn it on again.

    You can also put some conditions around it:

    class User < ActiveRecord:Base
      if column_names.include?("title_en")
        translatable_columns :title
        validates_translation_of :title
      end
      # .......
    end
  • Gravis

    Thanks Iain, it saved me a lot of time, I didn’t think of such simple solution :)
    I have a lot of localized model, and it’s definitely the way to go.

  • http://www.arcasys.de Hans

    Thanks for this plugin, which basically works great and is really simple to deal with.

    I’m trying to use translatable_columns to internationalize a Goldberg application. I started with the content_pages and Goldberg gets confused with saving pages.
    Another problem arises from Goldberg using find_by_sql in some places which seems not to be supported. Is there a simple way out or have I to go with the nasty workaround of inserting a translation in the sql string?

    Hans

  • http://www.arcasys.de Hans

    Just found another problem: if an object is serialized into a translated column the reference does not return the object but the raw yaml string.

    Hans

    BTW,
    if you want to use Goldberg with translatable_columns you got to copy the two lines in init.rb into Goldbergs init.rb to initialize translatable_columns before Goldberg startup.

  • http://Quotegasm.com Gene

    Nice plugin!

    It works great when a few columns need to be translated to a couple of languages. However, when many languages are required (20+) for most columns, this approach no longer scales.

  • http://www.yabo-concept.ch jujudellago

    Hi,

    I really like that plugin, just works the way I need it in most cases, for websites using 2-3 languages, in my case french, english and german.

    One thing I really like to offer to my clients is simple forms where some datas wil be translated, some won’t for example last name, first name, or company name would not be translated, while a presentation text could be written in 3 languages.

    this is when I love to have for example 3 text areas, using transalted columns:

    description_fr
    description_en
    description_de

    letting my user decide in what language they wish to fill their description.

    it works almost as I would expect, the use_default is fantastic, but I just have a problem: as the forms are submitted with all the transalted fields, if they are left blank the form, they are saved as blank string, instead of nil.

    in that case, when the page containing the datas is loaded, instead of getting a default value it just gives me an empty string.

    I made an ugly fix, in my model, using a “before save” in my model, but it’s so ugly….

    def before_save
    if self. description_fr.blank?
    self. description_fr=nil
    end
    if self. description_en.blank?
    self. description_en=nil
    end
    if self. description_de.blank?
    self. description_de=nil
    end
    end

    I’m so bad with metaprogramming, can’t figure out how to automatize such thing in the plugin, could you help ?

    and… do you think what I’m doing makes sense ?

    kind regards

  • http://iain.nl Iain Hecker

    How about something like:

    before_save :cleanup_fields
     
    private
     
    def cleanup_fields
      available_columns_of("description").each do |column|
        send("#{column}=", nil) if send(column).blank?
      end
    end

    You can also use the attribute normalization plugin

  • http://dmitry.eu/ Dmitry Polushkin

    Check out my model translation plugin: http://github.com/dmitry/has_translations
    It uses little different approach.

    Thanks,
    Dmitry

blog comments powered by Disqus
Fork me on GitHub