PJextra
How to approach multi language content management system?
I have a web based software using Gettext for multilanguage support that is working fine. The only limitation is that translations must be done upfront. Now, I’m thinking about letting users insert content in the different languages the system use, so I’m thinking in storing those translations in the database.
I couldn’t find much information about this and I’m not sure about the best approach: creating N schemas (one for each language) that are the same and then in the controller select the one to show? Use a package? I’m using a plug to set the language in the router for Gettext.
Most Liked
belaustegui
Hi!
There are multiple approaches available to solve this problem.
One of them is having a paralell separated schema for storing translations of each model. For example you may have an articles schema and a article_translations schema. This is the traditional approach used by the Ruby gem Globalize. There are some libraries that provide a similar (but I think that not exactly the same) functionality for Elixir.
Having said that, I want to propose you an alternative solution.
Having separated schemas for basic model data and its translations has some difficulties and affects the query performance since it requires multiple JOINs per translated model.
Since modern database support unstructured data such as JSON and Ecto provides support for this kind of data, I’ve built a library that leverages this support for storing model translations into a single column of the same model table.
The library is called Trans, it has some examples and documentation that you may find useful. There is also this article which explains why Trans was created and what improvements does it provide.
Hope you find it useful ![]()
yurko
we also went the embed way, but did it directly with ecto. Depending on your app if the contents don’t have to be queried (they can, but the syntax is not that nice yet) it’s relatively straightforward with nested embeds. Still not really simple but these things are rarely so ![]()
Here’s a simplified schema
defmodule App.Thing do
use App.Web, :model
alias App.Thing.Content
alias App.OtherThing
schema "thing" do
field :name, :string
field :order, :integer
embeds_one :en, Content, [on_replace: :delete]
embeds_one :de, Content, [on_replace: :delete]
has_many :other_things, OtherThing
timestamps()
end
each content can have own embeds one / embeds many. Example query
@spec only_lang(struct(), atom()) :: map()
def only_lang(queryable, lang) do
queryable
|> where([t], not(is_nil(field(t, ^lang))))
|> select([t], %{content: field(t, ^lang), id: t.id, uuid: t.uuid, name: t.name})
end
these fields are nullable so the user can have something for only one supported language. One thing to note is that we mostly deliver contents over the API without having the language in server side state
PJextra
After some testing I can see that all suggestions are good options.
Nevertheless, I was thinking in a simpler approach using the new Contexts mindset:..
- I’ll have in my schema a duplicated field for each language translations (post, postDe, postFr,…);
- In the client I’ll make sure that when this field is being populated translations are also populated (by default with the same original content);
- In my Context interface I’ll define each query in a way that depending on the language I’ll search the translations fields, not the original.
Theoretically I don’t see any problem and it seems very simple logic, as translations are clear (in the schema, client side and queries) and as not all schema fields are suitable for translation I don’t get too much complexity in the schema (making it explicit which fields expect translations).
Does this make sense for someone that already implemented this functionality or is it just a newbie wrong idea?
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








