# `MishkaSlug.Scripts`
[🔗](https://github.com/mishka-group/mishka_slug/blob/v0.0.1/lib/mishka_slug/scripts.ex#L5)

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`.

# `script`

```elixir
@type script() :: atom()
```

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

# `for_locales`

```elixir
@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`

```elixir
@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`

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

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

# `split`

```elixir
@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"]}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
