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:
bundleI18n::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_valueThen execute them:
rails db:migrateNext, 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
)
endCaveat: 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:backendThen execute them:
rails db:migrateNext, 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
)
endCaveat: 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
endCaveat: 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.