Skip to content

Rails ActionText backend to translate rich text #385

@sedubois

Description

@sedubois

I would like the :key_value backend to be configurable in order to support translating ActionText rich text.

Context

It appears Mobility is becoming the new "Rails I18n de-facto standard library for ActiveRecord model/data translation", to re-use Globalize's description 😉 I also guess that many apps, including my own bilingual content-centric app, use rich text rather than plain text, where ActionText is supposed to be the ad-hoc Rails solution. Additionally, Mobility describes itself as adapting to different storage needs. I would thus like Mobility and ActionText to work elegantly together.

Expected Behavior

Being able to store and query translated ActionText rich text using a single table join using the existing Mobility API.

Actual Behavior

I am not aware of a way to make ActionText and Mobility work together without requiring two levels of joining. This example shows that translating ActionText can be done, however it requires two nested joins and fragile patches of code.

Possible Fix

Similar to the :key_value backend, ActionText also works by establishing a polymorphic relation on the model. This raises an opportunity to translate ActionText rich text "for free", i.e. at no extra performance cost compared to untranslated rich text, by directly using the action_text_rich_texts table as a :key_value translation backend instead of the mobility_text_translations table.

Consider the Mobility and ActionText schemas, they already have a 1:1 mapping, except for the locale column: key <=> name, value <=> body, translatable_id <=> record_id, translatable_type <=> record_type:

create_table "mobility_text_translations", force: :cascade do |t|
  t.string   "locale"
  t.string   "key"
  t.text     "value"
  t.integer  "translatable_id"
  t.string   "translatable_type"
  ...
end
create_table "action_text_rich_texts", force: :cascade do |t|
  t.string "name", null: false
  t.text "body"
  t.bigint "record_id", null: false
  t.string "record_type", null: false
  ...
end

Whereas for the example above, ActionText would normally store:

{ record_type: 'Post', record_id: 1, name: 'content', body: '<h1>hello world!</h1>' }

... it would instead store:

{ record_type: 'Post', record_id: 1, name: 'content', locale: 'en', body: '<h1>hello world!</h1>' }
{ record_type: 'Post', record_id: 1, name: 'content', locale: 'fr', body: '<h1>bonjour le monde !</h1>' }

So if Mobility offered a way to configure the table name and columns for the :key_value backend, in theory everything should "just work" 🤞😄

Mobility could be instructed to achieve it with something like this:

Mobility.configure do |config|
  config.default_backend = :key_value
  config.backends.key_value.text.table = :action_text_rich_texts
  config.backends.key_value.text.translatable = :record
  config.backends.key_value.text.key = :name
  config.backends.key_value.text.value = :text
end

Or with a shorthand:

Mobility.configure do |config|
  config.default_backend = :key_value
  config.key_value_text_backend = :action_text
end

The migration should be adapted to first check if the table exists, and if it exists, to insert the required columns/indices.

Then at the model level the following (existing) API should suffice:

# app/models/post.rb
class Post < ApplicationRecord
  has_rich_text :content
  translates :content
end

@shioyama I understand that you do not have much bandwidth, but would you consider a PR? In that case I would be willing to give it a try with your guidance.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions