-
Notifications
You must be signed in to change notification settings - Fork 90
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Supply plurals forms header to Gettext.Plural callbacks #343
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -122,18 +122,52 @@ defmodule Gettext.Plural do | |||
|
||||
""" | ||||
|
||||
alias Expo.Messages | ||||
|
||||
# Types | ||||
|
||||
@type locale :: String.t() | ||||
|
||||
@type pluralization_context :: %{ | ||||
optional(:plural_forms_header) => String.t(), | ||||
required(:locale) => locale() | ||||
} | ||||
|
||||
@type plural_info :: term() | ||||
|
||||
# Behaviour definition. | ||||
|
||||
@doc """ | ||||
Initialize context for `nplurals/1` / `plurals/2`. | ||||
|
||||
Perform all preparations (for example parsing the plural forms header) per | ||||
for the provided locale to offload into compile time. | ||||
|
||||
If the `init/1` callback is not defined, the `plural_info` will be set to the | ||||
`locale`. | ||||
""" | ||||
@callback init(pluralization_context()) :: plural_info() | ||||
|
||||
@doc """ | ||||
Returns the number of possible plural forms in the given `locale`. | ||||
""" | ||||
@callback nplurals(locale :: String.t()) :: pos_integer | ||||
@callback nplurals(plural_info()) :: pos_integer() | ||||
|
||||
@doc """ | ||||
Returns the plural form in the given `locale` for the given `count` of | ||||
elements. | ||||
""" | ||||
@callback plural(locale :: String.t(), count :: integer) :: plural_form :: non_neg_integer | ||||
@callback plural(plural_info(), count :: integer()) :: plural_form :: non_neg_integer() | ||||
|
||||
@doc """ | ||||
Returns the plural forms header value for the given `locale`. | ||||
|
||||
|
||||
Fallback if not implemented: `"nplurals={nplurals};"`. | ||||
""" | ||||
@callback plural_forms_header(locale()) :: String.t() | nil | ||||
|
||||
@optional_callbacks init: 1, plural_forms_header: 1 | ||||
|
||||
defmodule UnknownLocaleError do | ||||
@moduledoc """ | ||||
|
@@ -434,10 +468,22 @@ defmodule Gettext.Plural do | |||
"sk" | ||||
] | ||||
|
||||
@doc false | ||||
def init(%{locale: locale, plural_forms_header: plural_forms_header}) do | ||||
case read_plural_forms_from_headers(plural_forms_header) do | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is here to keep It is however not consistent internally. I could for example merge with When using As an alternate solution, we could remove the option entirely and make the Line 41 in 41e16f2
Even though I personally like option 2 a lot more, I decided to go with the solution that does not cause breaking changes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we can deprecate the option and guide users towards the new solution. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @josevalim That is what I intended to do here: #343 (comment) Based on the discussion with @whatyouhide I will probably ignore the whole topic for the PR. |
||||
nil -> locale | ||||
nplurals -> {locale, nplurals} | ||||
end | ||||
end | ||||
|
||||
def init(%{locale: locale}), do: locale | ||||
|
||||
# Number of plural forms. | ||||
|
||||
def nplurals(locale) | ||||
|
||||
def nplurals({_locale, nplurals}), do: nplurals | ||||
|
||||
# All the groupable forms. | ||||
|
||||
for l <- @one_form do | ||||
|
@@ -511,6 +557,8 @@ defmodule Gettext.Plural do | |||
|
||||
def plural(locale, count) | ||||
|
||||
def plural({locale, _nplurals}, count), do: plural(locale, count) | ||||
|
||||
# All the `x_Y` languages that have different pluralization rules than `x`. | ||||
|
||||
def plural("pt_BR", n) when n in [0, 1], do: 0 | ||||
|
@@ -681,4 +729,69 @@ defmodule Gettext.Plural do | |||
_other -> raise UnknownLocaleError, locale | ||||
end | ||||
end | ||||
|
||||
@doc false | ||||
def plural_info(locale, messages_struct, plural_mod) do | ||||
ensure_loaded!(plural_mod) | ||||
|
||||
if function_exported?(plural_mod, :init, 1) do | ||||
pluralization_context = %{locale: locale} | ||||
|
||||
pluralization_context = | ||||
case Messages.get_header(messages_struct, "Plural-Forms") do | ||||
[] -> | ||||
pluralization_context | ||||
|
||||
plural_forms -> | ||||
Map.put( | ||||
pluralization_context, | ||||
:plural_forms_header, | ||||
IO.iodata_to_binary(plural_forms) | ||||
) | ||||
end | ||||
|
||||
plural_mod.init(pluralization_context) | ||||
else | ||||
locale | ||||
end | ||||
end | ||||
|
||||
@doc false | ||||
def plural_forms_header_impl(locale, messages_struct, plural_mod) do | ||||
ensure_loaded!(plural_mod) | ||||
|
||||
plural_forms_header = | ||||
if function_exported?(plural_mod, :plural_forms_header, 1) do | ||||
plural_mod.plural_forms_header(locale) | ||||
end | ||||
|
||||
if plural_forms_header do | ||||
plural_forms_header | ||||
else | ||||
nplurals = plural_mod.nplurals(plural_info(locale, messages_struct, plural_mod)) | ||||
|
||||
"nplurals=#{nplurals}" | ||||
end | ||||
end | ||||
|
||||
defp read_plural_forms_from_headers(header) do | ||||
with "nplurals=" <> rest <- String.trim(header), | ||||
{plural_forms, _rest} <- Integer.parse(rest) do | ||||
plural_forms | ||||
else | ||||
_other -> nil | ||||
end | ||||
end | ||||
|
||||
# TODO: remove when we depend on Elixir 1.12+ | ||||
if function_exported?(Code, :ensure_loaded!, 1) do | ||||
defp ensure_loaded!(mod), do: Code.ensure_loaded!(mod) | ||||
else | ||||
defp ensure_loaded!(mod) do | ||||
case Code.ensure_loaded(mod) do | ||||
{:module, ^mod} -> :ok | ||||
_other -> raise "<good error message>" | ||||
end | ||||
end | ||||
end | ||||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When does this return
nil
, and what does that mean semantically?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It means that it does not know a rule for that locale. The caller of the function will then just set a partial header with only nplurals defined.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ignore my previous comment, that is not factually correct. That's what's supposed to happen though. I'll have a look.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now it is as I described.