diff --git a/CHANGELOG.md b/CHANGELOG.md index be97a39..a23c244 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased + * Call `c:Gettext.Plural.init/1` once per PO file at compile time instead of twice. * Add experimental `mix gettext.generate` task: messages are persisted as module attributes during normal compilation (for backends with `automatic_extraction: true` in the application environment, for example `config :gettext, MyApp.Gettext, automatic_extraction: true` in `config/dev.exs`) and read back from the compiled BEAM files, so extraction no longer needs to force-recompile the project. ## v1.1.0 diff --git a/lib/gettext/compiler.ex b/lib/gettext/compiler.ex index 712f632..c6270a9 100644 --- a/lib/gettext/compiler.ex +++ b/lib/gettext/compiler.ex @@ -498,8 +498,9 @@ defmodule Gettext.Compiler do plural_forms_fun = :"#{locale}_#{domain}_plural" - plural_forms = compile_plural_forms(locale, messages_struct, plural_mod, plural_forms_fun) - nplurals = nplurals(locale, messages_struct, plural_mod) + plural_info = Plural.plural_info(locale, messages_struct, plural_mod) + plural_forms = compile_plural_forms(plural_info, plural_mod, plural_forms_fun) + nplurals = nplurals(plural_info, plural_mod) singular_fun = :"#{locale}_#{domain}_lgettext" plural_fun = :"#{locale}_#{domain}_lngettext" @@ -552,17 +553,14 @@ defmodule Gettext.Compiler do {locale, domain, singular_fun, plural_fun, quoted} end - defp nplurals(locale, messages_struct, plural_mod) do - plural_mod.nplurals(Plural.plural_info(locale, messages_struct, plural_mod)) + defp nplurals(plural_info, plural_mod) do + plural_mod.nplurals(plural_info) end - defp compile_plural_forms(locale, messages_struct, plural_mod, plural_fun) do + defp compile_plural_forms(plural_info, plural_mod, plural_fun) do quote do defp unquote(plural_fun)(n) do - unquote(plural_mod).plural( - unquote(Macro.escape(Plural.plural_info(locale, messages_struct, plural_mod))), - n - ) + unquote(plural_mod).plural(unquote(Macro.escape(plural_info)), n) end end end diff --git a/lib/gettext/extractor.ex b/lib/gettext/extractor.ex index 0def681..f2b5f52 100644 --- a/lib/gettext/extractor.ex +++ b/lib/gettext/extractor.ex @@ -462,7 +462,7 @@ defmodule Gettext.Extractor do defp read_contents_and_parse(path) do contents = File.read!(path) - {contents, PO.parse_file!(path, file: path)} + {contents, PO.parse_string!(contents, file: path)} end # This function "tags" a {path, _} tuple in order to distinguish POT files diff --git a/lib/gettext/interpolation/default.ex b/lib/gettext/interpolation/default.ex index 4d2c1a1..dbac30f 100644 --- a/lib/gettext/interpolation/default.ex +++ b/lib/gettext/interpolation/default.ex @@ -22,14 +22,30 @@ defmodule Gettext.Interpolation.Default do @doc false @spec to_interpolatable(String.t()) :: interpolatable() def to_interpolatable(string) when is_binary(string) do - start_pattern = :binary.compile_pattern("%{") - end_pattern = :binary.compile_pattern("}") + {start_pattern, end_pattern} = patterns() string |> to_interpolatable(_current = "", _acc = [], start_pattern, end_pattern) |> Enum.reverse() end + # The patterns are constant, but a compiled pattern is a reference, so it + # cannot be a module attribute. Compiling them on every call costs more than + # the split itself for short strings, so they are compiled once per VM. + @patterns_key {__MODULE__, :patterns} + + defp patterns do + case :persistent_term.get(@patterns_key, nil) do + nil -> + patterns = {:binary.compile_pattern("%{"), :binary.compile_pattern("}")} + :persistent_term.put(@patterns_key, patterns) + patterns + + patterns -> + patterns + end + end + defp to_interpolatable(string, current, acc, start_pattern, end_pattern) do case :binary.split(string, start_pattern) do # If we have one element, no %{ was found so this is the final part of the