diff --git a/.sobelow-skips b/.sobelow-skips index 70978cfa5..28a98366b 100644 --- a/.sobelow-skips +++ b/.sobelow-skips @@ -7,4 +7,5 @@ 155A1FB53DE39EC8EFCFD7FB94EA823D 73B351E4CB3AF715AD450A085F5E6304 BBACD7F0BACD4A6D3010C26604671692 -6D4D4A4821B93BCFAC9CDBB367B34C4B \ No newline at end of file +6D4D4A4821B93BCFAC9CDBB367B34C4B +5674F0D127852889ED0132DC2F442AAB \ No newline at end of file diff --git a/js/src/utils/i18n.ts b/js/src/utils/i18n.ts index 5e2231c0f..c772484d7 100644 --- a/js/src/utils/i18n.ts +++ b/js/src/utils/i18n.ts @@ -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, lang: string) { if (Object.prototype.hasOwnProperty.call(matches, lang)) { return matches[lang]; diff --git a/lib/graphql/error.ex b/lib/graphql/error.ex index 653a09051..e6b87b2c8 100644 --- a/lib/graphql/error.ex +++ b/lib/graphql/error.ex @@ -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 diff --git a/lib/graphql/resolvers/actor.ex b/lib/graphql/resolvers/actor.ex index 54e60293e..c1279d9b9 100644 --- a/lib/graphql/resolvers/actor.ex +++ b/lib/graphql/resolvers/actor.ex @@ -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 diff --git a/lib/mobilizon/config.ex b/lib/mobilizon/config.ex index 600ae63c8..c4e66c7bd 100644 --- a/lib/mobilizon/config.ex +++ b/lib/mobilizon/config.ex @@ -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, diff --git a/lib/service/activity/renderer/comment.ex b/lib/service/activity/renderer/comment.ex index 87f62796c..c7551c6d2 100644 --- a/lib/service/activity/renderer/comment.ex +++ b/lib/service/activity/renderer/comment.ex @@ -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] diff --git a/lib/service/activity/renderer/discussion.ex b/lib/service/activity/renderer/discussion.ex index 11328ed85..8c1d58f4f 100644 --- a/lib/service/activity/renderer/discussion.ex +++ b/lib/service/activity/renderer/discussion.ex @@ -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] diff --git a/lib/service/activity/renderer/event.ex b/lib/service/activity/renderer/event.ex index 7c8eb5993..809583bb0 100644 --- a/lib/service/activity/renderer/event.ex +++ b/lib/service/activity/renderer/event.ex @@ -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] diff --git a/lib/service/activity/renderer/group.ex b/lib/service/activity/renderer/group.ex index 15f7a66b9..a9df9d826 100644 --- a/lib/service/activity/renderer/group.ex +++ b/lib/service/activity/renderer/group.ex @@ -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] diff --git a/lib/service/activity/renderer/member.ex b/lib/service/activity/renderer/member.ex index 9581a1a92..64bdda534 100644 --- a/lib/service/activity/renderer/member.ex +++ b/lib/service/activity/renderer/member.ex @@ -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] diff --git a/lib/service/activity/renderer/post.ex b/lib/service/activity/renderer/post.ex index 4f92e4469..1491e58e8 100644 --- a/lib/service/activity/renderer/post.ex +++ b/lib/service/activity/renderer/post.ex @@ -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] diff --git a/lib/service/activity/renderer/resource.ex b/lib/service/activity/renderer/resource.ex index 31f7ccfcd..4e328b236 100644 --- a/lib/service/activity/renderer/resource.ex +++ b/lib/service/activity/renderer/resource.ex @@ -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] diff --git a/lib/service/metadata/utils.ex b/lib/service/metadata/utils.ex index 36416919d..2e1992dfb 100644 --- a/lib/service/metadata/utils.ex +++ b/lib/service/metadata/utils.ex @@ -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 diff --git a/lib/web/auth/context.ex b/lib/web/auth/context.ex index 468a0d692..68a1c4ffe 100644 --- a/lib/web/auth/context.ex +++ b/lib/web/auth/context.ex @@ -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 = diff --git a/lib/web/email/activity.ex b/lib/web/email/activity.ex index 339fcb9eb..17268424c 100644 --- a/lib/web/email/activity.ex +++ b/lib/web/email/activity.ex @@ -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() diff --git a/lib/web/email/admin.ex b/lib/web/email/admin.ex index 782c32709..bde93cf0d 100644 --- a/lib/web/email/admin.ex +++ b/lib/web/email/admin.ex @@ -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 diff --git a/lib/web/email/event.ex b/lib/web/email/event.ex index 5b3e39ddc..b49a3baae 100644 --- a/lib/web/email/event.ex +++ b/lib/web/email/event.ex @@ -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( diff --git a/lib/web/email/follow.ex b/lib/web/email/follow.ex index 25c3c953d..b6c1a523f 100644 --- a/lib/web/email/follow.ex +++ b/lib/web/email/follow.ex @@ -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 diff --git a/lib/web/email/group.ex b/lib/web/email/group.ex index 2c3029007..8e1e938ec 100644 --- a/lib/web/email/group.ex +++ b/lib/web/email/group.ex @@ -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 diff --git a/lib/web/email/notification.ex b/lib/web/email/notification.ex index e27b13efb..5800bf0f4 100644 --- a/lib/web/email/notification.ex +++ b/lib/web/email/notification.ex @@ -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() diff --git a/lib/web/email/participation.ex b/lib/web/email/participation.ex index dbd90e694..edf319906 100644 --- a/lib/web/email/participation.ex +++ b/lib/web/email/participation.ex @@ -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 diff --git a/lib/web/email/user.ex b/lib/web/email/user.ex index 7dd74b70b..fdbdcdf79 100644 --- a/lib/web/email/user.ex +++ b/lib/web/email/user.ex @@ -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 diff --git a/lib/web/endpoint.ex b/lib/web/endpoint.ex index 286ab9778..2b1455551 100644 --- a/lib/web/endpoint.ex +++ b/lib/web/endpoint.ex @@ -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) diff --git a/lib/web/gettext.ex b/lib/web/gettext.ex index 9f9604772..055113a85 100644 --- a/lib/web/gettext.ex +++ b/lib/web/gettext.ex @@ -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 diff --git a/lib/web/mobilizon_web.ex b/lib/web/mobilizon_web.ex index fa691357b..76f5b5fe1 100644 --- a/lib/web/mobilizon_web.ex +++ b/lib/web/mobilizon_web.ex @@ -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 diff --git a/lib/web/plugs/detect_locale_plug.ex b/lib/web/plugs/detect_locale_plug.ex new file mode 100644 index 000000000..66c339aca --- /dev/null +++ b/lib/web/plugs/detect_locale_plug.ex @@ -0,0 +1,67 @@ +# Portions of this file are derived from Pleroma: +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# 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?(?[\w\-]+)(?:;q=(?[\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 diff --git a/lib/web/plugs/set_locale_plug.ex b/lib/web/plugs/set_locale_plug.ex index 8bcc83324..0af554e13 100644 --- a/lib/web/plugs/set_locale_plug.ex +++ b/lib/web/plugs/set_locale_plug.ex @@ -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?(?[\w\-]+)(?:;q=(?[\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 diff --git a/lib/web/router.ex b/lib/web/router.ex index 915ac241e..bc3a5619e 100644 --- a/lib/web/router.ex +++ b/lib/web/router.ex @@ -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 diff --git a/lib/web/views/changeset_view.ex b/lib/web/views/changeset_view.ex deleted file mode 100644 index 0fa250a6e..000000000 --- a/lib/web/views/changeset_view.ex +++ /dev/null @@ -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 diff --git a/lib/web/views/error_helpers.ex b/lib/web/views/error_helpers.ex index be5a36c7f..5a42c31c2 100644 --- a/lib/web/views/error_helpers.ex +++ b/lib/web/views/error_helpers.ex @@ -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 diff --git a/lib/web/views/utils.ex b/lib/web/views/utils.ex index 28d167edf..ecc1018c4 100644 --- a/lib/web/views/utils.ex +++ b/lib/web/views/utils.ex @@ -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 diff --git a/mix.exs b/mix.exs index be869d327..866e157c3 100644 --- a/mix.exs +++ b/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, diff --git a/test/graphql/resolvers/event_test.exs b/test/graphql/resolvers/event_test.exs index ec07b5bd9..d2dc8c5ff 100644 --- a/test/graphql/resolvers/event_test.exs +++ b/test/graphql/resolvers/event_test.exs @@ -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 diff --git a/test/graphql/resolvers/user_test.exs b/test/graphql/resolvers/user_test.exs index 1b8f3d690..617bf9094 100644 --- a/test/graphql/resolvers/user_test.exs +++ b/test/graphql/resolvers/user_test.exs @@ -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 diff --git a/test/web/gettext_test.exs b/test/web/gettext_test.exs deleted file mode 100644 index 3ed4f9623..000000000 --- a/test/web/gettext_test.exs +++ /dev/null @@ -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 diff --git a/test/web/plugs/detect_locale_plug_test.exs b/test/web/plugs/detect_locale_plug_test.exs new file mode 100644 index 000000000..84d001b98 --- /dev/null +++ b/test/web/plugs/detect_locale_plug_test.exs @@ -0,0 +1,35 @@ +# Portions of this file are derived from Pleroma: +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# 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 diff --git a/test/web/plugs/set_locale_plug_test.exs b/test/web/plugs/set_locale_plug_test.exs index 8804a8a54..924765ea3 100644 --- a/test/web/plugs/set_locale_plug_test.exs +++ b/test/web/plugs/set_locale_plug_test.exs @@ -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