Traco

Build Status

Translatable attributes for Ruby on Rails, stored in the model table itself.

Inspired by Iain Hecker's translatable_columns.

To store translations outside the model, see Sven Fuchs' Globalize.

Usage

Say you want Post#title and Post#body to support both English and Swedish values.

Write a migration to get database columns with locale suffixes, e.g. title_sv and title_en, like:

class CreatePosts < ActiveRecord::Migration
  def change
    create_table :posts do |t|
      t.string :title_sv, :title_en
      t.text :body_sv, :body_en

      t.timestamps
    end
  end
end

Don't create a database column named title without a suffix, since Traco will define a method with that name.

If you use a locale format like pt-BR, the column name would be title_pt_br.

Declare the attributes in the model:

class Post < ActiveRecord::Base
  translates :title, :body
end

You can still use your accessors like title_sv and title_sv= in forms, validations and other code, but you also get:

#title: Shows the title in the current locale. If blank, falls back to default locale. Otherwise nil.

#title=: Assigns the title to the column for the current locale, if present. Raises if the column doesn't exist.

#title?: Is the title present? Respects the Traco fallback setting.

.human_attribute_name(:title_sv): Extends this standard method to return "Title (Swedish)" if you have a translation key i18n.languages.sv = "Swedish" and "Title (SV)" otherwise. Rails uses this method to build validation error messages and form labels.

.translatable_attributes: Returns an array like [:title, :body].

.locale_columns(:title): Returns an array like [:title_sv, :title_en] sorted with current locale first, then default locale, and then alphabetically. Suitable for looping in forms:

<% Post.locale_columns(:title).each do |column| %>
  <p>
    <%= form.label column %>
    <%= form.text_field column %>
  </p>
<% end %>

Or perhaps for things like:

attr_accessible *locale_columns(:title)

validates *locale_columns(:title), :uniqueness => true

You can also pass multiple attributes if you like:

attr_accessible *locale_columns(:title, :body)

The return value will be sorted like [:title_sv, :title_en, :body_sv, :body_en].

.current_locale_column(:title): Returns :title_sv if :sv is the current locale. Suitable for some SQL queries, such as sorting.

.locales_for_attribute(:title): Returns an array like [:sv, :en] sorted with current locale first, then default locale, and then alphabetically.

And the equivalent methods for body, of course.

Please note that your translates :title, :body declaration must be called before you call locale_columns. Otherwise you will get an error like "NoMethodError: undefined method `locale_columns' for #<Class:0x00000003f69188>".

Fallbacks

By default, Traco will fall back to the default locale if there is no translation in the current locale.

You can specify e.g. translates :title, fallback: false to never fall back and instead return nil.

You can specify e.g. translates :title, fallback: :any to fall back first to the default locale, then to any other locale.

You can specify e.g. translates :title, fallback: [:sv] to explicitly declare fallbacks as an array of any length.

You can override the default fallback strategy with a parameter passed to the reader: post.title(fallback: :any).

If you need to declare the default locale fallback, do post.title(fallback: :default).

Overriding methods

Methods are defined in an included module, so you can just override them and call Traco's implementation with super:

class Post < ActiveRecord::Base
  translates :title

  def title
    super.reverse
  end
end

Installation

Add this to your Gemfile:

gem "traco"

Then run

bundle

to install it.

Running the tests

bundle
rake

Benchmark

ruby benchmarks/overhead.rb

Contributors

  • Henrik Nyh
  • Andrii Malyshko
  • Tobias Bohwalli
  • Mario Alberto Chavez
  • Philip Arndt
  • PikachuEXE
  • Fernando Morgenstern
  • Tomáš Horáček
  • Joakim Kolsjö

License

MIT