diff --git a/docs/data/toolpad/core/components/account/AccountCustomLocaleText.js b/docs/data/toolpad/core/components/account/AccountCustomLocaleText.js index 5ed35873c32..3074a0f94d2 100644 --- a/docs/data/toolpad/core/components/account/AccountCustomLocaleText.js +++ b/docs/data/toolpad/core/components/account/AccountCustomLocaleText.js @@ -1,5 +1,5 @@ import * as React from 'react'; -import { AuthenticationContext, SessionContext } from '@toolpad/core/AppProvider'; +import { AppProvider } from '@toolpad/core/AppProvider'; import { Account } from '@toolpad/core/Account'; const demoSession = { @@ -31,17 +31,15 @@ export default function AccountCustomLocaleText() { }, []); return ( - - - {/* preview-start */} - - {/* preview-end */} - - + + {/* preview-start */} + + {/* preview-end */} + ); } diff --git a/docs/data/toolpad/core/components/account/AccountCustomLocaleText.tsx b/docs/data/toolpad/core/components/account/AccountCustomLocaleText.tsx index c74db6789c7..9fd5e3f3783 100644 --- a/docs/data/toolpad/core/components/account/AccountCustomLocaleText.tsx +++ b/docs/data/toolpad/core/components/account/AccountCustomLocaleText.tsx @@ -1,9 +1,5 @@ import * as React from 'react'; -import { - AuthenticationContext, - Session, - SessionContext, -} from '@toolpad/core/AppProvider'; +import { AppProvider, type Session } from '@toolpad/core/AppProvider'; import { Account } from '@toolpad/core/Account'; const demoSession = { @@ -35,17 +31,15 @@ export default function AccountCustomLocaleText() { }, []); return ( - - - {/* preview-start */} - - {/* preview-end */} - - + + {/* preview-start */} + + {/* preview-end */} + ); } diff --git a/docs/data/toolpad/core/components/account/AccountCustomLocaleText.tsx.preview b/docs/data/toolpad/core/components/account/AccountCustomLocaleText.tsx.preview index 882b98486be..1a812378926 100644 --- a/docs/data/toolpad/core/components/account/AccountCustomLocaleText.tsx.preview +++ b/docs/data/toolpad/core/components/account/AccountCustomLocaleText.tsx.preview @@ -1,6 +1,6 @@ \ No newline at end of file diff --git a/docs/data/toolpad/core/components/account/AccountCustomSlotProps.js b/docs/data/toolpad/core/components/account/AccountCustomSlotProps.js index 3995c7e1345..4c3544e3cec 100644 --- a/docs/data/toolpad/core/components/account/AccountCustomSlotProps.js +++ b/docs/data/toolpad/core/components/account/AccountCustomSlotProps.js @@ -1,7 +1,7 @@ import * as React from 'react'; import Logout from '@mui/icons-material/Logout'; import { Account } from '@toolpad/core/Account'; -import { AuthenticationContext, SessionContext } from '@toolpad/core/AppProvider'; +import { AppProvider } from '@toolpad/core/AppProvider'; const demoSession = { user: { @@ -32,36 +32,34 @@ export default function AccountCustomSlotProps() { }, []); return ( - - - {/* preview-start */} - , - }, - preview: { - variant: 'expanded', - slotProps: { - avatarIconButton: { - sx: { - width: 'fit-content', - margin: 'auto', - }, - }, - avatar: { - variant: 'rounded', + + {/* preview-start */} + , + }, + preview: { + variant: 'expanded', + slotProps: { + avatarIconButton: { + sx: { + width: 'fit-content', + margin: 'auto', }, }, + avatar: { + variant: 'rounded', + }, }, - }} - /> - {/* preview-end */} - - + }, + }} + /> + {/* preview-end */} + ); } diff --git a/docs/data/toolpad/core/components/account/AccountCustomSlotProps.tsx b/docs/data/toolpad/core/components/account/AccountCustomSlotProps.tsx index fcfa8bd028c..abfcf8085fe 100644 --- a/docs/data/toolpad/core/components/account/AccountCustomSlotProps.tsx +++ b/docs/data/toolpad/core/components/account/AccountCustomSlotProps.tsx @@ -1,11 +1,7 @@ import * as React from 'react'; import Logout from '@mui/icons-material/Logout'; import { Account } from '@toolpad/core/Account'; -import { - AuthenticationContext, - SessionContext, - Session, -} from '@toolpad/core/AppProvider'; +import { AppProvider, Session } from '@toolpad/core/AppProvider'; const demoSession = { user: { @@ -36,36 +32,34 @@ export default function AccountCustomSlotProps() { }, []); return ( - - - {/* preview-start */} - , - }, - preview: { - variant: 'expanded', - slotProps: { - avatarIconButton: { - sx: { - width: 'fit-content', - margin: 'auto', - }, - }, - avatar: { - variant: 'rounded', + + {/* preview-start */} + , + }, + preview: { + variant: 'expanded', + slotProps: { + avatarIconButton: { + sx: { + width: 'fit-content', + margin: 'auto', }, }, + avatar: { + variant: 'rounded', + }, }, - }} - /> - {/* preview-end */} - - + }, + }} + /> + {/* preview-end */} + ); } diff --git a/docs/data/toolpad/core/components/account/AccountCustomUserDetails.js b/docs/data/toolpad/core/components/account/AccountCustomUserDetails.js index f9ebd5d6e1a..b43327cd9d3 100644 --- a/docs/data/toolpad/core/components/account/AccountCustomUserDetails.js +++ b/docs/data/toolpad/core/components/account/AccountCustomUserDetails.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { Account } from '@toolpad/core/Account'; -import { AuthenticationContext, SessionContext } from '@toolpad/core/AppProvider'; +import { AppProvider } from '@toolpad/core/AppProvider'; import { UserOrg } from '../UserOrg'; const demoSession = { @@ -30,16 +30,14 @@ export default function AccountCustomUserDetails() { }, []); return ( - - - {/* preview-start */} - - {/* preview-end */} - - + + {/* preview-start */} + + {/* preview-end */} + ); } diff --git a/docs/data/toolpad/core/components/account/AccountCustomUserDetails.tsx b/docs/data/toolpad/core/components/account/AccountCustomUserDetails.tsx index 11aecc9f557..b2fc8ee9705 100644 --- a/docs/data/toolpad/core/components/account/AccountCustomUserDetails.tsx +++ b/docs/data/toolpad/core/components/account/AccountCustomUserDetails.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { Account } from '@toolpad/core/Account'; -import { AuthenticationContext, SessionContext } from '@toolpad/core/AppProvider'; +import { AppProvider } from '@toolpad/core/AppProvider'; import { UserOrg, CustomSession } from '../UserOrg'; const demoSession: CustomSession = { @@ -32,16 +32,14 @@ export default function AccountCustomUserDetails() { }, []); return ( - - - {/* preview-start */} - - {/* preview-end */} - - + + {/* preview-start */} + + {/* preview-end */} + ); } diff --git a/docs/data/toolpad/core/components/account/AccountDemoSignedIn.js b/docs/data/toolpad/core/components/account/AccountDemoSignedIn.js index 5f8ea177353..d9602d020d6 100644 --- a/docs/data/toolpad/core/components/account/AccountDemoSignedIn.js +++ b/docs/data/toolpad/core/components/account/AccountDemoSignedIn.js @@ -1,5 +1,5 @@ import * as React from 'react'; -import { AuthenticationContext, SessionContext } from '@toolpad/core/AppProvider'; +import { AppProvider } from '@toolpad/core/AppProvider'; import { Account } from '@toolpad/core/Account'; const demoSession = { @@ -24,12 +24,10 @@ export default function AccountDemoSignedIn() { }, []); return ( - - - {/* preview-start */} - - {/* preview-end */} - - + + {/* preview-start */} + + {/* preview-end */} + ); } diff --git a/docs/data/toolpad/core/components/account/AccountDemoSignedIn.tsx b/docs/data/toolpad/core/components/account/AccountDemoSignedIn.tsx index 718b4ed60fa..de5de86d2b5 100644 --- a/docs/data/toolpad/core/components/account/AccountDemoSignedIn.tsx +++ b/docs/data/toolpad/core/components/account/AccountDemoSignedIn.tsx @@ -1,9 +1,5 @@ import * as React from 'react'; -import { - AuthenticationContext, - SessionContext, - type Session, -} from '@toolpad/core/AppProvider'; +import { AppProvider, type Session } from '@toolpad/core/AppProvider'; import { Account } from '@toolpad/core/Account'; const demoSession = { @@ -28,12 +24,10 @@ export default function AccountDemoSignedIn() { }, []); return ( - - - {/* preview-start */} - - {/* preview-end */} - - + + {/* preview-start */} + + {/* preview-end */} + ); } diff --git a/docs/data/toolpad/core/components/account/AccountDemoSignedOut.js b/docs/data/toolpad/core/components/account/AccountDemoSignedOut.js index c63ddabcf9a..673420426a4 100644 --- a/docs/data/toolpad/core/components/account/AccountDemoSignedOut.js +++ b/docs/data/toolpad/core/components/account/AccountDemoSignedOut.js @@ -1,5 +1,9 @@ import * as React from 'react'; -import { AuthenticationContext, SessionContext } from '@toolpad/core/AppProvider'; +import { + AuthenticationContext, + LocalizationProvider, + SessionContext, +} from '@toolpad/core/AppProvider'; import { Account } from '@toolpad/core/Account'; const demoSession = { @@ -24,10 +28,14 @@ export default function AccountDemoSignedOut() { }, []); return ( - - - - - + // preview-start + + + + + + + + // preview-end ); } diff --git a/docs/data/toolpad/core/components/account/AccountDemoSignedOut.tsx b/docs/data/toolpad/core/components/account/AccountDemoSignedOut.tsx index 1ca57c63430..a0a8d01e75e 100644 --- a/docs/data/toolpad/core/components/account/AccountDemoSignedOut.tsx +++ b/docs/data/toolpad/core/components/account/AccountDemoSignedOut.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { AuthenticationContext, + LocalizationProvider, SessionContext, type Session, } from '@toolpad/core/AppProvider'; @@ -28,10 +29,14 @@ export default function AccountDemoSignedOut() { }, []); return ( - - - - - + // preview-start + + + + + + + + // preview-end ); } diff --git a/docs/data/toolpad/core/components/account/AccountDemoSignedOut.tsx.preview b/docs/data/toolpad/core/components/account/AccountDemoSignedOut.tsx.preview index b0489ce20ca..f3af4f1a296 100644 --- a/docs/data/toolpad/core/components/account/AccountDemoSignedOut.tsx.preview +++ b/docs/data/toolpad/core/components/account/AccountDemoSignedOut.tsx.preview @@ -1,5 +1,7 @@ - - - - - \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.js b/docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.js index c938fa78e17..b0b354e8e8e 100644 --- a/docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.js +++ b/docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.js @@ -1,5 +1,5 @@ import * as React from 'react'; -import { AuthenticationContext, SessionContext } from '@toolpad/core/AppProvider'; +import { AppProvider } from '@toolpad/core/AppProvider'; import { Account } from '@toolpad/core/Account'; import CustomMenu from './CustomMenu'; @@ -25,16 +25,14 @@ export default function AccountSlotsAccountSwitcher() { }, []); return ( - - - {/* preview-start */} - - {/* preview-end */} - - + + {/* preview-start */} + + {/* preview-end */} + ); } diff --git a/docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.tsx b/docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.tsx index 7e89f9663a3..2558e0c1d22 100644 --- a/docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.tsx +++ b/docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.tsx @@ -1,9 +1,5 @@ import * as React from 'react'; -import { - AuthenticationContext, - SessionContext, - type Session, -} from '@toolpad/core/AppProvider'; +import { AppProvider, type Session } from '@toolpad/core/AppProvider'; import { Account } from '@toolpad/core/Account'; import CustomMenu from './CustomMenu'; @@ -29,16 +25,14 @@ export default function AccountSlotsAccountSwitcher() { }, []); return ( - - - {/* preview-start */} - - {/* preview-end */} - - + + {/* preview-start */} + + {/* preview-end */} + ); } diff --git a/docs/data/toolpad/core/components/account/account.md b/docs/data/toolpad/core/components/account/account.md index c506aca73b7..cfc30537fbd 100644 --- a/docs/data/toolpad/core/components/account/account.md +++ b/docs/data/toolpad/core/components/account/account.md @@ -26,7 +26,7 @@ If a `session` object is present, the component is rendered as a dropdown contai When signed out, the component renders as an inline sign in button within the dashboard layout. -{{"demo": "AccountDemoSignedOut.js", "bg": "outlined", "defaultCodeOpen": false }} +{{"demo": "AccountDemoSignedOut.js", "bg": "outlined", "defaultCodeOpen": false}} ## Customization @@ -61,7 +61,7 @@ You can build advanced menus – such as a tenant switcher – by passing in a c {{"demo": "AccountSlotsAccountSwitcher.js", "bg": "outlined"}} -### Labels +### Localization You can pass in custom labels – including of different languages – using the `localeText` prop. diff --git a/docs/data/toolpad/core/components/sign-in-page/LocaleSignInPage.js b/docs/data/toolpad/core/components/sign-in-page/LocaleSignInPage.js new file mode 100644 index 00000000000..0e5ff86e899 --- /dev/null +++ b/docs/data/toolpad/core/components/sign-in-page/LocaleSignInPage.js @@ -0,0 +1,36 @@ +import * as React from 'react'; +import { AppProvider } from '@toolpad/core/AppProvider'; +import { SignInPage } from '@toolpad/core/SignInPage'; +import hiIN from '@toolpad/core/locales/hiIN'; + +const providers = [ + { id: 'github', name: 'GitHub' }, + { id: 'google', name: 'Google' }, + { id: 'credentials', name: 'Email and Password' }, +]; + +const signIn = async (provider) => { + const promise = new Promise((resolve) => { + setTimeout(() => { + console.log(`Sign in with ${provider.id}`); + resolve({ error: 'This is a mock error message.' }); + }, 500); + }); + return promise; +}; + +export default function LocaleSignInPage() { + return ( + // preview-start + + + + // preview-end + ); +} diff --git a/docs/data/toolpad/core/components/sign-in-page/LocaleSignInPage.tsx b/docs/data/toolpad/core/components/sign-in-page/LocaleSignInPage.tsx new file mode 100644 index 00000000000..cc64d7cc187 --- /dev/null +++ b/docs/data/toolpad/core/components/sign-in-page/LocaleSignInPage.tsx @@ -0,0 +1,42 @@ +import * as React from 'react'; +import { AppProvider } from '@toolpad/core/AppProvider'; +import { + SignInPage, + type AuthProvider, + type AuthResponse, +} from '@toolpad/core/SignInPage'; +import hiIN from '@toolpad/core/locales/hiIN'; + +const providers = [ + { id: 'github', name: 'GitHub' }, + { id: 'google', name: 'Google' }, + { id: 'credentials', name: 'Email and Password' }, +]; + +const signIn: (provider: AuthProvider) => void | Promise = async ( + provider, +) => { + const promise = new Promise((resolve) => { + setTimeout(() => { + console.log(`Sign in with ${provider.id}`); + resolve({ error: 'This is a mock error message.' }); + }, 500); + }); + return promise; +}; + +export default function LocaleSignInPage() { + return ( + // preview-start + + + + // preview-end + ); +} diff --git a/docs/data/toolpad/core/components/sign-in-page/LocaleSignInPage.tsx.preview b/docs/data/toolpad/core/components/sign-in-page/LocaleSignInPage.tsx.preview new file mode 100644 index 00000000000..49d7fc1ee6f --- /dev/null +++ b/docs/data/toolpad/core/components/sign-in-page/LocaleSignInPage.tsx.preview @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/docs/data/toolpad/core/components/sign-in-page/sign-in-page.md b/docs/data/toolpad/core/components/sign-in-page/sign-in-page.md index 9abf4d7bef6..32fffc5edea 100644 --- a/docs/data/toolpad/core/components/sign-in-page/sign-in-page.md +++ b/docs/data/toolpad/core/components/sign-in-page/sign-in-page.md @@ -1,7 +1,7 @@ --- productId: toolpad-core title: Sign-in Page -components: SignInPage, Account, NotificationsProvider +components: SignInPage, Account, NotificationsProvider, LocalizationProvider --- # Sign-in Page @@ -14,6 +14,26 @@ If this is your first time using Toolpad Core, it's recommended to read about th The `SignInPage` component is a quick way to generate a ready-to-use authentication page with multiple OAuth providers, or a credentials form. +## Basic Usage + +```tsx +import { AppProvider } from '@toolpad/core/AppProvider'; +import { SignInPage } from '@toolpad/core/SignInPage'; + +export default function App() { + return ( + + { + // Your sign in logic + }} + /> + + ); +} +``` + ## OAuth The `SignInPage` component can be set up with an OAuth provider by passing in a list of providers in the `providers` prop, along with a `signIn` function that accepts the `provider` as a parameter. @@ -264,6 +284,12 @@ You can use the `slotProps` prop to pass props to the underlying components of e {{"demo": "SlotPropsSignIn.js", "iframe": true, "height": 600 }} +### Localization + +Beyond the [global localization options](/toolpad/core/introduction/base-concepts/#localization) possible, you can customize the labels of the `SignInPage` using the `localeText` prop: + +{{"demo": "LocaleSignInPage.js", "iframe": true, "height": 700 }} + ### 🚧 Layouts The `SignInPage` component has versions with different layouts for authentication - one column, two column and others such. The APIs of these components are identical. This is in progress. diff --git a/docs/data/toolpad/core/introduction/base-concepts.md b/docs/data/toolpad/core/introduction/base-concepts.md index ae008ad48dc..72602c3c2ba 100644 --- a/docs/data/toolpad/core/introduction/base-concepts.md +++ b/docs/data/toolpad/core/introduction/base-concepts.md @@ -105,3 +105,124 @@ In this example: - The `slots` prop allows you to replace entire parts of the component. - The `slotProps` prop lets you pass additional props to specific slots. + +## Localization + +Toolpad components support translations between languages. You can modify text and translations inside Toolpad components in several ways. + +The default locale is English (United States). To use other locales, follow the instructions below. + +### Set translations globally + +#### Using the theme + +To translate all your Toolpad components, you can provide translations through the theme: + +```tsx +import { createTheme, ThemeProvider } from '@mui/material/styles'; +import hiIN from '@toolpad/core/locales/hiIN'; + +const theme = createTheme({ + { + palette: { + primary: { main: '#1976d2' }, + }, + }, + hiIN, +}); + + // ... + {children}; + +``` + +#### Using the `localeText` prop + +If you want to pass language translations without using `createTheme`, you can directly provide them through the `localeText` prop on the `AppProvider`: + +```tsx +import { AppProvider } from '@toolpad/core/AppProvider'; +import hiIN from '@toolpad/core/locales/hiIN'; + +function App({ children }) { + return ( + + {children} + + ); +} +``` + +If you are not using the `AppProvider` in your app, you can just use the `LocalizationProvider`: + +```tsx +import { LocalizationProvider } from '@toolpad/core/AppProvider'; +import hiIN from '@toolpad/core/locales/hiIN'; + +function App({ children }) { + return ( + + {children} + + ); +} +``` + +### Set translations locally + +If you want to customize some translations on a specific component, you can use the `localeText` prop exposed by all components. + +```tsx + +``` + +### Priority order + +The localization system follows a specific priority order when applying translations: + +1. `localeText` prop provided directly to a specific component (highest priority) +2. `localeText` prop provided directly to `AppProvider` +3. Translations provided through the theme +4. Default English translations (lowest priority) + +:::info +If you pass a locale text through the `AppProvider` or the theme, and you provide translation keys through the `localeText` prop of a component at the same time, then the latter will override the former to the extent of the keys which it has available: + +  + +```tsx + + + +``` + +  + +This will produce the following result: + +- `SignInPage` title with text **Sign In** taken from the `AppProvider` `localeText` prop +- `Account` with title **Compte** overridden by the `Account` `localeText` prop + +::: + +### Access localization keys + +You can access your localization keys in custom components using the `useLocaleText()` hook. + +```tsx +import { useLocaleText } from '@toolpad/core/AppProvider'; + +function CustomMenu() { + // ... + const localeText = useLocaleText(); + // ... +} +``` diff --git a/docs/data/toolpad/core/pagesApi.js b/docs/data/toolpad/core/pagesApi.js index c36b8e189db..94f6be469bd 100644 --- a/docs/data/toolpad/core/pagesApi.js +++ b/docs/data/toolpad/core/pagesApi.js @@ -6,6 +6,7 @@ module.exports = [ { pathname: '/toolpad/core/api/app-provider' }, { pathname: '/toolpad/core/api/dashboard-layout' }, { pathname: '/toolpad/core/api/dialogs-provider' }, + { pathname: '/toolpad/core/api/localization-provider' }, { pathname: '/toolpad/core/api/notifications-provider' }, { pathname: '/toolpad/core/api/page-container' }, { pathname: '/toolpad/core/api/page-header' }, diff --git a/docs/pages/toolpad/core/api/account.json b/docs/pages/toolpad/core/api/account.json index b762be1d771..0abec963639 100644 --- a/docs/pages/toolpad/core/api/account.json +++ b/docs/pages/toolpad/core/api/account.json @@ -3,7 +3,7 @@ "localeText": { "type": { "name": "shape", - "description": "{ iconButtonAriaLabel?: string, signInLabel?: string, signOutLabel?: string }" + "description": "{ accountPreviewIconButtonLabel?: string, accountPreviewTitle?: string, accountSignInLabel?: string, accountSignOutLabel?: string }" } }, "slotProps": { diff --git a/docs/pages/toolpad/core/api/app-provider.json b/docs/pages/toolpad/core/api/app-provider.json index 6918852f106..a1d5185667b 100644 --- a/docs/pages/toolpad/core/api/app-provider.json +++ b/docs/pages/toolpad/core/api/app-provider.json @@ -12,6 +12,12 @@ }, "default": "null" }, + "localeText": { + "type": { + "name": "shape", + "description": "{ accountPreviewIconButtonLabel?: string, accountPreviewTitle?: string, accountSignInLabel?: string, accountSignOutLabel?: string, alert?: string, cancel?: string, close?: string, confirm?: string, delete?: string, email?: string, loading?: string, magicLinkSignInTitle?: string, oauthSignInTitle?: string, ok?: string, or?: string, passkey?: string, passkeySignInTitle?: string, password?: string, save?: string, signInRememberMe?: string, signInSubtitle?: string, signInTitle?: string, to?: string, username?: string, with?: string }" + } + }, "navigation": { "type": { "name": "arrayOf", diff --git a/docs/pages/toolpad/core/api/dashboard-layout.json b/docs/pages/toolpad/core/api/dashboard-layout.json index 6cb95c85aa3..77c901b54ae 100644 --- a/docs/pages/toolpad/core/api/dashboard-layout.json +++ b/docs/pages/toolpad/core/api/dashboard-layout.json @@ -25,7 +25,7 @@ "slotProps": { "type": { "name": "shape", - "description": "{ appTitle?: { branding?: { homeUrl?: string, logo?: node, title?: string } }, sidebarFooter?: { mini: bool }, toolbarAccount?: { localeText?: { iconButtonAriaLabel?: string, signInLabel?: string, signOutLabel?: string }, slotProps?: { popover?: object, popoverContent?: object, preview?: object, signInButton?: object, signOutButton?: object }, slots?: { popover?: elementType, popoverContent?: elementType, preview?: elementType, signInButton?: elementType, signOutButton?: elementType } }, toolbarActions?: object }" + "description": "{ appTitle?: { branding?: { homeUrl?: string, logo?: node, title?: string } }, sidebarFooter?: { mini: bool }, toolbarAccount?: { localeText?: { accountPreviewIconButtonLabel?: string, accountPreviewTitle?: string, accountSignInLabel?: string, accountSignOutLabel?: string }, slotProps?: { popover?: object, popoverContent?: object, preview?: object, signInButton?: object, signOutButton?: object }, slots?: { popover?: elementType, popoverContent?: elementType, preview?: elementType, signInButton?: elementType, signOutButton?: elementType } }, toolbarActions?: object }" }, "default": "{}" }, diff --git a/docs/pages/toolpad/core/api/localization-provider.js b/docs/pages/toolpad/core/api/localization-provider.js new file mode 100644 index 00000000000..0c8377e375e --- /dev/null +++ b/docs/pages/toolpad/core/api/localization-provider.js @@ -0,0 +1,23 @@ +import * as React from 'react'; +import ApiPage from 'docs/src/modules/components/ApiPage'; +import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations'; +import jsonPageContent from './localization-provider.json'; + +export default function Page(props) { + const { descriptions, pageContent } = props; + return ; +} + +Page.getInitialProps = () => { + const req = require.context( + 'docs-toolpad/translations/api-docs/localization-provider', + false, + /\.\/localization-provider.*.json$/, + ); + const descriptions = mapApiPageTranslations(req); + + return { + descriptions, + pageContent: jsonPageContent, + }; +}; diff --git a/docs/pages/toolpad/core/api/localization-provider.json b/docs/pages/toolpad/core/api/localization-provider.json new file mode 100644 index 00000000000..fc9e679f9e5 --- /dev/null +++ b/docs/pages/toolpad/core/api/localization-provider.json @@ -0,0 +1,18 @@ +{ + "props": { + "localeText": { + "type": { + "name": "shape", + "description": "{ accountPreviewIconButtonLabel?: string, accountPreviewTitle?: string, accountSignInLabel?: string, accountSignOutLabel?: string, alert?: string, cancel?: string, close?: string, confirm?: string, delete?: string, email?: string, loading?: string, magicLinkSignInTitle?: string, oauthSignInTitle?: string, ok?: string, or?: string, passkey?: string, passkeySignInTitle?: string, password?: string, save?: string, signInRememberMe?: string, signInSubtitle?: string, signInTitle?: string, to?: string, username?: string, with?: string }" + } + } + }, + "name": "LocalizationProvider", + "imports": ["import { LocalizationProvider } from '@toolpad/core/AppProvider';"], + "classes": [], + "muiName": "LocalizationProvider", + "filename": "/packages/toolpad-core/src/AppProvider/LocalizationProvider.tsx", + "inheritance": null, + "demos": "", + "cssComponent": false +} diff --git a/docs/pages/toolpad/core/api/sign-in-page.json b/docs/pages/toolpad/core/api/sign-in-page.json index 132fb1607bc..e4b378b3898 100644 --- a/docs/pages/toolpad/core/api/sign-in-page.json +++ b/docs/pages/toolpad/core/api/sign-in-page.json @@ -1,5 +1,11 @@ { "props": { + "localeText": { + "type": { + "name": "shape", + "description": "{ email?: string, or?: string, passkey?: string, password?: string, signInRememberMe?: string, signInSubtitle?: string, signInTitle?: string, to?: string, with?: string }" + } + }, "providers": { "type": { "name": "arrayOf", "description": "Array<{ id: string, name: string }>" }, "default": "[]" diff --git a/docs/translations/api-docs/app-provider/app-provider.json b/docs/translations/api-docs/app-provider/app-provider.json index 7450a9fa118..7e60f68b465 100644 --- a/docs/translations/api-docs/app-provider/app-provider.json +++ b/docs/translations/api-docs/app-provider/app-provider.json @@ -4,6 +4,7 @@ "authentication": { "description": "Authentication methods." }, "branding": { "description": "Branding options for the app." }, "children": { "description": "The content of the app provider." }, + "localeText": { "description": "Locale text for components" }, "navigation": { "description": "Navigation definition for the app." }, "router": { "description": "Router implementation used inside Toolpad components." }, "session": { "description": "Session info about the current user." }, diff --git a/docs/translations/api-docs/localization-provider/localization-provider.json b/docs/translations/api-docs/localization-provider/localization-provider.json new file mode 100644 index 00000000000..4b8c01172af --- /dev/null +++ b/docs/translations/api-docs/localization-provider/localization-provider.json @@ -0,0 +1,5 @@ +{ + "componentDescription": "", + "propDescriptions": { "localeText": { "description": "Locale for components texts" } }, + "classDescriptions": {} +} diff --git a/docs/translations/api-docs/sign-in-page/sign-in-page.json b/docs/translations/api-docs/sign-in-page/sign-in-page.json index de36472fb55..d72225d77de 100644 --- a/docs/translations/api-docs/sign-in-page/sign-in-page.json +++ b/docs/translations/api-docs/sign-in-page/sign-in-page.json @@ -1,6 +1,7 @@ { "componentDescription": "", "propDescriptions": { + "localeText": { "description": "The labels for the account component." }, "providers": { "description": "The list of authentication providers to display." }, "signIn": { "description": "Callback fired when a user signs in.", diff --git a/packages/toolpad-core/src/Account/Account.tsx b/packages/toolpad-core/src/Account/Account.tsx index c63be63b1ff..3f09101dba2 100644 --- a/packages/toolpad-core/src/Account/Account.tsx +++ b/packages/toolpad-core/src/Account/Account.tsx @@ -10,7 +10,16 @@ import { AccountPreview, AccountPreviewProps } from './AccountPreview'; import { AccountPopoverHeader } from './AccountPopoverHeader'; import { AccountPopoverFooter } from './AccountPopoverFooter'; import { SessionContext, AuthenticationContext } from '../AppProvider/AppProvider'; -import { LocaleProvider, useLocaleText } from '../shared/locales/LocaleContext'; +import { useLocaleText, type LocaleText } from '../AppProvider/LocalizationProvider'; +import { AccountLocaleContext } from './AccountLocaleContext'; + +interface AccountLocaleText { + accountSignInLabel: string; + accountSignOutLabel: string; + + accountPreviewIconButtonLabel: string; + accountPreviewTitle: string; +} export interface AccountSlots { /** @@ -58,9 +67,16 @@ export interface AccountProps { /** * The labels for the account component. */ - localeText?: Partial>; + localeText?: Partial; } +const defaultAccountLocaleText: Pick = { + accountPreviewIconButtonLabel: 'Current User', + accountPreviewTitle: 'Account', + accountSignInLabel: 'Sign in', + accountSignOutLabel: 'Sign out', +}; + /** * * Demos: @@ -74,7 +90,12 @@ export interface AccountProps { * - [Account API](https://mui.com/toolpad/core/api/account) */ function Account(props: AccountProps) { - const { localeText } = props; + const { localeText: propsLocaleText } = props; + const globalLocaleText = useLocaleText(); + const localeText = React.useMemo( + () => ({ ...defaultAccountLocaleText, ...globalLocaleText, ...propsLocaleText }), + [globalLocaleText, propsLocaleText], + ); const { slots, slotProps } = props; const [anchorEl, setAnchorEl] = React.useState(null); const session = React.useContext(SessionContext); @@ -93,88 +114,92 @@ function Account(props: AccountProps) { return null; } + let accountContent = null; + if (!session?.user) { - return ( - - {slots?.signInButton ? ( - + accountContent = slots?.signInButton ? ( + + ) : ( + + ); + } else { + accountContent = ( + + {slots?.preview ? ( + ) : ( - + )} - + {slots?.popover ? ( + + ) : ( + + `drop-shadow(0px 2px 8px ${theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.10)' : 'rgba(0,0,0,0.32)'})`, + mt: 1, + '&::before': { + content: '""', + display: 'block', + position: 'absolute', + top: 0, + right: 14, + width: 10, + height: 10, + bgcolor: 'background.paper', + transform: 'translateY(-50%) rotate(45deg)', + zIndex: 0, + }, + }, + }, + ...slotProps?.popover?.slotProps, + }} + > + {slots?.popoverContent ? ( + + ) : ( + + + + + + + + + + )} + + )} + ); } return ( - - {slots?.preview ? ( - - ) : ( - - )} - {slots?.popover ? ( - - ) : ( - - `drop-shadow(0px 2px 8px ${theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.10)' : 'rgba(0,0,0,0.32)'})`, - mt: 1, - '&::before': { - content: '""', - display: 'block', - position: 'absolute', - top: 0, - right: 14, - width: 10, - height: 10, - bgcolor: 'background.paper', - transform: 'translateY(-50%) rotate(45deg)', - zIndex: 0, - }, - }, - }, - ...slotProps?.popover?.slotProps, - }} - > - {slots?.popoverContent ? ( - - ) : ( - - - - - - - - - - )} - - )} - + + {accountContent} + ); } @@ -187,9 +212,10 @@ Account.propTypes /* remove-proptypes */ = { * The labels for the account component. */ localeText: PropTypes.shape({ - iconButtonAriaLabel: PropTypes.string, - signInLabel: PropTypes.string, - signOutLabel: PropTypes.string, + accountPreviewIconButtonLabel: PropTypes.string, + accountPreviewTitle: PropTypes.string, + accountSignInLabel: PropTypes.string, + accountSignOutLabel: PropTypes.string, }), /** * The props used for each slot inside. diff --git a/packages/toolpad-core/src/Account/AccountLocaleContext.tsx b/packages/toolpad-core/src/Account/AccountLocaleContext.tsx new file mode 100644 index 00000000000..4479c4b2b66 --- /dev/null +++ b/packages/toolpad-core/src/Account/AccountLocaleContext.tsx @@ -0,0 +1,7 @@ +import * as React from 'react'; +import type { LocaleText } from '../AppProvider/LocalizationProvider'; + +/** + * @ignore - internal component. + */ +export const AccountLocaleContext = React.createContext | null>(null); diff --git a/packages/toolpad-core/src/Account/AccountPreview.tsx b/packages/toolpad-core/src/Account/AccountPreview.tsx index 0875eca70f4..a5f41386680 100644 --- a/packages/toolpad-core/src/Account/AccountPreview.tsx +++ b/packages/toolpad-core/src/Account/AccountPreview.tsx @@ -8,7 +8,8 @@ import Stack from '@mui/material/Stack'; import IconButton, { IconButtonProps } from '@mui/material/IconButton'; import MoreVertIcon from '@mui/icons-material/MoreVert'; import { SessionContext } from '../AppProvider'; -import { useLocaleText } from '../shared/locales/LocaleContext'; +import { useLocaleText } from '../AppProvider/LocalizationProvider'; +import { AccountLocaleContext } from './AccountLocaleContext'; export type AccountPreviewVariant = 'condensed' | 'expanded'; @@ -79,7 +80,9 @@ export interface AccountPreviewProps { function AccountPreview(props: AccountPreviewProps) { const { slots, variant = 'condensed', slotProps, open, handleClick, sx } = props; const session = React.useContext(SessionContext); - const localeText = useLocaleText(); + const globalLocaleText = useLocaleText(); + const accountLocaleText = React.useContext(AccountLocaleContext); + const localeText = { ...globalLocaleText, ...accountLocaleText }; if (!session || !session.user) { return null; @@ -128,14 +131,14 @@ function AccountPreview(props: AccountPreviewProps) { } return ( - + {slots?.avatarIconButton ? ( ) : ( - {localeText?.signInLabel || 'Sign In'} + {localeText?.accountSignInLabel} ); } diff --git a/packages/toolpad-core/src/Account/SignOutButton.tsx b/packages/toolpad-core/src/Account/SignOutButton.tsx index e9d8e1701b8..45add1b6fec 100644 --- a/packages/toolpad-core/src/Account/SignOutButton.tsx +++ b/packages/toolpad-core/src/Account/SignOutButton.tsx @@ -2,8 +2,9 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import Button, { ButtonProps } from '@mui/material/Button'; import LogoutIcon from '@mui/icons-material/Logout'; -import { AuthenticationContext } from '../AppProvider/AppProvider'; -import { useLocaleText } from '../shared/locales/LocaleContext'; +import { AuthenticationContext } from '../AppProvider'; +import { useLocaleText } from '../AppProvider/LocalizationProvider'; +import { AccountLocaleContext } from './AccountLocaleContext'; export type SignOutButtonProps = ButtonProps; @@ -19,7 +20,9 @@ export type SignOutButtonProps = ButtonProps; */ function SignOutButton(props: SignOutButtonProps) { const authentication = React.useContext(AuthenticationContext); - const localeText = useLocaleText(); + const globalLocaleText = useLocaleText(); + const accountLocaleText = React.useContext(AccountLocaleContext); + const localeText = { ...globalLocaleText, ...accountLocaleText }; return ( ); } diff --git a/packages/toolpad-core/src/AppProvider/AppProvider.tsx b/packages/toolpad-core/src/AppProvider/AppProvider.tsx index 0de8c74eec7..da4be530390 100644 --- a/packages/toolpad-core/src/AppProvider/AppProvider.tsx +++ b/packages/toolpad-core/src/AppProvider/AppProvider.tsx @@ -11,6 +11,7 @@ import { WindowContext, } from '../shared/context'; import { AppThemeProvider } from './AppThemeProvider'; +import { LocalizationProvider, type LocaleText } from './LocalizationProvider'; export interface NavigateOptions { history?: 'auto' | 'push' | 'replace'; @@ -103,6 +104,10 @@ export interface AppProviderProps { * @default null */ router?: Router; + /** + * Locale text for components + */ + localeText?: Partial; /** * Session info about the current user. * @default null @@ -147,6 +152,7 @@ function AppProvider(props: AppProviderProps) { theme = createTheme(), branding = null, navigation = [], + localeText, router = null, authentication = null, session = null, @@ -159,15 +165,17 @@ function AppProvider(props: AppProviderProps) { - - - - - {children} - - - - + + + + + + {children} + + + + + @@ -202,6 +210,36 @@ AppProvider.propTypes /* remove-proptypes */ = { * The content of the app provider. */ children: PropTypes.node, + /** + * Locale text for components + */ + localeText: PropTypes.shape({ + accountPreviewIconButtonLabel: PropTypes.string, + accountPreviewTitle: PropTypes.string, + accountSignInLabel: PropTypes.string, + accountSignOutLabel: PropTypes.string, + alert: PropTypes.string, + cancel: PropTypes.string, + close: PropTypes.string, + confirm: PropTypes.string, + delete: PropTypes.string, + email: PropTypes.string, + loading: PropTypes.string, + magicLinkSignInTitle: PropTypes.string, + oauthSignInTitle: PropTypes.string, + ok: PropTypes.string, + or: PropTypes.string, + passkey: PropTypes.string, + passkeySignInTitle: PropTypes.string, + password: PropTypes.string, + save: PropTypes.string, + signInRememberMe: PropTypes.string, + signInSubtitle: PropTypes.string, + signInTitle: PropTypes.string, + to: PropTypes.string, + username: PropTypes.string, + with: PropTypes.string, + }), /** * Navigation definition for the app. * @default [] diff --git a/packages/toolpad-core/src/AppProvider/LocalizationProvider.tsx b/packages/toolpad-core/src/AppProvider/LocalizationProvider.tsx new file mode 100644 index 00000000000..dc05312cae3 --- /dev/null +++ b/packages/toolpad-core/src/AppProvider/LocalizationProvider.tsx @@ -0,0 +1,132 @@ +'use client'; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { useTheme } from '@mui/material/styles'; +import DEFAULT_LOCALE from '../locales/en'; + +export interface LocaleText { + // Account + accountSignInLabel: string; + accountSignOutLabel: string; + + // AccountPreview + accountPreviewIconButtonLabel: string; + accountPreviewTitle: string; + + // SignInPage + signInTitle: string; + signInSubtitle: string; + oauthSignInTitle: string; + passkeySignInTitle: string; + magicLinkSignInTitle: string; + signInRememberMe: string; + + // Common authentication labels + email: string; + passkey: string; + username: string; + password: string; + + // Common action labels + or: string; + to: string; + with: string; + save: string; + cancel: string; + ok: string; + close: string; + delete: string; + alert: string; + confirm: string; + loading: string; +} + +export interface LocalizationProviderProps { + children?: React.ReactNode; + /** + * Locale for components texts + */ + localeText?: Partial; +} + +export const LocalizationContext = React.createContext>({}); + +const LocalizationProvider = function LocalizationProvider(props: LocalizationProviderProps) { + const { localeText: propsLocaleText, children } = props; + + const theme = useTheme(); + // @ts-ignore + const themeLocaleText = theme?.components?.MuiLocalizationProvider?.defaultProps?.localeText; + + const defaultLocaleText = + DEFAULT_LOCALE.components.MuiLocalizationProvider.defaultProps.localeText; + + /* The order of overrides is: + * 1. The `localeText` prop of the `AppProvider` supersedes + * 2. The localeText provided as an argument to the `createTheme` function, which supersedes + * 3. The default locale text + */ + + const localeText = React.useMemo( + () => ({ ...defaultLocaleText, ...themeLocaleText, ...propsLocaleText }), + [defaultLocaleText, themeLocaleText, propsLocaleText], + ); + + return {children}; +}; + +LocalizationProvider.propTypes /* remove-proptypes */ = { + // ┌────────────────────────────── Warning ──────────────────────────────┐ + // │ These PropTypes are generated from the TypeScript type definitions. │ + // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ + // └─────────────────────────────────────────────────────────────────────┘ + /** + * @ignore + */ + children: PropTypes.node, + /** + * Locale for components texts + */ + localeText: PropTypes.shape({ + accountPreviewIconButtonLabel: PropTypes.string, + accountPreviewTitle: PropTypes.string, + accountSignInLabel: PropTypes.string, + accountSignOutLabel: PropTypes.string, + alert: PropTypes.string, + cancel: PropTypes.string, + close: PropTypes.string, + confirm: PropTypes.string, + delete: PropTypes.string, + email: PropTypes.string, + loading: PropTypes.string, + magicLinkSignInTitle: PropTypes.string, + oauthSignInTitle: PropTypes.string, + ok: PropTypes.string, + or: PropTypes.string, + passkey: PropTypes.string, + passkeySignInTitle: PropTypes.string, + password: PropTypes.string, + save: PropTypes.string, + signInRememberMe: PropTypes.string, + signInSubtitle: PropTypes.string, + signInTitle: PropTypes.string, + to: PropTypes.string, + username: PropTypes.string, + with: PropTypes.string, + }), +} as any; + +export { LocalizationProvider }; +/** + * + * Demos: + * + * - [Sign-in Page](https://mui.com/toolpad/core/react-sign-in-page/) + * + * API: + * + * - [LocalizationProvider API](https://mui.com/toolpad/core/api/localization-provider) + */ +export function useLocaleText() { + return React.useContext(LocalizationContext); +} diff --git a/packages/toolpad-core/src/AppProvider/index.ts b/packages/toolpad-core/src/AppProvider/index.ts index bd2c0cdccb2..007fe400d13 100644 --- a/packages/toolpad-core/src/AppProvider/index.ts +++ b/packages/toolpad-core/src/AppProvider/index.ts @@ -1 +1,2 @@ export * from './AppProvider'; +export * from './LocalizationProvider'; diff --git a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx index 9146f9a53b0..8661491a1c8 100644 --- a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx +++ b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx @@ -546,9 +546,10 @@ DashboardLayout.propTypes /* remove-proptypes */ = { }), toolbarAccount: PropTypes.shape({ localeText: PropTypes.shape({ - iconButtonAriaLabel: PropTypes.string, - signInLabel: PropTypes.string, - signOutLabel: PropTypes.string, + accountPreviewIconButtonLabel: PropTypes.string, + accountPreviewTitle: PropTypes.string, + accountSignInLabel: PropTypes.string, + accountSignOutLabel: PropTypes.string, }), slotProps: PropTypes.shape({ popover: PropTypes.object, diff --git a/packages/toolpad-core/src/SignInPage/SignInPage.tsx b/packages/toolpad-core/src/SignInPage/SignInPage.tsx index 5a5c489d992..ea865d530e9 100644 --- a/packages/toolpad-core/src/SignInPage/SignInPage.tsx +++ b/packages/toolpad-core/src/SignInPage/SignInPage.tsx @@ -37,6 +37,7 @@ import KeycloakIcon from './icons/Keycloak'; import OktaIcon from './icons/Okta'; import FusionAuthIcon from './icons/FusionAuth'; import { BrandingContext, RouterContext } from '../shared/context'; +import { useLocaleText, type LocaleText } from '../AppProvider/LocalizationProvider'; const mergeSlotSx = (defaultSx: SxProps, slotProps?: { sx?: SxProps }) => { if (Array.isArray(slotProps?.sx)) { @@ -135,6 +136,18 @@ const IconProviderMap = new Map([ ['fusionauth', ], ]); +interface SignInPageLocaleText { + signInTitle: string; + signInSubtitle: string; + signInRememberMe: string; + email: string; + password: string; + or: string; + with: string; + passkey: string; + to: string; +} + export interface AuthProvider { /** * The unique identifier of the authentication provider. @@ -260,8 +273,24 @@ export interface SignInPageProps { * The prop used to customize the styles on the `SignInPage` container */ sx?: SxProps; + /** + * The labels for the account component. + */ + localeText?: Partial; } +const defaultLocaleText: Pick = { + signInTitle: 'Sign in', + signInSubtitle: 'Please sign in to continue', + signInRememberMe: 'Remember me', + email: 'Email', + password: 'Password', + or: 'or', + with: 'with', + passkey: 'Passkey', + to: 'to', +}; + /** * * Demos: @@ -273,10 +302,13 @@ export interface SignInPageProps { * - [SignInPage API](https://mui.com/toolpad/core/api/sign-in-page) */ function SignInPage(props: SignInPageProps) { - const { providers, signIn, slots, slotProps, sx } = props; + const { providers, signIn, slots, slotProps, sx, localeText: propsLocaleText } = props; const theme = useTheme(); const branding = React.useContext(BrandingContext); const router = React.useContext(RouterContext); + const globalLocaleText = useLocaleText(); + const localeText = { ...defaultLocaleText, ...globalLocaleText, ...propsLocaleText }; + const passkeyProvider = providers?.find((provider) => provider.id === 'passkey'); const credentialsProvider = providers?.find((provider) => provider.id === 'credentials'); const emailProvider = providers?.find((provider) => provider.id === 'nodemailer'); @@ -339,14 +371,15 @@ function SignInPage(props: SignInPageProps) { fontWeight: 600, }} > - Sign in {branding?.title ? `to ${branding.title}` : null} + {localeText.signInTitle}{' '} + {branding?.title ? `${localeText.to?.toLocaleLowerCase()} ${branding.title}` : null} )} {slots?.subtitle ? ( ) : ( - Welcome, please sign in to continue + {localeText?.signInSubtitle} )} @@ -391,7 +424,11 @@ function SignInPage(props: SignInPageProps) { textTransform: 'capitalize', }} > - Sign in with {provider.name} + + {localeText.oauthSignInTitle || + `${localeText.signInTitle} ${localeText.with}`}{' '} + {provider.name} + ); @@ -400,7 +437,9 @@ function SignInPage(props: SignInPageProps) { {passkeyProvider ? ( - {singleProvider ? null : or} + {singleProvider ? null : ( + {localeText.or} + )} {error && selectedProviderId === 'passkey' ? ( {error} @@ -429,7 +468,7 @@ function SignInPage(props: SignInPageProps) { ) : ( - Sign in with {passkeyProvider.name || 'Passkey'} + {localeText.passkeySignInTitle || + `${localeText.signInTitle} ${localeText.with}`}{' '} + {passkeyProvider.name || localeText.passkey} )} @@ -468,7 +509,9 @@ function SignInPage(props: SignInPageProps) { {emailProvider ? ( - {singleProvider ? null : or} + {singleProvider ? null : ( + {localeText.or} + )} {error && selectedProviderId === 'nodemailer' ? ( {error} @@ -503,7 +546,7 @@ function SignInPage(props: SignInPageProps) { ) : ( - Sign in with Email + {localeText.magicLinkSignInTitle || + `${localeText.signInTitle} ${localeText.with}`}{' '} + {localeText.email} )} @@ -542,7 +587,9 @@ function SignInPage(props: SignInPageProps) { {credentialsProvider ? ( - {singleProvider ? null : or} + {singleProvider ? null : ( + {localeText.or} + )} {error && selectedProviderId === 'credentials' ? ( {error} @@ -576,7 +623,7 @@ function SignInPage(props: SignInPageProps) { ) : ( } - label="Remember me" + label={localeText.signInRememberMe} {...slotProps?.rememberMe} slotProps={{ typography: { @@ -657,7 +704,7 @@ function SignInPage(props: SignInPageProps) { }} {...slotProps?.submitButton} > - Sign in + {localeText.signInTitle} )} @@ -681,6 +728,20 @@ SignInPage.propTypes /* remove-proptypes */ = { // │ These PropTypes are generated from the TypeScript type definitions. │ // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ // └─────────────────────────────────────────────────────────────────────┘ + /** + * The labels for the account component. + */ + localeText: PropTypes.shape({ + email: PropTypes.string, + or: PropTypes.string, + passkey: PropTypes.string, + password: PropTypes.string, + signInRememberMe: PropTypes.string, + signInSubtitle: PropTypes.string, + signInTitle: PropTypes.string, + to: PropTypes.string, + with: PropTypes.string, + }), /** * The list of authentication providers to display. * @default [] diff --git a/packages/toolpad-core/src/locales/en.tsx b/packages/toolpad-core/src/locales/en.tsx new file mode 100644 index 00000000000..0f55d82cd64 --- /dev/null +++ b/packages/toolpad-core/src/locales/en.tsx @@ -0,0 +1,41 @@ +import type { LocaleText } from '../AppProvider'; +import { getLocalization } from './getLocalization'; + +const en: LocaleText = { + // Account + accountSignInLabel: 'Sign In', + accountSignOutLabel: 'Sign Out', + + // AccountPreview + accountPreviewTitle: 'Account', + accountPreviewIconButtonLabel: 'Current User', + + // SignInPage + signInTitle: 'Sign In', + signInSubtitle: 'Welcome user, please sign in to continue', + signInRememberMe: 'Remember Me', + oauthSignInTitle: 'Sign in with OAuth', + passkeySignInTitle: 'Sign in with Passkey', + magicLinkSignInTitle: 'Sign in with Magic Link', + + // Common authentication labels + email: 'Email', + password: 'Password', + username: 'Username', + passkey: 'Passkey', + + // Common action labels + save: 'Save', + cancel: 'Cancel', + ok: 'Ok', + or: 'Or', + to: 'To', + with: 'With', + close: 'Close', + delete: 'Delete', + alert: 'Alert', + confirm: 'Confirm', + loading: 'Loading...', +}; + +export default getLocalization(en); diff --git a/packages/toolpad-core/src/locales/getLocalization.ts b/packages/toolpad-core/src/locales/getLocalization.ts new file mode 100644 index 00000000000..a99a3b5d418 --- /dev/null +++ b/packages/toolpad-core/src/locales/getLocalization.ts @@ -0,0 +1,13 @@ +import type { LocaleText } from '../AppProvider'; + +export const getLocalization = (translations: Partial) => { + return { + components: { + MuiLocalizationProvider: { + defaultProps: { + localeText: { ...translations }, + }, + }, + }, + }; +}; diff --git a/packages/toolpad-core/src/locales/hiIN.tsx b/packages/toolpad-core/src/locales/hiIN.tsx new file mode 100644 index 00000000000..16175c45125 --- /dev/null +++ b/packages/toolpad-core/src/locales/hiIN.tsx @@ -0,0 +1,41 @@ +import type { LocaleText } from '../AppProvider'; +import { getLocalization } from './getLocalization'; + +const hiINLabels: Partial = { + // Account + accountSignInLabel: 'साइन इन करें', + accountSignOutLabel: 'साइन आउट करें', + + // AccountPreview + accountPreviewTitle: 'खाता', + accountPreviewIconButtonLabel: 'वर्तमान उपयोगकर्ता', + + // SignInPage + signInTitle: 'साइन इन करें', + signInSubtitle: 'स्वागत है उपयोगकर्ता, कृपया जारी रखने के लिए साइन इन करें', + signInRememberMe: 'मुझे याद रखें', + oauthSignInTitle: 'साइन इन विकल्प', + passkeySignInTitle: 'साइन इन विकल्प', + magicLinkSignInTitle: 'साइन इन विकल्प', + + // Common authentication labels + email: 'ईमेल', + password: 'पासवर्ड', + username: 'उपयोगकर्ता नाम', + passkey: 'पासकी', + + // Common action labels + save: 'सहेजें', + cancel: 'रद्द करें', + ok: 'ठीक है', + or: 'या', + to: 'को', + with: 'के साथ', + close: 'बंद करें', + delete: 'हटाएं', + alert: 'सूचना', + confirm: 'पुष्टि करें', + loading: 'लोड हो रहा है...', +}; + +export default getLocalization(hiINLabels); diff --git a/packages/toolpad-core/src/shared/locales/LocaleContext.tsx b/packages/toolpad-core/src/shared/locales/LocaleContext.tsx deleted file mode 100644 index abfb38d6d5d..00000000000 --- a/packages/toolpad-core/src/shared/locales/LocaleContext.tsx +++ /dev/null @@ -1,39 +0,0 @@ -'use client'; - -import * as React from 'react'; -import DEFAULT_LOCALE_TEXT from './en'; - -export type LocaleContextType = { - // Account - signInLabel?: string; - signOutLabel?: string; - // Account Preview - iconButtonAriaLabel?: string; -}; - -export const LocaleContext = React.createContext(DEFAULT_LOCALE_TEXT); - -export interface LocaleProviderProps { - localeText?: Partial; - children: React.ReactNode; -} - -/** - * @ignore - internal component. - */ -export function LocaleProvider({ localeText, children }: LocaleProviderProps) { - const mergedLocaleText = React.useMemo( - () => ({ ...DEFAULT_LOCALE_TEXT, ...localeText }), - [localeText], - ); - - return {children}; -} - -/** - * @ignore - internal hook. - */ - -export function useLocaleText() { - return React.useContext(LocaleContext); -} diff --git a/packages/toolpad-core/src/shared/locales/en.tsx b/packages/toolpad-core/src/shared/locales/en.tsx deleted file mode 100644 index 9717c8b3a3c..00000000000 --- a/packages/toolpad-core/src/shared/locales/en.tsx +++ /dev/null @@ -1,9 +0,0 @@ -const TOOLPAD_CORE_DEFAULT_LOCALE_TEXT = { - // Account - signInLabel: 'Sign In', - signOutLabel: 'Sign Out', - // Account Preview - iconButtonAriaLabel: 'Current User', -}; - -export default TOOLPAD_CORE_DEFAULT_LOCALE_TEXT; diff --git a/packages/toolpad-core/src/useDialogs/useDialogs.tsx b/packages/toolpad-core/src/useDialogs/useDialogs.tsx index 192b20aac0a..f84417c42c0 100644 --- a/packages/toolpad-core/src/useDialogs/useDialogs.tsx +++ b/packages/toolpad-core/src/useDialogs/useDialogs.tsx @@ -11,6 +11,21 @@ import { useNonNullableContext } from '@toolpad/utils/react'; import invariant from 'invariant'; import * as React from 'react'; import { DialogsContext } from './DialogsContext'; +import { useLocaleText, type LocaleText } from '../AppProvider/LocalizationProvider'; + +interface DialogsProviderLocaleText { + alert: string; + confirm: string; + cancel: string; + ok: string; +} + +const defaultLocaleText: Pick = { + alert: 'Alert', + confirm: 'Confirm', + cancel: 'Cancel', + ok: 'Ok', +}; export interface OpenDialogOptions { /** @@ -192,14 +207,16 @@ export interface AlertDialogPayload extends AlertOptions { export interface AlertDialogProps extends DialogProps {} export function AlertDialog({ open, payload, onClose }: AlertDialogProps) { + const globalLocaleText = useLocaleText(); + const localeText = { ...defaultLocaleText, ...globalLocaleText }; const okButtonProps = useDialogLoadingButton(() => onClose()); return ( onClose()}> - {payload.title ?? 'Alert'} + {payload.title ?? localeText.alert} {payload.msg} - {payload.okText ?? 'Ok'} + {payload.okText ?? localeText.ok} @@ -213,18 +230,20 @@ export interface ConfirmDialogPayload extends ConfirmOptions { export interface ConfirmDialogProps extends DialogProps {} export function ConfirmDialog({ open, payload, onClose }: ConfirmDialogProps) { + const globalLocaleText = useLocaleText(); + const localeText = { ...defaultLocaleText, ...globalLocaleText }; const cancelButtonProps = useDialogLoadingButton(() => onClose(false)); const okButtonProps = useDialogLoadingButton(() => onClose(true)); return ( onClose(false)}> - {payload.title ?? 'Confirm'} + {payload.title ?? localeText.confirm} {payload.msg} - {payload.cancelText ?? 'Cancel'} + {payload.cancelText ?? localeText.cancel} - {payload.okText ?? 'Ok'} + {payload.okText ?? localeText.ok} @@ -238,6 +257,8 @@ export interface PromptDialogPayload extends PromptOptions { export interface PromptDialogProps extends DialogProps {} export function PromptDialog({ open, payload, onClose }: PromptDialogProps) { + const globalLocaleText = useLocaleText(); + const localeText = { ...defaultLocaleText, ...globalLocaleText }; const [input, setInput] = React.useState(''); const cancelButtonProps = useDialogLoadingButton(() => onClose(null)); @@ -266,7 +287,7 @@ export function PromptDialog({ open, payload, onClose }: PromptDialogProps) { }, }} > - {payload.title ?? 'Confirm'} + {payload.title ?? localeText.confirm} {payload.msg} - {payload.cancelText ?? 'Cancel'} + {payload.cancelText ?? localeText.cancel} - {payload.okText ?? 'Ok'} + {payload.okText ?? localeText.ok} diff --git a/packages/toolpad-core/src/useNotifications/NotificationsProvider.tsx b/packages/toolpad-core/src/useNotifications/NotificationsProvider.tsx index 8a7ab8cabb2..12a35999c42 100644 --- a/packages/toolpad-core/src/useNotifications/NotificationsProvider.tsx +++ b/packages/toolpad-core/src/useNotifications/NotificationsProvider.tsx @@ -19,8 +19,7 @@ import type { ShowNotification, ShowNotificationOptions, } from './useNotifications'; - -const closeText = 'Close'; +import { useLocaleText, type LocaleText } from '../AppProvider/LocalizationProvider'; export interface NotificationsProviderSlotProps { snackbar: SnackbarProps; @@ -36,6 +35,14 @@ export interface NotificationsProviderSlots { const RootPropsContext = React.createContext(null); +interface NotificationsProviderLocaleText { + close: string; +} + +const defaultLocaleText: Pick = { + close: 'Close', +}; + interface NotificationProps { notificationKey: string; badge: string | null; @@ -45,6 +52,8 @@ interface NotificationProps { } function Notification({ notificationKey, open, message, options, badge }: NotificationProps) { + const globalLocaleText = useLocaleText(); + const localeText = { ...defaultLocaleText, ...globalLocaleText }; const { close } = useNonNullableContext(NotificationsContext); const { severity, actionText, onAction, autoHideDuration } = options; @@ -68,8 +77,8 @@ function Notification({ notificationKey, open, message, options, badge }: Notifi ) : null}