diff --git a/docs/architecture.md b/docs/architecture.md index 7352bdb0..2b7a8803 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -279,6 +279,7 @@ All authenticated routes live inside a single `live_session` with three `on_moun | Module | Purpose | |--------|---------| +| `ErrorMessages` | Maps internal error terms (atoms, structs) to user-friendly gettext strings via `friendly_message/1` | | `Markdown` | Markdown-to-HTML conversion with `[[double bracket]]` link syntax | | `Duration` | Milliseconds to human-readable duration formatting | | `LiveHelpers.Params` | Pagination param parsing from URL query params | diff --git a/docs/project-conventions.md b/docs/project-conventions.md index ca885de8..e72418b6 100644 --- a/docs/project-conventions.md +++ b/docs/project-conventions.md @@ -51,6 +51,7 @@ Rules extracted from commit history that are specific to this project and not al ## Error Handling - **Toast notifications:** `put_toast/3` (arity 3) in LiveViews, `put_toast!/2` (arity 2) in LiveComponents. `:info` for success, `:error` for failures. +- **User-facing error reasons use `ErrorMessages.friendly_message/1`** — never `inspect(reason)`. Call sites keep their contextual prefix (e.g. `gettext("Error refreshing cover")`) and append `": " <> ErrorMessages.friendly_message(reason)` for the reason part. `Logger.error` calls keep `inspect` for debugging. - **`handle_async` always handles three cases:** `{:ok, {:ok, result}}`, `{:ok, {:error, reason}}`, and `{:exit, reason}`. - **Data cascade on upstream changes:** When artist metadata changes, regenerate dependent record embeddings. diff --git a/lib/music_library_web/components/barcode_scanner.ex b/lib/music_library_web/components/barcode_scanner.ex index 103eebbe..07dc8be2 100644 --- a/lib/music_library_web/components/barcode_scanner.ex +++ b/lib/music_library_web/components/barcode_scanner.ex @@ -4,6 +4,7 @@ defmodule MusicLibraryWeb.Components.BarcodeScanner do alias MusicBrainz.ReleaseGroupSearchResult alias MusicLibrary.BarcodeScan alias MusicLibrary.Records + alias MusicLibraryWeb.ErrorMessages alias MusicLibraryWeb.RecordComponents require Logger @@ -410,7 +411,7 @@ defmodule MusicLibraryWeb.Components.BarcodeScanner do errors -> errors_summary = Enum.map_join(errors, "\n", fn {number, reason} -> - "#{number}: #{inspect(reason)}" + "#{number}: #{ErrorMessages.friendly_message(reason)}" end) put_toast( diff --git a/lib/music_library_web/components/record_form.ex b/lib/music_library_web/components/record_form.ex index 6dc862c7..07842625 100644 --- a/lib/music_library_web/components/record_form.ex +++ b/lib/music_library_web/components/record_form.ex @@ -7,6 +7,7 @@ defmodule MusicLibraryWeb.Components.RecordForm do alias MusicLibrary.{Assets, Records} alias MusicLibrary.Assets.Image alias MusicLibrary.Records.Record + alias MusicLibraryWeb.ErrorMessages @impl true def mount(socket) do @@ -538,14 +539,20 @@ defmodule MusicLibraryWeb.Components.RecordForm do def handle_async(:cover_search, {:ok, {:error, reason}}, socket) do {:noreply, socket - |> assign(:cover_search_error, "Search failed: #{inspect(reason)}") + |> assign( + :cover_search_error, + gettext("Search failed") <> ": " <> ErrorMessages.friendly_message(reason) + ) |> assign(:cover_search_loading, false)} end def handle_async(:cover_search, {:exit, reason}, socket) do {:noreply, socket - |> assign(:cover_search_error, "Search failed: #{inspect(reason)}") + |> assign( + :cover_search_error, + gettext("Search failed") <> ": " <> ErrorMessages.friendly_message(reason) + ) |> assign(:cover_search_loading, false)} end @@ -571,14 +578,20 @@ defmodule MusicLibraryWeb.Components.RecordForm do def handle_async(:cover_download, {:ok, {:error, reason}}, socket) do {:noreply, socket - |> assign(:cover_search_error, "Download failed: #{inspect(reason)}") + |> assign( + :cover_search_error, + gettext("Download failed") <> ": " <> ErrorMessages.friendly_message(reason) + ) |> assign(:cover_search_loading, false)} end def handle_async(:cover_download, {:exit, reason}, socket) do {:noreply, socket - |> assign(:cover_search_error, "Download failed: #{inspect(reason)}") + |> assign( + :cover_search_error, + gettext("Download failed") <> ": " <> ErrorMessages.friendly_message(reason) + ) |> assign(:cover_search_loading, false)} end diff --git a/lib/music_library_web/components/release.ex b/lib/music_library_web/components/release.ex index e61de6a1..1fc86e8a 100644 --- a/lib/music_library_web/components/release.ex +++ b/lib/music_library_web/components/release.ex @@ -6,6 +6,7 @@ defmodule MusicLibraryWeb.Components.Release do alias MusicBrainz.Release alias MusicLibrary.ScrobbleActivity alias MusicLibraryWeb.Duration + alias MusicLibraryWeb.ErrorMessages alias Phoenix.LiveView.AsyncResult def open(id), do: Fluxon.open_dialog(id) @@ -267,7 +268,7 @@ defmodule MusicLibraryWeb.Components.Release do put_toast!( :error, - gettext("Error scrobbling release") <> "," <> inspect(reason) + gettext("Error scrobbling release") <> ": " <> ErrorMessages.friendly_message(reason) ) {:noreply, socket} @@ -298,7 +299,7 @@ defmodule MusicLibraryWeb.Components.Release do put_toast!( :error, - gettext("Error scrobbling disc") <> "," <> inspect(reason) + gettext("Error scrobbling disc") <> ": " <> ErrorMessages.friendly_message(reason) ) {:noreply, socket} @@ -372,7 +373,8 @@ defmodule MusicLibraryWeb.Components.Release do put_toast!( :error, - gettext("Error scrobbling selected tracks") <> "," <> inspect(reason) + gettext("Error scrobbling selected tracks") <> + ": " <> ErrorMessages.friendly_message(reason) ) {:noreply, socket} diff --git a/lib/music_library_web/error_messages.ex b/lib/music_library_web/error_messages.ex new file mode 100644 index 00000000..943e0f16 --- /dev/null +++ b/lib/music_library_web/error_messages.ex @@ -0,0 +1,67 @@ +defmodule MusicLibraryWeb.ErrorMessages do + @moduledoc """ + Maps internal error terms to user-friendly messages. + + Used at call sites that previously showed `inspect(reason)` in toasts or assigns. + Each call site keeps its own contextual prefix (e.g. "Error refreshing cover") + and appends `": " <> friendly_message(reason)` for the reason part. + """ + + use Gettext, backend: MusicLibraryWeb.Gettext + + # App error atoms + def friendly_message(:cover_not_available), do: gettext("cover art is not available") + def friendly_message(:no_duration), do: gettext("release has no track duration information") + def friendly_message(:medium_not_found), do: gettext("the specified disc was not found") + def friendly_message(:no_session_key), do: gettext("Last.fm session key is not configured") + + def friendly_message(:already_collected), + do: gettext("this record is already in your collection") + + def friendly_message(:not_found), do: gettext("the resource was not found") + def friendly_message(:no_discogs_data), do: gettext("no Discogs profile available") + def friendly_message(:image_not_found), do: gettext("no image could be found") + def friendly_message(:invalid_parameters), do: gettext("invalid parameters were provided") + def friendly_message(:download_failed), do: gettext("the download failed") + + # Last.fm API error atoms + def friendly_message(:authentication_failed), do: gettext("Last.fm authentication failed") + + def friendly_message(:invalid_session_key), + do: gettext("Last.fm session has expired, please reconnect") + + def friendly_message(:invalid_api_key), do: gettext("Last.fm API key is invalid") + def friendly_message(:suspended_api_key), do: gettext("Last.fm API key has been suspended") + + def friendly_message(:rate_limit_exceeded), + do: gettext("rate limit exceeded, please try again later") + + def friendly_message(:service_offline), + do: gettext("service is currently offline, please try again later") + + def friendly_message(:transient_error), + do: gettext("a temporary error occurred, please try again") + + def friendly_message(:operation_failed), do: gettext("the operation failed, please try again") + + def friendly_message(:invalid_resource), + do: gettext("the requested resource does not exist") + + # Structured errors + def friendly_message(%Req.TransportError{reason: :timeout}), + do: gettext("the request timed out, please try again") + + def friendly_message(%Req.TransportError{reason: :econnrefused}), + do: gettext("the service is not reachable") + + def friendly_message(%Req.TransportError{reason: :nxdomain}), + do: gettext("the service could not be found") + + def friendly_message(%Req.TransportError{}), + do: gettext("a connection error occurred, please try again") + + def friendly_message(%Ecto.Changeset{}), do: gettext("validation failed") + + # Fallback + def friendly_message(_), do: gettext("something went wrong, please try again") +end diff --git a/lib/music_library_web/live/artist_live/form.ex b/lib/music_library_web/live/artist_live/form.ex index 9c3fe2a7..774cb7b1 100644 --- a/lib/music_library_web/live/artist_live/form.ex +++ b/lib/music_library_web/live/artist_live/form.ex @@ -6,6 +6,7 @@ defmodule MusicLibraryWeb.ArtistLive.Form do alias MusicLibrary.Artists alias MusicLibrary.Assets alias MusicLibrary.Assets.Image + alias MusicLibraryWeb.ErrorMessages @impl true def mount(socket) do @@ -249,14 +250,20 @@ defmodule MusicLibraryWeb.ArtistLive.Form do def handle_async(:image_search, {:ok, {:error, reason}}, socket) do {:noreply, socket - |> assign(:image_search_error, "Search failed: #{inspect(reason)}") + |> assign( + :image_search_error, + gettext("Search failed") <> ": " <> ErrorMessages.friendly_message(reason) + ) |> assign(:image_search_loading, false)} end def handle_async(:image_search, {:exit, reason}, socket) do {:noreply, socket - |> assign(:image_search_error, "Search failed: #{inspect(reason)}") + |> assign( + :image_search_error, + gettext("Search failed") <> ": " <> ErrorMessages.friendly_message(reason) + ) |> assign(:image_search_loading, false)} end @@ -283,14 +290,20 @@ defmodule MusicLibraryWeb.ArtistLive.Form do def handle_async(:image_download, {:ok, {:error, reason}}, socket) do {:noreply, socket - |> assign(:image_search_error, "Download failed: #{inspect(reason)}") + |> assign( + :image_search_error, + gettext("Download failed") <> ": " <> ErrorMessages.friendly_message(reason) + ) |> assign(:image_search_loading, false)} end def handle_async(:image_download, {:exit, reason}, socket) do {:noreply, socket - |> assign(:image_search_error, "Download failed: #{inspect(reason)}") + |> assign( + :image_search_error, + gettext("Download failed") <> ": " <> ErrorMessages.friendly_message(reason) + ) |> assign(:image_search_loading, false)} end diff --git a/lib/music_library_web/live/artist_live/show.ex b/lib/music_library_web/live/artist_live/show.ex index 47ea4894..96435f98 100644 --- a/lib/music_library_web/live/artist_live/show.ex +++ b/lib/music_library_web/live/artist_live/show.ex @@ -6,6 +6,7 @@ defmodule MusicLibraryWeb.ArtistLive.Show do alias MusicLibrary.{Artists, Records} alias MusicLibrary.Artists.ArtistInfo + alias MusicLibraryWeb.ErrorMessages attr :country, :map, required: true @@ -544,7 +545,8 @@ defmodule MusicLibraryWeb.ArtistLive.Show do socket |> put_toast( :error, - gettext("Error refreshing artist info") <> "," <> inspect(reason) + gettext("Error refreshing artist info") <> + ": " <> ErrorMessages.friendly_message(reason) )} end end @@ -563,7 +565,8 @@ defmodule MusicLibraryWeb.ArtistLive.Show do socket |> put_toast( :error, - gettext("Error refreshing Wikipedia data") <> "," <> inspect(reason) + gettext("Error refreshing Wikipedia data") <> + ": " <> ErrorMessages.friendly_message(reason) )} end end @@ -585,7 +588,8 @@ defmodule MusicLibraryWeb.ArtistLive.Show do socket |> put_toast( :error, - gettext("Error refreshing Last.fm data") <> "," <> inspect(reason) + gettext("Error refreshing Last.fm data") <> + ": " <> ErrorMessages.friendly_message(reason) )} end end @@ -603,7 +607,8 @@ defmodule MusicLibraryWeb.ArtistLive.Show do socket |> put_toast( :error, - gettext("Error refreshing artist image") <> "," <> inspect(reason) + gettext("Error refreshing artist image") <> + ": " <> ErrorMessages.friendly_message(reason) )} end end @@ -624,7 +629,7 @@ defmodule MusicLibraryWeb.ArtistLive.Show do socket |> put_toast( :error, - gettext("Error importing record") <> "," <> inspect(changeset.errors) + gettext("Error importing record") <> ": " <> ErrorMessages.friendly_message(changeset) )} end end diff --git a/lib/music_library_web/live/collection_live/index.ex b/lib/music_library_web/live/collection_live/index.ex index 1b4d96c1..28f61ae7 100644 --- a/lib/music_library_web/live/collection_live/index.ex +++ b/lib/music_library_web/live/collection_live/index.ex @@ -9,6 +9,7 @@ defmodule MusicLibraryWeb.CollectionLive.Index do alias MusicLibrary.Collection alias MusicLibrary.Records alias MusicLibraryWeb.CollectionLive.Show + alias MusicLibraryWeb.ErrorMessages @default_records_list_params %{ query: "", @@ -272,20 +273,14 @@ defmodule MusicLibraryWeb.CollectionLive.Index do |> put_toast(:info, gettext("Record imported successfully")) |> push_navigate(to: ~p"/collection/#{record.id}")} - {:error, %Ecto.Changeset{} = changeset} -> + {:error, reason} -> {:noreply, socket |> put_toast( :error, - gettext("Error importing record") <> "," <> inspect(changeset.errors) + gettext("Error importing record") <> ": " <> ErrorMessages.friendly_message(reason) ) |> push_patch(to: ~p"/collection")} - - {:error, reason} -> - {:noreply, - socket - |> put_toast(:error, gettext("Error importing record") <> "," <> inspect(reason)) - |> push_patch(to: ~p"/collection")} end end diff --git a/lib/music_library_web/live/collection_live/show.ex b/lib/music_library_web/live/collection_live/show.ex index a9fa3c12..82fbcf35 100644 --- a/lib/music_library_web/live/collection_live/show.ex +++ b/lib/music_library_web/live/collection_live/show.ex @@ -21,6 +21,7 @@ defmodule MusicLibraryWeb.CollectionLive.Show do alias MusicLibrary.{Records, RecordSets, ScrobbleActivity} alias MusicLibrary.Records.Similarity + alias MusicLibraryWeb.ErrorMessages alias Phoenix.LiveView.JS @impl true @@ -370,7 +371,8 @@ defmodule MusicLibraryWeb.CollectionLive.Show do socket |> put_toast( :error, - gettext("Error refreshing MusicBrainz data") <> "," <> inspect(reason) + gettext("Error refreshing MusicBrainz data") <> + ": " <> ErrorMessages.friendly_message(reason) )} end end @@ -389,7 +391,7 @@ defmodule MusicLibraryWeb.CollectionLive.Show do socket |> put_toast( :error, - gettext("Error") <> "," <> inspect(reason) + gettext("Error") <> ": " <> ErrorMessages.friendly_message(reason) )} end end @@ -409,7 +411,7 @@ defmodule MusicLibraryWeb.CollectionLive.Show do socket |> put_toast( :error, - gettext("Error refreshing cover") <> "," <> inspect(reason) + gettext("Error refreshing cover") <> ": " <> ErrorMessages.friendly_message(reason) )} end end @@ -429,7 +431,7 @@ defmodule MusicLibraryWeb.CollectionLive.Show do socket |> put_toast( :error, - gettext("Error extracting colors") <> ": " <> inspect(reason) + gettext("Error extracting colors") <> ": " <> ErrorMessages.friendly_message(reason) )} end end @@ -448,7 +450,7 @@ defmodule MusicLibraryWeb.CollectionLive.Show do socket |> put_toast( :error, - gettext("Error") <> "," <> inspect(reason) + gettext("Error") <> ": " <> ErrorMessages.friendly_message(reason) )} end end diff --git a/lib/music_library_web/live/maintenance_live/index.ex b/lib/music_library_web/live/maintenance_live/index.ex index 247b5183..a85480f1 100644 --- a/lib/music_library_web/live/maintenance_live/index.ex +++ b/lib/music_library_web/live/maintenance_live/index.ex @@ -6,6 +6,7 @@ defmodule MusicLibraryWeb.MaintenanceLive.Index do alias MusicLibrary.Artists alias MusicLibrary.Maintenance alias MusicLibrary.Records + alias MusicLibraryWeb.ErrorMessages @poll_interval 2_000 @@ -268,7 +269,10 @@ defmodule MusicLibraryWeb.MaintenanceLive.Index do {:noreply, socket - |> put_toast(:error, "Database vacuum failed: #{inspect(reason)}.")} + |> put_toast( + :error, + gettext("Database vacuum failed") <> ": " <> ErrorMessages.friendly_message(reason) + )} end end @@ -284,7 +288,10 @@ defmodule MusicLibraryWeb.MaintenanceLive.Index do {:noreply, socket - |> put_toast(:error, "Database optimize failed: #{inspect(reason)}.")} + |> put_toast( + :error, + gettext("Database optimize failed") <> ": " <> ErrorMessages.friendly_message(reason) + )} end end end diff --git a/lib/music_library_web/live/scrobble_live/show.ex b/lib/music_library_web/live/scrobble_live/show.ex index f7988f44..1fb5a3e0 100644 --- a/lib/music_library_web/live/scrobble_live/show.ex +++ b/lib/music_library_web/live/scrobble_live/show.ex @@ -5,6 +5,7 @@ defmodule MusicLibraryWeb.ScrobbleLive.Show do import MusicLibraryWeb.RecordComponents, only: [country_label: 1] alias MusicLibrary.ScrobbleActivity + alias MusicLibraryWeb.ErrorMessages @impl true def render(assigns) do @@ -156,7 +157,7 @@ defmodule MusicLibraryWeb.ScrobbleLive.Show do socket |> put_toast( :error, - gettext("Error scrobbling release") <> "," <> inspect(reason) + gettext("Error scrobbling release") <> ": " <> ErrorMessages.friendly_message(reason) )} end end @@ -177,7 +178,7 @@ defmodule MusicLibraryWeb.ScrobbleLive.Show do socket |> put_toast( :error, - gettext("Error scrobbling disc") <> "," <> inspect(reason) + gettext("Error scrobbling disc") <> ": " <> ErrorMessages.friendly_message(reason) )} end end @@ -205,7 +206,8 @@ defmodule MusicLibraryWeb.ScrobbleLive.Show do socket |> put_toast( :error, - gettext("Error scrobbling selected tracks") <> "," <> inspect(reason) + gettext("Error scrobbling selected tracks") <> + ": " <> ErrorMessages.friendly_message(reason) )} end end diff --git a/lib/music_library_web/live/stats_live/index.ex b/lib/music_library_web/live/stats_live/index.ex index d7533cbb..304ca1ae 100644 --- a/lib/music_library_web/live/stats_live/index.ex +++ b/lib/music_library_web/live/stats_live/index.ex @@ -9,6 +9,7 @@ defmodule MusicLibraryWeb.StatsLive.Index do alias MusicLibrary.Assets.Transform alias MusicLibrary.{Collection, ListeningStats, Records, Wishlist} + alias MusicLibraryWeb.ErrorMessages alias MusicLibraryWeb.StatsLive.{TopAlbums, TopArtists} @impl true @@ -392,18 +393,13 @@ defmodule MusicLibraryWeb.StatsLive.Index do |> put_toast(:info, gettext("Record wishlisted successfully")) |> push_navigate(to: ~p"/wishlist/#{record.id}")} - {:error, %Ecto.Changeset{} = changeset} -> + {:error, reason} -> {:noreply, socket |> put_toast( :error, - gettext("Error wishlisting record") <> "," <> inspect(changeset.errors) + gettext("Error wishlisting record") <> ": " <> ErrorMessages.friendly_message(reason) )} - - {:error, reason} -> - {:noreply, - socket - |> put_toast(:error, gettext("Error wishlisting record") <> "," <> inspect(reason))} end end diff --git a/lib/music_library_web/live/wishlist_live/index.ex b/lib/music_library_web/live/wishlist_live/index.ex index 01006709..a4f8d8c8 100644 --- a/lib/music_library_web/live/wishlist_live/index.ex +++ b/lib/music_library_web/live/wishlist_live/index.ex @@ -7,6 +7,7 @@ defmodule MusicLibraryWeb.WishlistLive.Index do alias MusicLibrary.Records alias MusicLibrary.Wishlist + alias MusicLibraryWeb.ErrorMessages alias MusicLibraryWeb.WishlistLive.Show @default_records_list_params %{ @@ -242,20 +243,14 @@ defmodule MusicLibraryWeb.WishlistLive.Index do |> put_toast(:info, gettext("Record wishlisted successfully")) |> push_navigate(to: ~p"/wishlist/#{record.id}")} - {:error, %Ecto.Changeset{} = changeset} -> + {:error, reason} -> {:noreply, socket |> put_toast( :error, - gettext("Error wishlisting record") <> "," <> inspect(changeset.errors) + gettext("Error wishlisting record") <> ": " <> ErrorMessages.friendly_message(reason) ) |> push_patch(to: ~p"/wishlist")} - - {:error, reason} -> - {:noreply, - socket - |> put_toast(:error, gettext("Error wishlisting record") <> "," <> inspect(reason)) - |> push_patch(to: ~p"/wishlist")} end end diff --git a/lib/music_library_web/live/wishlist_live/show.ex b/lib/music_library_web/live/wishlist_live/show.ex index b669bae8..cced9dd8 100644 --- a/lib/music_library_web/live/wishlist_live/show.ex +++ b/lib/music_library_web/live/wishlist_live/show.ex @@ -19,6 +19,7 @@ defmodule MusicLibraryWeb.WishlistLive.Show do alias MusicLibrary.OnlineStoreTemplates alias MusicLibrary.{Records, RecordSets} alias MusicLibrary.Records.Similarity + alias MusicLibraryWeb.ErrorMessages @impl true def render(assigns) do @@ -319,7 +320,8 @@ defmodule MusicLibraryWeb.WishlistLive.Show do socket |> put_toast( :error, - gettext("Error refreshing MusicBrainz data") <> "," <> inspect(reason) + gettext("Error refreshing MusicBrainz data") <> + ": " <> ErrorMessages.friendly_message(reason) )} end end @@ -339,7 +341,7 @@ defmodule MusicLibraryWeb.WishlistLive.Show do socket |> put_toast( :error, - gettext("Error refreshing Cover") <> "," <> inspect(reason) + gettext("Error refreshing cover") <> ": " <> ErrorMessages.friendly_message(reason) )} end end @@ -358,7 +360,7 @@ defmodule MusicLibraryWeb.WishlistLive.Show do socket |> put_toast( :error, - gettext("Error") <> "," <> inspect(reason) + gettext("Error") <> ": " <> ErrorMessages.friendly_message(reason) )} end end @@ -394,7 +396,7 @@ defmodule MusicLibraryWeb.WishlistLive.Show do socket |> put_toast( :error, - gettext("Error extracting colors") <> ": " <> inspect(reason) + gettext("Error extracting colors") <> ": " <> ErrorMessages.friendly_message(reason) )} end end diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index 27d9040c..237ba001 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -279,12 +279,8 @@ msgstr "" msgid "Cover refreshed successfully" msgstr "" -#: lib/music_library_web/live/wishlist_live/show.ex -#, elixir-autogen, elixir-format -msgid "Error refreshing Cover" -msgstr "" - #: lib/music_library_web/live/collection_live/show.ex +#: lib/music_library_web/live/wishlist_live/show.ex #, elixir-autogen, elixir-format msgid "Error refreshing cover" msgstr "" @@ -2069,3 +2065,150 @@ msgstr "" #, elixir-autogen, elixir-format msgid "Extract colors" msgstr "" + +#: lib/music_library_web/live/maintenance_live/index.ex +#, elixir-autogen, elixir-format +msgid "Database optimize failed" +msgstr "" + +#: lib/music_library_web/live/maintenance_live/index.ex +#, elixir-autogen, elixir-format +msgid "Database vacuum failed" +msgstr "" + +#: lib/music_library_web/components/record_form.ex +#: lib/music_library_web/live/artist_live/form.ex +#, elixir-autogen, elixir-format +msgid "Download failed" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "Last.fm API key has been suspended" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "Last.fm API key is invalid" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "Last.fm authentication failed" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "Last.fm session has expired, please reconnect" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "Last.fm session key is not configured" +msgstr "" + +#: lib/music_library_web/components/record_form.ex +#: lib/music_library_web/live/artist_live/form.ex +#, elixir-autogen, elixir-format +msgid "Search failed" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "a connection error occurred, please try again" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "a temporary error occurred, please try again" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "cover art is not available" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "invalid parameters were provided" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "no Discogs profile available" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "no image could be found" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "rate limit exceeded, please try again later" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "release has no track duration information" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "service is currently offline, please try again later" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "something went wrong, please try again" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "the download failed" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "the operation failed, please try again" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "the request timed out, please try again" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "the requested resource does not exist" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "the resource was not found" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "the service could not be found" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "the service is not reachable" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "the specified disc was not found" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "this record is already in your collection" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "validation failed" +msgstr "" diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index a23ad98c..2f72e432 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -279,12 +279,8 @@ msgstr "" msgid "Cover refreshed successfully" msgstr "" -#: lib/music_library_web/live/wishlist_live/show.ex -#, elixir-autogen, elixir-format -msgid "Error refreshing Cover" -msgstr "" - #: lib/music_library_web/live/collection_live/show.ex +#: lib/music_library_web/live/wishlist_live/show.ex #, elixir-autogen, elixir-format msgid "Error refreshing cover" msgstr "" @@ -2069,3 +2065,150 @@ msgstr "" #, elixir-autogen, elixir-format msgid "Extract colors" msgstr "" + +#: lib/music_library_web/live/maintenance_live/index.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Database optimize failed" +msgstr "" + +#: lib/music_library_web/live/maintenance_live/index.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Database vacuum failed" +msgstr "" + +#: lib/music_library_web/components/record_form.ex +#: lib/music_library_web/live/artist_live/form.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Download failed" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "Last.fm API key has been suspended" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "Last.fm API key is invalid" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "Last.fm authentication failed" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "Last.fm session has expired, please reconnect" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "Last.fm session key is not configured" +msgstr "" + +#: lib/music_library_web/components/record_form.ex +#: lib/music_library_web/live/artist_live/form.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Search failed" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "a connection error occurred, please try again" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "a temporary error occurred, please try again" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "cover art is not available" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "invalid parameters were provided" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "no Discogs profile available" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "no image could be found" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "rate limit exceeded, please try again later" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "release has no track duration information" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "service is currently offline, please try again later" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "something went wrong, please try again" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "the download failed" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "the operation failed, please try again" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "the request timed out, please try again" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "the requested resource does not exist" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "the resource was not found" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "the service could not be found" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "the service is not reachable" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "the specified disc was not found" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "this record is already in your collection" +msgstr "" + +#: lib/music_library_web/error_messages.ex +#, elixir-autogen, elixir-format +msgid "validation failed" +msgstr "" diff --git a/test/music_library_web/error_messages_test.exs b/test/music_library_web/error_messages_test.exs new file mode 100644 index 00000000..9590a30b --- /dev/null +++ b/test/music_library_web/error_messages_test.exs @@ -0,0 +1,90 @@ +defmodule MusicLibraryWeb.ErrorMessagesTest do + use ExUnit.Case, async: true + + alias MusicLibraryWeb.ErrorMessages + + describe "friendly_message/1" do + test "maps known app error atoms" do + assert ErrorMessages.friendly_message(:cover_not_available) == "cover art is not available" + + assert ErrorMessages.friendly_message(:no_duration) == + "release has no track duration information" + + assert ErrorMessages.friendly_message(:medium_not_found) == + "the specified disc was not found" + + assert ErrorMessages.friendly_message(:no_session_key) == + "Last.fm session key is not configured" + + assert ErrorMessages.friendly_message(:already_collected) == + "this record is already in your collection" + + assert ErrorMessages.friendly_message(:not_found) == "the resource was not found" + assert ErrorMessages.friendly_message(:no_discogs_data) == "no Discogs profile available" + assert ErrorMessages.friendly_message(:image_not_found) == "no image could be found" + + assert ErrorMessages.friendly_message(:invalid_parameters) == + "invalid parameters were provided" + + assert ErrorMessages.friendly_message(:download_failed) == "the download failed" + end + + test "maps Last.fm API error atoms" do + assert ErrorMessages.friendly_message(:authentication_failed) == + "Last.fm authentication failed" + + assert ErrorMessages.friendly_message(:invalid_session_key) == + "Last.fm session has expired, please reconnect" + + assert ErrorMessages.friendly_message(:invalid_api_key) == "Last.fm API key is invalid" + + assert ErrorMessages.friendly_message(:suspended_api_key) == + "Last.fm API key has been suspended" + + assert ErrorMessages.friendly_message(:rate_limit_exceeded) == + "rate limit exceeded, please try again later" + + assert ErrorMessages.friendly_message(:service_offline) == + "service is currently offline, please try again later" + + assert ErrorMessages.friendly_message(:transient_error) == + "a temporary error occurred, please try again" + + assert ErrorMessages.friendly_message(:operation_failed) == + "the operation failed, please try again" + + assert ErrorMessages.friendly_message(:invalid_resource) == + "the requested resource does not exist" + end + + test "maps Req.TransportError with common reasons" do + assert ErrorMessages.friendly_message(%Req.TransportError{reason: :timeout}) == + "the request timed out, please try again" + + assert ErrorMessages.friendly_message(%Req.TransportError{reason: :econnrefused}) == + "the service is not reachable" + + assert ErrorMessages.friendly_message(%Req.TransportError{reason: :nxdomain}) == + "the service could not be found" + + assert ErrorMessages.friendly_message(%Req.TransportError{reason: :closed}) == + "a connection error occurred, please try again" + end + + test "maps Ecto.Changeset" do + changeset = Ecto.Changeset.change(%MusicLibrary.Records.Record{}) + assert ErrorMessages.friendly_message(changeset) == "validation failed" + end + + test "returns fallback for unknown terms" do + assert ErrorMessages.friendly_message(:unknown_error) == + "something went wrong, please try again" + + assert ErrorMessages.friendly_message("some string") == + "something went wrong, please try again" + + assert ErrorMessages.friendly_message({:complex, :term}) == + "something went wrong, please try again" + end + end +end