Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions LOCALIZATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Adding a New Language

This guide covers adding MUI built-in component label support for a new language — things like DataGrid column menus, DatePicker buttons, and Autocomplete labels. These are separate from any i18next/app-level strings and are resolved through MUI's own locale bundle system.

`localizeTheme(theme, muiLocaleString)` is the single entry point. It accepts a MUI locale string (e.g. `"frFR"`) and resolves the correct bundles for MUI core, Data Grid, and Date Pickers dynamically — no changes needed here for locales MUI already ships. The only time this repo needs updating is when **MUI X doesn't ship a locale** and a hand-authored bundle is required.

---

## Steps

### 1. Check MUI core

Try importing the locale from `@mui/material/locale`:

```ts
import { frFR } from "@mui/material/locale";
```

If it resolves, MUI core is handled automatically — no changes needed in this repo.

If it doesn't resolve, a hand-authored core bundle is needed. This is rare — MUI core ships bundles for most major languages.

### 2. Check MUI X Data Grid

Try importing the locale from `@mui/x-data-grid-pro/locales`:

```ts
import { frFR } from "@mui/x-data-grid-pro/locales";
```

**If it resolves** — no changes needed, the dynamic resolver in `src/i18n/datagrid.ts` picks it up automatically.

**If it doesn't resolve** — create a hand-authored bundle at `src/i18n/datagrid-locales/<muiLocaleString>.ts`. Use `src/i18n/datagrid-locales/hi-IN.ts` as a reference, then register it in the `OVERRIDES` map in `src/i18n/datagrid.ts`:

```ts
import { frFR } from "./datagrid-locales/fr-FR";

const OVERRIDES: Record<string, Partial<GridLocaleText>> = {
hiIN,
frFR, // add here
};
```

### 3. Check MUI X Date Pickers

Same pattern — try importing from `@mui/x-date-pickers-pro/locales`:

```ts
import { frFR } from "@mui/x-date-pickers-pro/locales";
```

**If it resolves** — no changes needed.

**If it doesn't resolve** — create `src/i18n/datepickers-locales/<muiLocaleString>.ts` using `src/i18n/datepickers-locales/hi-IN.ts` as a reference, then register it in `OVERRIDES` in `src/i18n/datepickers.ts`.

### 4. Build and publish

```bash
npm run build
npm version patch # or minor/major depending on the change
npm publish --access public
```

### 5. Update consuming apps

Bump `@zesty-io/material` and add the BCP 47 → MUI locale string mapping in the app. For example in manager-ui, add an entry to `MUI_LOCALE` in `LocalizedThemeProvider.tsx`:

```ts
const MUI_LOCALE: Record<SupportedLocale, string> = {
...
"fr-FR": "frFR",
};
```

---

## Notes

