PhoenixKitEcommerce.LocalizedSlug (PhoenixKitEcommerce v0.2.2)

Copy Markdown View Source

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

Summary

Functions

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

Fill missing per-language slugs on changeset from source_field.

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

Functions

fallback(text)

@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(changeset, source_field)

@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(changeset, source_field, opts \\ [])

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