Skip to content

i18n-rtl

AdaptTable is i18n-agnostic at its core — it never imports an i18n library. Strings come in through a labels prop, so you can use your own stack (i18next, react-intl, …) or the ready presets in @adapttable/i18n.

labels translates the chrome, dir flips the layout, and locale makes per-column i18n data paths resolve — three independent props, one line each:

import { DataTable } from "@adapttable/mantine";
import { getDirection, getLabels } from "@adapttable/i18n";
type Person = {
id: string;
name: string;
nameAr: string;
department: { name: string };
hiredAt: string;
};
const locale = "ar";
export function People({ data }: { data: Person[] }) {
return (
<DataTable
data={data}
columns={[
{
key: "name",
header: "الاسم",
i18n: { ar: "nameAr" },
sortable: true,
},
{ key: "department.name", header: "القسم" },
{ key: "hiredAt", header: "تاريخ التعيين", filter: "dateRange" },
]}
rowKey={(r) => r.id}
locale={locale}
labels={getLabels(locale)} // Arabic chrome strings
dir={getDirection(locale)} // "ar" → "rtl"
/>
);
}

Ten label sets ship in @adapttable/i18n. getLabels matches the primary subtag ("ar-EG" → Arabic, "de-AT" → German) and falls back to English for unknown locales; hasLocale(locale) tells you whether a preset exists.

PresetLanguageDirection
enEnglishltr
arArabicrtl
deGermanltr
esSpanishltr
frFrenchltr
heHebrewrtl
itItalianltr
jaJapaneseltr
ptPortugueseltr
zhChineseltr

ColumnDef.i18n maps locales to alternate data paths for the column’s value. The table locale picks the path — exact tag first, then its primary subtag, then key:

// Flat fields:
{ key: "nameEn", i18n: { ar: "nameAr" } }
// Nested objects:
{ key: "name.en", i18n: { ar: "name.ar" } }

The cell, the client-side sort, and the column’s declarative filter all follow the resolved path, so searching and filtering match what the user sees. Header text stays whatever you pass in header — translate it through your label pipeline, not i18n.

RTL is first-class. Pass dir="rtl" (or dir={getDirection(locale)}) and the adapter applies it through its direction provider and logical CSS:

  • Layout, alignment, and the filter drawer side flip automatically.
  • Column pinning is logical: pins use insetInlineStart/insetInlineEnd, so a “left” pin sticks to the correct edge in RTL too.
  • Column resizing is direction-aware: the handle sits on the column’s inline-end edge, and dragging outward (or pressing the leading arrow key) widens the column in both LTR and RTL.

Helpers from @adapttable/i18n:

  • getDirection(locale)"ltr" | "rtl"
  • isRtlLocale(locale) — covers ar, he, fa, ur, ps, and more
  • RTL_LANGUAGES — the raw list of RTL primary subtags
  • primarySubtag(locale)"ar-EG""ar"

labels accepts a partial TableLabels; missing keys fall back to the English defaults. Count-and-range strings are functions, so any word order works:

<DataTable
data={data}
columns={columns}
rowKey={(r) => r.id}
labels={{
searchPlaceholder: "Search people…",
noResults: "Nothing matches your filters",
showing: ({ from, to, total }) => `${from}${to} of ${total}`,
pageOf: ({ page, total }) => `Page ${page} of ${total}`,
selectedCount: (count) => `${count} selected`,
}}
/>

Need a language without a preset? Spread one and override:

import { en } from "@adapttable/i18n";
const sw = { ...en, search: "Tafuta", noData: "Hakuna data" };