Open petrus-jvrensburg opened 2 years ago
@petrus-jvrensburg very sorry for the slow response. ElixirConf is over so I can get back to L10N now. A lot of what you propose makes a lot of sense. Let me work up some implementation ideas for your consideration over the next few days. I want to keep backwards compatibility with trans
but after than, an easy developer experience and better support for "extended" translation structs are good ideas for sure!
Thanks. I have since realised that if I name the embedded struct just right, then it's picked up by the code that is generated by the translations
macro. But I'm sure it would be better if that were explicit.
My current approach is to use models that look like this:
defmodule MyApp.Product do
use Ecto.Schema
use Nohara.Cldr.Trans, translates: [:title, :description]
import Ecto.Changeset
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "products" do
field(:title, :string)
field(:description, :string)
field(:brand, :string)
field(:image, :string)
field(:is_active, :boolean, default: true)
field(:price, :decimal)
...
translations(:translations)
timestamps()
end
def translation_locales(), do: __MODULE__.Translations.__schema__(:fields)
def translatable_fields(), do: __MODULE__.__trans__(:fields)
@doc false
def changeset(product, attrs) do
product
|> cast(attrs, [
:title,
:description,
:brand,
:image,
:is_active,
:price,
...
])
# use 'cast_embed' to handle values for the 'translations' map-field with
# a nested changeset
|> cast_embed(:translations, with: &translations_changeset/2)
|> validate_required([:title])
end
defp translations_changeset(translations, params) do
translations
|> cast(params, [])
|> then(fn tmp_changeset ->
# use 'cast_embed' to handle values for translated fields for each of the
# configured languages
translation_locales()
|> Enum.reduce(tmp_changeset, fn locale, acc ->
cast_embed(acc, locale)
end)
end)
end
end
defmodule MyApp.Product.Translations.Fields do
use Ecto.Schema
import Ecto.Changeset
@primary_key false
embedded_schema do
for translatable_field <- MyApp.Product.translatable_fields() do
field translatable_field, :string
end
field :updated_at, :naive_datetime
field :updated_by, Ecto.UUID
end
@doc false
def changeset(translation, attrs) do
translation
|> cast(attrs, MyApp.Product.translatable_fields() ++ [:updated_at, :updated_by])
end
end
Currently, the translations macro accepts a
translation_module
argument, so that the developer can pass in their own module for handling translations. Such a module might look like:But, to me, this doesn't seem to be terribly useful. Relying on the macro to generate the embedded schema, with embedded fields for each configured locale seems easier.
However, being able to override the module that is used for the underlying
MyApp.Article.Translations.Fields
would be useful to me. It would allow me to add fields for implementing logic on a per-locale basis, likeWould this be a useful change for anyone else?