هذه الصفحة غير مترجمة بعد، وهي معروضة بالإنجليزية.

Languages other than English, slotsmith’s default, come as locale packs. For another language, import its locale pack and pass it as locale to a component, or to SlotsmithProvider for a whole app. A pack translates every string the components render and writes numbers and plurals the way its language does.

Available packs

slotsmith ships one pack per language below. Each is its own entry, slotsmith/locales/<code>, so an app bundles only the languages it imports. This table is read from the library’s src/locales/ folder when the site is built, so it lists exactly what ships.

LanguageImportExportDirection
Arabicslotsmith/locales/ararrtl
Arabic (Egypt)slotsmith/locales/ar-EGarEGrtl
Arabic (Iraq)slotsmith/locales/ar-IQarIQrtl
Arabic (Saudi Arabia)slotsmith/locales/ar-SAarSArtl
Germanslotsmith/locales/dedeltr
Spanishslotsmith/locales/esesltr
Persianslotsmith/locales/fafartl
Frenchslotsmith/locales/frfrltr
Hebrewslotsmith/locales/hehertl
Hindislotsmith/locales/hihiltr
Indonesianslotsmith/locales/ididltr
Italianslotsmith/locales/ititltr
Japaneseslotsmith/locales/jajaltr
Koreanslotsmith/locales/kokoltr
Brazilian Portugueseslotsmith/locales/pt-BRptBRltr
Russianslotsmith/locales/rurultr
Turkishslotsmith/locales/trtrltr
Chinese (China)slotsmith/locales/zh-CNzhCNltr

There is no en pack: English is every component’s default. The regional Arabic packs (ar-EG, ar-IQ, ar-SA) reuse the ar text and write numbers the way their region does. The packs other than Arabic were drafted from the English labels; corrections from native speakers are welcome on the repository’s issues.

Try it

Pick a language. The table, the autocomplete, the date picker and the uploader switch together, because they sit inside one SlotsmithProvider handed that pack. For the right-to-left packs the stage also flips to dir="rtl". Only the components’ own strings change: the rows and the column headers are your data, in your language.

