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.
Summary
Functions
The configured prefixes to hide, trimmed and with blanks dropped.
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.
The setting key, so the settings UI and tests do not re-spell it.
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.
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.
Functions
@spec prefixes() :: [String.t()]
The configured prefixes to hide, trimmed and with blanks dropped.
@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.
@spec setting_key() :: String.t()
The setting key, so the settings UI and tests do not re-spell it.
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/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.