MishkaSlug.Scripts (Mishka Slug v0.0.1)

Copy Markdown View Source

The writing systems of letters and of a site's languages.

MishkaSlug.slugify/2 uses it for two rules:

  • Allow-list. A site's languages (locales: ["fa", "en"]) become the scripts a slug may use ([:arabic, :latin]). Latin is always allowed, because English words and brand names appear in every language. A letter of any other script is dropped.
  • One script per word. A word that changes script, like pаypal with a Cyrillic а, is cut at the change or refused. Japanese (Han with Hiragana and Katakana), Korean (Han with Hangul) and Chinese with Bopomofo are one family each, so they are never cut. All the scripts of a part must fit one family: 夏の幻ㄒㄧㄤ is cut before the Bopomofo, because Han carries Hiragana in from the start and no family holds Hiragana with Bopomofo.

A combining mark belongs to the letter before it and is dropped with it, so a dropped letter never leaves its mark behind. A mark of one script never completes a letter of another: a Khudawadi vowel sign after a Latin a is dropped, as latin: :ascii drops it too. Characters that belong to no single script (ASCII digits, the Japanese long-vowel mark ー) are neutral: they stay with the letters around them. A digit of a script (Persian ۱۴۰۳, Devanagari १२) follows the allow-list like a letter, so locales: ["en"] drops it, but it never makes a word mixed: iphone۱۵ stays one word. digits: :ascii writes it as 0-9 first, so it is kept. A letter of a script this module does not know by name (for example Tibetan) counts as :other.

Summary

Types

A script a slug may use, as MishkaSlug.Scripts.for_locales/1 returns it.

Functions

Returns the scripts the languages of a site are written in, always with :latin.

Returns the scripts of an ISO 15924 script code, as CLDR and BCP 47 tags write them.

Returns the language part of a locale code, lowercased: "pt-BR" gives "pt".

Cuts one word into the parts a slug keeps.

Types

script()

@type script() :: atom()

A script a slug may use, as MishkaSlug.Scripts.for_locales/1 returns it.

Functions

for_locales(locales, resolver \\ nil)

@spec for_locales([String.t()], {module(), atom(), list()} | nil) ::
  {:ok, :any | [script()]} | {:error, {:unknown_locale, String.t()}}

Returns the scripts the languages of a site are written in, always with :latin.

A locale is a language code as Mishka CMS stores it ("fa", "pt-br", "zh-hans"). A script subtag picks the script ("sr-Latn" is Latin, "pa-Arab" Arabic); otherwise the language part is looked up in a built-in table. An empty list means no restriction and returns :any.

resolver lets the host decide instead of the built-in table: a {module, function, args} called as apply(module, function, [locale | args]) for each locale, returning {:ok, [script()]} or :error. When it is nil, the :script_resolver of the :mishka_slug application config is used, and without one the built-in table. :error gives {:error, {:unknown_locale, locale}}.

iex> MishkaSlug.Scripts.for_locales(["fa", "en"])
{:ok, [:arabic, :latin]}

iex> MishkaSlug.Scripts.for_locales(["sr-Latn"])
{:ok, [:latin]}

iex> MishkaSlug.Scripts.for_locales(["xx"])
{:error, {:unknown_locale, "xx"}}

from_iso15924(code)

@spec from_iso15924(String.t() | atom()) :: {:ok, [script()]} | :error

Returns the scripts of an ISO 15924 script code, as CLDR and BCP 47 tags write them.

The code is a string or an atom in any case ("Arab", :Cyrl). Compound codes give every script of their family: "Jpan" is Han with both kana, "Kore" Hangul with Han. A script this library does not know by name gives :error. A :script_resolver can return its result as is.

iex> MishkaSlug.Scripts.from_iso15924("Arab")
{:ok, [:arabic]}

iex> MishkaSlug.Scripts.from_iso15924(:Jpan)
{:ok, [:han, :hiragana, :katakana]}

iex> MishkaSlug.Scripts.from_iso15924("Tibt")
:error

language(locale)

@spec language(String.t()) :: String.t()

Returns the language part of a locale code, lowercased: "pt-BR" gives "pt".

split(word, allowed, mode)

@spec split(String.t(), :any | [script()], :split | :reject) ::
  {:ok, [String.t()]} | {:error, :mixed_scripts}

Cuts one word into the parts a slug keeps.

allowed is :any or a list from for_locales/1. With :split the word is cut where its script changes and the parts in a script that is not allowed are dropped; with :reject a word that changes script returns {:error, :mixed_scripts}.

iex> MishkaSlug.Scripts.split("pаypal", :any, :split)
{:ok, ["p", "а", "ypal"]}

iex> MishkaSlug.Scripts.split("pаypal", [:latin], :split)
{:ok, ["p", "ypal"]}