---
url: /i18n.md
description: >-
  Translate and customize Okapi error titles and messages by error kind or HTTP
  status, using static translations or runtime resolvers
---

# i18n

Okapi provides English titles and messages by default. Pass `i18n` options to a mapper factory or
core mapping function to replace them with static translations or resolve them at runtime.

```ts
import { createAxiosErrorMapper } from '@alexovn/okapi/axios'

const mapAxiosError = createAxiosErrorMapper({
  i18n: {
    titles: { network: 'Connection problem' },
    messages: { network: 'Check your internet connection and try again.' },
  },
})
```

The same `i18n` object is available through the shared `OkapiErrorOptions`,
`MapOkapiErrorOptions`, and `OkapiErrorAdapterOptions` types. See [Types](/types#configuration).

## Types

### `OkapiErrorI18nOptions`

Options for customizing mapped error titles and messages.

```ts
interface OkapiErrorI18nOptions<
  TCustomKind extends string = never,
  TValidationErrors = ApiValidationErrors,
> {
  titles?: OkapiErrorTitles<TCustomKind>
  statusTitles?: OkapiErrorStatusTitles
  messages?: OkapiErrorMessages<TCustomKind>
  statusMessages?: OkapiErrorStatusMessages
  resolveTitle?: OkapiErrorTitleResolver<TCustomKind, TValidationErrors>
  resolveMessage?: OkapiErrorMessageResolver<TCustomKind, TValidationErrors>
}
```

## Static Translations

Use static translations when the translated strings are known while configuring the mapper.
Kind-based values provide general copy for an error category; status-based values override that
copy for a specific HTTP response.

### `titles`

Custom titles keyed by built-in or application-specific error kind.

* Type:

```ts
type OkapiErrorTitles<TCustomKind extends string = never> = Partial<
  Record<OkapiErrorKind<TCustomKind>, string>
>
```

### `statusTitles`

Custom titles keyed by HTTP status code.

* Type:

```ts
type OkapiErrorStatusTitles = Partial<Record<number, string>>
```

### `messages`

Custom messages keyed by built-in or application-specific error kind.

* Type:

```ts
type OkapiErrorMessages<TCustomKind extends string = never> = Partial<
  Record<OkapiErrorKind<TCustomKind>, string>
>
```

### `statusMessages`

Custom messages keyed by HTTP status code.

* Type:

```ts
type OkapiErrorStatusMessages = Partial<Record<number, string>>
```

### Example

```ts
import type { OkapiErrorAdapterOptions } from '@alexovn/okapi'

const options: OkapiErrorAdapterOptions = {
  i18n: {
    titles: {
      'not-found': 'Not found',
      network: 'Connection problem',
    },
    statusTitles: {
      503: 'Temporarily unavailable',
    },
    messages: {
      'not-found': 'The requested resource was not found.',
      network: 'Check your internet connection and try again.',
    },
    statusMessages: {
      503: 'The service is temporarily unavailable. Please try again later.',
    },
  },
}
```

## Dynamic Translations

Use resolvers when translation depends on runtime data or an i18n library. Each resolver receives
the complete normalized `OkapiError`, including its `kind`, `source`, `statusCode`, `raw` response,
and original `cause`.

### `resolveTitle`

A function that derives a custom title from a normalized error.

* Type:

```ts
type OkapiErrorTitleResolver<
  TCustomKind extends string = never,
  TValidationErrors = ApiValidationErrors,
> = (error: OkapiError<TCustomKind, TValidationErrors>) => string | undefined
```

### `resolveMessage`

A function that derives a custom message from a normalized error.

* Type:

```ts
type OkapiErrorMessageResolver<
  TCustomKind extends string = never,
  TValidationErrors = ApiValidationErrors,
> = (error: OkapiError<TCustomKind, TValidationErrors>) => string | undefined
```

### Example

```ts
import type { OkapiErrorAdapterOptions } from '@alexovn/okapi'

const options: OkapiErrorAdapterOptions = {
  i18n: {
    resolveTitle: ({ statusCode }) => {
      if (statusCode === 503) {
        return 'Temporarily unavailable'
      }

      return undefined
    },
    resolveMessage: ({ kind, statusCode }) => {
      if (statusCode !== undefined) {
        return translate(`errors.http.${statusCode}`)
      }

      return translate(`errors.api.${kind}`)
    },
  },
}
```

## Translation Resolution Order

Titles are resolved in this order:

1. `i18n.resolveTitle`
2. `i18n.statusTitles[statusCode]`
3. `i18n.titles[kind]`
4. Built-in title

Messages are resolved in this order:

1. `i18n.resolveMessage`
2. `i18n.statusMessages[statusCode]`
3. `i18n.messages[kind]`
4. Built-in message

> A resolver can return `undefined` to continue to the next fallback.
