# Internationalization (https://v2.s3.dimah.dev/docs/i18n)

English needs no setup. To localize the React client, define a partial
translation map and pass it to the provider.

```ts title="lib/translations-fa.ts"
import type { Translations } from "@dimah-s3/react";

export const fa = {
  "Upload failed(toast)": "آپلود ناموفق بود",
  "Cancel(dialog button)": "لغو",
} satisfies Partial<Translations>; // [!code highlight]
```

```ts title="lib/s3-client.ts"
"use client";

import { createS3Client } from "@dimah-s3/react";

export const s3Client = createS3Client();
export const S3Provider = s3Client.Provider;
```

```tsx title="app/layout.tsx"
import { S3Provider } from "@/lib/s3-client";
import { fa } from "@/lib/translations-fa"; // [!code ++]

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    // [!code ++]
    <html lang="fa" dir="rtl">
      <body>
        {/* [!code ++] */}
        <S3Provider translations={fa}>{children}</S3Provider>
      </body>
    </html>
  );
}
```

Vite and other client-only apps can pass `translations` to
`<s3Client.Provider>` instead.

Missing keys stay English. `satisfies Partial<Translations>` flags a key that
does not exist, so a renamed key shows up as a type error when you upgrade.

## Numbers and sizes [#numbers-and-sizes]

Pass `locale` to the provider for native digits in sizes, counts, and
progress. Unit labels are translation keys, so the same map can rename them:

```ts title="lib/translations-fa.ts"
export const fa = {
  "KB(file size unit)": "کیلوبایت", // [!code ++]
  "MB(file size unit)": "مگابایت", // [!code ++]
  "GB(file size unit)": "گیگابایت", // [!code ++]
} satisfies Partial<Translations>;
```

```tsx title="app/layout.tsx"
<S3Provider translations={fa} locale="fa">
  {children}
</S3Provider>
```

Sizes then read `۱۲٫۴ مگابایت`. Without `locale`, they read `12.4 MB`. Your
own components can use the same formatters with `useFormatFileSize()` and
`useFormatNumber()` from `@dimah-s3/react`.

The accepted types under a dropzone use the same map. Groups such as
`image/*` are keys (`"Images(file type group)"`); extensions and MIME types
read as they are, such as `PDF`. `useFormatAcceptLabels()` gives your own
components the same labels.

## API errors [#api-errors]

The server always responds in English. Every error carries a catalog `code`
and its `params`, such as `{ size, maxFileSize }` for `FILE_TOO_LARGE`, so
the client can build its own message. `@dimah-s3/ui` localizes errors with the
same `translations` map. Headless hooks expose the original `APIError`; pass it
to [`useFormatDimahError`](https://v2.s3.dimah.dev/docs/react/helpers) before rendering a localized
message:

```tsx title="components/upload-error.tsx"
"use client";

import { useFormatDimahError, type UseUploadReturn } from "@dimah-s3/react";

export function UploadError({ upload }: { upload: UseUploadReturn }) {
  const formatError = useFormatDimahError();
  if (!upload.error) return null;
  return <p role="alert">{formatError(upload.error)}</p>;
}
```

Sizes and counts in the message use the provider `locale`.
