Fix and improve language handling
- Refactor plugs to detect and set language - Translate ecto validation errors - Use Gettext directly, not Mobilizon.Web.Gettext - Set the language in the <html> attribute according to the one loaded on front-end Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
7c943dc09a
commit
a670a7d7a7
@ -8,3 +8,4 @@
|
||||
73B351E4CB3AF715AD450A085F5E6304
|
||||
BBACD7F0BACD4A6D3010C26604671692
|
||||
6D4D4A4821B93BCFAC9CDBB367B34C4B
|
||||
5674F0D127852889ED0132DC2F442AAB
|
@ -39,9 +39,19 @@ const loadedLanguages = [DEFAULT_LOCALE];
|
||||
|
||||
function setI18nLanguage(lang: string): string {
|
||||
i18n.locale = lang;
|
||||
setLanguageInDOM(lang);
|
||||
return lang;
|
||||
}
|
||||
|
||||
function setLanguageInDOM(lang: string): void {
|
||||
const fixedLang = lang.replaceAll("_", "-");
|
||||
const html = document.documentElement;
|
||||
const documentLang = html.getAttribute("lang");
|
||||
if (documentLang !== fixedLang) {
|
||||
html.setAttribute("lang", fixedLang);
|
||||
}
|
||||
}
|
||||
|
||||
function fileForLanguage(matches: Record<string, string>, lang: string) {
|
||||
if (Object.prototype.hasOwnProperty.call(matches, lang)) {
|
||||
return matches[lang];
|
||||
|
@ -5,7 +5,8 @@ defmodule Mobilizon.GraphQL.Error do
|
||||
|
||||
require Logger
|
||||
alias __MODULE__
|
||||
import Mobilizon.Web.Gettext
|
||||
alias Mobilizon.Web.Gettext, as: GettextBackend
|
||||
import Mobilizon.Web.Gettext, only: [dgettext: 2]
|
||||
|
||||
defstruct [:code, :message, :status_code, :field]
|
||||
|
||||
@ -44,7 +45,7 @@ defmodule Mobilizon.GraphQL.Error do
|
||||
|
||||
defp handle(%Ecto.Changeset{} = changeset) do
|
||||
changeset
|
||||
|> Ecto.Changeset.traverse_errors(fn {err, _opts} -> err end)
|
||||
|> Ecto.Changeset.traverse_errors(&translate_error/1)
|
||||
|> Enum.map(fn {k, v} ->
|
||||
%Error{
|
||||
code: :validation,
|
||||
@ -96,4 +97,27 @@ defmodule Mobilizon.GraphQL.Error do
|
||||
Logger.warn("Unhandled error code: #{inspect(code)}")
|
||||
{422, to_string(code)}
|
||||
end
|
||||
|
||||
# Translates an error message using gettext.
|
||||
defp translate_error({msg, opts}) do
|
||||
# Because error messages were defined within Ecto, we must
|
||||
# call the Gettext module passing our Gettext backend. We
|
||||
# also use the "errors" domain as translations are placed
|
||||
# in the errors.po file.
|
||||
# Ecto will pass the :count keyword if the error message is
|
||||
# meant to be pluralized.
|
||||
# On your own code and templates, depending on whether you
|
||||
# need the message to be pluralized or not, this could be
|
||||
# written simply as:
|
||||
#
|
||||
# dngettext "errors", "1 file", "%{count} files", count
|
||||
# dgettext "errors", "is invalid"
|
||||
#
|
||||
|
||||
if count = opts[:count] do
|
||||
Gettext.dngettext(GettextBackend, "errors", msg, msg, count, opts)
|
||||
else
|
||||
Gettext.dgettext(GettextBackend, "errors", msg, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -9,7 +9,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Actor do
|
||||
alias Mobilizon.Federation.ActivityPub
|
||||
alias Mobilizon.Service.Workers.Background
|
||||
alias Mobilizon.Users.User
|
||||
import Mobilizon.Web.Gettext
|
||||
import Mobilizon.Web.Gettext, only: [dgettext: 2]
|
||||
|
||||
require Logger
|
||||
|
||||
|
@ -348,8 +348,7 @@ defmodule Mobilizon.Config do
|
||||
end
|
||||
|
||||
def generate_terms(locale) do
|
||||
import Mobilizon.Web.Gettext
|
||||
put_locale(locale)
|
||||
Gettext.put_locale(locale)
|
||||
|
||||
Phoenix.View.render_to_string(
|
||||
Mobilizon.Web.APIView,
|
||||
@ -363,8 +362,7 @@ defmodule Mobilizon.Config do
|
||||
end
|
||||
|
||||
def generate_privacy(locale) do
|
||||
import Mobilizon.Web.Gettext
|
||||
put_locale(locale)
|
||||
Gettext.put_locale(locale)
|
||||
|
||||
Phoenix.View.render_to_string(
|
||||
Mobilizon.Web.APIView,
|
||||
|
@ -5,7 +5,7 @@ defmodule Mobilizon.Service.Activity.Renderer.Comment do
|
||||
alias Mobilizon.Activities.Activity
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Service.Activity.Renderer
|
||||
alias Mobilizon.Web.{Endpoint, Gettext}
|
||||
alias Mobilizon.Web.Endpoint
|
||||
alias Mobilizon.Web.Router.Helpers, as: Routes
|
||||
import Mobilizon.Web.Gettext, only: [dgettext: 3]
|
||||
|
||||
|
@ -5,7 +5,7 @@ defmodule Mobilizon.Service.Activity.Renderer.Discussion do
|
||||
alias Mobilizon.Activities.Activity
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Service.Activity.Renderer
|
||||
alias Mobilizon.Web.{Endpoint, Gettext}
|
||||
alias Mobilizon.Web.Endpoint
|
||||
alias Mobilizon.Web.Router.Helpers, as: Routes
|
||||
import Mobilizon.Web.Gettext, only: [dgettext: 3]
|
||||
|
||||
|
@ -5,7 +5,7 @@ defmodule Mobilizon.Service.Activity.Renderer.Event do
|
||||
alias Mobilizon.Activities.Activity
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Service.Activity.Renderer
|
||||
alias Mobilizon.Web.{Endpoint, Gettext}
|
||||
alias Mobilizon.Web.Endpoint
|
||||
alias Mobilizon.Web.Router.Helpers, as: Routes
|
||||
import Mobilizon.Web.Gettext, only: [dgettext: 3]
|
||||
|
||||
|
@ -5,7 +5,7 @@ defmodule Mobilizon.Service.Activity.Renderer.Group do
|
||||
alias Mobilizon.Activities.Activity
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Service.Activity.Renderer
|
||||
alias Mobilizon.Web.{Endpoint, Gettext}
|
||||
alias Mobilizon.Web.Endpoint
|
||||
alias Mobilizon.Web.Router.Helpers, as: Routes
|
||||
import Mobilizon.Web.Gettext, only: [dgettext: 3]
|
||||
|
||||
|
@ -5,7 +5,7 @@ defmodule Mobilizon.Service.Activity.Renderer.Member do
|
||||
alias Mobilizon.Activities.Activity
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Service.Activity.Renderer
|
||||
alias Mobilizon.Web.{Endpoint, Gettext}
|
||||
alias Mobilizon.Web.Endpoint
|
||||
alias Mobilizon.Web.Router.Helpers, as: Routes
|
||||
import Mobilizon.Web.Gettext, only: [dgettext: 3]
|
||||
|
||||
|
@ -5,7 +5,7 @@ defmodule Mobilizon.Service.Activity.Renderer.Post do
|
||||
alias Mobilizon.Activities.Activity
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Service.Activity.Renderer
|
||||
alias Mobilizon.Web.{Endpoint, Gettext}
|
||||
alias Mobilizon.Web.Endpoint
|
||||
alias Mobilizon.Web.Router.Helpers, as: Routes
|
||||
import Mobilizon.Web.Gettext, only: [dgettext: 3]
|
||||
|
||||
|
@ -5,7 +5,7 @@ defmodule Mobilizon.Service.Activity.Renderer.Resource do
|
||||
alias Mobilizon.Activities.Activity
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Service.Activity.Renderer
|
||||
alias Mobilizon.Web.{Endpoint, Gettext}
|
||||
alias Mobilizon.Web.Endpoint
|
||||
alias Mobilizon.Web.Router.Helpers, as: Routes
|
||||
import Mobilizon.Web.Gettext, only: [dgettext: 3]
|
||||
|
||||
|
@ -6,7 +6,7 @@ defmodule Mobilizon.Service.Metadata.Utils do
|
||||
alias Mobilizon.Service.{Address, DateTime}
|
||||
alias Mobilizon.Service.Formatter.HTML, as: HTMLFormatter
|
||||
alias Phoenix.HTML
|
||||
import Mobilizon.Web.Gettext
|
||||
import Mobilizon.Web.Gettext, only: [gettext: 1]
|
||||
|
||||
@slice_limit 200
|
||||
|
||||
|
@ -22,17 +22,19 @@ defmodule Mobilizon.Web.Auth.Context do
|
||||
def set_user_information_in_context(conn) do
|
||||
context = %{ip: conn.remote_ip |> :inet.ntoa() |> to_string()}
|
||||
|
||||
context =
|
||||
{conn, context} =
|
||||
case Guardian.Plug.current_resource(conn) do
|
||||
%User{id: user_id, email: user_email} = user ->
|
||||
if SentryAdapter.enabled?() do
|
||||
Sentry.Context.set_user_context(%{id: user_id, name: user_email})
|
||||
end
|
||||
|
||||
Map.put(context, :current_user, user)
|
||||
context = Map.put(context, :current_user, user)
|
||||
conn = assign(conn, :user_locale, user.locale)
|
||||
{conn, context}
|
||||
|
||||
nil ->
|
||||
context
|
||||
{conn, context}
|
||||
end
|
||||
|
||||
context =
|
||||
|
@ -10,7 +10,7 @@ defmodule Mobilizon.Web.Email.Activity do
|
||||
alias Mobilizon.Activities.Activity
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Config
|
||||
alias Mobilizon.Web.{Email, Gettext}
|
||||
alias Mobilizon.Web.Email
|
||||
|
||||
@spec direct_activity(String.t(), list(), String.t()) ::
|
||||
Bamboo.Email.t()
|
||||
|
@ -13,7 +13,7 @@ defmodule Mobilizon.Web.Email.Admin do
|
||||
alias Mobilizon.Reports.Report
|
||||
alias Mobilizon.Users.User
|
||||
|
||||
alias Mobilizon.Web.{Email, Gettext}
|
||||
alias Mobilizon.Web.Email
|
||||
|
||||
@spec report(User.t(), Report.t(), String.t()) :: Bamboo.Email.t()
|
||||
def report(%User{email: email} = user, %Report{} = report, default_locale \\ "en") do
|
||||
|
@ -16,7 +16,6 @@ defmodule Mobilizon.Web.Email.Event do
|
||||
alias Mobilizon.Users.{Setting, User}
|
||||
|
||||
alias Mobilizon.Web.Email
|
||||
alias Mobilizon.Web.Gettext, as: GettextBackend
|
||||
|
||||
@important_changes [:title, :begins_on, :ends_on, :status, :physical_address]
|
||||
|
||||
@ -31,7 +30,7 @@ defmodule Mobilizon.Web.Email.Event do
|
||||
timezone \\ "Etc/UTC",
|
||||
locale \\ "en"
|
||||
) do
|
||||
GettextBackend.put_locale(locale)
|
||||
Gettext.put_locale(locale)
|
||||
|
||||
subject =
|
||||
gettext(
|
||||
|
@ -11,7 +11,7 @@ defmodule Mobilizon.Web.Email.Follow do
|
||||
alias Mobilizon.Actors.{Actor, Follower}
|
||||
alias Mobilizon.Federation.ActivityPub.Relay
|
||||
alias Mobilizon.Users.User
|
||||
alias Mobilizon.Web.{Email, Gettext}
|
||||
alias Mobilizon.Web.Email
|
||||
|
||||
@doc """
|
||||
Send follow notification to admins if the followed actor
|
||||
|
@ -10,7 +10,7 @@ defmodule Mobilizon.Web.Email.Group do
|
||||
alias Mobilizon.{Actors, Config, Users}
|
||||
alias Mobilizon.Actors.{Actor, Member}
|
||||
alias Mobilizon.Users.User
|
||||
alias Mobilizon.Web.{Email, Gettext}
|
||||
alias Mobilizon.Web.Email
|
||||
|
||||
@doc """
|
||||
Send emails to local user
|
||||
|
@ -9,7 +9,7 @@ defmodule Mobilizon.Web.Email.Notification do
|
||||
|
||||
alias Mobilizon.Events.{Event, Participant}
|
||||
alias Mobilizon.Users.{Setting, User}
|
||||
alias Mobilizon.Web.{Email, Gettext}
|
||||
alias Mobilizon.Web.Email
|
||||
|
||||
@spec before_event_notification(String.t(), Participant.t(), String.t()) ::
|
||||
Bamboo.Email.t()
|
||||
|
@ -12,7 +12,7 @@ defmodule Mobilizon.Web.Email.Participation do
|
||||
alias Mobilizon.Events.{Event, Participant}
|
||||
alias Mobilizon.Users
|
||||
alias Mobilizon.Users.User
|
||||
alias Mobilizon.Web.{Email, Gettext}
|
||||
alias Mobilizon.Web.Email
|
||||
|
||||
@doc """
|
||||
Send participation emails to local user
|
||||
|
@ -7,13 +7,13 @@ defmodule Mobilizon.Web.Email.User do
|
||||
|
||||
import Bamboo.Phoenix
|
||||
|
||||
import Mobilizon.Web.Gettext
|
||||
import Mobilizon.Web.Gettext, only: [gettext: 2]
|
||||
|
||||
alias Mobilizon.{Config, Crypto, Users}
|
||||
alias Mobilizon.Storage.Repo
|
||||
alias Mobilizon.Users.User
|
||||
|
||||
alias Mobilizon.Web.{Email, Gettext}
|
||||
alias Mobilizon.Web.Email
|
||||
|
||||
require Logger
|
||||
|
||||
|
@ -12,7 +12,7 @@ defmodule Mobilizon.Web.Endpoint do
|
||||
use Phoenix.Endpoint, otp_app: :mobilizon
|
||||
use Absinthe.Phoenix.Endpoint
|
||||
|
||||
plug(Mobilizon.Web.Plugs.SetLocalePlug)
|
||||
plug(Mobilizon.Web.Plugs.DetectLocalePlug)
|
||||
|
||||
if Application.fetch_env!(:mobilizon, :env) !== :dev do
|
||||
plug(Mobilizon.Web.Plugs.HTTPSecurityPlug)
|
||||
|
@ -21,30 +21,4 @@ defmodule Mobilizon.Web.Gettext do
|
||||
See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
|
||||
"""
|
||||
use Gettext, otp_app: :mobilizon
|
||||
|
||||
def put_locale(locale) do
|
||||
locale = determine_best_locale(locale)
|
||||
Gettext.put_locale(__MODULE__, locale)
|
||||
end
|
||||
|
||||
@spec determine_best_locale(String.t()) :: String.t()
|
||||
def determine_best_locale(locale) do
|
||||
locale = String.trim(locale)
|
||||
locales = Gettext.known_locales(__MODULE__)
|
||||
default = Keyword.get(Mobilizon.Config.instance_config(), :default_language, "en") || "en"
|
||||
|
||||
cond do
|
||||
# Default if nothing provided
|
||||
locale == "" -> default
|
||||
# Either it matches directly, eg: "en" => "en", "fr" => "fr"
|
||||
locale in locales -> locale
|
||||
# Either the first part matches, "fr_CA" => "fr"
|
||||
split_locale(locale) in locales -> split_locale(locale)
|
||||
# Otherwise set to default
|
||||
true -> default
|
||||
end
|
||||
end
|
||||
|
||||
# Keep only the first part of the locale
|
||||
defp split_locale(locale), do: locale |> String.split("_", trim: true, parts: 2) |> hd
|
||||
end
|
||||
|
@ -40,7 +40,6 @@ defmodule Mobilizon.Web do
|
||||
use Phoenix.HTML
|
||||
|
||||
import Mobilizon.Web.Router.Helpers
|
||||
import Mobilizon.Web.ErrorHelpers
|
||||
import Mobilizon.Web.Gettext
|
||||
end
|
||||
end
|
||||
|
67
lib/web/plugs/detect_locale_plug.ex
Normal file
67
lib/web/plugs/detect_locale_plug.ex
Normal file
@ -0,0 +1,67 @@
|
||||
# Portions of this file are derived from Pleroma:
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
# NOTE: this module is based on https://github.com/smeevil/set_locale
|
||||
defmodule Mobilizon.Web.Plugs.DetectLocalePlug do
|
||||
@moduledoc """
|
||||
Plug to set locale for Gettext
|
||||
"""
|
||||
import Plug.Conn, only: [get_req_header: 2, assign: 3]
|
||||
alias Mobilizon.Web.Gettext, as: GettextBackend
|
||||
|
||||
def init(_), do: nil
|
||||
|
||||
def call(conn, _) do
|
||||
locale = get_locale_from_header(conn)
|
||||
assign(conn, :detected_locale, locale)
|
||||
end
|
||||
|
||||
defp get_locale_from_header(conn) do
|
||||
conn
|
||||
|> extract_accept_language()
|
||||
|> Enum.find(&supported_locale?/1)
|
||||
end
|
||||
|
||||
defp extract_accept_language(conn) do
|
||||
case get_req_header(conn, "accept-language") do
|
||||
[value | _] ->
|
||||
value
|
||||
|> String.split(",")
|
||||
|> Enum.map(&parse_language_option/1)
|
||||
|> Enum.sort(&(&1.quality > &2.quality))
|
||||
|> Enum.map(& &1.tag)
|
||||
|> Enum.reject(&is_nil/1)
|
||||
|> ensure_language_fallbacks()
|
||||
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
defp supported_locale?(locale) do
|
||||
GettextBackend
|
||||
|> Gettext.known_locales()
|
||||
|> Enum.member?(locale)
|
||||
end
|
||||
|
||||
defp parse_language_option(string) do
|
||||
captures = Regex.named_captures(~r/^\s?(?<tag>[\w\-]+)(?:;q=(?<quality>[\d\.]+))?$/i, string)
|
||||
|
||||
quality =
|
||||
case Float.parse(captures["quality"] || "1.0") do
|
||||
{val, _} -> val
|
||||
:error -> 1.0
|
||||
end
|
||||
|
||||
%{tag: captures["tag"], quality: quality}
|
||||
end
|
||||
|
||||
defp ensure_language_fallbacks(tags) do
|
||||
Enum.flat_map(tags, fn tag ->
|
||||
[language | _] = String.split(tag, "-")
|
||||
if Enum.member?(tags, language), do: [tag], else: [tag, language]
|
||||
end)
|
||||
end
|
||||
end
|
@ -8,61 +8,55 @@ defmodule Mobilizon.Web.Plugs.SetLocalePlug do
|
||||
@moduledoc """
|
||||
Plug to set locale for Gettext
|
||||
"""
|
||||
import Plug.Conn, only: [get_req_header: 2, assign: 3]
|
||||
import Plug.Conn, only: [assign: 3]
|
||||
alias Mobilizon.Web.Gettext, as: GettextBackend
|
||||
|
||||
def init(_), do: nil
|
||||
|
||||
def call(conn, _) do
|
||||
locale = get_locale_from_header(conn)
|
||||
GettextBackend.put_locale(locale)
|
||||
locale =
|
||||
[
|
||||
conn.assigns[:user_locale],
|
||||
conn.assigns[:detected_locale],
|
||||
default_locale(),
|
||||
"en"
|
||||
]
|
||||
|> Enum.map(&determine_best_locale/1)
|
||||
|> Enum.filter(&supported_locale?/1)
|
||||
|> hd()
|
||||
|
||||
Gettext.put_locale(locale)
|
||||
assign(conn, :locale, locale)
|
||||
end
|
||||
|
||||
defp get_locale_from_header(conn) do
|
||||
conn
|
||||
|> extract_accept_language()
|
||||
|> Enum.find("", &supported_locale?/1)
|
||||
end
|
||||
|
||||
defp extract_accept_language(conn) do
|
||||
case get_req_header(conn, "accept-language") do
|
||||
[value | _] ->
|
||||
value
|
||||
|> String.split(",")
|
||||
|> Enum.map(&parse_language_option/1)
|
||||
|> Enum.sort(&(&1.quality > &2.quality))
|
||||
|> Enum.map(& &1.tag)
|
||||
|> Enum.reject(&is_nil/1)
|
||||
|> ensure_language_fallbacks()
|
||||
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
defp supported_locale?(locale) do
|
||||
GettextBackend
|
||||
|> Gettext.known_locales()
|
||||
|> Enum.member?(locale)
|
||||
end
|
||||
|
||||
defp parse_language_option(string) do
|
||||
captures = Regex.named_captures(~r/^\s?(?<tag>[\w\-]+)(?:;q=(?<quality>[\d\.]+))?$/i, string)
|
||||
|
||||
quality =
|
||||
case Float.parse(captures["quality"] || "1.0") do
|
||||
{val, _} -> val
|
||||
:error -> 1.0
|
||||
end
|
||||
|
||||
%{tag: captures["tag"], quality: quality}
|
||||
defp default_locale do
|
||||
Keyword.get(Mobilizon.Config.instance_config(), :default_language, "en")
|
||||
end
|
||||
|
||||
defp ensure_language_fallbacks(tags) do
|
||||
Enum.flat_map(tags, fn tag ->
|
||||
[language | _] = String.split(tag, "-")
|
||||
if Enum.member?(tags, language), do: [tag], else: [tag, language]
|
||||
end)
|
||||
@spec determine_best_locale(String.t()) :: String.t()
|
||||
def determine_best_locale(locale) when is_binary(locale) do
|
||||
locale = String.trim(locale)
|
||||
locales = Gettext.known_locales(GettextBackend)
|
||||
|
||||
cond do
|
||||
locale == "" -> nil
|
||||
# Either it matches directly, eg: "en" => "en", "fr" => "fr"
|
||||
locale in locales -> locale
|
||||
# Either the first part matches, "fr_CA" => "fr"
|
||||
split_locale(locale) in locales -> split_locale(locale)
|
||||
# Otherwise set to default
|
||||
true -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def determine_best_locale(_), do: nil
|
||||
|
||||
# Keep only the first part of the locale
|
||||
defp split_locale(locale), do: locale |> String.split("_", trim: true, parts: 2) |> hd
|
||||
end
|
||||
|
@ -7,10 +7,12 @@ defmodule Mobilizon.Web.Router do
|
||||
pipeline :graphql do
|
||||
# plug(:accepts, ["json"])
|
||||
plug(Mobilizon.Web.Auth.Pipeline)
|
||||
plug(Mobilizon.Web.Plugs.SetLocalePlug)
|
||||
end
|
||||
|
||||
pipeline :graphiql do
|
||||
plug(Mobilizon.Web.Auth.Pipeline)
|
||||
plug(Mobilizon.Web.Plugs.SetLocalePlug)
|
||||
|
||||
plug(Mobilizon.Web.Plugs.HTTPSecurityPlug,
|
||||
script_src: ["cdn.jsdelivr.net"],
|
||||
@ -46,6 +48,8 @@ defmodule Mobilizon.Web.Router do
|
||||
plug(:accepts, ["html", "activity-json"])
|
||||
plug(:put_secure_browser_headers)
|
||||
|
||||
plug(Mobilizon.Web.Plugs.SetLocalePlug)
|
||||
|
||||
plug(Cldr.Plug.AcceptLanguage,
|
||||
cldr_backend: Mobilizon.Cldr,
|
||||
no_match_log_level: :debug
|
||||
@ -60,6 +64,8 @@ defmodule Mobilizon.Web.Router do
|
||||
pipeline :browser do
|
||||
plug(Plug.Static, at: "/", from: "priv/static")
|
||||
|
||||
plug(Mobilizon.Web.Plugs.SetLocalePlug)
|
||||
|
||||
plug(Cldr.Plug.AcceptLanguage,
|
||||
cldr_backend: Mobilizon.Cldr,
|
||||
no_match_log_level: :debug
|
||||
|
@ -1,22 +0,0 @@
|
||||
defmodule Mobilizon.Web.ChangesetView do
|
||||
@moduledoc """
|
||||
View for changesets in case of errors
|
||||
"""
|
||||
use Mobilizon.Web, :view
|
||||
|
||||
@doc """
|
||||
Traverses and translates changeset errors.
|
||||
|
||||
See `Ecto.Changeset.traverse_errors/2` and
|
||||
`Mobilizon.Web.ErrorHelpers.translate_error/1` for more details.
|
||||
"""
|
||||
def translate_errors(changeset) do
|
||||
Ecto.Changeset.traverse_errors(changeset, &translate_error/1)
|
||||
end
|
||||
|
||||
def render("error.json", %{changeset: changeset}) do
|
||||
# When encoded, the changeset returns its errors
|
||||
# as a JSON object. So we just pass it forward.
|
||||
%{errors: translate_errors(changeset)}
|
||||
end
|
||||
end
|
@ -5,15 +5,6 @@ defmodule Mobilizon.Web.ErrorHelpers do
|
||||
|
||||
use Phoenix.HTML
|
||||
|
||||
@doc """
|
||||
Generates tag for inlined form input errors.
|
||||
"""
|
||||
def error_tag(form, field) do
|
||||
Enum.map(Keyword.get_values(form.errors, field), fn error ->
|
||||
content_tag(:span, translate_error(error), class: "help-block")
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Translates an error message using gettext.
|
||||
"""
|
||||
@ -31,6 +22,7 @@ defmodule Mobilizon.Web.ErrorHelpers do
|
||||
# dngettext "errors", "1 file", "%{count} files", count
|
||||
# dgettext "errors", "is invalid"
|
||||
#
|
||||
|
||||
if count = opts[:count] do
|
||||
Gettext.dngettext(Mobilizon.Web.Gettext, "errors", msg, msg, count, opts)
|
||||
else
|
||||
|
@ -53,27 +53,6 @@ defmodule Mobilizon.Web.Views.Utils do
|
||||
|
||||
@spec get_locale(Plug.Conn.t()) :: String.t()
|
||||
def get_locale(%Plug.Conn{assigns: assigns}) do
|
||||
assigns
|
||||
|> Map.get(:locale)
|
||||
|> check_locale()
|
||||
end
|
||||
|
||||
def get_locale(_), do: default_locale()
|
||||
|
||||
defp check_locale(nil) do
|
||||
default_locale()
|
||||
|> check_locale()
|
||||
end
|
||||
|
||||
defp check_locale("") do
|
||||
check_locale(nil)
|
||||
end
|
||||
|
||||
defp check_locale(locale) when is_binary(locale), do: locale
|
||||
|
||||
defp default_locale do
|
||||
Mobilizon.Config.instance_config()
|
||||
|> Keyword.get(:default_language, "en")
|
||||
|> Kernel.||("en")
|
||||
Map.get(assigns, :locale)
|
||||
end
|
||||
end
|
||||
|
1
mix.exs
1
mix.exs
@ -300,7 +300,6 @@ defmodule Mobilizon.Mixfile do
|
||||
Mobilizon.Web.ChangesetView,
|
||||
Mobilizon.Web.JsonLD.ObjectView,
|
||||
Mobilizon.Web.EmailView,
|
||||
Mobilizon.Web.ErrorHelpers,
|
||||
Mobilizon.Web.ErrorView,
|
||||
Mobilizon.Web.LayoutView,
|
||||
Mobilizon.Web.PageView,
|
||||
|
@ -374,7 +374,7 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
||||
)
|
||||
|
||||
assert hd(res["errors"])["message"] == %{
|
||||
"maximum_attendee_capacity" => ["must be greater than or equal to %{number}"]
|
||||
"maximum_attendee_capacity" => ["must be greater than or equal to 0"]
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -308,6 +308,7 @@ defmodule Mobilizon.GraphQL.Resolvers.UserTest do
|
||||
test "create_user/3 doesn't allow two users with the same email", %{conn: conn} do
|
||||
res =
|
||||
conn
|
||||
|> put_req_header("accept-language", "fr")
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @create_user_mutation,
|
||||
variables: @user_creation
|
||||
@ -397,6 +398,7 @@ defmodule Mobilizon.GraphQL.Resolvers.UserTest do
|
||||
|
||||
test "register_person/3 doesn't register a profile from an unknown email", %{conn: conn} do
|
||||
conn
|
||||
|> put_req_header("accept-language", "fr")
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @create_user_mutation,
|
||||
variables: @user_creation
|
||||
@ -416,6 +418,7 @@ defmodule Mobilizon.GraphQL.Resolvers.UserTest do
|
||||
|
||||
test "register_person/3 can't be called with an existing profile", %{conn: conn} do
|
||||
conn
|
||||
|> put_req_header("accept-language", "fr")
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @create_user_mutation,
|
||||
variables: @user_creation
|
||||
@ -423,6 +426,7 @@ defmodule Mobilizon.GraphQL.Resolvers.UserTest do
|
||||
|
||||
res =
|
||||
conn
|
||||
|> put_req_header("accept-language", "fr")
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @register_person_mutation,
|
||||
variables: @user_creation
|
||||
@ -447,6 +451,7 @@ defmodule Mobilizon.GraphQL.Resolvers.UserTest do
|
||||
insert(:actor, preferred_username: "myactor")
|
||||
|
||||
conn
|
||||
|> put_req_header("accept-language", "fr")
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @create_user_mutation,
|
||||
variables: @user_creation
|
||||
|
@ -1,39 +0,0 @@
|
||||
defmodule Mobilizon.Web.GettextTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Mobilizon.Config
|
||||
alias Mobilizon.Web.Gettext, as: GettextBackend
|
||||
|
||||
describe "test determine_best_locale/1" do
|
||||
setup do
|
||||
Config.put([:instance, :default_language], "en")
|
||||
:ok
|
||||
end
|
||||
|
||||
test "with empty string returns the default locale" do
|
||||
assert GettextBackend.determine_best_locale("") == "en"
|
||||
end
|
||||
|
||||
test "with empty string returns the default configured locale" do
|
||||
Config.put([:instance, :default_language], "es")
|
||||
assert GettextBackend.determine_best_locale("") == "es"
|
||||
end
|
||||
|
||||
test "with empty string returns english as a proper fallback if the default configured locale is nil" do
|
||||
Config.put([:instance, :default_language], nil)
|
||||
assert GettextBackend.determine_best_locale("") == "en"
|
||||
end
|
||||
|
||||
test "returns fallback with an unexisting locale" do
|
||||
assert GettextBackend.determine_best_locale("yolo") == "en"
|
||||
end
|
||||
|
||||
test "maps the correct part if the locale has multiple ones" do
|
||||
assert GettextBackend.determine_best_locale("fr_CA") == "fr"
|
||||
end
|
||||
|
||||
test "returns the locale if valid" do
|
||||
assert GettextBackend.determine_best_locale("es") == "es"
|
||||
end
|
||||
end
|
||||
end
|
35
test/web/plugs/detect_locale_plug_test.exs
Normal file
35
test/web/plugs/detect_locale_plug_test.exs
Normal file
@ -0,0 +1,35 @@
|
||||
# Portions of this file are derived from Pleroma:
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Mobilizon.Web.Plugs.DetectLocalePlugTest do
|
||||
use ExUnit.Case, async: true
|
||||
use Plug.Test
|
||||
|
||||
alias Mobilizon.Web.Plugs.DetectLocalePlug
|
||||
alias Plug.Conn
|
||||
|
||||
test "use supported locale from `accept-language`" do
|
||||
conn =
|
||||
:get
|
||||
|> conn("/cofe")
|
||||
|> Conn.put_req_header(
|
||||
"accept-language",
|
||||
"ru, fr-CH, fr;q=0.9, en;q=0.8, *;q=0.5"
|
||||
)
|
||||
|> DetectLocalePlug.call([])
|
||||
|
||||
assert %{detected_locale: "ru"} == conn.assigns
|
||||
end
|
||||
|
||||
test "returns empty string if `accept-language` header is empty" do
|
||||
conn =
|
||||
:get
|
||||
|> conn("/cofe")
|
||||
|> Conn.put_req_header("accept-language", "tlh")
|
||||
|> DetectLocalePlug.call([])
|
||||
|
||||
assert %{detected_locale: nil} == conn.assigns
|
||||
end
|
||||
end
|
@ -7,42 +7,87 @@ defmodule Mobilizon.Web.Plugs.SetLocalePlugTest do
|
||||
use ExUnit.Case, async: true
|
||||
use Plug.Test
|
||||
|
||||
alias Mobilizon.Web.Gettext, as: GettextBackend
|
||||
alias Mobilizon.Config
|
||||
alias Mobilizon.Web.Plugs.SetLocalePlug
|
||||
alias Plug.Conn
|
||||
|
||||
test "default locale is `en`" do
|
||||
conn =
|
||||
:get
|
||||
|> conn("/cofe")
|
||||
|> SetLocalePlug.call([])
|
||||
describe "test assigning locale to conn" do
|
||||
test "use supported locale from `accept-language`" do
|
||||
conn =
|
||||
:get
|
||||
|> conn("/cofe")
|
||||
|> assign(:detected_locale, "ru")
|
||||
|> SetLocalePlug.call([])
|
||||
|
||||
assert "en" == Gettext.get_locale()
|
||||
assert %{locale: ""} == conn.assigns
|
||||
assert "ru" == Gettext.get_locale()
|
||||
assert %{locale: "ru", detected_locale: "ru"} == conn.assigns
|
||||
end
|
||||
|
||||
test "use default locale if locale from `accept-language` is not supported" do
|
||||
conn =
|
||||
:get
|
||||
|> conn("/cofe")
|
||||
|> Conn.put_req_header("accept-language", "tlh")
|
||||
|> SetLocalePlug.call([])
|
||||
|
||||
assert "en" == Gettext.get_locale()
|
||||
assert %{locale: "en"} == conn.assigns
|
||||
end
|
||||
end
|
||||
|
||||
test "use supported locale from `accept-language`" do
|
||||
conn =
|
||||
:get
|
||||
|> conn("/cofe")
|
||||
|> Conn.put_req_header(
|
||||
"accept-language",
|
||||
"ru, fr-CH, fr;q=0.9, en;q=0.8, *;q=0.5"
|
||||
)
|
||||
|> SetLocalePlug.call([])
|
||||
describe "test getting default locale from instance" do
|
||||
test "default locale is `en`" do
|
||||
conn =
|
||||
:get
|
||||
|> conn("/cofe")
|
||||
|> SetLocalePlug.call([])
|
||||
|
||||
assert "ru" == Gettext.get_locale(GettextBackend)
|
||||
assert %{locale: "ru"} == conn.assigns
|
||||
assert "en" == Gettext.get_locale()
|
||||
assert %{locale: "en"} == conn.assigns
|
||||
end
|
||||
|
||||
test "with empty string returns the default configured locale" do
|
||||
Config.put([:instance, :default_language], "es")
|
||||
|
||||
conn =
|
||||
:get
|
||||
|> conn("/cofe")
|
||||
|> SetLocalePlug.call([])
|
||||
|
||||
assert %{locale: "es"} == conn.assigns
|
||||
|
||||
Config.put([:instance, :default_language], "en")
|
||||
end
|
||||
|
||||
test "with empty string returns english as a proper fallback if the default configured locale is nil" do
|
||||
Config.put([:instance, :default_language], nil)
|
||||
|
||||
conn =
|
||||
:get
|
||||
|> conn("/cofe")
|
||||
|> SetLocalePlug.call([])
|
||||
|
||||
assert %{locale: "en"} == conn.assigns
|
||||
|
||||
Config.put([:instance, :default_language], "en")
|
||||
end
|
||||
end
|
||||
|
||||
test "use default locale if locale from `accept-language` is not supported" do
|
||||
conn =
|
||||
:get
|
||||
|> conn("/cofe")
|
||||
|> Conn.put_req_header("accept-language", "tlh")
|
||||
|> SetLocalePlug.call([])
|
||||
describe "test determine_best_locale/1" do
|
||||
test "with empty string returns the default locale" do
|
||||
assert SetLocalePlug.determine_best_locale("") == nil
|
||||
end
|
||||
|
||||
assert "en" == Gettext.get_locale(GettextBackend)
|
||||
assert %{locale: ""} == conn.assigns
|
||||
test "returns fallback with an unexisting locale" do
|
||||
assert SetLocalePlug.determine_best_locale("yolo") == nil
|
||||
end
|
||||
|
||||
test "maps the correct part if the locale has multiple ones" do
|
||||
assert SetLocalePlug.determine_best_locale("fr_CA") == "fr"
|
||||
end
|
||||
|
||||
test "returns the locale if valid" do
|
||||
assert SetLocalePlug.determine_best_locale("es") == "es"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user