elixir-gettext / gettext

Internationalization and localization support for Elixir.
https://hexdocs.pm/gettext
469 stars 86 forks source link

Interpolation #9

Closed josevalim closed 9 years ago

josevalim commented 9 years ago

Although gettext does not support interpolation, we could support it out of the box. The reasoning is that interpolation is common and supporting it out of the box is going to allow us to do some optimizations similar to the ones Chris did in linguist.

The idea is that lgettext and lngettext will receive an extra argument with interpolation: a map or a list (where a list is internally converted to a map). A naïve way of supporting interpolation is this:

def lgettext("pt", "default", "hello world %{something}", interpolation) do
  Gettext.Interpolation.interpolate("olá mundo %{something}", interpolation)
end

However, it would be better if we extracted all occurrence of "%{count}" or "%{whatever}" from strings during compilation time. In other words, we could compile to something like:

def lgettext("pt", "default", "hello world %{something}", interpolation) do
  case interpolation do
    # Check all keys are in interpolation
    %{something: something} -> {:ok, "olá mundo " <> something}
    # Otherwise detect which keys are missing and return an error
    _ -> {:error, Gettext.Interpolation.missing_interpolation_keys(interpolation, [:something])
  end
end

Where missing_interpolation_key will look at the interpolation map, the required keys, and return a string saying: "missing interpolation keys: foo, bar, something".

Note though we still need to have the runtime behaviour, because the default messages need to be translated at runtime, as we never compile them. So the fallback clause still is:

def lgettext(_locale, _context, default, interpolation) do
  case Gettext.Interpolation.interpolate(default, interpolation) do
    {:ok, interpolated} -> {:default, interpolated}
    {:error, _} = error -> error
  end
end

Thoughts?

whatyouhide commented 9 years ago

Ok, I think I got it. I'll start working on this :)

whatyouhide commented 9 years ago

Hey there @josevalim, quick question (I'm posting here so that others can benefit from the answer as well :smiley:).

I implemented lgettext with support for interpolation and I'm now implementing lngettext; should we still provide an argument n to lngettext which is the number of elements or should we extract that from the bindings passed to lngettext, standardizing on a name like %{count}?

Example:

msgid "One error"
msgid_plural "%{count} errors"
msgstr[0] "Un errore"
msgstr[1] "%{count} errori"

With the example above, should we call lngettext(_, _, "One error", "%{count} errors", 4, %{}) (no bindings and count as the standard for the number of elements), lngettext(_, _, "One error", "%{count}", count: 4) or the most explicit lngettext(_, _, "One error", "%{count} errors", 4, count: 4)?

josevalim commented 9 years ago

I would go with lngettext(_, _, "One error", "%{count} errors", 4, %{}) which will automatically set count: 4.