# `PhoenixKitEcommerce.LocalizedSlug`
[🔗](https://github.com/BeamLabEU/phoenix_kit_ecommerce/blob/0.5.10/lib/phoenix_kit_ecommerce/localized_slug.ex#L1)

Shared per-language slug generation for Product and Category.

Both schemas used to carry a private copy of this reduce. They drifted
twice — Cyrillic, then German — and PR #21's empty-result guard would
have been the third copy of the same function. There is one rule:

  * an existing non-blank slug is left alone (write-once; renaming
    must not move a live URL)
  * a blank or missing slug is derived from that language's title/name
  * `Slug.slugify/2` returning `""` (CJK, Arabic, emoji — scripts with
    no romanizer) must not write `""`. An empty value used to squat on
    the old primary-slug index and lock the shop out; after V171 it
    is simply not a URL. Either way the product is unreachable.
  * the fallback is a stable `item-<sha256>` of the source text, so two
    different unromanizable titles never collide and the same title
    always produces the same slug
  * leftover `""` values already in the map are scrubbed, so a legacy
    row self-heals on its next save

# `fallback`

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

Stable ASCII slug for text `Slug.slugify/2` cannot romanize.

The hash is of the source text alone, so the same title under two
languages shares one value (allowed: uniqueness is per language) and
two different titles almost never collide.

# `maybe_generate`

```elixir
@spec maybe_generate(Ecto.Changeset.t(), atom()) :: Ecto.Changeset.t()
```

Fill missing per-language slugs on `changeset` from `source_field`.

`source_field` is `:title` on products and `:name` on categories.

# `put_plain_fallback`

```elixir
@spec put_plain_fallback(Ecto.Changeset.t(), atom(), keyword()) :: Ecto.Changeset.t()
```

Fill a blank *string* slug (shipping methods) when `put_slug/3` left it
empty because the source did not romanize.

The column is `NOT NULL`. Leaving it blank is a failed insert, not a
missing URL — the same empty-slugify case as products, via a plain
unique column instead of the jsonb projection.

---

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