Lena ParkEngineerPlatform
Omar HaddadDesignerProduct
Sofia ReyesEngineerPayments
Drag and drop, or browseimage/* · up to 2 MB · max 3 files

The strings you cannot see change too: focus a row’s checkbox or the uploader and a screen reader reads its name from the pack.

Passing a locale

Every component takes locale. Hand it a pack as an object, and that component uses it, with or without a provider:

import { DatePicker } from "slotsmith/date-picker";
import { ar } from "slotsmith/locales/ar";

// A pack passed as an object: no provider needed, and it covers this picker only.
export default function ObjectForm() {
  return (
    <div dir={ar.dir} lang={ar.code}>
      <DatePicker locale={ar} aria-label="تاريخ التسليم" />
    </div>
  );
}

For a whole app, give SlotsmithProvider the active tag and the packs it may resolve to. SlotsmithProvider and useSlotsmithLocale come from slotsmith/provider. The same provider also takes components, which replaces slots for every component below it.

import type { ReactNode } from "react";
import { SlotsmithProvider, useSlotsmithLocale } from "slotsmith/provider";
import { ar } from "slotsmith/locales/ar";
import { fr } from "slotsmith/locales/fr";

// The provider renders no element, so the direction goes on your own markup.
function Page({ children }: { children: ReactNode }) {
  const { code, dir } = useSlotsmithLocale();
  return (
    <main lang={code} dir={dir}>
      {children}
    </main>
  );
}

// A tag resolves against the packs in `locales`. "ar-EG" has no pack of its
// own here, so it uses ar and writes numbers and dates the Egyptian way.
// English is the default and needs no pack.
export function App({ language, children }: { language: "en-US" | "ar-EG" | "fr"; children: ReactNode }) {
  return (
    <SlotsmithProvider locale={language} locales={[ar, fr]}>
      <Page>{children}</Page>
    </SlotsmithProvider>
  );
}

A tag finds its pack in this order: the exact tag (ar-EG finds an ar-EG pack), then the bare language (ar-EG finds ar), then any pack of the same language. The tag is still what Intl formats with, so ar-EG resolved to the ar pack writes Egyptian digits. With no match the labels stay English, numbers and dates follow the tag, and the console warns once per tag during development. A tag whose language is en never warns.

The provider renders no element, so it cannot set the direction for you. useSlotsmithLocale() reads the active tag and direction back (en-US and ltr outside a provider) for the element that should carry lang and dir.

Your own pack

defineLocale from slotsmith/locale types a custom locale and fills in its direction from the tag. A locale has one section per component: table, autocomplete, datePicker, fileUploader, and fileValidation for the uploader’s rejection messages. Every section is optional, but one that is present must have every key, so the type checker refuses a half-finished translation.

AmsterdamNoord-Holland
RotterdamZuid-Holland

A locale written in plain JavaScript, or one that predates a newer key, is still safe: the missing string falls back to English and nothing throws. Register a custom locale with the provider like any pack, locales={[ar, nl]}, or hand it straight to a component.

Overriding labels

The labels prop replaces single strings on one component and keeps the rest from its locale:

import { DataTable, type DataTableColumnDef } from "slotsmith/data-table";
import { ar } from "slotsmith/locales/ar";

type Order = { id: string; customer: string };

const columns: DataTableColumnDef<Order>[] = [{ accessorKey: "customer", header: "العميل" }];

// Every string from the Arabic pack, except `empty`, replaced for this table only.
export default function Override() {
  return (
    <div dir="rtl" lang="ar">
      <DataTable locale={ar} labels={{ empty: "لا توجد طلبات بعد" }} data={[]} columns={columns} />
    </div>
  );
}

Each component’s labels and their English defaults are listed on its API page, for example the data table’s labels.

Precedence

Every string is resolved on its own. Each step overrides the one before it:

  1. The English default.
  2. The locale of the nearest SlotsmithProvider.
  3. The component’s own locale. It replaces the provider’s whole, so one component never mixes two languages.
  4. The component’s labels prop.

Plurals

createPlural(code) from slotsmith/locale picks the form a count needs through Intl.PluralRules and writes the formatted count into {count}. Give one string per category the language uses; only other is required. Russian has three forms for whole numbers and Arabic has six. Pick a count:

Выбраны 2 строки

createNumber(code) writes a number the way a tag does, and a malformed tag falls back to English instead of throwing. Every shipped pack is written with these two helpers, and a section can be a function of the active tag, as the Dutch table section above is, so one text serves every region of its language.

Calendars

The date picker is always Gregorian. Month and weekday names come from Intl in the active language, but the grid and every date written around it use the Gregorian calendar, even for tags such as ar-SA or fa whose default calendar is another. The value is written the same way in every language. Other calendars are on the roadmap.

Next.js

A pack holds functions, its plurals and number formatting, so it cannot be passed as a prop from a server component to a client component. Import packs in a "use client" file and let the server pass only the tag. In the App Router that file holds the provider:

"use client";

import { createContext, useContext, useState, type ReactNode } from "react";
import { SlotsmithProvider } from "slotsmith/provider";
import { ar } from "slotsmith/locales/ar";

export type Language = "en" | "ar";

const LanguageContext = createContext<{ language: Language; toggle: () => void }>({ language: "en", toggle: () => {} });

/** The client boundary: it holds the language and gives every slotsmith component below it the matching pack. */
export function Providers({ initialLanguage, children }: { initialLanguage: Language; children: ReactNode }) {
  const [language, setLanguage] = useState(initialLanguage);
  const toggle = () => {
    const next = language === "en" ? "ar" : "en";
    setLanguage(next);
    document.cookie = `lang=${next}; path=/; max-age=31536000`;
    document.documentElement.lang = next;
    document.documentElement.dir = next === "ar" ? "rtl" : "ltr";
  };
  return (
    <LanguageContext value={{ language, toggle }}>
      {/* English is the default, so it needs no pack. */}
      <SlotsmithProvider locale={language === "ar" ? "ar" : undefined} locales={[ar]}>
        {children}
      </SlotsmithProvider>
    </LanguageContext>
  );
}

