Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow custom languages in IntlProvider #158

Merged
merged 5 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
130 changes: 85 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,91 @@ 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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have updated the README to be more clear on this and moved it to the top under getting started.


Ory Elements supports translations out-of-the-box with the `IntlProvider`. The
`IntlProvider` is required by Ory Elements and allows the language to be mapped
correctly, specifically American English.

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.

When providing a translation, it is important to note that it will be merged
with an existing supported language, with your provided values taking precedent.
This is to allow modifying only one key from an already supported language,
rather than modifying the entire translation 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"`. Ory Elements will now use the updated
value `Email` instead of `ID` for this specific label, but will still keep the
other defaults in-tact.

Another scenario is when we add partial keys to an unsupported language such as
`af` (Afrikaans). I add my key-value only for one entry
`"identities.messages.1070004": "E-posadres"`, however, the language has no
default inside Ory Elements. As a safe-guard we fall-back to English for the
rest of the labels.

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)

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

const RootComponent = () => {
const myCustomTranslations: CustomLanguageFormats = {
en: {
"login.title": "Login",
},
}

return (
<ThemeProvider>
<IntlProvider<CustomTranslations>
customTranslations={myCustomTranslations}
locale="en"
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 +416,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
43 changes: 41 additions & 2 deletions examples/nextjs-spa/src/components/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,53 @@
import { IntlProvider, Nav, ThemeProvider } from "@ory/elements"
import {
CustomLanguageFormats,
CustomTranslations,
IntlProvider,
Nav,
ThemeProvider,
} 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
// this merges the custom translations with the default translations
// if a custom language is provided, but no standard translation
// exists, the english translation will be merged instead for missing values.
//
// For example, if you provide a custom translation for the "login.title" label
// in the "af" language (Afrikaans), but no standard translation exists for "af",
// the english translation will be used for the remaining labels.
//
// 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: {
"login.title": "Login",
"identities.messages.1070004": "Email",
},
nl: {
"login.title": "Inloggen",
"identities.messages.1070004": "E-mail",
},
af: {
"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
42 changes: 40 additions & 2 deletions examples/preact-spa/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ 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,
} from "@ory/elements-preact"

// Ory Elements
// optional fontawesome icons
Expand All @@ -27,9 +32,42 @@ 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
// this merges the custom translations with the default translations
// if a custom language is provided, but no standard translation
// exists, the english translation will be merged instead for missing values.
//
// For example, if you provide a custom translation for the "login.title" label
// in the "af" language (Afrikaans), but no standard translation exists for "af",
// the english translation will be used for the remaining labels.
//
// 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: {
"login.title": "Login",
"identities.messages.1070004": "Email",
},
nl: {
"login.title": "Inloggen",
"identities.messages.1070004": "E-mail",
},
af: {
"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 @@ -64,7 +64,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 @@ -67,7 +67,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 @@ -66,7 +66,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
43 changes: 41 additions & 2 deletions examples/react-spa/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { ThemeProvider, IntlProvider } from "@ory/elements"
import {
ThemeProvider,
IntlProvider,
CustomTranslations,
CustomLanguageFormats,
} from "@ory/elements"

// optional global css reset
import "@ory/elements/assets/normalize.css"
Expand Down Expand Up @@ -28,12 +33,46 @@ import "@ory/elements/assets/jetbrains-mono-font.css"
// required styles for Ory Elements
import "@ory/elements/style.css"

// adds custom translations labels to the default translations
// this merges the custom translations with the default translations
// if a custom language is provided, but no standard translation
// exists, the english translation will be merged instead for missing values.
//
// For example, if you provide a custom translation for the "login.title" label
// in the "af" language (Afrikaans), but no standard translation exists for "af",
// the english translation will be used for the remaining labels.
//
// 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: {
"login.title": "Login",
"identities.messages.1070004": "Email",
},
nl: {
"login.title": "Inloggen",
"identities.messages.1070004": "E-mail",
},
af: {
"login.title": "Meld aan",
"identities.messages.1070004": "E-posadres",
},
}

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<BrowserRouter>
{/* We add the Ory themes here */}
<ThemeProvider themeOverrides={{}}>
<IntlProvider>
{/* We dont need to pass any custom translations */}
{/* <IntlProvider> */}
{/* We pass custom translations */}
<IntlProvider<CustomTranslations>
locale="af"
defaultLocale="en"
customTranslations={customTranslations}
>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/login" element={<Login />} />
Expand Down
Loading
Loading