crbelaus/trans

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])