Skip to content

Commit

Permalink
feat: allow custom languages in IntlProvider (#158)
Browse files Browse the repository at this point in the history
* feat: allow custom languages in `IntlProvider`

* chore: remove hard-coded title in examples

* chore: update language codes variable

* chore: update README

* refactor: don't auto-merge translations
  • Loading branch information
Benehiko authored Oct 11, 2023
1 parent 4bdbac1 commit e4394b8
Show file tree
Hide file tree
Showing 13 changed files with 445 additions and 75 deletions.
165 changes: 120 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,126 @@ have explicitly told our React app to use through the `VITE_ORY_SDK_URL` export.
Now you can see Ory Elements in action by opening <http://localhost:3000> in
your browser!

## Internalization (i18n)

Ory Elements supports translations out-of-the-box with the `IntlProvider`. The
`IntlProvider` is required by Ory Elements and maps American English as the
default language.

```tsx
import { ThemeProvider } from "@ory/elements"

const RootComponent = () => {
return (
<ThemeProvider>
<IntlProvider>// children</IntlProvider>
</ThemeProvider>
)
}
```

To switch the language the UI should use, you can pass in the language code
through the `locale` prop.

```tsx
import { ThemeProvider } from "@ory/elements"

const RootComponent = () => {
return (
<ThemeProvider>
<IntlProvider locale="nl" defaultLocale="en">
// children
</IntlProvider>
</ThemeProvider>
)
}
```

The `IntlProvider` has the ability to accept custom translations through a
`CustomLanguageFormats` type. You can specify to the `<IntlProvider>` that you
would like to use a `CustomTranslations` instead of the
`SupportedLanguages (default)` type which will require providing the
`customTranslations` prop.

More information on the Ory messages can be found
[in the docs](https://www.ory.sh/docs/kratos/concepts/ui-user-interface#ui-message-codes)

When providing a translation, you can merge an existing supported locale from
Ory Elements so that you do not need to provide all keys for the entire
tranlsation file :)

For example, I want to adjust the English translation to say `Email` instead of
`ID` when a Login card is shown. So I provide the key-value pair
`"identities.messages.1070004": "Email"`. Another unsupported language such as
`af` (Afrikaans) is also added only for one entry
`"identities.messages.1070004": "E-posadres"`. We merge the supported `en`
locale from Ory Elements so that we don't need to provide all key-value pairs.

```tsx
import {
ThemeProvider,
IntlProvider,
CustomTranslations,
locales,
} from "@ory/elements"

const RootComponent = () => {
const myCustomTranslations: CustomLanguageFormats = {
...locales,
en: {
...locales.en,
"identities.messages.1070004": "Email",
},
af: {
// fallback to English on other labels
...locales.en,
"identities.messages.1070004": "E-posadres",
},
}

return (
<ThemeProvider>
<IntlProvider<CustomTranslations>
customTranslations={myCustomTranslations}
locale="af"
defaultLocale="en"
>
// children
</IntlProvider>
</ThemeProvider>
)
}
```

It is of course also possible to provide the `IntlProvider` directly from the
[react-intl](https://formatjs.io/docs/react-intl/) library to format messages
and provide translations. The default translations of Ory Elements are located
in the `src/locales` directory.

```tsx
import { IntlProvider } from "react-intl"
import { locales } from "@ory/elements"

const customMessages = {
...locales,
de: {
...locales.de,
"login.title": "Login",
},
}

const Main = () => {
return (
<IntlProvider locale={customMessageLocale} messages={customMessages}>
<Router>
<Route path="/" component={Dashboard} />
{/* ... */}
</Router>
</IntlProvider>
)
}
```

## End-to-end Testing with Playwright

Ory Elements provides an end-to-end library based on
Expand Down Expand Up @@ -331,51 +451,6 @@ const Main = () => {
}
```

### Internalization (i18n)

Ory Elements uses [react-intl](https://formatjs.io/docs/react-intl/) to format
messages and provide translations. The default language is american English, but
you can provide your own translations by using the `IntlProvider` component. The
default translations of Ory Elements are located in the `src/locales` directory.
They can be loaded using the `IntlProvider` from Ory Elements. Please note that
it is necessary to wrap all Ory Element components either in the `IntlProvider`
from `react-intl` or Ory Elements.

```tsx
import { IntlProvider } from "@ory/elements"

const Main = () => {
return (
<IntlProvider locale="de">
<Router>
<Route path="/" component={Dashboard} />
{/* ... */}
</Router>
</IntlProvider>
)
}
```

Custom translations can be provided using the `IntlProvider` from `react-intl`.
For reference, it is best to start with the auto-generated English defaults, as
they include all keys. More information on the Kratos messages can be found
[in the docs](https://www.ory.sh/docs/kratos/concepts/ui-user-interface#ui-message-codes).

```tsx
import { IntlProvider } from "react-intl"

const Main = () => {
return (
<IntlProvider locale={customMessageLocale} messages={customMessages}>
<Router>
<Route path="/" component={Dashboard} />
{/* ... */}
</Router>
</IntlProvider>
)
}
```

### Theme CSS in Express.js

For Express.js the library also exports a helper function which registers all
Expand Down
41 changes: 39 additions & 2 deletions examples/nextjs-spa/src/components/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,51 @@
import { IntlProvider, Nav, ThemeProvider } from "@ory/elements"
import {
CustomLanguageFormats,
CustomTranslations,
IntlProvider,
Nav,
ThemeProvider,
locales,
} from "@ory/elements"
import Head from "next/head"

interface LayoutProps {
children: React.ReactNode
}

export default function Layout({ children }: LayoutProps) {
// adds custom translations labels to the default translations
//
// You can also contribute your custom translations to the Ory Elements project
// by submitting a pull request to the following repository:
// https://github.com/ory/elements
const customTranslations: CustomLanguageFormats = {
en: {
...locales.en,
"login.title": "Login",
"identities.messages.1070004": "Email",
},
nl: {
...locales.nl,
"login.title": "Inloggen",
"identities.messages.1070004": "E-mail",
},
af: {
// merging English since no default Afrikaans translations are available
...locales.en,
"login.title": "Meld aan",
"identities.messages.1070004": "E-posadres",
},
}
return (
<ThemeProvider themeOverrides={{}}>
<IntlProvider>
{/* We dont need to pass any custom translations */}
{/* <IntlProvider> */}
{/* We pass custom translations */}
<IntlProvider<CustomTranslations>
customTranslations={customTranslations}
locale="af"
defaultLocale="en"
>
<Head>
<title>Next.js w/ Elements</title>
<link rel="icon" href="/ory.svg" />
Expand Down
1 change: 0 additions & 1 deletion examples/nextjs-spa/src/pages/registration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ const Registration: NextPageWithLayout = () => {
// create a registration form that dynamically renders based on the flow data using Ory Elements
<UserAuthCard
cardImage="/ory.svg"
title={"Registration"}
// This defines what kind of card we want to render.
flowType={"registration"}
// we always need to pass the flow to the card since it contains the form fields, error messages and csrf token
Expand Down
1 change: 0 additions & 1 deletion examples/preact-spa/src/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ export const Login = () => {
forgotPasswordURL: "/recovery",
signupURL: "/registration",
}}
title={"Login"}
includeScripts={true}
onSubmit={({ body }) => submitFlow(body as UpdateLoginFlowBody)}
/>
Expand Down
40 changes: 38 additions & 2 deletions examples/preact-spa/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ import { Recovery } from "./recovery"
import { Register } from "./register"
import { Settings } from "./settings"
import { Verification } from "./verification"
import { IntlProvider, ThemeProvider } from "@ory/elements-preact"
import {
CustomLanguageFormats,
CustomTranslations,
IntlProvider,
ThemeProvider,
locales,
} from "@ory/elements-preact"

// Ory Elements
// optional fontawesome icons
Expand All @@ -27,9 +33,39 @@ import "@ory/elements-preact/assets/jetbrains-mono-font.css"
import "@ory/elements-preact/style.css"

const Main = () => {
// adds custom translations labels to the default translations
//
// You can also contribute your custom translations to the Ory Elements project
// by submitting a pull request to the following repository:
// https://github.com/ory/elements
const customTranslations: CustomLanguageFormats = {
en: {
...locales.en,
"login.title": "Login",
"identities.messages.1070004": "Email",
},
nl: {
...locales.nl,
"login.title": "Inloggen",
"identities.messages.1070004": "E-mail",
},
af: {
// merging English since no default Afrikaans translations are available
...locales.en,
"login.title": "Meld aan",
"identities.messages.1070004": "E-posadres",
},
}
return (
<ThemeProvider>
<IntlProvider>
{/* We dont need to pass any custom translations */}
{/* <IntlProvider> */}
{/* We pass custom translations */}
<IntlProvider<CustomTranslations>
customTranslations={customTranslations}
locale="af"
defaultLocale="en"
>
<Router>
<Route path="/" component={Dashboard} />
<Route path="/login" component={Login} />
Expand Down
1 change: 0 additions & 1 deletion examples/preact-spa/src/recovery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ export const Recovery = () => {

return flow ? (
<UserAuthCard
title="Recovery"
flow={flow}
flowType={"recovery"}
additionalProps={{ loginURL: "/login" }}
Expand Down
1 change: 0 additions & 1 deletion examples/preact-spa/src/register.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ export const Register = () => {
<UserAuthCard
flow={flow}
flowType={"registration"}
title={"Registration"}
additionalProps={{
loginURL: "/login",
}}
Expand Down
1 change: 0 additions & 1 deletion examples/preact-spa/src/verification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ export const Verification = () => {
additionalProps={{
signupURL: "/registration",
}}
title="Verification"
// submit the verification form data to Ory
onSubmit={({ body }) => submitFlow(body as UpdateVerificationFlowBody)}
/>
Expand Down
1 change: 0 additions & 1 deletion examples/react-spa/src/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ export const Login = (): JSX.Element => {
return flow ? (
// we render the login form using Ory Elements
<UserAuthCard
title={"Login"}
flowType={"login"}
// we always need the flow data which populates the form fields and error messages dynamically
flow={flow}
Expand Down
1 change: 0 additions & 1 deletion examples/react-spa/src/Recovery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ export const Recovery = () => {
return flow ? (
// We create a dynamic Recovery form based on the flow using Ory Elements
<UserAuthCard
title="Recovery"
flowType={"recovery"}
// the flow is always required since it contains the UI form elements, UI error messages and csrf token
flow={flow}
Expand Down
1 change: 0 additions & 1 deletion examples/react-spa/src/Registration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ export const Registration = () => {
return flow ? (
// create a registration form that dynamically renders based on the flow data using Ory Elements
<UserAuthCard
title={"Registration"}
flowType={"registration"}
// we always need to pass the flow to the card since it contains the form fields, error messages and csrf token
flow={flow}
Expand Down
Loading

0 comments on commit e4394b8

Please sign in to comment.