220 lines
		
	
	
	
		
			5.5 KiB
		
	
	
	
		
			Elixir
		
	
	
	
	
	
			
		
		
	
	
			220 lines
		
	
	
	
		
			5.5 KiB
		
	
	
	
		
			Elixir
		
	
	
	
	
	
| # Pleroma: A lightweight social networking server
 | |
| # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
 | |
| # SPDX-License-Identifier: AGPL-3.0-only
 | |
| 
 | |
| defmodule Pleroma.Web.Gettext do
 | |
|   @moduledoc """
 | |
|   A module providing Internationalization with a gettext-based API.
 | |
| 
 | |
|   By using [Gettext](https://hexdocs.pm/gettext),
 | |
|   your module gains a set of macros for translations, for example:
 | |
| 
 | |
|       import Pleroma.Web.Gettext
 | |
| 
 | |
|       # Simple translation
 | |
|       gettext "Here is the string to translate"
 | |
| 
 | |
|       # Plural translation
 | |
|       ngettext "Here is the string to translate",
 | |
|                "Here are the strings to translate",
 | |
|                3
 | |
| 
 | |
|       # Domain-based translation
 | |
|       dgettext "errors", "Here is the error message to translate"
 | |
| 
 | |
|   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
 | 