/** A button that switches between English and Arabic. */
export function LanguageToggle() {
  const { language, toggle } = useContext(LanguageContext);
  return (
    <button type="button" onClick={toggle}>
      {language === "en" ? "العربية" : "English"}
    </button>
  );
}

The root layout reads the language on the server, sets lang and dir on <html>, and passes the tag down:

import type { ReactNode } from "react";
import { cookies } from "next/headers";
import "slotsmith/data-table.css";
import { Providers } from "./providers";

export const metadata = { title: "slotsmith with Next.js" };

export default async function RootLayout({ children }: { children: ReactNode }) {
  // The language lives in a cookie, so the server renders the right lang and dir from the first byte.
  const language = (await cookies()).get("lang")?.value === "ar" ? "ar" : "en";
  return (
    <html lang={language} dir={language === "ar" ? "rtl" : "ltr"}>
      <body>
        <Providers initialLanguage={language}>{children}</Providers>
      </body>
    </html>
  );
}

Getting started has the whole example.

next-intl and i18next

If your app already translates with next-intl or i18next, keep your messages where they are. Build a locale with defineLocale whose labels call your t(), rebuild it when the language changes, and hand it to the provider. There is no adapter package to install.

With next-intl, write counts as ICU plurals in your messages and next-intl picks the form:

"use client";
// Display only: the docs do not install next-intl. Written against next-intl's public API.
import { useMemo, type ReactNode } from "react";
import { useLocale, useTranslations } from "next-intl";
import { defineLocale } from "slotsmith/locale";
import { SlotsmithProvider } from "slotsmith/provider";

// The labels live in your messages, under "slotsmith.datePicker".
export function SlotsmithIntl({ children }: { children: ReactNode }) {
  const code = useLocale();
  const t = useTranslations("slotsmith.datePicker");

  // Write counts as ICU plurals in the messages, and next-intl picks the form:
  // "count": "{count, plural, one {# date selected} other {# dates selected}}"
  const locale = useMemo(
    () =>
      defineLocale({
        code,
        datePicker: {
          placeholder: t("placeholder"),
          clear: t("clear"),
          previous: t("previous"),
          next: t("next"),
          today: t("today"),
          month: t("month"),
          year: t("year"),
          dialog: t("dialog"),
          count: (count) => t("count", { count }),
          rangeStart: (from) => t("rangeStart", { from }),
          range: (from, to) => t("range", { from, to }),
        },
      }),
    [code, t],
  );

  return <SlotsmithProvider locale={locale}>{children}</SlotsmithProvider>;
}

With i18next, plural forms come from your resources when t() is given count: one key per Intl.PluralRules category the language uses, suffixed with the category. English needs _one and _other; Arabic also needs _zero, _two, _few and _many:

"use client";
// Display only: the docs do not install i18next. Written against react-i18next's public API.
import { useMemo, type ReactNode } from "react";
import { useTranslation } from "react-i18next";
import { defineLocale } from "slotsmith/locale";
import { SlotsmithProvider } from "slotsmith/provider";

// The labels live in your i18next resources, under a "slotsmith" namespace.
export function SlotsmithI18next({ children }: { children: ReactNode }) {
  const { t, i18n } = useTranslation("slotsmith");
  const code = i18n.resolvedLanguage ?? i18n.language;

  // Rebuilt when the language changes. i18next picks plural forms itself, so a
  // label that takes a count passes { count } to t().
  const locale = useMemo(
    () =>
      defineLocale({
        code,
        datePicker: {
          placeholder: t("datePicker.placeholder"),
          clear: t("datePicker.clear"),
          previous: t("datePicker.previous"),
          next: t("datePicker.next"),
          today: t("datePicker.today"),
          month: t("datePicker.month"),
          year: t("datePicker.year"),
          dialog: t("datePicker.dialog"),
          count: (count) => t("datePicker.count", { count }),
          rangeStart: (from) => t("datePicker.rangeStart", { from }),
          range: (from, to) => t("datePicker.range", { from, to }),
        },
      }),
    [code, t],
  );

  return <SlotsmithProvider locale={locale}>{children}</SlotsmithProvider>;
}

These two samples are shown, not run: the docs site does not install next-intl or i18next.