Localisation
Every word the grid puts on screen — overlays, pager labels, editor placeholders and the screen-reader announcements nobody sees — comes from one object. Pass a translation, get a translated grid, with no template forks and no partial-translation penalty.
Français — extending a bundled set
A thirteen-string bundled set with two of its strings replaced. Filter Prénom for a name nobody has: the overlay, the pager and the retry button are French, and everything the set does not carry is still English.
GRID_MESSAGES['fr'] carries thirteen strings — the overlays, the
pager and the retry button. Spread it and change the two this screen words differently:
messages: {
...GRID_MESSAGES['fr'],
empty: 'Aucun employé ne correspond',
gridLabel: 'Répertoire du personnel',
}
Filter Prénom for a name nobody has and the overlay reads Aucun employé ne correspond. The pager under the grid says Lignes par page and sur, and neither string was written here — both came with the bundled set.
What a partial set does with the rest
The other sixty-nine keys are not in fr, so they fall back to English
key by key — never to blank. Tab to the header checkbox in this grid and a screen
reader still announces Select all rows. That is the honest state of a thirteen-string
language file, and it is why a set with six strings in it is a working set rather than a broken
one. The Arabic grid below is the other end of the same rule.
العربية — messages and direction together
A complete set, so even the strings only a screen reader hears are translated. dir: 'rtl' flips this grid alone — the two grids around it, and the page itself, stay left to right.
ar is one of the two complete sets, so nothing here falls back: the pager, the
overlays and the strings only a screen reader hears are all Arabic. The same header
checkbox that announced Select all rows in French announces
تحديد كل الصفوف here.
Direction is set separately, and only for this grid
dir: 'rtl' is a second option, next to messages. It moves the layout —
the columns run right to left, and so does the pager — while the two grids above and below stay
left to right. The page around them never changes direction:
config = {
dir: 'rtl',
messages: { ...GRID_MESSAGES['ar'], gridLabel: 'دليل الموظفين' },
};
RTL_LOCALES ships beside the messages and holds ar, fa and
ur, so a language switch can decide direction without keeping its own list.
English — what you still override
No translation needed, which leaves the two things a language file cannot supply: the grid's accessible name, and the column headers that were always yours.
English needs no translation — every string is already its own default. That makes this the example that shows what is left to do once the language is handled, because the two things below are not translations and no language file can supply them.
gridLabel— the name a screen reader announces on entering the grid. Employee directory says what this grid holds; the default, Data Grid, says only that it is one.- The column names —
First Name,Departmentand the rest are declared on the columns, so they are yours to translate, in all three grids on this page.
messages: { gridLabel: 'Employee directory' }
Twelve languages ship with the library
GRID_MESSAGES is exported alongside the grid and carries
en, ar, fr, de, es, pt, fa, ur, tr, zh, ja and ru. Localising is
an import, not a translation project:
import { GRID_MESSAGES, RTL_LOCALES, GridDirectionService } from '@gridcraft/angular';
const locale = inject(GridDirectionService);
locale.setMessages(GRID_MESSAGES[lang] ?? GRID_MESSAGES['en']);
locale.setDir(RTL_LOCALES.has(lang) ? 'rtl' : 'ltr');
en and ar are complete, at eighty-three strings each. The other ten carry
thirteen — the overlays, the pager, the retry button — and anything they omit falls back to
English key by key, so a partial set is a working set rather than a half-broken
one. The first two examples above are those two cases side by side.
Overriding a shipped set
Spread it, then change what your product words differently. What you do not mention keeps the shipped translation:
const FR = {
...GRID_MESSAGES['fr'],
empty: 'Aucun employé ne correspond',
gridLabel: 'Répertoire du personnel',
};
gridLabel is the reason to do this per screen rather than per language: it is the
grid's own accessible name, so it should say what this grid holds, not what every grid in
the app holds. The French example sets exactly those two on top of the bundled set.
Column names are yours, not the grid's
messages covers the strings the grid produces. The column headers are strings
you wrote, declared on the columns, so they go through whatever i18n your app already uses
— $localize, a JSON bundle, or your translation service. Each grid above declares its
own; a real language switch has to move both halves at once.
The strings nobody sees
A translated grid that still announces "sorted ascending" in English is only half translated, and it is the half that fails the readers who depend on it most. These keys never appear on screen, so they are the ones that get forgotten:
gridLabel— the grid's own accessible name, announced on entering it.selectAllRows,selectRow— the checkbox labels.filterFor,editorFor,rowDetailsToggle— the labels on the filter inputs, the inline editors and the row-detail toggle.announceSortAsc,announceSortDesc,announceSortCleared,announceResults,announceLoaded— what is read out after an action.
See Accessibility for what each announcement is used for.
Placeholders
A few strings are templates rather than fixed text. Keep the placeholder in your translation and the grid fills it in. Drop one and the value it carried is simply gone from the sentence:
{col}— a column's name.filterFor,editorFor,announceSortAsc,announceSortDesc,announceEditDone,validationRequired.{n}— a count.announceResults,announceLoaded,announceSelectedAll,quickFilterResults.{page}and{pages}— the current page and the total, inannouncePage.{total}— a second count beside{n}, inannounceLoadedMore.{min}and{max}— the bound that was broken, invalidationMinandvalidationMax.
They can sit anywhere in the string, which is what makes a language with a different word order
translatable rather than merely re-worded. Every other message is plain text —
pageOf included: the pager prints the two numbers around it, so a translation is the
word alone.
Three places to set them
The same object is accepted at three levels, and they combine key by key — a grid that overrides one string keeps every other one from the level above.
- App-wide, once —
provideGridDefaults({ messages })in your application config. Every grid inherits it, including grids you add later. - At runtime —
GridDirectionService.setMessages(). Call it when the reader changes language and every grid on screen updates; nothing is re-created.patchMessages()merges a few keys instead of replacing the set. - Per grid —
config.messages, which wins over both. Use it for the strings that belong to one screen, such asgridLabel. Each grid on this page uses it, which is why three languages sit on one screen at once.
Direction is a separate switch
Translating the strings does not move the layout, and moving the layout does not translate the
strings — deliberately independent, because plenty of apps need one without the other.
config.dir is per grid, so the Arabic example above reads right to left while the
rest of this page, and the two grids around it, stay left to right. See
RTL Support for what direction changes on its own.
Messages are plain strings, not a message format, so they compose with whatever you already use
to produce them — pull them from $localize, a JSON bundle or your translation
service and hand the resulting object over.
Localisation options
| Property | Type | Default | Description |
|---|---|---|---|
| GRID_MESSAGES | Record<string, Partial<GridMessages>> | — | Bundled translations: en, ar, fr, de, es, pt, fa, ur, tr, zh, ja, ru. Spread one to extend it; anything a set omits falls back to English key by key. |
| RTL_LOCALES | Set<string> | { 'ar', 'fa', 'ur' } | The bundled languages that read right to left — pair it with config.dir. |
| config.messages | Partial<GridMessages> | English defaults | Every string the grid renders. Any key you omit keeps its default, so a partial translation is valid. |
| messages.gridLabel | string | 'Data Grid' | The grid's own accessible name — what a screen reader announces on entering it. Worth setting per screen. |
| messages.empty / error / retry | string | English defaults | The overlay text and the retry button. See Loading & Empty States. |
| messages.rowsPerPage / pageOf | string | 'Rows per page' / 'of' | The pager labels under a paged grid. pageOf is the word between the two numbers, not a template. |
| messages.firstPage / prevPage / nextPage / lastPage | string | English defaults | Accessible labels on the four page buttons. |
| messages.records / allLoaded | string | 'records' / 'All records loaded' | The count line and end-of-list note under a grid that scrolls rather than pages. |
| messages.filterFor / editorFor | string | 'Filter {col}' / 'Edit {col}' | Labels on the filter inputs and inline editors. {col} becomes the column name. |
| messages.announceSortAsc / …Desc / …Cleared | string | English defaults | Announced after a sort changes. {col} becomes the column name. |
| messages.search / noResults / pickDate / clearDate | string | English defaults | Strings inside the select and date editors. |
| config.dir | 'ltr' | 'rtl' | 'ltr' | Layout direction — independent of the messages, and set per grid. See RTL Support. |
| GridDirectionService.setMessages() | (m: Partial<GridMessages>) => void | — | Sets the app-wide messages at runtime; every grid on screen follows. A per-grid config.messages still wins. |
| GridDirectionService.patchMessages() | (m: Partial<GridMessages>) => void | — | Merges a few keys into the current app-wide messages instead of replacing them. |
| provideGridDefaults({ messages }) | Partial<GridConfig> | — | The app-wide baseline, merged key by key with each grid's own messages. |