Skip to main content

Localization (i18n)

Every built-in string in the library — picker headlines and buttons, the Snackbar dismiss button, the Badge announcement, Chip "Remove …", sheet and dialog close buttons, and so on — is English by default and can be replaced in two ways:

  • Per instance, with the component's *Label / get*Label props (<Snackbar dismissLabel="閉じる" />).
  • For the whole app (or a subtree), with LocaleProvider. It also sets the default locale of the date and time pickers.

A prop on a component always wins over the provider.

LocaleProvider​

import { LocaleProvider, ThemeProvider } from 'm3-expressive-react'
import { ja } from './i18n/ja' // your translations (see below)

export function App() {
return (
<ThemeProvider seedColor="#6750A4">
<LocaleProvider locale="ja-JP" labels={ja}>
{/* … */}
</LocaleProvider>
</ThemeProvider>
)
}
PropTypeDefaultDescription
localestring (BCP 47)'en-US'Default locale of DatePicker, DatePickerField and TimePicker: month / weekday names, the first day of the week, the typed-date field order and the TimePicker's hour cycle.
ampmbooleanfrom localeDefault ampm of every TimePicker: true = 12-hour clock with AM / PM, false = 24-hour.
labelsLocaleLabels{}Built-in strings per component — see the keys.

useLocale() returns the settings in effect — { locale, ampm, labels } — so your own formatting can use the same locale:

const { locale } = useLocale()
const text = new Intl.DateTimeFormat(locale, { dateStyle: 'medium' }).format(date)

LocaleProvider is also a subpath entry: import LocaleProvider from 'm3-expressive-react/LocaleProvider'.

Next.js App Router

LocaleProvider is a client component. A Server Component can render it with locale / ampm and string labels, but functions (getLabel, getHourLabel, …) cannot cross the server → client boundary. Put the provider and your translations in your own 'use client' module:

// app/locale-provider.tsx
'use client'
import { LocaleProvider } from 'm3-expressive-react'
import { ja } from './i18n/ja'

export function AppLocaleProvider({ children }: { children: React.ReactNode }) {
return <LocaleProvider locale="ja-JP" labels={ja}>{children}</LocaleProvider>
}

Precedence​

  1. The component's own prop (dismissLabel, locale, ampm, …)
  2. The nearest LocaleProvider
  3. An outer LocaleProvider — a nested provider overrides single keys and inherits everything else
  4. The English default ('en-US')
<LocaleProvider locale="ja-JP" labels={ja}>
{/* Only this subtree reads "翌月" for the next-month button. */}
<LocaleProvider labels={{ DatePicker: { nextMonthLabel: '翌月' } }}>
<DatePicker />
</LocaleProvider>
</LocaleProvider>

Locale, week start and the 24-hour clock​

From locale the pickers derive, with the platform's Intl data:

  • Month and weekday names and the headline date format.
  • The first day of the week — Intl.Locale(locale).getWeekInfo() (or weekInfo) where the browser supports it, else Sunday. A Unicode extension such as en-US-u-fw-mon changes it where supported.
  • The typed-date field order and the helper text of the date input (MM/DD/YYYY for en-US, YYYY/MM/DD for ja-JP).
  • The TimePicker's hour cycle — Intl.DateTimeFormat(locale, { hour: 'numeric' }).resolvedOptions().hourCycle (ja-JP, en-GB, de-DE → 24-hour). MD3 sets the 24-hour preference outside the time picker, typically from system settings; pass ampm to follow your app's setting.

The library never reads navigator.language: without a provider, server and client render the same en-US output. To follow the browser, pass the language you resolved yourself (e.g. from the Accept-Language header on the server) to locale.

Japanese example​

The library ships English only. This is a Japanese translation of the date / time pickers, the Badge announcement and the Snackbar:

日付を選択
10月15日(木)
2026年10月
日曜日月曜日火曜日水曜日木曜日金曜日土曜日
時刻を選択
18時30分
新しい通知 3 件
保存しました
// i18n/ja.ts
import type { LocaleLabels } from 'm3-expressive-react'

