Skip to content


Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time



Trans provides a way to manage and query translations embedded into schemas and removes the necessity of maintaining extra tables only for translation storage. It is inspired by the great hstore translate gem for Ruby.

Trans is published on and the documentation is also available online. Source code is available in this same repository under the Apache2 License.

On April 17th, 2017, Trans was featured in HackerNoon

Optional Requirements

Having Ecto SQL and Postgrex in your application will allow you to use the Trans.QueryBuilder component to generate database queries based on translated data. You can still use the Trans.Translator component without those dependencies though.

  • Ecto SQL 3.0 or higher
  • PostgreSQL 9.4 or higher (since Trans leverages the JSONB datatype)

Why Trans?

The traditional approach to content internationalization consists on using an additional table for each translatable schema. This table works only as a storage for the original schema translations. For example, we may have a posts and a posts_translations tables.

This approach has a few disadvantages:

  • It complicates the database schema because it creates extra tables that are coupled to the "main" ones.
  • It makes migrations and schemas more complicated, since we always have to keep the two tables in sync.
  • It requires constant JOINs in order to filter or fetch records along with their translations.

The approach used by Trans is based on modern RDBMSs support for unstructured datatypes. Instead of storing the translations in a different table, each translatable schema has an extra column that contains all of its translations. This approach drastically reduces the number of required JOINs when filtering or fetching records.

Trans is lightweight and modularized. The Trans module provides metadata that is used by the Trans.Translator and Trans.QueryBuilder modules, which implement the main functionality of this library.


Imagine that we have an Article schema that we want to translate:

defmodule MyApp.Article do
  use Ecto.Schema

  schema "articles" do
    field :title, :string
    field :body, :string

The first step would be to add a new JSON column to the table so we can store the translations in it.

defmodule MyApp.Repo.Migrations.AddTranslationsToArticles do
  use Ecto.Migration

  def change do
    alter table(:articles) do
      add :translations, :map

Once we have the new database column, we can update the Article schema to include the translations

defmodule MyApp.Article do
  use Ecto.Schema
  use Trans, translates: [:title, :body]

  schema "articles" do
    field :title, :string
    field :body, :string
    embeds_one :translations, Translations, on_replace: :update, primary_key: false do
      embeds_one :es, MyApp.Article.Translation, on_replace: :update
      embeds_one :fr, MyApp.Article.Translation, on_replace: :update

defmodule MyApp.Article.Translation do
  use Ecto.Schema

  @primary_key false
  embedded_schema do
    field :title, :string
    field :body, :string

After doing this we can leverage the Trans.Translator and Trans.QueryBuilder modules to fetch and query translations from the database.

The translation storage can be done using normal Ecto.Changeset functions just like any other fields.

Is Trans dead?

Trans has a slow release cadence, but that does not mean that it is dead. Trans can be considered as "done" in the sense that it does one thing and does it well.

New releases will happen when there are bugs or new changes. If the last release is from a long time ago you should take this as a sign of stability and maturity, not as a sign of abandonment.