Trans.Translator.translate/2 does not work as expected
jfayad opened this issue · 3 comments
I have the following schema:
embeds_one :translations, Translations, on_replace: :update, primary_key: false do
embeds_one :fr, XXX.Translation, on_replace: :update
embeds_one :es, XXX.Translation, on_replace: :update
end
Then the following changeset
|> cast_embed(:translations, with: {__MODULE__, :update_translations, []})
...
def update_translations(changeset, attrs) do
changeset
|> cast(attrs, [])
|> cast_embed(:fr)
|> cast_embed(:es)
end
finally the translation embed changeset is as follow:
def changeset(translation, attrs) do
translation
|> cast(attrs, [:name, :description])
|> validate_required(:name)
end
(side note, I think the above should be better documented as it might not be straightforward for someone who has never worked with embed schema)
When using the above, I can add and edit translations as expected. Yet if I do not provide an :es translation and then use the
Trans.Translator.translate(Struct, :es) function, instead of getting the default value for :name and :description in my initial schema I'm getting nil
If instead I call Trans.Translator.translate(Struct, :name, :es)
I'm observing the correct behaviour (ie the value returned is the one of the original field).
I was expecting translate/2 to work similarly to translate/3 but on the whole struct level (automatically replacing the fields based on the translatable: list provided when declaring use Trans at the top of my main module...)
seems like adding a simple case solves the issue:
# Line 148 of Trans.Translator module
defp translate_fields(%{__struct__: module} = struct, locale) do
fields = module.__trans__(:fields)
Enum.reduce(fields, struct, fn field, struct ->
case translate_field(struct, locale, field) do
:error -> struct
nil -> struct # <<<<<------- Adding this case seems to fix the issue
translation -> Map.put(struct, field, translation)
end
end)
end
the above does not cut it as it will mix up fields in multiple languages.
What needs to be done is to check the existence of the language in the translations container:
locale = if is_atom(locale), do: locale, else: String.to_existing_atom(locale)
if not is_nil(struct |> Map.get(module.__trans__(:container)) |> Map.get(locale)) do
Enum.reduce(fields, struct, fn field, struct ->
case translate_field(struct, locale, field) do
:error -> struct
translation -> Map.put(struct, field, translation)
end
end)
else
struct
end
This should be improved by the just released Trans 3.0.0 as it allows you to manually specify a fallback chain of locales to resolve in case the one that you are looking for does not exist.
In your initial example you could do the following:
# Return the ES translation or, if it does not exist, fallback to EN.
Trans.Translator.translate(Struct, :name, [:es, :en])