# Globalize: translating ActiveRecord attributes by moving them into a translation table

> The gem Rails projects reach for when user generated content needs a title and body in several languages, plus an honest look at its maintenance status and the alternatives the README names.

**globalize/globalize** — Rails I18n de-facto standard library for ActiveRecord model/data translation.

- Repository: https://github.com/globalize/globalize
- Stars: 2,156 · Forks: 500
- Language: Ruby
- License: NOASSERTION
- Published: 2026-10-06 · Updated: 2026-10-06 · Language: en
- Canonical page: https://hysenlabs.com/projects/globalize-globalize

## What Globalize adds on top of the Rails I18n API

Globalize builds on the I18n API in Ruby on Rails to add model translations to ActiveRecord models. The README is specific about what that means: a way to translate actual user-generated content, for example a single blog post with multiple translations.

That phrase user-generated is the whole distinction. The I18n gem that ships with Rails already handles interface strings, keys like `common.help`, and it handles them well. What it does not do is hold a third language version of a row somebody typed into a form. Globalize closes that gap, which is why it is a separate gem rather than a Rails feature.

The declaration is one line in the model:

```ruby
class Post < ActiveRecord::Base
  translates :title, :text
end
```

From there, reads go through the locale, using the ordinary Rails mechanism:

```ruby
I18n.locale = :en
post.title # => Globalize rocks!
```

Writes accept the same switch, either attribute by attribute or through mass assignment with a locale key. The second form matters because it keeps a translated write inside a normal `update` call rather than requiring a separate writer object.

## Installing against current ActiveRecord, and against older ones

The requirement line is short: ActiveRecord >= 7.0 plus I18n. For the version compatible with ActiveRecord 7.x and 8.x, a plain install works:

```ruby
gem install globalize
```

Under Bundler the README asks for a pessimistic pin instead, which is the line to copy into a real application:

```ruby
gem "globalize", "~> 7.0"
```

The `~>` is doing real work here. Globalize extends ActiveRecord behaviour rather than sitting beside it, so a major version bump in either gem is the thing most likely to break an application, and the pin makes that explicit instead of incidental.

For older ActiveRecord the README points down rather than sideways: use version 6.3 or lower, with ActiveRecord 4.2 through 6.1 covered by a `~> 6.3` pin. There is also a documented path for pre-release code, which installs from the repository's main branch rather than the released gem:

```ruby
gem "globalize", git: "https://github.com/globalize/globalize", branch: "main"
```

The README asks you to report what works and what does not when using that form, which is a reasonable thing to honour if you take it.

## create_translation_table! and the ordering trap in migrations

Declaring `translates` does not change the database. The schema change is a separate step, and the README is emphatic about the ordering: you have to define the translated attributes via `translates` in your model before you can create a translation table, and the model itself must already exist.

The helper is `create_translation_table!`, and it belongs in a migration wrapped so the down direction works:

```ruby
class CreatePosts < ActiveRecord::Migration
  def change
    create_table :posts do |t|
      t.timestamps
    end

    reversible do |dir|
      dir.up do
        Post.create_translation_table! :title => :string, :text => :text
      end

      dir.down do
        Post.drop_translation_table!
      end
    end
  end
end
```

The restriction behind that shape is stated plainly: the two helpers can only be used inside the `up` and `down` instance methods respectively, not inside `change`. They are not operations the migration framework can invert on its own, so you have to say what the reverse is.

Column options are passed inline as a hash when a translated attribute needs more than a bare type. The README example sets `:text` to a non-null column with a default:

```ruby
        Post.create_translation_table! :title => :string,
          :text => {:type => :text, :null => false, :default => "abc"}
```

Any attribute you want indexed or constrained has to be declared there, because after the migration the values live in a different table. Later additions go through `add_translation_fields!`.

## Migrating existing rows and the point of no return

For an existing application, creating the table is only half the job. The same helper migrates current data into the default locale, and it works in reverse too, restoring translations back into the model when you drop the translation table.

The mechanism underneath is `untranslated_attributes`, which gives access to the model's attributes as they were before translation was applied. The migration passes `:migrate_data => true`:

```ruby
class TranslatePosts < ActiveRecord::Migration
  def change
    reversible do |dir|
      dir.up do
        Post.create_translation_table!({
          :title => :string,
          :text => :text
        }, {
          :migrate_data => true
        })
      end

      dir.down do
        Post.drop_translation_table! :migrate_data => true
      end
    end
  end
end
```

There is a warning attached, and it deserves attention: make sure you drop the translated columns from the parent table after all your data is safely migrated. The parent table keeps the original `title` and `text` columns after the migration, which means two copies of your content and a window where a write reaches only one of them. That is the classic bug this shape invites.

If you want the columns removed in the same migration, the option is `:remove_source_columns`, and the README switches to explicit `self.up` and `self.down` methods for it:

```ruby
  def self.up
    Post.create_translation_table!({
      :title => :string,
      :text => :text
    }, {
      :migrate_data => true,
      :remove_source_columns => true
    })
  end

  def self.down
    Post.drop_translation_table! :migrate_data => true
  end
```

That direction is not reversible in the ordinary sense, so the safe sequence is to migrate, verify the data, then remove the columns in a later migration.

## Choosing a locale for migrated data

One more detail in the data migration is easy to get wrong on a database that already has non-English content. Without saying anything, migrated rows land in the default locale. To choose, wrap the call in `I18n.with_locale`:

```ruby
    I18n.with_locale(:bo) do
      Post.create_translation_table!({
        :title => :string,
        :text => :text
      }, {
        :migrate_data => true
      })
    end
```

The same mechanism handles ordinary reads, so nothing new has to be learned for it. It is worth naming what Globalize does not do here: no fallback policy is described anywhere in the README. If `I18n.locale` is `:he` and a row has no Hebrew translation, the result is whatever ActiveRecord gives back for a missing value, and the choice of policy is yours to write. On a content model where some rows are untranslated by design, that is a decision you have to make in your own code rather than inherit.

There is a docker-compose file at the root of the repository that starts both a Postgres 11 and a MySQL 8.0 service on their default ports with empty root passwords, which tells you the test suite runs against both engines. Nothing in the README suggests you need either one to use the gem in an application.

## Maintenance status stated plainly by the maintainers

The README has a section titled Current state of the gem, and it does not oversell anything. Globalize is described as relatively feature complete and not very actively maintained, with the reason given plainly: none of the current maintainers actively use it. The expectation expressed is that it should still work just fine, and that the project tries to keep up with new Ruby and Rails releases. Pull requests are described as very welcome, with the caveat of a possible delayed response, especially for compatibility with new versions of Rails.

That is a specific and unusual thing for a project to publish about itself, and it is more useful than a green CI badge. The last push was on 2026-09-16, and the release line has been steady through it: v7.1.3 on 2026-05-25, v7.1.2 on 2026-05-18, v7.1.1 on 2025-12-23. Compatibility work on a gem that extends ActiveRecord is exactly the kind of thing that shows up as a release with a very small diff.

The alternative section is the most opinionated part of the README. Mobility is described as a pluggable translation framework supporting many strategies, including translatable columns, translation tables and hstore or jsonb, and it is said to be seen by many, Globalize maintainers included, as a natural successor to Globalize. Traco uses multiple columns in the same model, hstore_translate and json_translate store translations in Postgres native types instead of separate tables, and Trasto stores them directly in a Postgres hstore column. A separate related project, friendly_id-globalize, uses Globalize to translate slugs. The storage decision is the real fork in the road, and each of those takes a different side of it.

## Conclusion

Globalize fits a specific shape of problem: an existing ActiveRecord model whose columns hold content a human wrote, in more than one language, where you want to keep using the same model API instead of writing a content abstraction. The cost is structural, because every translated attribute leaves the parent table and every existing query touching those columns has to change. The README also says plainly that none of the maintainers actively use the gem, so treat the version pin in your Gemfile as the thing that keeps a Rails upgrade from becoming your problem. Read the alternative list before starting, since it names Mobility as the successor and Traco, hstore_translate, json_translate and Trasto as the different storage strategies.

## FAQ

### What does the Globalize gem do for a Rails application?

It builds on the Rails I18n API to add model translations to ActiveRecord models, so attributes such as a post title and body can hold a different value per locale. You declare the fields with a translates call and then move them into a separate translation table with a migration helper.

### Which version of Globalize should I add to my Gemfile?

For ActiveRecord 7.x and 8.x the README asks for gem "globalize", "~> 7.0". Applications on ActiveRecord 4.2 through 6.1 should use version 6.3 or lower instead, and the stated requirement for current versions is ActiveRecord 7.0 alongside the I18n gem.

### Is Globalize still maintained?

The README says Globalize is relatively feature complete and not very actively maintained, because none of the current maintainers actively use it, and it expects the gem to keep working while staying compatible with new Ruby and Rails releases. The last push was on 2026-09-16, with v7.1.3 released on 2026-05-25.

### What are the alternatives to Globalize for translating ActiveRecord attributes?

The README lists Mobility, a pluggable framework with several storage strategies that maintainers call a natural successor to Globalize, plus Traco for multiple columns in one model, hstore_translate and json_translate for Postgres native types, and Trasto for a Postgres hstore column. The differences come down to where the translated values are stored.

## Sources

- [globalize/globalize on GitHub](https://github.com/globalize/globalize)
- [Issues](https://github.com/globalize/globalize/issues)
- [README](https://github.com/globalize/globalize/blob/main/README.md)
- [Releases](https://github.com/globalize/globalize/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/globalize-globalize
