Fix tagpolicy to also work with Update
[akkoma] / lib / pleroma / web / gettext.ex
index f40fd04c0ebfb75c42d71959df68533c85196383..7afcd38f03892cf390bd8b2ca6bb99ff3cc911c9 100644 (file)
@@ -1,5 +1,5 @@
 # Pleroma: A lightweight social networking server
-# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.Gettext do
@@ -25,4 +25,196 @@ defmodule Pleroma.Web.Gettext do
   See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
   """
   use Gettext, otp_app: :pleroma
+
+  def language_tag do
+    # Naive implementation: HTML lang attribute uses BCP 47, which
+    # uses - as a separator.
+    # https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang
+
+    Gettext.get_locale()
+    |> String.replace("_", "-", global: true)
+  end
+
+  def normalize_locale(locale) do
+    if is_binary(locale) do
+      String.replace(locale, "-", "_", global: true)
+    else
+      nil
+    end
+  end
+
+  def supports_locale?(locale) do
+    Pleroma.Web.Gettext
+    |> Gettext.known_locales()
+    |> Enum.member?(locale)
+  end
+
+  def variant?(locale), do: String.contains?(locale, "_")
+
+  def language_for_variant(locale) do
+    Enum.at(String.split(locale, "_"), 0)
+  end
+
+  def ensure_fallbacks(locales) do
+    locales
+    |> Enum.flat_map(fn locale ->
+      others =
+        other_supported_variants_of_locale(locale)
+        |> Enum.filter(fn l -> not Enum.member?(locales, l) end)
+
+      [locale] ++ others
+    end)
+  end
+
+  def other_supported_variants_of_locale(locale) do
+    cond do
+      supports_locale?(locale) ->
+        []
+
+      variant?(locale) ->
+        lang = language_for_variant(locale)
+        if supports_locale?(lang), do: [lang], else: []
+
+      true ->
+        Gettext.known_locales(Pleroma.Web.Gettext)
+        |> Enum.filter(fn l -> String.starts_with?(l, locale <> "_") end)
+    end
+  end
+
+  def get_locales do
+    Process.get({Pleroma.Web.Gettext, :locales}, [])
+  end
+
+  def is_locale_list(locales) do
+    Enum.all?(locales, &is_binary/1)
+  end
+
+  def put_locales(locales) do
+    if is_locale_list(locales) do
+      Process.put({Pleroma.Web.Gettext, :locales}, Enum.uniq(locales))
+      Gettext.put_locale(Enum.at(locales, 0, Gettext.get_locale()))
+      :ok
+    else
+      {:error, :not_locale_list}
+    end
+  end
+
+  def locale_or_default(locale) do
+    if supports_locale?(locale) do
+      locale
+    else
+      Gettext.get_locale()
+    end
+  end
+
+  def with_locales_func(locales, fun) do
+    prev_locales = Process.get({Pleroma.Web.Gettext, :locales})
+    put_locales(locales)
+
+    try do
+      fun.()
+    after
+      if prev_locales do
+        put_locales(prev_locales)
+      else
+        Process.delete({Pleroma.Web.Gettext, :locales})
+        Process.delete(Gettext)
+      end
+    end
+  end
+
+  defmacro with_locales(locales, do: fun) do
+    quote do
+      Pleroma.Web.Gettext.with_locales_func(unquote(locales), fn ->
+        unquote(fun)
+      end)
+    end
+  end
+
+  def to_locale_list(locale) when is_binary(locale) do
+    locale
+    |> String.split(",")
+    |> Enum.filter(&supports_locale?/1)
+  end
+
+  def to_locale_list(_), do: []
+
+  defmacro with_locale_or_default(locale, do: fun) do
+    quote do
+      Pleroma.Web.Gettext.with_locales_func(
+        Pleroma.Web.Gettext.to_locale_list(unquote(locale))
+        |> Enum.concat(Pleroma.Web.Gettext.get_locales()),
+        fn ->
+          unquote(fun)
+        end
+      )
+    end
+  end
+
+  defp next_locale(locale, list) do
+    index = Enum.find_index(list, fn item -> item == locale end)
+
+    if not is_nil(index) do
+      Enum.at(list, index + 1)
+    else
+      nil
+    end
+  end
+
+  # We do not yet have a proper English translation. The "English"
+  # version is currently but the fallback msgid. However, this
+  # will not work if the user puts English as the first language,
+  # and at the same time specifies other languages, as gettext will
+  # think the English translation is missing, and call
+  # handle_missing_translation functions. This may result in
+  # text in other languages being shown even if English is preferred
+  # by the user.
+  #
+  # To prevent this, we do not allow fallbacking when the current
+  # locale missing a translation is English.
+  defp should_fallback?(locale) do
+    locale != "en"
+  end
+
+  def handle_missing_translation(locale, domain, msgctxt, msgid, bindings) do
+    next = next_locale(locale, get_locales())
+
+    if is_nil(next) or not should_fallback?(locale) do
+      super(locale, domain, msgctxt, msgid, bindings)
+    else
+      {:ok,
+       Gettext.with_locale(next, fn ->
+         Gettext.dpgettext(Pleroma.Web.Gettext, domain, msgctxt, msgid, bindings)
+       end)}
+    end
+  end
+
+  def handle_missing_plural_translation(
+        locale,
+        domain,
+        msgctxt,
+        msgid,
+        msgid_plural,
+        n,
+        bindings
+      ) do
+    next = next_locale(locale, get_locales())
+
+    if is_nil(next) or not should_fallback?(locale) do
+      super(locale, domain, msgctxt, msgid, msgid_plural, n, bindings)
+    else
+      {:ok,
+       Gettext.with_locale(next, fn ->
+         Gettext.dpngettext(
+           Pleroma.Web.Gettext,
+           domain,
+           msgctxt,
+           msgid,
+           msgid_plural,
+           n,
+           bindings
+         )
+       end)}
+    end
+  end
 end