From 7e950787e49ddd079b3603a62a8c4d1e035dab44 Mon Sep 17 00:00:00 2001
From: John Betancur <1385932+jbetancur@users.noreply.github.com>
Date: Wed, 20 May 2026 19:58:47 -0400
Subject: [PATCH 1/3] feat: add localization support for pagination, filtering,
and expandable rows (#1322)
- Introduced a new Localization type to manage user-visible strings for pagination, filtering, and expandable rows.
- Updated TableCol, TablePaginationFooter, TableRow, HeadContext, and RowContext components to accept localization props.
- Added localization support in various locales including Arabic, German, English, Spanish, French, Hebrew, Japanese, Korean, Portuguese, Ukrainian, and Chinese.
- Created a new locales index file to export all localization files.
- Updated types to include localization options in relevant components and hooks.
- Enhanced the build configuration to include a locales bundle.
- Adjusted test coverage settings to exclude locale files.
---
apps/docs/src/layouts/DocsLayout.astro | 1 +
apps/docs/src/pages/docs/expandable.astro | 37 ++
apps/docs/src/pages/docs/filtering.astro | 40 ++
apps/docs/src/pages/docs/localization.astro | 176 ++++++++
apps/docs/src/pages/docs/pagination.astro | 46 +-
apps/docs/src/pages/docs/rtl.astro | 30 ++
apps/docs/tsconfig.json | 3 +-
package-lock.json | 475 ++++++++++----------
package.json | 13 +-
src/__tests__/ColumnFilter.test.tsx | 123 +++++
src/__tests__/Pagination.test.tsx | 80 ++++
src/components/ColumnFilter.tsx | 77 ++--
src/components/DataTable.tsx | 5 +
src/components/DataTableHead.tsx | 2 +
src/components/ExpanderButton.tsx | 12 +-
src/components/Pagination.tsx | 14 +-
src/components/TableCellExpander.tsx | 7 +-
src/components/TableCol.tsx | 8 +-
src/components/TablePaginationFooter.tsx | 5 +-
src/components/TableRow.tsx | 2 +
src/context/HeadContext.tsx | 11 +-
src/context/RowContext.tsx | 2 +
src/hooks/useHeadContextValue.ts | 1 +
src/hooks/useRowContextValue.ts | 1 +
src/index.ts | 1 +
src/locales/ar-EG.ts | 52 +++
src/locales/ar-LV.ts | 52 +++
src/locales/ar.ts | 52 +++
src/locales/de.ts | 52 +++
src/locales/en.ts | 52 +++
src/locales/es.ts | 52 +++
src/locales/fr.ts | 52 +++
src/locales/he.ts | 52 +++
src/locales/index.ts | 14 +
src/locales/ja.ts | 52 +++
src/locales/ko.ts | 52 +++
src/locales/pt-BR.ts | 52 +++
src/locales/uk.ts | 52 +++
src/locales/zh-CN.ts | 52 +++
src/locales/zh-TW.ts | 52 +++
src/types.ts | 78 ++++
tsup.config.ts | 10 +
vitest.config.ts | 2 +-
43 files changed, 1715 insertions(+), 289 deletions(-)
create mode 100644 apps/docs/src/pages/docs/localization.astro
create mode 100644 src/locales/ar-EG.ts
create mode 100644 src/locales/ar-LV.ts
create mode 100644 src/locales/ar.ts
create mode 100644 src/locales/de.ts
create mode 100644 src/locales/en.ts
create mode 100644 src/locales/es.ts
create mode 100644 src/locales/fr.ts
create mode 100644 src/locales/he.ts
create mode 100644 src/locales/index.ts
create mode 100644 src/locales/ja.ts
create mode 100644 src/locales/ko.ts
create mode 100644 src/locales/pt-BR.ts
create mode 100644 src/locales/uk.ts
create mode 100644 src/locales/zh-CN.ts
create mode 100644 src/locales/zh-TW.ts
diff --git a/apps/docs/src/layouts/DocsLayout.astro b/apps/docs/src/layouts/DocsLayout.astro
index b06e1655..57e88215 100644
--- a/apps/docs/src/layouts/DocsLayout.astro
+++ b/apps/docs/src/layouts/DocsLayout.astro
@@ -59,6 +59,7 @@ const nav = [
links: [
{ label: 'Animations', href: '/docs/animations' },
{ label: 'Accessibility', href: '/docs/accessibility' },
+ { label: 'Localization', href: '/docs/localization' },
{ label: 'RTL Support', href: '/docs/rtl' },
{ label: 'Mobile', href: '/docs/mobile' },
],
diff --git a/apps/docs/src/pages/docs/expandable.astro b/apps/docs/src/pages/docs/expandable.astro
index 79015e14..32e0ea35 100644
--- a/apps/docs/src/pages/docs/expandable.astro
+++ b/apps/docs/src/pages/docs/expandable.astro
@@ -250,6 +250,43 @@ export default function App() {
expandableInheritConditionalStylesbooleanApply the parent row's conditionalRowStyles to the expander row
onRowExpandToggled(expanded: boolean, row: T) => voidFired when a row is expanded or collapsed
animateRowsbooleanAnimate expand/collapse (respects prefers-reduced-motion)
+ localizationLocalizationLocalizable aria-labels for the expand/collapse button — see Localization below.
+
+ Localization
+
+ Override the aria-labels on the expand/collapse button via the localization prop.
+ Use a pre-built locale or supply your own — every key is optional.
+
+
+ `} />
+
+ `} />
diff --git a/apps/docs/src/pages/docs/filtering.astro b/apps/docs/src/pages/docs/filtering.astro
index 92601677..e2f1b664 100644
--- a/apps/docs/src/pages/docs/filtering.astro
+++ b/apps/docs/src/pages/docs/filtering.astro
@@ -205,6 +205,46 @@ isFilterActive({ condition1: { operator: 'contains' } }); // false —
isFilterActive({ condition1: { operator: 'contains', value: 'a' } }); // true
isFilterActive({ condition1: { operator: 'blank' } }); // true — no value needed`} />
+ Localization
+
+ Use the localization prop to swap every string in the table UI.
+ Import a pre-built locale or build your own — all keys are optional and fall
+ back to English defaults.
+
+
+ `} />
+
+ `} />
+
+ `} />
+
Headless usage
Use useColumnFilter directly when building a custom table with the headless hooks.
diff --git a/apps/docs/src/pages/docs/localization.astro b/apps/docs/src/pages/docs/localization.astro
new file mode 100644
index 00000000..cfdcb1df
--- /dev/null
+++ b/apps/docs/src/pages/docs/localization.astro
@@ -0,0 +1,176 @@
+---
+import DocsLayout from '../../layouts/DocsLayout.astro';
+import CodeBlock from '../../components/CodeBlock.astro';
+---
+
+
+ Localization
+
+
+ Pass a localization object to DataTable to override every
+ string and aria-label rendered by the table — filter panel, pagination navigation, and
+ expand/collapse buttons. All keys are optional and fall back to English defaults.
+
+
+ Quick start
+
+ Import one of the pre-built locales from the /locales subpath and pass it as
+ localization. That's all you need for a fully translated table.
+
+
+ `} />
+
+ Built-in locales
+
+
+
+
+ Export
+ Language
+
+
+
+ enEnglish (default)
+ frFrench
+ esSpanish
+ deGerman
+ ptBRBrazilian Portuguese
+ arArabic (Modern Standard)
+ heHebrew
+ arEGArabic (Egyptian)
+ arLVArabic (Levantine)
+ zhCNChinese (Simplified)
+ zhTWChinese (Traditional)
+ jaJapanese
+ koKorean
+ ukUkrainian
+
+
+
+
+ Each locale is a standalone module. Only the locale you import enters your bundle — the
+ rest are tree-shaken away.
+
+
+ Partial override
+
+ Spread an existing locale and replace only the keys you need.
+
+
+ `} />
+
+ Custom locale from scratch
+
+ Build a locale object from the Localization type. Every key is optional —
+ any omitted key falls back to the English default.
+
+
+ `} />
+
+ The Localization type
+
+ All three sub-objects are optional. Each key within them is also optional.
+
+
+
+
+ Scope
+
+ localization covers strings rendered by the table's built-in UI. Visible text
+ in the pagination bar (rowsPerPageText, rangeSeparatorText,
+ selectAllRowsItemText) is still controlled by
+ paginationComponentOptions, since those affect layout as well as text.
+
+
diff --git a/apps/docs/src/pages/docs/pagination.astro b/apps/docs/src/pages/docs/pagination.astro
index d4713bc8..b806d08a 100644
--- a/apps/docs/src/pages/docs/pagination.astro
+++ b/apps/docs/src/pages/docs/pagination.astro
@@ -572,7 +572,7 @@ function MyPagination({ rowsPerPage, rowCount, currentPage, onChangePage }: Pagi
paginationComponentOptions
PaginationOptions
-
- Options forwarded to the default pagination component (label customisation, etc.).
+ Options forwarded to the default pagination component. Covers visible text (rowsPerPageText, rangeSeparatorText, selectAllRowsItemText). Navigation aria-labels are set via the localization prop.
paginationIconFirstPage
@@ -612,4 +612,48 @@ function MyPagination({ rowsPerPage, rowCount, currentPage, onChangePage }: Pagi
+
+ Localization
+
+ All navigation aria-labels on the built-in pagination bar are controlled via the
+ localization prop. Use a pre-built locale or supply your own — every key is
+ optional and falls back to the English default. Visible text such as
+ rowsPerPageText and rangeSeparatorText is still set via
+ paginationComponentOptions.
+
+
+ `} />
+
+ `} />
+
+ `} />
diff --git a/apps/docs/src/pages/docs/rtl.astro b/apps/docs/src/pages/docs/rtl.astro
index 83258e04..5a891f4b 100644
--- a/apps/docs/src/pages/docs/rtl.astro
+++ b/apps/docs/src/pages/docs/rtl.astro
@@ -84,6 +84,36 @@ import RTLDemo from '../../components/demos/RTLDemo.tsx';
Column right: true alignment continues to work as expected. Align numbers relative to the reading direction of the cell content, not the page.
+ Pairing with localization
+
+ direction controls layout; localization controls strings. For a
+ fully translated RTL table, use both together. The library keeps them separate on purpose —
+ you may want French strings without changing layout direction, or an Arabic locale on a
+ mixed-direction page where you manage dir yourself.
+
+
+ `} />
+
+
+ If your app already sets <html dir="rtl">, use Direction.AUTO
+ so the table follows the page without an explicit prop:
+
+
+ `} />
+
Prop reference
diff --git a/apps/docs/tsconfig.json b/apps/docs/tsconfig.json
index 7e633708..f01ba595 100644
--- a/apps/docs/tsconfig.json
+++ b/apps/docs/tsconfig.json
@@ -4,7 +4,8 @@
"jsx": "react-jsx",
"jsxImportSource": "react",
"paths": {
- "react-data-table-component": ["../../src/index.ts"]
+ "react-data-table-component": ["../../src/index.ts"],
+ "react-data-table-component/locales": ["../../src/locales/index.ts"]
}
}
}
diff --git a/package-lock.json b/package-lock.json
index 2f1bb0f5..dbee524a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "react-data-table-component",
- "version": "8.0.1",
+ "version": "8.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "react-data-table-component",
- "version": "8.0.1",
+ "version": "8.2.0",
"license": "Apache-2.0",
"devDependencies": {
"@testing-library/dom": "^10.4.1",
@@ -14,9 +14,9 @@
"@testing-library/react": "^16.3.2",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
- "@typescript-eslint/eslint-plugin": "^8.59.2",
- "@typescript-eslint/parser": "^8.59.2",
- "@vitest/coverage-v8": "^4.1.6",
+ "@typescript-eslint/eslint-plugin": "^8.59.4",
+ "@typescript-eslint/parser": "^8.59.4",
+ "@vitest/coverage-v8": "^4.1.7",
"concurrently": "^9.2.1",
"eslint": "^9.39.4",
"eslint-config-prettier": "^10.1.8",
@@ -30,7 +30,7 @@
"react-dom": "^18.3.1",
"tsup": "^8.5.1",
"typescript": "^6.0.3",
- "vitest": "^4.1.6"
+ "vitest": "^4.1.7"
},
"peerDependencies": {
"react": ">=18.0.0",
@@ -2195,17 +2195,17 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.59.3",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.3.tgz",
- "integrity": "sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw==",
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.4.tgz",
+ "integrity": "sha512-PegsU+XfyJJNjd4+u/k6f9yTyp0lEXXiPopUNobZcIAUJFGICFLN+sP0Rb3JehVmiij1Ph0dFGYqODoRo/2+6A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.12.2",
- "@typescript-eslint/scope-manager": "8.59.3",
- "@typescript-eslint/type-utils": "8.59.3",
- "@typescript-eslint/utils": "8.59.3",
- "@typescript-eslint/visitor-keys": "8.59.3",
+ "@typescript-eslint/scope-manager": "8.59.4",
+ "@typescript-eslint/type-utils": "8.59.4",
+ "@typescript-eslint/utils": "8.59.4",
+ "@typescript-eslint/visitor-keys": "8.59.4",
"ignore": "^7.0.5",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.5.0"
@@ -2218,7 +2218,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.59.3",
+ "@typescript-eslint/parser": "^8.59.4",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
@@ -2234,16 +2234,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.59.3",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.3.tgz",
- "integrity": "sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==",
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.4.tgz",
+ "integrity": "sha512-zORHqO/tuhxY1zWuTvMUqddRxpiFJ72xVfcNoWpqdLjs6lfPbuQBJuW4pk+49/uBMy7Ssr4bzgjiKmmDB1UbZQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.59.3",
- "@typescript-eslint/types": "8.59.3",
- "@typescript-eslint/typescript-estree": "8.59.3",
- "@typescript-eslint/visitor-keys": "8.59.3",
+ "@typescript-eslint/scope-manager": "8.59.4",
+ "@typescript-eslint/types": "8.59.4",
+ "@typescript-eslint/typescript-estree": "8.59.4",
+ "@typescript-eslint/visitor-keys": "8.59.4",
"debug": "^4.4.3"
},
"engines": {
@@ -2259,14 +2259,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
- "version": "8.59.3",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.3.tgz",
- "integrity": "sha512-ECiUWa/KYRGDFUqTNehaRgzDshnJfkTABJxVemHk4ko22gcr0ukloKjWvyQ64g8YCV/UI47kN1dbmjf/GaQYng==",
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.4.tgz",
+ "integrity": "sha512-Ly00Vu4oAacfDeHp2Zg85ioNG6l8HG+tN1D7J+xTHSxu9y0awYKJ2zH1rFBn8ZSfuGK+7FxK3Cgl3uAz0aZZLg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.59.3",
- "@typescript-eslint/types": "^8.59.3",
+ "@typescript-eslint/tsconfig-utils": "^8.59.4",
+ "@typescript-eslint/types": "^8.59.4",
"debug": "^4.4.3"
},
"engines": {
@@ -2281,14 +2281,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.59.3",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.3.tgz",
- "integrity": "sha512-t2LvZnoEfzKtnPjgeEu41xw5gxq9mQVfYy4OoZ4Vlt0sk3JwxmhCca/AR7DwOiHrjWgjAj6as4AhRLKSDfvZIA==",
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.4.tgz",
+ "integrity": "sha512-mUeR/3H1WrTAddJrwut8OoPjfauaztMQmRwV5fQTUyNVJCLiUXXe4lGEyYIL2oFDpP7UtgbGJXCt72wT0z2S3Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.59.3",
- "@typescript-eslint/visitor-keys": "8.59.3"
+ "@typescript-eslint/types": "8.59.4",
+ "@typescript-eslint/visitor-keys": "8.59.4"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2299,9 +2299,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.59.3",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.3.tgz",
- "integrity": "sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw==",
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.4.tgz",
+ "integrity": "sha512-DLCpnKgD4alVxTBSKulK+gU1KCqOgUXfDRDXh2mZgzokQKa/70ax93I2uVO3m/LLvIAtWZIFoiifudmIqAxpMA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2316,15 +2316,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.59.3",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.3.tgz",
- "integrity": "sha512-g71d8QD8UaiHGvrJwyIS1hCX5r63w6Jll+4VEYhEAHXTDIqX1JgxhTAbEHtKntL9kuc4jRo7/GWw5xfCepSccQ==",
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.4.tgz",
+ "integrity": "sha512-uonTuPAAKr9XaBGqJ3LjYTh72zy5DyGesljO9gtmk/eFW0W1fRHjnwVYKB35Lm8d5Q5CluEW3gPHjTvZTmgrfA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.59.3",
- "@typescript-eslint/typescript-estree": "8.59.3",
- "@typescript-eslint/utils": "8.59.3",
+ "@typescript-eslint/types": "8.59.4",
+ "@typescript-eslint/typescript-estree": "8.59.4",
+ "@typescript-eslint/utils": "8.59.4",
"debug": "^4.4.3",
"ts-api-utils": "^2.5.0"
},
@@ -2341,9 +2341,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.59.3",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.3.tgz",
- "integrity": "sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg==",
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.4.tgz",
+ "integrity": "sha512-F1o7WJcCq+bc8dwcO/YsSEOudAH8RDtaOhM6wcAQhcUsFhnWQl81JKy48q1hoxAU0qrzM89+31GYh1515Zde3Q==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2355,16 +2355,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.59.3",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.3.tgz",
- "integrity": "sha512-CbRjVRAf7Lr9Kr8RopKcbY45p2VfmmHrm0ygOCYFi7oU8q19m0Fs/6iHS7kNOmwpp+ob07ZVcAqlxUod9lYdmg==",
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.4.tgz",
+ "integrity": "sha512-F+RuOmcDXo4+TPdfd/TCLS3m2nw8gE9XXyZLrA3JBfaA5tz9TtdkyD3YJFmPxulyc2cKbEok/CvFE3MgSLWnag==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/project-service": "8.59.3",
- "@typescript-eslint/tsconfig-utils": "8.59.3",
- "@typescript-eslint/types": "8.59.3",
- "@typescript-eslint/visitor-keys": "8.59.3",
+ "@typescript-eslint/project-service": "8.59.4",
+ "@typescript-eslint/tsconfig-utils": "8.59.4",
+ "@typescript-eslint/types": "8.59.4",
+ "@typescript-eslint/visitor-keys": "8.59.4",
"debug": "^4.4.3",
"minimatch": "^10.2.2",
"semver": "^7.7.3",
@@ -2383,16 +2383,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.59.3",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.3.tgz",
- "integrity": "sha512-JAvT14goBzRzzzZyqq3P9BLArIxTtQURUtFgQ/V7FO+eU+Gg6ES+5ymOPP1wRxXcxAYeivCk4uS3jCKWI1K8Zg==",
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.4.tgz",
+ "integrity": "sha512-cYXeNAUsG4lJo5dbc1FcKm+JwIWrj1/UpTORsC6tGMjEZ81DYcvIr9/ueikhMa/Y/gDQYGp+YX9/xQrXje5BJw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
- "@typescript-eslint/scope-manager": "8.59.3",
- "@typescript-eslint/types": "8.59.3",
- "@typescript-eslint/typescript-estree": "8.59.3"
+ "@typescript-eslint/scope-manager": "8.59.4",
+ "@typescript-eslint/types": "8.59.4",
+ "@typescript-eslint/typescript-estree": "8.59.4"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2407,13 +2407,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.59.3",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.3.tgz",
- "integrity": "sha512-f1UQF7ggd42YiwI5wGrRaPsa+P0CINBlrkLPmGfpq/u/I/oVtecoEIfFR9ag/oa1sLOsRNZ6xehf6qMZhQGBDg==",
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.4.tgz",
+ "integrity": "sha512-U3gxVaDVnuZKhSspW/MzMxE1kq7zOdc072FcSNoqA1I9p8HyKbBFfEHoWckBAMgNMph4MamwS5iTVzFmrnt8TQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.59.3",
+ "@typescript-eslint/types": "8.59.4",
"eslint-visitor-keys": "^5.0.0"
},
"engines": {
@@ -2438,14 +2438,14 @@
}
},
"node_modules/@vitest/coverage-v8": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.6.tgz",
- "integrity": "sha512-36l628fQ/9a/8ihy97eOtEnvWQEdqULQOJtcaxtoNq0G1w3Mxd4szSahOaMM9/NGyZ+hyKcMtIW/WIxq0XQViQ==",
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.7.tgz",
+ "integrity": "sha512-qsYPeXc5Q9dFLd1i8Ap+Bx8sQgcp+rFVQo4R0dDsWNBzl26ldVF1qOO+RL24K7FDrR6pA+50XedRLSoSG24bVQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@bcoe/v8-coverage": "^1.0.2",
- "@vitest/utils": "4.1.6",
+ "@vitest/utils": "4.1.7",
"ast-v8-to-istanbul": "^1.0.0",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
@@ -2459,8 +2459,8 @@
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
- "@vitest/browser": "4.1.6",
- "vitest": "4.1.6"
+ "@vitest/browser": "4.1.7",
+ "vitest": "4.1.7"
},
"peerDependenciesMeta": {
"@vitest/browser": {
@@ -2479,16 +2479,16 @@
}
},
"node_modules/@vitest/expect": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz",
- "integrity": "sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==",
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.7.tgz",
+ "integrity": "sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.1.0",
"@types/chai": "^5.2.2",
- "@vitest/spy": "4.1.6",
- "@vitest/utils": "4.1.6",
+ "@vitest/spy": "4.1.7",
+ "@vitest/utils": "4.1.7",
"chai": "^6.2.2",
"tinyrainbow": "^3.1.0"
},
@@ -2496,10 +2496,37 @@
"url": "https://opencollective.com/vitest"
}
},
+ "node_modules/@vitest/mocker": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.7.tgz",
+ "integrity": "sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "4.1.7",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.21"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@vitest/pretty-format": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.6.tgz",
- "integrity": "sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==",
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.7.tgz",
+ "integrity": "sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2510,13 +2537,13 @@
}
},
"node_modules/@vitest/runner": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.6.tgz",
- "integrity": "sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA==",
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.7.tgz",
+ "integrity": "sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/utils": "4.1.6",
+ "@vitest/utils": "4.1.7",
"pathe": "^2.0.3"
},
"funding": {
@@ -2524,14 +2551,14 @@
}
},
"node_modules/@vitest/snapshot": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.6.tgz",
- "integrity": "sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==",
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.7.tgz",
+ "integrity": "sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "4.1.6",
- "@vitest/utils": "4.1.6",
+ "@vitest/pretty-format": "4.1.7",
+ "@vitest/utils": "4.1.7",
"magic-string": "^0.30.21",
"pathe": "^2.0.3"
},
@@ -2540,9 +2567,9 @@
}
},
"node_modules/@vitest/spy": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.6.tgz",
- "integrity": "sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==",
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.7.tgz",
+ "integrity": "sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==",
"dev": true,
"license": "MIT",
"funding": {
@@ -2550,13 +2577,13 @@
}
},
"node_modules/@vitest/utils": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.6.tgz",
- "integrity": "sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==",
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.7.tgz",
+ "integrity": "sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "4.1.6",
+ "@vitest/pretty-format": "4.1.7",
"convert-source-map": "^2.0.0",
"tinyrainbow": "^3.1.0"
},
@@ -3575,6 +3602,13 @@
"node": ">= 0.4"
}
},
+ "node_modules/es-module-lexer": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz",
+ "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
@@ -6129,6 +6163,19 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/pirates": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
@@ -7115,19 +7162,6 @@
}
}
},
- "node_modules/tinyglobby/node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
"node_modules/tinyrainbow": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz",
@@ -8038,20 +8072,98 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/vite": {
+ "version": "8.0.13",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz",
+ "integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lightningcss": "^1.32.0",
+ "picomatch": "^4.0.4",
+ "postcss": "^8.5.14",
+ "rolldown": "1.0.1",
+ "tinyglobby": "^0.2.16"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.1.18",
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
"node_modules/vitest": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.6.tgz",
- "integrity": "sha512-6lvjbS3p9b4CrdCmguzbh2/4uoXhGE2q71R4OX5sqF9R1bo9Xd6fGrMAfvp5wnCzlBnFVdCOp6onuTQVbo8iUQ==",
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.7.tgz",
+ "integrity": "sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/expect": "4.1.6",
- "@vitest/mocker": "4.1.6",
- "@vitest/pretty-format": "4.1.6",
- "@vitest/runner": "4.1.6",
- "@vitest/snapshot": "4.1.6",
- "@vitest/spy": "4.1.6",
- "@vitest/utils": "4.1.6",
+ "@vitest/expect": "4.1.7",
+ "@vitest/mocker": "4.1.7",
+ "@vitest/pretty-format": "4.1.7",
+ "@vitest/runner": "4.1.7",
+ "@vitest/snapshot": "4.1.7",
+ "@vitest/spy": "4.1.7",
+ "@vitest/utils": "4.1.7",
"es-module-lexer": "^2.0.0",
"expect-type": "^1.3.0",
"magic-string": "^0.30.21",
@@ -8079,12 +8191,12 @@
"@edge-runtime/vm": "*",
"@opentelemetry/api": "^1.9.0",
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
- "@vitest/browser-playwright": "4.1.6",
- "@vitest/browser-preview": "4.1.6",
- "@vitest/browser-webdriverio": "4.1.6",
- "@vitest/coverage-istanbul": "4.1.6",
- "@vitest/coverage-v8": "4.1.6",
- "@vitest/ui": "4.1.6",
+ "@vitest/browser-playwright": "4.1.7",
+ "@vitest/browser-preview": "4.1.7",
+ "@vitest/browser-webdriverio": "4.1.7",
+ "@vitest/coverage-istanbul": "4.1.7",
+ "@vitest/coverage-v8": "4.1.7",
+ "@vitest/ui": "4.1.7",
"happy-dom": "*",
"jsdom": "*",
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
@@ -8128,53 +8240,6 @@
}
}
},
- "node_modules/vitest/node_modules/@vitest/mocker": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.6.tgz",
- "integrity": "sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@vitest/spy": "4.1.6",
- "estree-walker": "^3.0.3",
- "magic-string": "^0.30.21"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- },
- "peerDependencies": {
- "msw": "^2.4.9",
- "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
- },
- "peerDependenciesMeta": {
- "msw": {
- "optional": true
- },
- "vite": {
- "optional": true
- }
- }
- },
- "node_modules/vitest/node_modules/es-module-lexer": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz",
- "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/vitest/node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
"node_modules/vitest/node_modules/tinyexec": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz",
@@ -8185,84 +8250,6 @@
"node": ">=18"
}
},
- "node_modules/vitest/node_modules/vite": {
- "version": "8.0.13",
- "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz",
- "integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "lightningcss": "^1.32.0",
- "picomatch": "^4.0.4",
- "postcss": "^8.5.14",
- "rolldown": "1.0.1",
- "tinyglobby": "^0.2.16"
- },
- "bin": {
- "vite": "bin/vite.js"
- },
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- },
- "funding": {
- "url": "https://github.com/vitejs/vite?sponsor=1"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.3"
- },
- "peerDependencies": {
- "@types/node": "^20.19.0 || >=22.12.0",
- "@vitejs/devtools": "^0.1.18",
- "esbuild": "^0.27.0 || ^0.28.0",
- "jiti": ">=1.21.0",
- "less": "^4.0.0",
- "sass": "^1.70.0",
- "sass-embedded": "^1.70.0",
- "stylus": ">=0.54.8",
- "sugarss": "^5.0.0",
- "terser": "^5.16.0",
- "tsx": "^4.8.1",
- "yaml": "^2.4.2"
- },
- "peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- },
- "@vitejs/devtools": {
- "optional": true
- },
- "esbuild": {
- "optional": true
- },
- "jiti": {
- "optional": true
- },
- "less": {
- "optional": true
- },
- "sass": {
- "optional": true
- },
- "sass-embedded": {
- "optional": true
- },
- "stylus": {
- "optional": true
- },
- "sugarss": {
- "optional": true
- },
- "terser": {
- "optional": true
- },
- "tsx": {
- "optional": true
- },
- "yaml": {
- "optional": true
- }
- }
- },
"node_modules/w3c-xmlserializer": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
diff --git a/package.json b/package.json
index ff3c042c..b2e61b89 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,11 @@
"import": "./dist/index.mjs",
"require": "./dist/index.js"
},
+ "./locales": {
+ "types": "./dist/locales/index.d.ts",
+ "import": "./dist/locales/index.mjs",
+ "require": "./dist/locales/index.js"
+ },
"./css": "./dist/DataTable.css"
},
"files": [
@@ -50,9 +55,9 @@
"@testing-library/react": "^16.3.2",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
- "@typescript-eslint/eslint-plugin": "^8.59.2",
- "@typescript-eslint/parser": "^8.59.2",
- "@vitest/coverage-v8": "^4.1.6",
+ "@typescript-eslint/eslint-plugin": "^8.59.4",
+ "@typescript-eslint/parser": "^8.59.4",
+ "@vitest/coverage-v8": "^4.1.7",
"concurrently": "^9.2.1",
"eslint": "^9.39.4",
"eslint-config-prettier": "^10.1.8",
@@ -66,7 +71,7 @@
"react-dom": "^18.3.1",
"tsup": "^8.5.1",
"typescript": "^6.0.3",
- "vitest": "^4.1.6"
+ "vitest": "^4.1.7"
},
"peerDependencies": {
"react": ">=18.0.0",
diff --git a/src/__tests__/ColumnFilter.test.tsx b/src/__tests__/ColumnFilter.test.tsx
index 12b1d836..2ec5a373 100644
--- a/src/__tests__/ColumnFilter.test.tsx
+++ b/src/__tests__/ColumnFilter.test.tsx
@@ -244,3 +244,126 @@ describe('ColumnFilter:date filter type', () => {
expect(input.type).toBe('date');
});
});
+
+describe('ColumnFilter:localization (options prop)', () => {
+ const options = {
+ filterColumnAriaLabel: 'test-filter-col',
+ filterActiveAriaLabel: 'test-filter-active',
+ filterPanelAriaLabel: 'test-filter-panel',
+ operatorAriaLabel: 'test-operator',
+ valuePlaceholder: 'test-placeholder',
+ valueAriaLabel: 'test-value',
+ value2AriaLabel: 'test-value2',
+ value2Placeholder: 'test-placeholder2',
+ betweenSeparatorText: 'test-sep',
+ removeConditionAriaLabel: 'test-remove',
+ addConditionAriaLabel: 'test-add',
+ addConditionLabel: 'test-add-label',
+ clearLabel: 'test-clear',
+ applyLabel: 'test-apply',
+ andLabel: 'test-and',
+ orLabel: 'test-or',
+ operators: { contains: 'test-contains', equals: 'test-equals' },
+ };
+
+ test('filter icon button uses custom aria-label', () => {
+ const { container } = setup({ options });
+ const btn = container.querySelector('button') as HTMLButtonElement;
+ expect(btn.getAttribute('aria-label')).toBe('test-filter-col');
+ });
+
+ test('active filter uses custom active aria-label', () => {
+ const { container } = setup({
+ options,
+ filterValue: { condition1: { operator: 'contains', value: 'x' } },
+ });
+ const btn = container.querySelector('button') as HTMLButtonElement;
+ expect(btn.getAttribute('aria-label')).toBe('test-filter-active');
+ });
+
+ function openPanelByClass(container: HTMLElement) {
+ const btn = container.querySelector('button.rdt_filterIcon') as HTMLElement;
+ fireEvent.click(btn);
+ }
+
+ test('panel uses custom aria-label', () => {
+ const { container } = setup({ options });
+ openPanelByClass(container);
+ expect(container.querySelector('[role="dialog"]')?.getAttribute('aria-label')).toBe('test-filter-panel');
+ });
+
+ test('operator select uses custom aria-label', () => {
+ const { container } = setup({ options });
+ openPanelByClass(container);
+ expect(container.querySelector('select')?.getAttribute('aria-label')).toBe('test-operator');
+ });
+
+ test('value input uses custom placeholder and aria-label', () => {
+ const { container } = setup({ options });
+ openPanelByClass(container);
+ const input = container.querySelector('input') as HTMLInputElement;
+ expect(input.getAttribute('aria-label')).toBe('test-value');
+ expect(input.placeholder).toBe('test-placeholder');
+ });
+
+ test('custom operator labels appear in select; untranslated keys fall back to default', () => {
+ const { container } = setup({ options });
+ openPanelByClass(container);
+ const select = container.querySelector('select') as HTMLSelectElement;
+ const optionTexts = Array.from(select.options).map(o => o.text);
+ expect(optionTexts).toContain('test-contains');
+ expect(optionTexts).toContain('test-equals');
+ expect(optionTexts).toContain('Does not contain'); // untranslated key falls back to English default
+ });
+
+ test('Apply and Clear buttons use custom labels', () => {
+ const { container } = setup({ options });
+ openPanelByClass(container);
+ const btns = Array.from(container.querySelectorAll('.rdt_filterActions button')).map(b => b.textContent);
+ expect(btns).toContain('test-clear');
+ expect(btns).toContain('test-apply');
+ });
+
+ test('add-condition button uses custom label and aria-label', () => {
+ const { container } = setup({ options });
+ openPanelByClass(container);
+ const addBtn = container.querySelector('.rdt_filterAddCondition') as HTMLButtonElement;
+ expect(addBtn.textContent).toBe('test-add-label');
+ expect(addBtn.getAttribute('aria-label')).toBe('test-add');
+ });
+
+ test('AND / OR buttons use custom labels', () => {
+ const { container } = setup({ options });
+ openPanelByClass(container);
+ fireEvent.click(container.querySelector('.rdt_filterAddCondition') as HTMLElement);
+ const logicBtns = Array.from(container.querySelectorAll('.rdt_filterLogicBtn')).map(b => b.textContent);
+ expect(logicBtns).toContain('test-and');
+ expect(logicBtns).toContain('test-or');
+ });
+
+ test('remove-condition button uses custom aria-label', () => {
+ const { container } = setup({ options });
+ openPanelByClass(container);
+ fireEvent.click(container.querySelector('.rdt_filterAddCondition') as HTMLElement);
+ const removeBtn = container.querySelector('.rdt_filterRemoveBtn') as HTMLButtonElement;
+ expect(removeBtn.getAttribute('aria-label')).toBe('test-remove');
+ });
+
+ test('between separator uses custom text', () => {
+ const { container } = setup({ options, filterType: 'number', filterValue: emptyFilterState('number') });
+ openPanelByClass(container);
+ const select = container.querySelector('select') as HTMLSelectElement;
+ fireEvent.change(select, { target: { value: 'between' } });
+ expect(container.querySelector('.rdt_filterBetweenSep')?.textContent).toBe('test-sep');
+ });
+
+ test('second value input uses custom aria-label and placeholder', () => {
+ const { container } = setup({ options, filterType: 'number', filterValue: emptyFilterState('number') });
+ openPanelByClass(container);
+ const select = container.querySelector('select') as HTMLSelectElement;
+ fireEvent.change(select, { target: { value: 'between' } });
+ const input2 = container.querySelector('input[aria-label="test-value2"]') as HTMLInputElement;
+ expect(input2).not.toBeNull();
+ expect(input2.placeholder).toBe('test-placeholder2');
+ });
+});
diff --git a/src/__tests__/Pagination.test.tsx b/src/__tests__/Pagination.test.tsx
index 26cbc74f..c1507253 100644
--- a/src/__tests__/Pagination.test.tsx
+++ b/src/__tests__/Pagination.test.tsx
@@ -204,6 +204,86 @@ describe('when changing rows per page', () => {
});
});
+describe('Pagination:localization', () => {
+ const localization = {
+ navigationAriaLabel: 'test-nav',
+ firstPageAriaLabel: 'test-first',
+ previousPageAriaLabel: 'test-prev',
+ nextPageAriaLabel: 'test-next',
+ lastPageAriaLabel: 'test-last',
+ };
+
+ test('navigation nav uses custom navigationAriaLabel', () => {
+ const { container } = renderWithTheme(
+ ,
+ );
+ expect(container.querySelector('nav')?.getAttribute('aria-label')).toBe('test-nav');
+ });
+
+ test('first-page button uses custom firstPageAriaLabel', () => {
+ const { container } = renderWithTheme(
+ ,
+ );
+ expect(container.querySelector('#pagination-first-page')?.getAttribute('aria-label')).toBe('test-first');
+ });
+
+ test('previous-page button uses custom previousPageAriaLabel', () => {
+ const { container } = renderWithTheme(
+ ,
+ );
+ expect(container.querySelector('#pagination-previous-page')?.getAttribute('aria-label')).toBe('test-prev');
+ });
+
+ test('next-page button uses custom nextPageAriaLabel', () => {
+ const { container } = renderWithTheme(
+ ,
+ );
+ expect(container.querySelector('#pagination-next-page')?.getAttribute('aria-label')).toBe('test-next');
+ });
+
+ test('last-page button uses custom lastPageAriaLabel', () => {
+ const { container } = renderWithTheme(
+ ,
+ );
+ expect(container.querySelector('#pagination-last-page')?.getAttribute('aria-label')).toBe('test-last');
+ });
+});
+
describe('when the screensize is small', () => {
test('paginationComponentOption noRowsPerPage should be respected', () => {
globalThis.innerWidth = 500;
diff --git a/src/components/ColumnFilter.tsx b/src/components/ColumnFilter.tsx
index 63745390..bfecad29 100644
--- a/src/components/ColumnFilter.tsx
+++ b/src/components/ColumnFilter.tsx
@@ -1,11 +1,13 @@
import * as React from 'react';
import '../DataTable.css';
-import type { FilterState, FilterCondition, FilterOperator, FilterType } from '../types';
+import type { FilterState, FilterCondition, FilterOperator, FilterType, Localization } from '../types';
+
+type ColumnFilterOptions = NonNullable;
import { emptyFilterState, isFilterActive } from '../hooks/useColumnFilter';
type OperatorOption = { value: FilterOperator; label: string; noInput?: boolean; twoInputs?: boolean };
-const TEXT_OPERATORS: OperatorOption[] = [
+const DEFAULT_TEXT_OPERATORS: OperatorOption[] = [
{ value: 'contains', label: 'Contains' },
{ value: 'notContains', label: 'Does not contain' },
{ value: 'equals', label: 'Equals' },
@@ -16,7 +18,7 @@ const TEXT_OPERATORS: OperatorOption[] = [
{ value: 'notBlank', label: 'Not blank', noInput: true },
];
-const NUMBER_OPERATORS: OperatorOption[] = [
+const DEFAULT_NUMBER_OPERATORS: OperatorOption[] = [
{ value: 'equals', label: 'Equals' },
{ value: 'notEquals', label: 'Does not equal' },
{ value: 'gt', label: 'Greater than' },
@@ -28,7 +30,7 @@ const NUMBER_OPERATORS: OperatorOption[] = [
{ value: 'notBlank', label: 'Not blank', noInput: true },
];
-const DATE_OPERATORS: OperatorOption[] = [
+const DEFAULT_DATE_OPERATORS: OperatorOption[] = [
{ value: 'equals', label: 'Equals' },
{ value: 'before', label: 'Before' },
{ value: 'after', label: 'After' },
@@ -37,10 +39,15 @@ const DATE_OPERATORS: OperatorOption[] = [
{ value: 'notBlank', label: 'Not blank', noInput: true },
];
-function operatorsFor(filterType: FilterType): OperatorOption[] {
- if (filterType === 'number') return NUMBER_OPERATORS;
- if (filterType === 'date') return DATE_OPERATORS;
- return TEXT_OPERATORS;
+function operatorsFor(filterType: FilterType, overrides?: ColumnFilterOptions['operators']): OperatorOption[] {
+ const base =
+ filterType === 'number'
+ ? DEFAULT_NUMBER_OPERATORS
+ : filterType === 'date'
+ ? DEFAULT_DATE_OPERATORS
+ : DEFAULT_TEXT_OPERATORS;
+ if (!overrides) return base;
+ return base.map(op => (overrides[op.value] ? { ...op, label: overrides[op.value]! } : op));
}
function defaultOperator(filterType: FilterType): FilterOperator {
@@ -54,12 +61,13 @@ function emptyCondition(filterType: FilterType): FilterCondition {
type ConditionRowProps = {
condition: FilterCondition;
filterType: FilterType;
+ options: ColumnFilterOptions;
onChange: (next: FilterCondition) => void;
onRemove?: () => void;
};
-function ConditionRow({ condition, filterType, onChange, onRemove }: ConditionRowProps): JSX.Element {
- const operators = operatorsFor(filterType);
+function ConditionRow({ condition, filterType, options, onChange, onRemove }: ConditionRowProps): JSX.Element {
+ const operators = operatorsFor(filterType, options.operators);
const selected = operators.find(o => o.value === condition.operator) ?? operators[0];
const inputType = filterType === 'number' ? 'number' : filterType === 'date' ? 'date' : 'text';
@@ -69,7 +77,7 @@ function ConditionRow({ condition, filterType, onChange, onRemove }: ConditionRo
className="rdt_filterSelect"
value={condition.operator}
onChange={e => onChange({ operator: e.target.value as FilterOperator })}
- aria-label="Filter operator"
+ aria-label={options.operatorAriaLabel ?? 'Filter operator'}
>
{operators.map(op => (
@@ -83,30 +91,35 @@ function ConditionRow({ condition, filterType, onChange, onRemove }: ConditionRo
className="rdt_filterInput"
type={inputType}
value={condition.value ?? ''}
- placeholder="Value"
+ placeholder={options.valuePlaceholder ?? 'Value'}
onChange={e => onChange({ ...condition, value: e.target.value })}
onKeyDown={e => e.stopPropagation()}
- aria-label="Filter value"
+ aria-label={options.valueAriaLabel ?? 'Filter value'}
/>
)}
{selected.twoInputs && (
<>
- and
+ {options.betweenSeparatorText ?? 'and'}
onChange({ ...condition, value2: e.target.value })}
onKeyDown={e => e.stopPropagation()}
- aria-label="Filter second value"
+ aria-label={options.value2AriaLabel ?? 'Filter second value'}
/>
>
)}
{onRemove && (
-
+
✕
)}
@@ -118,6 +131,7 @@ type ColumnFilterProps = {
columnId: string | number;
filterValue: FilterState;
filterType?: FilterType;
+ options?: ColumnFilterOptions;
onFilterChange: (columnId: string | number, filter: FilterState) => void;
};
@@ -125,6 +139,7 @@ export default function ColumnFilter({
columnId,
filterValue,
filterType = 'text',
+ options = {},
onFilterChange,
}: ColumnFilterProps): JSX.Element {
const [open, setOpen] = React.useState(false);
@@ -216,7 +231,11 @@ export default function ColumnFilter({
ref={buttonRef}
type="button"
className={['rdt_filterIcon', isActive && 'rdt_filterIconActive'].filter(Boolean).join(' ')}
- aria-label={isActive ? 'Filter active' : 'Filter column'}
+ aria-label={
+ isActive
+ ? (options.filterActiveAriaLabel ?? 'Filter active')
+ : (options.filterColumnAriaLabel ?? 'Filter column')
+ }
aria-pressed={open}
onClick={e => {
e.stopPropagation();
@@ -243,10 +262,15 @@ export default function ColumnFilter({
ref={panelRef}
className="rdt_filterPanel"
role="dialog"
- aria-label="Column filter"
+ aria-label={options.filterPanelAriaLabel ?? 'Column filter'}
style={{ position: 'fixed', top: panelPos.top, left: panelPos.left }}
>
-
+
{pending.condition2 ? (
<>
@@ -259,7 +283,7 @@ export default function ColumnFilter({
aria-pressed={pending.logic !== 'OR'}
onClick={() => handleLogicChange('AND')}
>
- AND
+ {options.andLabel ?? 'AND'}
handleLogicChange('OR')}
>
- OR
+ {options.orLabel ?? 'OR'}
@@ -283,19 +308,19 @@ export default function ColumnFilter({
- + Add condition
+ {options.addConditionLabel ?? '+ Add condition'}
)}
- Clear
+ {options.clearLabel ?? 'Clear'}
- Apply
+ {options.applyLabel ?? 'Apply'}
diff --git a/src/components/DataTable.tsx b/src/components/DataTable.tsx
index 01242f2b..dd9c4c8f 100644
--- a/src/components/DataTable.tsx
+++ b/src/components/DataTable.tsx
@@ -109,6 +109,7 @@ function DataTableInner(props: TableProps, ref: React.ForwardedRef(props: TableProps, ref: React.ForwardedRef(props: TableProps, ref: React.ForwardedRef(props: TableProps, ref: React.ForwardedRef
)}
@@ -562,6 +566,7 @@ function DataTableInner(props: TableProps, ref: React.ForwardedRef
)}
diff --git a/src/components/DataTableHead.tsx b/src/components/DataTableHead.tsx
index 6640c200..bd57ca2f 100644
--- a/src/components/DataTableHead.tsx
+++ b/src/components/DataTableHead.tsx
@@ -44,6 +44,7 @@ function DataTableHead({
draggingColumnId,
draggingGroupKey,
filterValues,
+ localization: filterLocalization,
columnWidths,
pinnedOffsets,
resizable,
@@ -156,6 +157,7 @@ function DataTableHead({
sortIcon,
sortServer,
filterValue: filterValues[column.id!] ?? emptyFilterState(column.filterType),
+ filterLocalization,
resizedWidth: columnWidths[column.id!],
onSort,
onFilterChange,
diff --git a/src/components/ExpanderButton.tsx b/src/components/ExpanderButton.tsx
index 5bb7e44b..949044da 100644
--- a/src/components/ExpanderButton.tsx
+++ b/src/components/ExpanderButton.tsx
@@ -1,12 +1,15 @@
import * as React from 'react';
import '../DataTable.css';
import { useStyles } from '../context/StylesContext';
-import type { ExpandableIcon } from '../types';
+import type { ExpandableIcon, Localization } from '../types';
+
+type ExpandableRowsOptions = NonNullable;
type ExpanderButtonProps = {
disabled?: boolean;
expanded?: boolean;
expandableIcon: ExpandableIcon;
+ expandableRowsOptions?: ExpandableRowsOptions;
id: string | number;
row: T;
onToggled?: (row: T) => void;
@@ -16,6 +19,7 @@ function ExpanderButton({
disabled = false,
expanded = false,
expandableIcon,
+ expandableRowsOptions,
id,
row,
onToggled,
@@ -32,7 +36,11 @@ function ExpanderButton({
onClick={handleToggle}
data-testid={`expander-button-${id}`}
disabled={disabled}
- aria-label={expanded ? 'Collapse Row' : 'Expand Row'}
+ aria-label={
+ expanded
+ ? (expandableRowsOptions?.collapseRowAriaLabel ?? 'Collapse Row')
+ : (expandableRowsOptions?.expandRowAriaLabel ?? 'Expand Row')
+ }
type="button"
>
{icon}
diff --git a/src/components/Pagination.tsx b/src/components/Pagination.tsx
index c6b7e63b..0d4f5791 100644
--- a/src/components/Pagination.tsx
+++ b/src/components/Pagination.tsx
@@ -6,7 +6,7 @@ import { getNumberOfPages } from '../util';
import useWindowSize from '../hooks/useWindowSize';
import useRTL from '../hooks/useRTL';
import { Direction } from '../constants';
-import type { PaginationIcons, PaginationOptions, PaginationChangePage } from '../types';
+import type { PaginationIcons, PaginationOptions, PaginationChangePage, Localization } from '../types';
import { defaultProps, DEFAULT_PAGINATION_ICONS } from '../defaultProps';
const defaultComponentOptions = {
@@ -25,6 +25,7 @@ interface PaginationProps {
paginationRowsPerPageOptions?: number[];
paginationIcons?: PaginationIcons;
paginationComponentOptions?: PaginationOptions;
+ localization?: Localization['pagination'];
onChangePage: PaginationChangePage;
onChangeRowsPerPage: (numRows: number, currentPage: number) => void;
}
@@ -37,6 +38,7 @@ function Pagination({
paginationRowsPerPageOptions = defaultProps.paginationRowsPerPageOptions,
paginationIcons = DEFAULT_PAGINATION_ICONS,
paginationComponentOptions = defaultProps.paginationComponentOptions,
+ localization,
onChangeRowsPerPage = defaultProps.onChangeRowsPerPage,
onChangePage = defaultProps.onChangePage,
}: PaginationProps): JSX.Element {
@@ -102,7 +104,7 @@ function Pagination({
return (
{!options.noRowsPerPage && shouldShow && (
@@ -116,7 +118,7 @@ function Pagination({