Handle errors and translate messages

v0.0.1

MishkaSlug.slugify/2 returns atoms such as {:error, :empty}, never text. This guide handles each reason in code, turns a reason into a sentence for a form and translates those sentences with Gettext. You finish with a title field that tells the user what to fix.

Before you begin

Mishka Slug installed. See Get Started. The API reference lists every reason.

Match each reason

Handle each reason in a case or a with. Never fall back to an empty slug, and never fall back to the raw text.

elixir
case MishkaSlug.slugify(title, locales: ["fa", "en"]) do
  {:ok, slug} -> slug
  {:error, :empty} -> "post-" <> id
  {:error, reason} -> {:error, MishkaSlug.Messages.error(reason)}
end

:empty is the usual one, for a title with no letters or digits. :no_allowed_script means the text has letters, just not in a script the site allows. Tell the user to write with the site's languages, not that the text is blank. Ask for another title, or build a slug from an id as above.

Turn a reason into a message

MishkaSlug.Messages.error/1 returns a sentence for any reason. It reads as a clause that follows the field name, such as "Title has no letters or digits to make a slug from".

elixir
MishkaSlug.Messages.error(:empty)
#=> "has no letters or digits to make a slug from"

MishkaSlug.Messages.error(:no_allowed_script)
#=> "uses letters or digits that the site languages do not allow in a slug"

MishkaSlug.Messages.error(:mixed_scripts)
#=> "mixes writing systems inside one word"

MishkaSlug.Messages.error(:invalid_utf8)
#=> "is not valid UTF-8 text"

MishkaSlug.Messages.error({:unknown_locale, "xx"})
#=> "uses a site language the slug does not know: xx"

MishkaSlug.Messages.not_a_string()
#=> "is not a string value"

Do not show inspect(reason) to users. MishkaSlug.Change uses the same messages for its changeset errors.

Translate the messages

The library ships English texts in the mishka_slug Gettext domain. To translate them, use your own Gettext backend.

  1. Point Mishka Slug at your backend in config/config.exs:
    elixir
    config :mishka_slug, :gettext_backend, MyAppWeb.Gettext
  2. Copy priv/gettext/mishka_slug.pot from the mishka_slug dependency into your project's priv/gettext.
  3. Make a LC_MESSAGES/mishka_slug.po for each locale, such as priv/gettext/fa/LC_MESSAGES/mishka_slug.po, and translate the msgstr entries:
    text
    msgid "has no letters or digits to make a slug from"
    msgstr "هیچ حرف یا رقمی برای ساخت نشانی وجود ندارد"
    
    msgid "uses a site language the slug does not know: %{code}"
    msgstr "زبان سایت ناشناخته است: %{code}"

Keep the %{code} placeholder in the translation. The text is looked up in the locale of the calling process. A message with no translation stays in English.

elixir
MishkaSlug.Messages.error(:empty)
#=> "has no letters or digits to make a slug from"

Gettext.put_locale("fa")

MishkaSlug.Messages.error(:empty)
#=> "هیچ حرف یا رقمی برای ساخت نشانی وجود ندارد"

MishkaSlug.Messages.error(:mixed_scripts)
#=> "mixes writing systems inside one word"

Keep the domain name mishka_slug. It is fixed.

Know what raises

Some mistakes raise instead of returning an error, because they are bugs in the calling code and not bad user input.

  • slugify!/2 raises ArgumentError when no slug can be made: "cannot make a slug: :empty".
  • An unknown option, or a value outside the accepted ones, raises ArgumentError in slugify/2 and slugify!/2.
  • A text that is not a string raises FunctionClauseError.
elixir
MishkaSlug.slugify("x", foo: 1)
#=> ** (ArgumentError) invalid options: foo: Unauthorized keys are present in the sent data.

MishkaSlug.slugify("x", keep: ["!"])
#=> ** (ArgumentError) invalid options: keep: accepts only ".", "_" and "~", got ["!"]

Next steps