# `PhoenixKitEcommerce.SlugResolver`
[🔗](https://github.com/BeamLabEU/phoenix_kit_ecommerce/blob/v0.1.8/lib/phoenix_kit_ecommerce/slug_resolver.ex#L1)

Resolves URL slugs to Products and Categories with language awareness.

This module provides language-aware slug resolution for the Shop module,
supporting per-language URL slugs for SEO optimization.

## Features

- Per-language SEO-friendly URL slugs
- Fallback to canonical slug when translation not found
- Base code matching (e.g., "en" matches "en-US")
- Efficient queries using JSONB operators

## URL Architecture

```
/shop/products/geometric-planter           # Default language
/es/shop/products/maceta-geometrica        # Spanish (SEO slug)
/ru/shop/products/geometricheskoe-kashpo   # Russian (SEO slug)
```

## Usage Examples

    # Find product by slug in specific language
    SlugResolver.find_product_by_slug("maceta-geometrica", "es-ES")
    # => {:ok, %Product{}}

    # Find product with base code (resolves to full dialect)
    SlugResolver.find_product_by_slug("geometric-planter", "en")
    # => {:ok, %Product{}} (matches en-US via base code)

    # Find category by slug
    SlugResolver.find_category_by_slug("jarrones-macetas", "es-ES")
    # => {:ok, %Category{}}

## Query Behavior

The resolver checks both translated slugs and canonical slugs:

1. First tries `translations->'language'->>'slug' = ?`
2. Falls back to canonical `slug = ?`

This ensures URLs work even for products without translations.

# `category_slug`

```elixir
@spec category_slug(PhoenixKitEcommerce.Category.t(), String.t()) :: String.t() | nil
```

Gets the best slug for a category in a specific language.

Returns translated slug if available, otherwise canonical slug.

## Examples

    iex> SlugResolver.category_slug(category, "es-ES")
    "jarrones-macetas"

# `category_slug_exists?`

```elixir
@spec category_slug_exists?(String.t(), String.t(), keyword()) :: boolean()
```

Checks if a category slug exists for a specific language.

## Examples

    iex> SlugResolver.category_slug_exists?("vases-planters", "en-US")
    true

# `find_categories_by_slugs`

```elixir
@spec find_categories_by_slugs([String.t()], String.t(), keyword()) :: [
  PhoenixKitEcommerce.Category.t()
]
```

Finds multiple categories by their slugs for a specific language.

## Examples

    iex> SlugResolver.find_categories_by_slugs(["cat-1", "cat-2"], "en-US")
    [%Category{}, %Category{}]

# `find_category_by_any_slug`

```elixir
@spec find_category_by_any_slug(
  String.t(),
  keyword()
) :: {:ok, PhoenixKitEcommerce.Category.t(), String.t()} | {:error, :not_found}
```

Finds a category by slug in any language.

## Examples

    iex> SlugResolver.find_category_by_any_slug("jarrones-macetas")
    {:ok, %Category{}, "es"}

# `find_category_by_slug`

```elixir
@spec find_category_by_slug(String.t(), String.t(), keyword()) ::
  {:ok, PhoenixKitEcommerce.Category.t()} | {:error, :not_found}
```

Finds a category by URL slug for a specific language.

## Parameters

  - `url_slug` - The URL slug to search for
  - `language` - Language code (supports both full and base codes)
  - `opts` - Optional keyword list:
    - `:preload` - Associations to preload (default: [])
    - `:status` - Filter by status (e.g., "active")

## Examples

    iex> SlugResolver.find_category_by_slug("jarrones-macetas", "es-ES")
    {:ok, %Category{name: "Jarrones y Macetas", ...}}

    iex> SlugResolver.find_category_by_slug("vases-planters", "en")
    {:ok, %Category{}}

    iex> SlugResolver.find_category_by_slug("nonexistent", "en-US")
    {:error, :not_found}

# `find_category_by_translated_slug`

```elixir
@spec find_category_by_translated_slug(String.t(), String.t(), keyword()) ::
  {:ok, PhoenixKitEcommerce.Category.t()} | {:error, :not_found}
```

Finds a category by slug, requiring exact language match.

Does not fall back to canonical slug.

## Examples

    iex> SlugResolver.find_category_by_translated_slug("jarrones-macetas", "es-ES")
    {:ok, %Category{}}

# `find_product_by_any_slug`

```elixir
@spec find_product_by_any_slug(
  String.t(),
  keyword()
) :: {:ok, PhoenixKitEcommerce.Product.t(), String.t()} | {:error, :not_found}
```

Finds a product by slug in any language.

Searches across all translated slugs to find the product.
Useful for cross-language redirect when user visits with a slug
from a different language.

## Parameters

  - `url_slug` - The URL slug to search for
  - `opts` - Optional keyword list:
    - `:preload` - Associations to preload (default: [])
    - `:status` - Filter by status (e.g., "active")

## Examples

    iex> SlugResolver.find_product_by_any_slug("maceta-geometrica")
    {:ok, %Product{}, "es"}  # Returns product with language that matched

    iex> SlugResolver.find_product_by_any_slug("geometric-planter")
    {:ok, %Product{}, "en"}

    iex> SlugResolver.find_product_by_any_slug("nonexistent")
    {:error, :not_found}

# `find_product_by_slug`

```elixir
@spec find_product_by_slug(String.t(), String.t(), keyword()) ::
  {:ok, PhoenixKitEcommerce.Product.t()} | {:error, :not_found}
```

Finds a product by URL slug for a specific language.

## Parameters

  - `url_slug` - The URL slug to search for
  - `language` - Language code (supports both "es-ES" and base codes like "en")
  - `opts` - Optional keyword list:
    - `:preload` - Associations to preload (default: [])
    - `:status` - Filter by status (e.g., "active")

## Examples

    iex> SlugResolver.find_product_by_slug("maceta-geometrica", "es-ES")
    {:ok, %Product{title: "Maceta Geométrica", ...}}

    iex> SlugResolver.find_product_by_slug("geometric-planter", "en")
    {:ok, %Product{}}  # Matches en-US via base code resolution

    iex> SlugResolver.find_product_by_slug("nonexistent", "en-US")
    {:error, :not_found}

## Query Details

The query checks both:
1. Translated slug: `translations->'lang'->>'slug'`
2. Canonical slug: `slug` column

This ensures backward compatibility with products that have no translations.

# `find_product_by_translated_slug`

```elixir
@spec find_product_by_translated_slug(String.t(), String.t(), keyword()) ::
  {:ok, PhoenixKitEcommerce.Product.t()} | {:error, :not_found}
```

Finds a product by slug, requiring exact language match.

Unlike `find_product_by_slug/3`, this does not fall back to canonical slug.
Useful when you need to ensure the translation exists.

## Examples

    iex> SlugResolver.find_product_by_translated_slug("maceta-geometrica", "es-ES")
    {:ok, %Product{}}

    iex> SlugResolver.find_product_by_translated_slug("maceta-geometrica", "en-US")
    {:error, :not_found}  # No fallback to canonical

# `find_products_by_slugs`

```elixir
@spec find_products_by_slugs([String.t()], String.t(), keyword()) :: [
  PhoenixKitEcommerce.Product.t()
]
```

Finds multiple products by their slugs for a specific language.

Useful for preloading products in listing pages.

## Examples

    iex> SlugResolver.find_products_by_slugs(["planter-1", "planter-2"], "en-US")
    [%Product{}, %Product{}]

# `normalize_language_public`

Normalizes a language code to dialect format.

Converts base codes to full dialect (e.g., "en" -> "en-US").
Used by import system to ensure consistent language keys in JSONB fields.

# `product_slug`

```elixir
@spec product_slug(PhoenixKitEcommerce.Product.t(), String.t()) :: String.t() | nil
```

Gets the best slug for a product in a specific language.

Returns translated slug if available, otherwise canonical slug.

## Examples

    iex> SlugResolver.product_slug(product, "es-ES")
    "maceta-geometrica"

    iex> SlugResolver.product_slug(product, "fr-FR")
    "geometric-planter"  # Falls back to canonical

# `product_slug_exists?`

```elixir
@spec product_slug_exists?(String.t(), String.t(), keyword()) :: boolean()
```

Checks if a product slug exists for a specific language.

Useful for slug validation during product creation/editing.

## Parameters

  - `slug` - The slug to check
  - `language` - Language code
  - `exclude_uuid` - Product UUID to exclude from check (for edits)

## Examples

    iex> SlugResolver.product_slug_exists?("geometric-planter", "en-US")
    true

    iex> SlugResolver.product_slug_exists?("geometric-planter", "en-US", exclude_uuid: "some-uuid")
    false  # Excludes product with given UUID from check

---

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