I18n::PostgresJson
Internationalization backed by Postgres JSON columns
Usage
PostgreSQL's support for storing objects as JSON and JSONB
columns, integrated with ActiveRecord
column-aware
models makes it an ideal candidate to store dynamic,
editable application translation text.
This gem's inspiration comes from the Rails Internationalization (i18n) Guides mention of
i18n-active_record
.
Cascading translations by combining a dynamic, database driven
translation source ahead of the default I18n::Backend::Simple
enable dynamic
copy editing, with an in-code foundational source.
Installation
Add this line to your application's Gemfile
:
gem 'i18n-postgres_json'
And then execute:
bundle
I18n::PostgresJson::KeyValue::Store
The I18n::PostgresJson::KeyValue::Store
class serves as a store
for the
I18n::Backend::KeyValue
backend.
The I18n::PostgresJson::KeyValue::Store
interacts with a Postgres table
consisting of:
Table "public.i18n_postgres_json_key_value_store_translations"
Column | Type | Collation | Nullable | Default
--------------+-----------------------------+-----------+----------+-----------------------------------------------------------------------------
id | integer | | not null | nextval('i18n_postgres_json_key_value_store_translations_id_seq'::regclass)
translations | json | | not null | '{}'::json
created_at | timestamp without time zone | | not null |
updated_at | timestamp without time zone | | not null |
Indexes:
"i18n_postgres_json_key_value_store_translations_pkey" PRIMARY KEY, btree (id)
This style of backend store all translations, regardless of locale, within the
same translations
column, backed by JSON.
This table is only ever intended to store a single row.
To generate this table, install the necessary migrations:
rails i18n_postgres_json:install:key_value
Then execute them:
rails db:migrate
Next, configure your I18n.backend
(either in a Rails environment configuration
block, or an initializer of its own):
# config/initializers/i18n.rb
ActiveSupport.on_load(:i18n) do
require "i18n/postgres_json/key_value/store"
I18n.backend = I18n::Backend::Chain.new(
I18n::Backend::KeyValue.new(I18n::PostgresJson::KeyValue::Store.new),
I18n.backend, # typically defaults to I18n::Backend::Simple
)
end
Caveat: It's worth noting that according to the I18n::Backend::KeyValue
documentation, this backend cannot store serialized Ruby
Proc
instances:
Since these stores only supports string, all values are converted to JSON before being stored, allowing it to also store booleans, hashes and arrays. However, this store does not support Procs.
I18n::PostgresJson::Backend
The I18n::PostgresJson::Backend
class serves as an I18n::Backend
implementation of its own.
The I18n::PostgresJson::Backend
interacts with a Postgres table
consisting of:
Table "public.i18n_postgres_json_backend_translations"
Column | Type | Collation | Nullable | Default
--------------+-----------------------------+-----------+----------+---------------------------------------------------------------------
id | integer | | not null | nextval('i18n_postgres_json_backend_translations_id_seq'::regclass)
locale | character varying | | not null |
translations | json | | not null | '{}'::json
created_at | timestamp without time zone | | not null |
updated_at | timestamp without time zone | | not null |
Indexes:
"i18n_postgres_json_backend_translations_pkey" PRIMARY KEY, btree (id)
"index_i18n_postgres_json_backend_translations_on_locale" UNIQUE, btree (locale)
This style of backend splits each locale
into its own row, separating out
translations into one lookup table per-locale. The translations
column is
backed by JSON.
To generate this table, install the necessary migrations:
rails i18n_postgres_json:install:backend
Then execute them:
rails db:migrate
Next, configure your I18n.backend
(either in a Rails environment configuration
block, or an initializer of its own):
# config/initializers/i18n.rb
ActiveSupport.on_load(:i18n) do
require "i18n/postgres_json/backend"
I18n.backend = I18n::Backend::Chain.new(
I18n::PostgresJson::Backend.new,
I18n.backend, # typically defaults to I18n::Backend::Simple
)
end
Caveat: It's worth noting that the I18n::PostgresJson::Backend
does not
currently support translation linking.
Writing to the tables
Regardless of whether your application integrates with
I18n::PostgresJson::KeyValue::Store
, or I18n::PostgresJson::Backend
, the
implementation for updating existing translations will be the same: use
I18n::Backend::Base.store_translations
.
If you were to edit a translation through an HTML <form>
element that
submitted to a Rails controller, it might look something like this:
<!-- app/views/posts/index.html.erb -->
<form action="/translations" method="post">
<input type="hidden" name="translation[locale]" value="en">
<input type="hidden" name="translation[key]" value="posts.index.title">
<label for="translation_value">
Translation for posts.index.title
</label>
<input type="text" id="translation_value" name="translation[value]">
<button>
Save Translation
</button>
</form>
When that <form>
element is submitted, the resulting controller action (in
this example, translations#create
) would pass along the submitted translation
to store_translations
:
class TranslationsController < ApplicationController
def create
I18n.backend.store_translation(
translation_params.fetch(:locale),
translation_params.fetch(:key) => translation_params.fetch(:value),
)
redirect_to posts_url
end
private
def translation_params
params.require(:translation).permit(
:key,
:locale,
:value,
)
end
end
Caveat: This is a potentially disruptive (and destructive!) action, so the application would want to limit control to authenticated content editors. This example omits those details for the sake of brevity.
Testing
In addition to being exercised by a test harness specific to this gem, each
backend is covered by the i18n-ruby
-provided API Tests.
These test cover a range of behavior, including:
- The Basics of translating text
- Translation lookup
- Falling back to provided defaults
- Interpolation
- Translation key linking
- Localizing Dates and Times
- Pluralizing text
- Storing Ruby Procs
Contributing
See CONTRIBUTING.md.
License
The gem is available as open source under the terms of the MIT License.