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






