export const ja: LocaleLabels = {
Badge: {
// count: the text the badge shows ('8', '999+'), undefined for a dot
getLabel: (count) => (count == null ? '新しい通知' : `新しい通知 ${count} 件`),
},
DatePicker: {
titleLabel: '日付を選択',
rangeTitleLabel: '期間を選択',
rangeInputTitleLabel: '期間を入力',
noSelectionLabel: '日付',
inputNoSelectionLabel: '日付',
previousMonthLabel: '前の月',
nextMonthLabel: '次の月',
selectYearLabel: '年を選択',
todayLabel: '今日',
startDateLabel: '開始日',
endDateLabel: '終了日',
inRangeLabel: '期間内',
inputLabel: '日付',
switchToInputLabel: 'テキスト入力に切り替え',
switchToCalendarLabel: 'カレンダーに切り替え',
getErrorLabel: (error, pattern) =>
error === 'invalid'
? `日付の形式が正しくありません: ${pattern}`
: error === 'invalidRange'
? '期間の入力が正しくありません'
: 'この日付は選択できません',
okLabel: 'OK',
cancelLabel: 'キャンセル',
},
DatePickerField: {
label: '日付',
openCalendarLabel: 'カレンダーを開く',
dialogLabel: '日付を選択',
previousMonthLabel: '前の月',
nextMonthLabel: '次の月',
previousYearLabel: '前の年',
nextYearLabel: '次の年',
selectMonthLabel: '月を選択',
selectYearLabel: '年を選択',
todayLabel: '今日',
},
TimePicker: {
titleLabel: '時刻を選択',
inputTitleLabel: '時刻を入力',
hourLabel: '時',
minuteLabel: '分',
selectHourLabel: '時を選択',
selectMinuteLabel: '分を選択',
periodLabel: '午前または午後',
amLabel: '午前',
pmLabel: '午後',
switchToInputLabel: 'キーボード入力に切り替え',
switchToDialLabel: '時計の文字盤に切り替え',
switchToScrollLabel: 'スクロール入力に切り替え',
getHourLabel: (hour) => `${hour}時`,
getMinuteLabel: (minute) => `${minute}分`,
getErrorLabel: (field, ampm) =>
field === 'minute'
? '分は 0〜59 で入力してください'
: `時は ${ampm ? '1〜12' : '0〜23'} で入力してください`,
okLabel: 'OK',
cancelLabel: 'キャンセル',
},
Snackbar: { dismissLabel: '閉じる' },
SearchBar: { placeholder: '検索', backLabel: '戻る', suggestionsLabel: '候補を表示しています' },
Chip: { getRemoveLabel: (label) => `${label}を削除` },
Dialog: { closeLabel: '閉じる', fallbackLabel: 'ダイアログ' },
BottomSheet: { dragHandleLabel: '閉じる', partialDragHandleLabel: 'ドラッグハンドル', fallbackLabel: 'ボトムシート' },
SideSheet: { closeLabel: '閉じる', backLabel: '戻る', fallbackLabel: 'サイドシート' },
NavigationDrawer: { fallbackLabel: 'ナビゲーション' },
NavigationRail: { modalLabel: 'ナビゲーションレール' },
Carousel: {
getItemLabel: (position, count) => `${count} 件中 ${position} 件目`,
roleDescriptionLabel: 'カルーセル',
itemRoleDescriptionLabel: 'スライド',
},
Slider: { rangeStartLabel: '最小値', rangeEndLabel: '最大値' },
SplitButton: { trailingAriaLabel: 'その他のオプション' },
Autocomplete: { toggleLabel: '候補の表示を切り替え' },
TextField: { getCounterLabel: (length, max) => `文字数: ${max} 文字中 ${length} 文字` },
PullToRefresh: { refreshingLabel: '更新しています' },
}

Label keys​

labels is keyed by component name. Each key is the component prop of the same name — same type, same English default (see each component's props table) — except the provider-only keys marked below.

ComponentKeys
DatePickertitleLabel, noSelectionLabel, previousMonthLabel, nextMonthLabel, selectYearLabel, todayLabel, startDateLabel, endDateLabel, inRangeLabel, inputLabel, switchToInputLabel, switchToCalendarLabel, getErrorLabel, okLabel, cancelLabel; provider-only: rangeTitleLabel ('Select dates'), rangeInputTitleLabel ('Enter dates'), inputNoSelectionLabel ('Entered date')
DatePickerFieldlabel, openCalendarLabel, dialogLabel, getErrorLabel, previousMonthLabel, nextMonthLabel, previousYearLabel, nextYearLabel, selectMonthLabel, selectYearLabel, todayLabel
TimePickertitleLabel, hourLabel, minuteLabel, selectHourLabel, selectMinuteLabel, periodLabel, amLabel, pmLabel, switchToInputLabel, switchToDialLabel, switchToScrollLabel, getHourLabel, getMinuteLabel, getErrorLabel, okLabel, cancelLabel; provider-only: inputTitleLabel ('Enter time')
Badgeprovider-only: getLabel(count) — also used for the badges of NavigationBarItem, NavigationRailItem and Tab
SnackbardismissLabel (also the dismiss button of SnackbarProvider snackbars)
SearchBarbackLabel, suggestionsLabel, placeholder
ChipgetRemoveLabel
DialogcloseLabel; provider-only: fallbackLabel ('Dialog')
BottomSheetdragHandleLabel ('Close'); provider-only: partialDragHandleLabel ('Drag handle'), fallbackLabel ('Bottom sheet')
SideSheetcloseLabel, backLabel; provider-only: fallbackLabel ('Side sheet')
NavigationDrawerprovider-only: fallbackLabel ('Navigation')
NavigationRailmodalLabel
CarouselgetItemLabel, roleDescriptionLabel, itemRoleDescriptionLabel
SliderrangeStartLabel, rangeEndLabel
SplitButtontrailingAriaLabel
AutocompletetoggleLabel
TextFieldgetCounterLabel
PullToRefreshrefreshingLabel
  • Per-mode headlines. Where a component shows a different built-in string per mode, each mode has its own provider key (as Compose has one string resource each): DatePicker titleLabel is the single-date title, rangeTitleLabel / rangeInputTitleLabel the range titles; TimePicker titleLabel is the dial / scroll headline, inputTitleLabel the input one; BottomSheet dragHandleLabel names the closing handle, partialDragHandleLabel the expand / collapse one. The props of the same name still apply to every mode.
  • fallbackLabel is the accessible name used only when the dialog / sheet / drawer has no title, aria-label or aria-labelledby.
  • Not covered: strings the app always provides (e.g. FabMenu ariaLabel), and the DatePickerField helper text, which shows the locale's input pattern (MM/DD/YYYY) — set supportingText to replace it.