- **English variants** (e.g. `en-PH`, `en-AU`) require no changes here — they fall back to MUI's built-in English strings automatically. In the consuming app, map them to `"enUS"`.
- **MUI X does not need an explicit English fallback.** `getDataGridLocaleText` and `getDatePickersLocaleText` return `undefined` for unmapped locales — MUI X treats `undefined` as "use built-in defaults", which are already English. This is intentional and differs from MUI core, which always needs a locale object merged into the theme.
- **The localization layer is i18next-free.** MUI's built-in label system is separate from app-level strings (form labels, page titles, notifications, etc.) — those remain the consuming app's responsibility.
- Consuming apps that don't use `localizeTheme` are unaffected by changes here.
15 changes: 8 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"react-docgen-typescript": "^2.2.2",
"react-dom": "^17.0.2",
"react-json-view": "^1.21.3",
"typescript": "^4.6.3"
"typescript": "^5.9.3"
},
"files": [
"cjs",
Expand Down
17 changes: 15 additions & 2 deletions src/SSOButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ export interface SSOButtonProps {
* Optional properties for underlying MUI Button component.
*/
ButtonProps?: ButtonProps;

/**
* Optional pre-translated label to render instead of the default
* "Continue with {service}" text. Consumers are responsible for
* translation; this component does not perform any i18n itself.
*/
label?: string;
}

const ssoServiceMap = {
Expand All @@ -42,7 +49,13 @@ const ssoServiceMap = {
},
}

const SSOButton = ({ sx, service, onClick, ButtonProps }: SSOButtonProps) => {
const SSOButton = ({
sx,
service,
onClick,
ButtonProps,
label,
}: SSOButtonProps) => {
const ssoService = ssoServiceMap[service];

return (
Expand All @@ -60,7 +73,7 @@ const SSOButton = ({ sx, service, onClick, ButtonProps }: SSOButtonProps) => {
sx={{ justifyContent: "flex-start", ...sx }}
{...ButtonProps}
>
Continue with {ssoService.name}
{label ?? `Continue with ${ssoService.name}`}
</Button>
);
};
Expand Down
207 changes: 207 additions & 0 deletions src/i18n/datagrid-locales/hi-IN.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import type { GridLocaleText } from "@mui/x-data-grid-pro";

// Custom MUI X Data Grid localeText for Hindi (hi-IN).
//
// MUI X ships no Hindi Data Grid locale, so we supply our own. This is a FULL
// translation of GridLocaleText (every key MUI exposes), not a reachable-only
// subset — the grid is used across the whole app in many configurations
// (toolbar, filtering, export, row selection, aggregation, detail panels…),
// so translating everything avoids silent English fallback when any grid
// enables a feature.
//
// Typed Partial<GridLocaleText>: any key MUI adds in a future version that we
// haven't translated yet falls back to its built-in English rather than
// erroring. The 7 function entries mirror MUI's default signatures.
//
// To add another unshipped locale: create a sibling file here exporting a
// Partial<GridLocaleText> and register it in ../datagrid.ts.
export const hiIN: Partial<GridLocaleText> = {
// Overlays
noRowsLabel: "कोई पंक्तियाँ नहीं हैं",
noResultsOverlayLabel: "कोई परिणाम नहीं मिला।",

// Toolbar — density
toolbarDensity: "घनत्व",
toolbarDensityLabel: "घनत्व",
toolbarDensityCompact: "संकुचित",
toolbarDensityStandard: "मानक",
toolbarDensityComfortable: "आरामदायक",

// Toolbar — columns
toolbarColumns: "कॉलम",
toolbarColumnsLabel: "कॉलम चुनें",

// Toolbar — filters
toolbarFilters: "फ़िल्टर",
toolbarFiltersLabel: "फ़िल्टर दिखाएं",
toolbarFiltersTooltipHide: "फ़िल्टर छुपाएं",
toolbarFiltersTooltipShow: "फ़िल्टर दिखाएं",
toolbarFiltersTooltipActive: (count) => `${count} सक्रिय फ़िल्टर`,

// Toolbar — quick filter
toolbarQuickFilterPlaceholder: "खोजें…",
toolbarQuickFilterLabel: "खोजें",
toolbarQuickFilterDeleteIconLabel: "साफ़ करें",

// Toolbar — export
toolbarExport: "निर्यात",
toolbarExportLabel: "निर्यात",
toolbarExportCSV: "CSV के रूप में डाउनलोड करें",
toolbarExportPrint: "प्रिंट करें",
toolbarExportExcel: "Excel के रूप में डाउनलोड करें",

// Columns management panel
columnsManagementSearchTitle: "खोजें",
columnsManagementNoColumns: "कोई कॉलम नहीं",
columnsManagementShowHideAllText: "सभी दिखाएं/छुपाएं",
columnsManagementReset: "रीसेट करें",
columnsManagementDeleteIconLabel: "साफ़ करें",

// Filter panel
filterPanelAddFilter: "फ़िल्टर जोड़ें",
filterPanelRemoveAll: "सभी हटाएं",
filterPanelDeleteIconLabel: "हटाएं",
filterPanelLogicOperator: "तार्किक ऑपरेटर",
filterPanelOperator: "ऑपरेटर",
filterPanelOperatorAnd: "और",
filterPanelOperatorOr: "या",
filterPanelColumns: "कॉलम",
filterPanelInputLabel: "मान",
filterPanelInputPlaceholder: "फ़िल्टर मान",

// Filter operators
filterOperatorContains: "शामिल है",
filterOperatorDoesNotContain: "शामिल नहीं है",
filterOperatorEquals: "बराबर है",
filterOperatorDoesNotEqual: "बराबर नहीं है",
filterOperatorStartsWith: "से शुरू होता है",
filterOperatorEndsWith: "से समाप्त होता है",
filterOperatorIs: "है",
filterOperatorNot: "नहीं है",
filterOperatorAfter: "के बाद है",
filterOperatorOnOrAfter: "को या उसके बाद है",
filterOperatorBefore: "के पहले है",
filterOperatorOnOrBefore: "को या उससे पहले है",
filterOperatorIsEmpty: "खाली है",
filterOperatorIsNotEmpty: "खाली नहीं है",
filterOperatorIsAnyOf: "इनमें से कोई है",
"filterOperator=": "=",
"filterOperator!=": "!=",
"filterOperator>": ">",
"filterOperator>=": ">=",
"filterOperator<": "<",
"filterOperator<=": "<=",

// Header filter operators
headerFilterOperatorContains: "शामिल है",
headerFilterOperatorDoesNotContain: "शामिल नहीं है",
headerFilterOperatorEquals: "बराबर है",
headerFilterOperatorDoesNotEqual: "बराबर नहीं है",
headerFilterOperatorStartsWith: "से शुरू होता है",
headerFilterOperatorEndsWith: "से समाप्त होता है",
headerFilterOperatorIs: "है",
headerFilterOperatorNot: "नहीं है",
headerFilterOperatorAfter: "के बाद है",
headerFilterOperatorOnOrAfter: "को या उसके बाद है",
headerFilterOperatorBefore: "के पहले है",
headerFilterOperatorOnOrBefore: "को या उससे पहले है",
headerFilterOperatorIsEmpty: "खाली है",
headerFilterOperatorIsNotEmpty: "खाली नहीं है",
headerFilterOperatorIsAnyOf: "इनमें से कोई है",
"headerFilterOperator=": "बराबर है",
"headerFilterOperator!=": "बराबर नहीं है",
"headerFilterOperator>": "इससे बड़ा है",
"headerFilterOperator>=": "इससे बड़ा या बराबर है",
"headerFilterOperator<": "इससे छोटा है",
"headerFilterOperator<=": "इससे छोटा या बराबर है",

// Filter values
filterValueAny: "कोई भी",
filterValueTrue: "सत्य",
filterValueFalse: "असत्य",

// Column menu
columnMenuLabel: "मेनू",
columnMenuAriaLabel: (columnName) => `${columnName} कॉलम मेनू`,
columnMenuShowColumns: "कॉलम दिखाएं",
columnMenuManageColumns: "कॉलम प्रबंधित करें",
columnMenuFilter: "फ़िल्टर",
columnMenuHideColumn: "कॉलम छुपाएं",
columnMenuUnsort: "क्रमबद्धता हटाएं",
columnMenuSortAsc: "बढ़ते क्रम (ASC) में क्रमबद्ध करें",
columnMenuSortDesc: "घटते क्रम (DESC) में क्रमबद्ध करें",

// Column header
columnHeaderFiltersTooltipActive: (count) => `${count} सक्रिय फ़िल्टर`,
columnHeaderFiltersLabel: "फ़िल्टर दिखाएं",
columnHeaderSortIconLabel: "क्रमबद्ध करें",

// Footer
footerRowSelected: (count) =>
count !== 1
? `${count.toLocaleString()} पंक्तियाँ चयनित`
: `${count.toLocaleString()} पंक्ति चयनित`,
footerTotalRows: "कुल पंक्तियाँ:",
footerTotalVisibleRows: (visibleCount, totalCount) =>
`${totalCount.toLocaleString()} में से ${visibleCount.toLocaleString()}`,

// Checkbox selection
checkboxSelectionHeaderName: "चेकबॉक्स चयन",
checkboxSelectionSelectAllRows: "सभी पंक्तियाँ चुनें",
checkboxSelectionUnselectAllRows: "सभी पंक्तियों का चयन रद्द करें",
checkboxSelectionSelectRow: "पंक्ति चुनें",
checkboxSelectionUnselectRow: "पंक्ति का चयन रद्द करें",

// Boolean cell
booleanCellTrueLabel: "हाँ",
booleanCellFalseLabel: "नहीं",

// Actions cell
actionsCellMore: "अधिक",

// Column pinning (DataGridPro)
pinToLeft: "बाईं ओर पिन करें",
pinToRight: "दाईं ओर पिन करें",
unpin: "पिन हटाएं",

// Tree data
treeDataGroupingHeaderName: "समूह",
treeDataExpand: "उप-आइटम देखें",
treeDataCollapse: "उप-आइटम छुपाएं",

// Grouping
groupingColumnHeaderName: "समूह",
groupColumn: (name) => `${name} के अनुसार समूहित करें`,
unGroupColumn: (name) => `${name} के अनुसार समूहन रोकें`,

// Master/detail
detailPanelToggle: "विवरण पैनल टॉगल करें",
expandDetailPanel: "विस्तृत करें",
collapseDetailPanel: "संक्षिप्त करें",

// Row reordering
rowReorderingHeaderName: "पंक्ति पुनर्क्रमण",

// Aggregation
aggregationMenuItemHeader: "समुच्चय",
aggregationFunctionLabelSum: "योग",
aggregationFunctionLabelAvg: "औसत",
aggregationFunctionLabelMin: "न्यूनतम",
aggregationFunctionLabelMax: "अधिकतम",
aggregationFunctionLabelSize: "संख्या",

// Pagination footer
MuiTablePagination: {
labelRowsPerPage: "प्रति पृष्ठ पंक्तियाँ:",
labelDisplayedRows: ({ from, to, count }) =>
`${
count !== -1 ? count.toLocaleString() : `${to.toLocaleString()} से अधिक`
} में से ${from.toLocaleString()}–${to.toLocaleString()}`,
getItemAriaLabel: (type) => {
if (type === "first") { return "पहले पृष्ठ पर जाएं"; }
if (type === "last") { return "अंतिम पृष्ठ पर जाएं"; }
if (type === "next") { return "अगले पृष्ठ पर जाएं"; }
return "पिछले पृष्ठ पर जाएं";
},
},
};
Loading