# `PhoenixKitEcommerce.NamePrefix`
[🔗](https://github.com/BeamLabEU/phoenix_kit_ecommerce/blob/0.5.11/lib/phoenix_kit_ecommerce/name_prefix.ex#L1)

Hides a redundant vocabulary prefix from storefront-displayed category
and product names — "3D Printed Costume Masks" -> "Costume Masks" —
driven by the `shop_name_prefixes` setting: a comma-separated list of
prefixes (a shop may have more than one), empty by default so no
existing install changes behaviour.

## Display-time only

No STORED value is ever rewritten — not a product/category's own
name, and not a cart or order line's snapshotted `product_title`/
`"name"`. Category and product names in this shop come from Shopify
collection/product titles and are re-synced from Shopify on every sync
run — a persisted rename would either be silently overwritten on the
next sync, or would have to be excluded from sync, which then hides
real upstream renames. A cart/order line's snapshot exists so a later
price or catalogue change can't retroactively alter what the shopper
was shown (the same principle as its snapshotted price); rewriting
that stored string would defeat the snapshot for no reason, since a
pure display-time strip achieves the same visible result.

`strip/1` is a pure string function with no persistence and no
Shopify reach, called from two places: `PhoenixKitEcommerce.
Translations.get_display/3` (for a live `%Product{}`/`%Category{}`
read on a storefront page) and directly, on a cart/order line's
snapshotted title string, from the storefront pages that render one
(cart, checkout, order confirmation, the customer's own order
history) — see `Translations.get_display/3`'s doc for why the
snapshot itself stays untouched while its on-page rendering doesn't.
Nothing in the Shopify diff/apply path
(`PhoenixKitEcommerce.Shopify.ProductDiff`,
`PhoenixKitEcommerce.Shopify.CollectionSync`), an admin edit form, or
any write path calls it — all of those read or persist the raw stored
value exactly as before.

A library cannot ship one shop's vocabulary, so this is a setting
rather than a hardcoded literal — read through this wrapper, never
directly, tolerating a malformed stored value by falling back to the
safe default (no prefixes, i.e. no stripping) rather than raising.

# `prefixes`

```elixir
@spec prefixes() :: [String.t()]
```

The configured prefixes to hide, trimmed and with blanks dropped.

# `separators`

```elixir
@spec separators() :: [String.t()]
```

The separators `strip/1` consumes after a matched prefix, so a test can
iterate the real list rather than a second, independently-maintained
copy that could silently drop an entry the source still recognizes.

# `setting_key`

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

The setting key, so the settings UI and tests do not re-spell it.

# `strip`

```elixir
@spec strip(any()) :: any()
```

Strips the LONGEST configured prefix that matches the START of `name`,
case-insensitively, consuming any following whitespace and then an
optional `-`/`–`/`—`/`|`/`:` separator plus its whitespace.

Longest, not first-configured: with `"3D, 3D Printed"` configured, a
first-match-wins rule would strip only `"3D"` from `"3D Printed Costume
Masks"` and leave the dangling fragment `"Printed Costume Masks"`. There
is no shop-visible upside to first-match, so the more specific (longer)
prefix always wins regardless of configuration order.

Leaves `name` untouched when:
  * no configured prefix matches
  * the prefix match isn't followed by whitespace, a separator, or the
    end of the string (so "3D Printedstuff" is never mangled into
    "stuff")
  * stripping would leave nothing (a category literally named "3D
    Printed" keeps its full name rather than rendering blank)

Any non-binary (`nil` included) passes through unchanged.

`strip/1` reads the setting (`prefixes/0`) on every call — a cached
read, but one that goes through core's settings-cache process, so a
page rendering a hundred names serializes a hundred round-trips
through it. A component rendering a LIST computes `prefixes/0` once
and calls `strip/2` with it instead.

# `strip`

```elixir
@spec strip(any(), [String.t()] | any()) :: any()
```

`strip/1` with the configured prefix list already in hand — the form
every per-row render should use (see `strip/1`). `prefixes` is what
`prefixes/0` returns; anything else (a malformed value) is treated as
"no prefixes", so the name renders exactly as stored.

---

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