# `Localize.HTML.Hreflang`
[🔗](https://github.com/elixir-localize/localize_web/blob/main/lib/localize/html/hreflang.ex#L1)

Generates `rel="alternate"` `hreflang` annotations.

An `hreflang` set is how a site tells search engines and agents which
locale-specific variants of a page exist, and it is consumed rather than
negotiated: nothing sends `Accept-Language` on the reader's behalf, so the
declaration is the only signal.

Which is why it is validated here rather than emitted verbatim. A malformed
annotation is *silently* ignored by every consumer, so the failure never
shows up in production — the site simply looks monolingual. Every locale is
therefore validated and rendered in its canonical BCP 47 form, and every URL
must be fully qualified.

# `links`

```elixir
@spec links(
  map() | nil,
  keyword()
) :: {:ok, Phoenix.HTML.safe()} | {:error, Exception.t()}
```

Builds `link` tags for a map of locale to URL.

### Arguments

* `alternates` is a map of locale to fully-qualified URL. A locale may be
  any term `Localize.validate_locale/1` accepts, or the string `"x-default"`
  to designate the fallback variant.

* `options` is a keyword list of options.

### Options

* `:canonicalize` determines whether locales are rendered in canonical BCP 47
  form — `"es_MX"` becomes `"es-MX"`, `"en-us"` becomes `"en-US"`. The
  default is `true`. Set it to `false` to emit exactly what was supplied.

### Returns

* `{:ok, safe}` where `safe` is rendered HTML, or

* `{:error, exception}` if a locale is not recognised or a URL is not fully
  qualified.

### Examples

    iex> {:ok, {:safe, html}} = Localize.HTML.Hreflang.links(%{
    ...>   "en" => "https://example.com/",
    ...>   "de" => "https://example.com/de/"
    ...> })
    iex> IO.iodata_to_binary(html) =~ ~s|hreflang="de"|
    true

    # Underscores are canonicalised; BCP 47 requires a hyphen
    iex> {:ok, {:safe, html}} = Localize.HTML.Hreflang.links(%{"es_MX" => "https://example.com/mx/"})
    iex> IO.iodata_to_binary(html) =~ ~s|hreflang="es-MX"|
    true

    # A locale that does not exist is an error, not silent bad markup
    iex> Localize.HTML.Hreflang.links(%{"es-SP" => "https://example.com/es/"})
    {:error, %Localize.InvalidLocaleError{locale_id: "es-SP"}}

    # So is a relative URL, which every consumer ignores
    iex> {:error, error} = Localize.HTML.Hreflang.links(%{"de" => "/de/"})
    iex> error.__struct__
    Localize.RelativeUrlError

---

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