Skip to content

Migrating from Globalize

Chris Salzberg edited this page Mar 18, 2018 · 58 revisions

Mobility's behaviour and interface is largely the same as Globalize, so migrating is straightforward. Note that there are some minor differences, so having a comprehensive test suite for your existing application is important.

Basic setup

Change your Gemfile to remove globalize and add mobility

-gem 'globalize'
+gem 'mobility'

Run rails generate mobility:install to create the initializer.

Edit config/initializers/mobility.rb and change the default backend to table and turn on dirty tracking. ("Table" is the name of the backend where translations are stored in another table, equivalent to what Globalize does. See the wiki page on the Table backend for details.)

Although not required, it is also recommended to enable locale accessors for use with the dirty plugin, and set I18n.available_locales.

 Mobility.configure do |config|
+  config.default_backend = :table
-  config.default_backend = :key_value
   config.accessor_method = :translates
   config.query_method    = :i18n
+  config.default_options[:dirty] = true
+  config.default_options[:locale_accessors] = true # recommended
end

Extend Mobility from ApplicationRecord (or just from the classes that call translates):

 class ApplicationRecord < ActiveRecord::Base
+  extend Mobility
   self.abstract_class = true
 end

Fallbacks

If you were using fallbacks, also turn them on in mobility:

 Mobility.configure do |config|
   config.default_backend = :table
   config.accessor_method = :translates
   config.query_method    = :i18n
   config.default_options[:dirty] = true
+  config.default_options[:fallbacks] = { en: :ja, ja: :en }
end

Note that fallbacks (like all options) can be customized for each model, by passing the fallbacks option to translates. The default_options above just sets the default in case none is specified in the model.

Dirty Tracking

Mobility supports tracking changed (translated) attributes like Globalize. To enable this, enable the dirty option in your mobility initializer (as mentioned above).

 Mobility.configure do |config|
   config.default_backend = :table
   config.accessor_method = :translates
   config.query_method    = :i18n
+  config.default_options[:dirty] = true
end

There is a subtle difference in how Mobility handles dirty attributes. In Globalize, a change to the title attribute in any locale will be tracked as a change to title. Mobility does things differently: changes to an attribute in a given locale will be tracked with a suffix containing the locale, so that you can see changes to multiple locales at once.

post.changed
["title_en", "title_ja"]

Again, like fallbacks, you can enable or disable dirty tracking on each model if you like by passing the dirty option to translates.

globalize-accessors

Mobility supports having locale specific accessors (e.g., title_en or title_ja) like the globalize-accessors gem does.

To use this, first remove globalize-accessors from your Gemfile

-gem 'globalize-accessors'

Then turn on the locale_accessors plugin in your mobility initializer:

 Mobility.configure do |config|
   config.default_backend = :table
   config.accessor_method = :translates
   config.query_method    = :i18n
   config.default_options[:dirty] = true
+  config.default_options[:locale_accessors] = true
end

This will define locale accessor methods for all locales in I18n.available_locales.

Also be sure to remove any globalize_accessors from your models. Locale accessors can be customized per model using the locale_accessors option to translates.

class Post < ApplicationRecord
  translates :title, locale_accessors: [:en, :fr, :de] # overrides default
end

Associations

The association from model to translations is the same as Globalize, a has_many relation (by default) named translations:

post = Post.first
post.translations #=> returns post translations

(You can actually change the name of the association to something else with the association_name option to translates.)

However, whereas the inverse association in Globalize is called globalized_model, it is called translated_model in Mobility:

translation = post.translations.first
translation.translated_model          #=> returns the post

This means, among other things, that if you are using fixtures, you'll need to change globalized_model everywhere to translated_model.

Migrations

Mobility does not have migration helper methods like Post.create_translation_table! to create new translation tables for translated models. Instead, there are rails generators for this.

If you have a model Post and want to add translations for attributes title (string) and content (text), you would do this with:

rails generate mobility:translations post title:string content:text

Querying

Mobility supports querying on translated attributes like Globalize. However, unlike Globalize, this is not enabled by default. To query on translated attributes, you need to use the i18n scope (the name of this scope can be customized using the query_method configuration option):

Post.i18n.where(title: "foo")
Post.i18n.find_by(title: "foo")
Post.i18n.find_by_title("foo")

Although it is not recommended, you can make this query scope enabled by default using a default_scope, like this, in your model class:

default_scope { i18n }

Then you can query on translated attributes like with Globalize, without any extra scope:

Post.where(title: "foo")
Post.find_by(title: "foo")
Post.find_by_title("foo")

To enable the i18n by default on all models, put the default scope above in ApplicationRecord.

Note that Mobility does not currently support querying with fallbacks like Globalize does, although this is in the roadmap. It also does not support ordering on translated attributes (there is an issue to add support for this). (These are the only issues remaining before Mobility can claim full feature parity with Globalize.)

Interpolation

Mobility does not support interpolation like Globalize does, but you can get the same effect of code like this (in Globalize):

greeter.greeting(name: 'Chris')

with the slightly more verbose:

I18n.interpolate(greeter.greeting, name: 'Chris')

See #162 for more discussion on this.

Gem Integrations

More info

See the Table Backend section of the wiki.