lib/fluent.ex

defmodule Fluent do
  @moduledoc """
  Module
  """

  @typedoc """
  Represents locale. Basically it's a string, that has valid locale identifier
  """
  @type locale :: String.t()

  @typedoc """
  Native container, that is firstly identified and then used for translations
  """
  @type bundle :: reference()

  @typedoc """
  Name for Fluent's assembly. Should be valid module, that *uses* `Fluent.Assembly`
  """
  @type assembly :: module

  @spec put_locale(locale) :: nil
  def put_locale(locale) when is_binary(locale), do: Process.put(Fluent, locale)

  def put_locale(locale),
    do: raise(ArgumentError, "put_locale/1 only accepts binary locales, got: #{inspect(locale)}")

  @doc """
  Gets the locale for the current process and the given assembly.
  This function returns the value of the locale for the current process and the
  given `assembly`. If there is no locale for the current process and the given
  assembly, then either the global Fluent locale (if set), or the default locale
  for the given assembly, or the global default locale is returned. See the
  "Locale" section in the module documentation for more information.
  ## Examples
      Fluent.get_locale(MyApp.Fluent)
      #=> "en"
  """
  @spec get_locale(assembly) :: locale
  def get_locale(assembly) do
    Process.get(assembly) || Process.get(Fluent) || assembly.__config__(:default_locale)
  end

  @doc """
  Sets the locale for the current process and the given `assembly`.
  The locale is stored in the process dictionary. `locale` must be a string; if
  it's not, an `ArgumentError` exception is raised.
  ## Examples
      Fluent.put_locale(MyApp.Fluent, "pt_BR")
      #=> nil
      Fluent.get_locale(MyApp.Fluent)
      #=> "pt_BR"
  """
  @spec put_locale(assembly, locale) :: nil
  def put_locale(assembly, locale) when is_binary(locale), do: Process.put(assembly, locale)

  def put_locale(_assembly, locale),
    do: raise(ArgumentError, "put_locale/2 only accepts binary locales, got: #{inspect(locale)}")

  @doc """
  Runs `fun` with the global Fluent locale set to `locale`.
  This function just sets the global Fluent locale to `locale` before running
  `fun` and sets it back to its previous value afterwards. Note that
  `put_locale/2` is used to set the locale, which is thus set only for the
  current process (keep this in mind if you plan on spawning processes inside
  `fun`).
  The value returned by this function is the return value of `fun`.
  ## Examples
      Fluent.put_locale("fr")
      MyApp.Fluent.ftl("Hello world")
      #=> "Bonjour monde"
      Fluent.Assembly.with_locale "it", fn ->
        MyApp.Fluent.ftl("Hello world")
      end
      #=> "Ciao mondo"
      MyApp.Fluent.ftl("Hello world")
      #=> "Bonjour monde"
  """
  @spec with_locale(locale, (-> result)) :: result when result: var
  def with_locale(locale, fun) do
    previous_locale = Process.get(Fluent)
    Fluent.put_locale(locale)

    try do
      fun.()
    after
      if previous_locale do
        Fluent.put_locale(previous_locale)
      else
        Process.delete(Fluent)
      end
    end
  end

  @doc """
  Runs `fun` with the `Fluent.Assembly` locale set to `locale` for the given `assembly`.
  This function just sets the Fluent.Assembly locale for `assembly` to `locale` before
  running `fun` and sets it back to its previous value afterwards. Note that
  `put_locale/2` is used to set the locale, which is thus set only for the
  current process (keep this in mind if you plan on spawning processes inside
  `fun`).
  The value returned by this function is the return value of `fun`.
  ## Examples
      Fluent.put_locale(MyApp.Fluent, "fr")
      MyApp.Fluent.ftl("Hello world")
      #=> "Bonjour monde"
      Fluent.with_locale MyApp.Fluent, "it", fn ->
        MyApp.Fluent.ftl("Hello world")
      end
      #=> "Ciao mondo"
      MyApp.Fluent.ftl("Hello world")
      #=> "Bonjour monde"
  """
  @spec with_locale(assembly, locale, (-> result)) :: result when result: var
  def with_locale(assembly, locale, fun) do
    previous_locale = Process.get(assembly)
    Fluent.put_locale(assembly, locale)

    try do
      fun.()
    after
      if previous_locale do
        Fluent.put_locale(assembly, previous_locale)
      else
        Process.delete(assembly)
      end
    end
  end

  @doc """
  Returns all the locales for which FTL files exist for the given `assembly`.
  If the translations directory for the given assembly doesn't exist, then an
  empty list is returned.
  ## Examples
  With the following assembly:
      defmodule MyApp.Fluent do
        use Fluent.Assembly, otp_app: :my_app
      end
  and the following translations directory:
      my_app/priv/fluent
      ├─ en
      ├─ it
      └─ pt_BR
  then:
      Fluent.known_locales(MyApp.Fluent)
      #=> ["en", "it", "pt_BR"]
  """
  @spec known_locales(assembly) :: [locale]
  def known_locales(assembly) do
    Fluent.Store.known_locales(assembly.__store__())
  end
end