Localization prop (experimental)
Clerk offers the ability to override the strings for all of the elements in each of the Clerk components. This allows you to provide localization for your users or change the wording to suit your brand.
@clerk/localizations
The @clerk/localizations
package contains predefined localizations for the Clerk components.
Languages
Clerk currently supports the following languages with English as the default:
English name | Language tag (BCP 47) | Key |
---|---|---|
Arabic (Saudi) | ar-SA | arSA |
Belarus | be-BY | beBY |
Bulgarian | bg-BG | bgBG |
Catalan | ca-ES | caES |
Chinese (Simplified) | zh-CN | zhCN |
Chinese (Traditional) | zh-TW | zhTW |
Croatian | hr-HR | hrHR |
Czech | cs-CZ | csCZ |
Danish | da-DK | daDK |
Dutch | nl-BE | nlBE |
Dutch | nl-NL | nlNL |
English (GB) | en-GB | enGB |
English (US) | en-US | enUS |
Finnish | fi-FI | fiFI |
French | fr-FR | frFR |
German | de-DE | deDE |
Greek | el-GR | elGR |
Hebrew | he-IL | heIL |
Hungarian | hu-HU | huHU |
Icelandic | is-IS | isIS |
Italian | it-IT | itIT |
Indonesian | id-ID | idID |
Japanese | ja-JP | jaJP |
Korean | ko-KR | koKR |
Mongolian | mn-MN | mnMN |
Norwegian | nb-NO | nbNO |
Polish | pl-PL | plPL |
Portuguese (BR) | pt-BR | ptBR |
Portuguese (PT) | pt-PT | ptPT |
Romanian | ro-RO | roRO |
Russian | ru-RU | ruRU |
Serbian | sr-RS | srRS |
Slovak | sk-SK | skSK |
Spanish | es-ES | esES |
Spanish (Mexico) | es-MX | esMX |
Spanish (Uruguay) | es-UY | esUY |
Swedish | sv-SE | svSE |
Thai | th-TH | thTH |
Turkish | tr-TR | trTR |
Ukrainian | uk-UA | ukUA |
Vietnamese | vi-VN | viVN |
Usage
To get started, install the @clerk/localizations
package.
npm install @clerk/localizations
yarn add @clerk/localizations
pnpm add @clerk/localizations
bun add @clerk/localizations
Once the @clerk/localizations
package is installed, you can import the localizations you need by removing the "-" from the locale.
In the following example, the fr-FR locale is imported as frFR
. The imported localization is then passed to the localization
prop on the <ClerkProvider>
.
import { ClerkProvider } from '@clerk/nextjs'
import './globals.css'
// fr-FR locale is imported as frFR
import { frFR } from '@clerk/localizations'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<ClerkProvider localization={frFR}>
<html lang="en">
<body>{children}</body>
</html>
</ClerkProvider>
)
}
import { ClerkProvider } from '@clerk/nextjs'
// fr-FR locale is imported as frFR
import { frFR } from '@clerk/localizations'
import type { AppProps } from 'next/app'
function MyApp({ Component, pageProps }: AppProps) {
return (
// Add the localization prop to the ClerkProvider
<ClerkProvider localization={frFR} {...pageProps}>
<Component {...pageProps} />
</ClerkProvider>
)
}
export default MyApp
import { defineConfig } from 'astro/config'
import clerk from '@clerk/astro'
// fr-FR locale is imported as frFR
import { frFR } from '@clerk/localizations'
export default defineConfig({
integrations: [
clerk({
localization: frFR,
}),
],
})
In the following example, the fr-FR locale is imported as frFR
. The imported localization is then passed to the localization
prop on the method.
Use the following tabs to view the code necessary for each file.
import { Clerk } from '@clerk/clerk-js'
// fr-FR locale is imported as frFR
import { frFR } from '@clerk/localizations'
const clerkPubKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY
const clerk = new Clerk(clerkPubKey)
await clerk.load({
localization: frFR,
})
if (clerk.user) {
document.getElementById('app').innerHTML = `
<div id="user-button"></div>
`
const userButtonDiv = document.getElementById('user-button')
clerk.mountUserButton(userButtonDiv)
} else {
document.getElementById('app').innerHTML = `
<div id="sign-in"></div>
`
const signInDiv = document.getElementById('sign-in')
clerk.mountSignIn(signInDiv)
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Clerk + JavaScript App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="main.js" async crossorigin="anonymous"></script>
</body>
</html>
In the following example, the fr-FR locale is imported as frFR
. The imported localization is then passed to the localization
prop in the options.
import type { MetaFunction, LoaderFunction } from '@remix-run/node'
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from '@remix-run/react'
import { rootAuthLoader } from '@clerk/remix/ssr.server'
// Import ClerkApp
import { ClerkApp } from '@clerk/remix'
// fr-FR locale is imported as frFR
import { frFR } from '@clerk/localizations'
export const meta: MetaFunction = () => [
{
charset: 'utf-8',
title: 'New Remix App',
viewport: 'width=device-width,initial-scale=1',
},
]
export const loader: LoaderFunction = (args) => rootAuthLoader(args)
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
)
}
function App() {
return <Outlet />
}
export default ClerkApp(App, {
localization: frFR,
})
import { createApp } from 'vue'
import App from './App.vue'
import { clerkPlugin } from '@clerk/vue'
// fr-FR locale is imported as frFR
import { frFR } from '@clerk/localizations'
const app = createApp(App)
app.use(clerkPlugin, {
localization: frFR,
})
app.mount('#app')
// fr-FR locale is imported as frFR
import { frFR } from '@clerk/localizations'
export default defineNuxtConfig({
modules: ['@clerk/nuxt'],
clerk: {
localization: frFR,
},
})
Adding or updating a localization
Clerk's localizations are customer-sourced and we encourage customers to add or update localizations. To do so, follow these steps:
- Fork the https://github.com/clerk/javascript/ repo.
- Clone it locally to edit it.
- Review Clerk's Contributing guide.
- If you are updating an existing localization locate the file in
packages/localizations/src
- If you are adding a new language, copy the
en-US.ts
file and name it according to your language. The naming is the abbreviated language-region. For example, for French in Canada, it would befr-CA.ts.
- Go through the file and edit the entries.
- If you are adding a new localization, add the language to the
packages/localizations/src/index.ts
file. - Commit your changes to git and push them to your fork. Create a Pull Request from your fork to Clerk's repo against the
main
branch. We will review and either approve or ask for updates.
Custom localizations
You can also provide your own localizations for the Clerk components. This is useful if you want to provide limited or quick localization for a language Clerk doesn't currently support, adjust the wording to match your brand, or customize default error messages.
First, you need to find the key for the element that you want to customize. To find the key for your translation, open up Clerk's English localization file. Search the file for the term that you want to customize.
For example, say you want to change the text of the "Continue" button on the <SignIn />
component to say "LETS GO!". In this case, you'd search for "Continue". The first result that comes up is formButtonPrimary
, which is the key for the "Continue" button.
Now that you know the key, you can pass it to the localization
prop and set the value to the text you want to display. In this case, you'd set the value to "LETS GO!", as shown in the following example.
In Next.js, pass the localization
prop to the <ClerkProvider>
component.
import { ClerkProvider } from '@clerk/nextjs'
import './globals.css'
// Set your customizations in a `localization` object
const localization = {
formButtonPrimary: 'LETS GO!',
}
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
// Pass the `localization` object to the `localization` prop on the `<ClerkProvider>` component
<ClerkProvider localization={localization}>
<html lang="en">
<body>{children}</body>
</html>
</ClerkProvider>
)
}
In Astro, pass the localization
prop to the integration.
import { defineConfig } from 'astro/config'
import clerk from '@clerk/astro'
export default defineConfig({
integrations: [
clerk({
localization: {
formButtonPrimary: 'LETS GO!',
},
}),
],
})
In JavaScript, pass the localization
prop to the method.
Use the following tabs to view the code necessary for each file.
import { Clerk } from '@clerk/clerk-js'
const clerkPubKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY
const clerk = new Clerk(clerkPubKey)
await clerk.load({
localization: {
formButtonPrimary: 'LETS GO!',
},
})
if (clerk.user) {
document.getElementById('app').innerHTML = `
<div id="user-button"></div>
`
const userButtonDiv = document.getElementById('user-button')
clerk.mountUserButton(userButtonDiv)
} else {
document.getElementById('app').innerHTML = `
<div id="sign-in"></div>
`
const signInDiv = document.getElementById('sign-in')
clerk.mountSignIn(signInDiv)
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Clerk + JavaScript App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="main.js" async crossorigin="anonymous"></script>
</body>
</html>
In Remix, pass the localization
prop to the options.
import type { MetaFunction, LoaderFunction } from '@remix-run/node'
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from '@remix-run/react'
import { rootAuthLoader } from '@clerk/remix/ssr.server'
// Import ClerkApp
import { ClerkApp } from '@clerk/remix'
export const meta: MetaFunction = () => [
{
charset: 'utf-8',
title: 'New Remix App',
viewport: 'width=device-width,initial-scale=1',
},
]
export const loader: LoaderFunction = (args) => rootAuthLoader(args)
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
)
}
function App() {
return <Outlet />
}
export default ClerkApp(App, {
localization: {
formButtonPrimary: 'LETS GO!',
},
})
In Vue, pass the localization
prop to the integration.
import { createApp } from 'vue'
import App from './App.vue'
import { clerkPlugin } from '@clerk/vue'
const app = createApp(App)
app.use(clerkPlugin, {
localization: {
formButtonPrimary: 'LETS GO!',
},
})
app.mount('#app')
In Nuxt, pass the localization
prop to the integration.
export default defineNuxtConfig({
modules: ['@clerk/nuxt'],
clerk: {
localization: {
formButtonPrimary: 'LETS GO!',
},
},
})
You can also customize multiple entries by passing multiple keys. The following example updates the "to continue to" subtitles on the <SignUp />
component to say "to access" instead.
In Next.js, pass the localization
prop to the <ClerkProvider>
component.
import { ClerkProvider } from '@clerk/nextjs'
import './globals.css'
// Set your customizations in a `localization` object
const localization = {
signUp: {
start: {
subtitle: 'to access {{applicationName}}',
},
emailCode: {
subtitle: 'to access {{applicationName}}',
},
},
}
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
// Pass the `localization` object to the `localization` prop on the `<ClerkProvider>` component
<ClerkProvider localization={localization}>
<html lang="en">
<body>{children}</body>
</html>
</ClerkProvider>
)
}
In Astro, pass the localization
prop to the integration.
import { defineConfig } from 'astro/config'
import clerk from '@clerk/astro'
export default defineConfig({
integrations: [
clerk({
localization: {
signUp: {
start: {
subtitle: 'to access {{applicationName}}',
},
emailCode: {
subtitle: 'to access {{applicationName}}',
},
},
},
}),
],
})
In JavaScript, pass the localization
prop to the method.
Use the following tabs to view the code necessary for each file.
import { Clerk } from '@clerk/clerk-js'
const clerkPubKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY
const clerk = new Clerk(clerkPubKey)
await clerk.load({
localization: {
signUp: {
start: {
subtitle: 'to access {{applicationName}}',
},
emailCode: {
subtitle: 'to access {{applicationName}}',
},
},
},
})
if (clerk.user) {
document.getElementById('app').innerHTML = `
<div id="user-button"></div>
`
const userButtonDiv = document.getElementById('user-button')
clerk.mountUserButton(userButtonDiv)
} else {
document.getElementById('app').innerHTML = `
<div id="sign-in"></div>
`
const signInDiv = document.getElementById('sign-in')
clerk.mountSignIn(signInDiv)
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Clerk + JavaScript App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="main.js" async crossorigin="anonymous"></script>
</body>
</html>
In Remix, pass the localization
prop to the options.
import type { MetaFunction, LoaderFunction } from '@remix-run/node'
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from '@remix-run/react'
import { rootAuthLoader } from '@clerk/remix/ssr.server'
// Import ClerkApp
import { ClerkApp } from '@clerk/remix'
export const meta: MetaFunction = () => [
{
charset: 'utf-8',
title: 'New Remix App',
viewport: 'width=device-width,initial-scale=1',
},
]
export const loader: LoaderFunction = (args) => rootAuthLoader(args)
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
)
}
function App() {
return <Outlet />
}
export default ClerkApp(App, {
localization: {
signUp: {
start: {
subtitle: 'to access {{applicationName}}',
},
emailCode: {
subtitle: 'to access {{applicationName}}',
},
},
},
})
In Vue, pass the localization
prop to the integration.
import { createApp } from 'vue'
import App from './App.vue'
import { clerkPlugin } from '@clerk/vue'
const app = createApp(App)
app.use(clerkPlugin, {
localization: {
signUp: {
start: {
subtitle: 'to access {{applicationName}}',
},
emailCode: {
subtitle: 'to access {{applicationName}}',
},
},
},
})
app.mount('#app')
In Nuxt, pass the localization
prop to the integration.
export default defineNuxtConfig({
modules: ['@clerk/nuxt'],
clerk: {
localization: {
signUp: {
start: {
subtitle: 'to access {{applicationName}}',
},
emailCode: {
subtitle: 'to access {{applicationName}}',
},
},
},
},
})
Example: Customize error messages
You can customize Clerk's default error messages by targeting the unstable__errors
key. This key lets you define specific error keys for different error types and assign them custom message strings. You can find the full list of error keys in the English localization file. Search for the unstable__errors
object to find the keys you can customize.
The following example updates the not_allowed_access
error message. This message appears when a user tries to sign in with an email domain that isn't allowed to access your application.
In Next.js, pass the localization
prop to the <ClerkProvider>
component.
import { ClerkProvider } from '@clerk/nextjs'
import './globals.css'
const localization = {
unstable__errors: {
not_allowed_access:
'Send us an email if you want your corporate email domain allowlisted for access',
},
}
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
// Add the localization prop to the ClerkProvider
<ClerkProvider localization={localization}>
<html lang="en">
<body>{children}</body>
</html>
</ClerkProvider>
)
}
import { ClerkProvider } from '@clerk/nextjs'
import type { AppProps } from 'next/app'
const localization = {
unstable__errors: {
not_allowed_access:
'Send us an email if you want your corporate email domain allowlisted for access',
},
}
function MyApp({ Component, pageProps }: AppProps) {
return (
// Add the localization prop to the ClerkProvider
<ClerkProvider localization={localization} {...pageProps}>
<Component {...pageProps} />
</ClerkProvider>
)
}
export default MyApp
In Astro, pass the localization
prop to the integration.
import { defineConfig } from 'astro/config'
import clerk from '@clerk/astro'
export default defineConfig({
integrations: [
clerk({
localization: {
unstable__errors: {
not_allowed_access:
'Send us an email if you want your corporate email domain allowlisted for access',
},
},
}),
],
})
In JavaScript, pass the localization
prop to the method.
Use the following tabs to view the code necessary for each file.
import { Clerk } from '@clerk/clerk-js'
const clerkPubKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY
const clerk = new Clerk(clerkPubKey)
await clerk.load({
localization: {
unstable__errors: {
not_allowed_access:
'Send us an email if you want your corporate email domain allowlisted for access',
},
},
})
if (clerk.user) {
document.getElementById('app').innerHTML = `
<div id="user-button"></div>
`
const userButtonDiv = document.getElementById('user-button')
clerk.mountUserButton(userButtonDiv)
} else {
document.getElementById('app').innerHTML = `
<div id="sign-in"></div>
`
const signInDiv = document.getElementById('sign-in')
clerk.mountSignIn(signInDiv)
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Clerk + JavaScript App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="main.js" async crossorigin="anonymous"></script>
</body>
</html>
In Remix, pass the localization
prop to the options.
import type { MetaFunction, LoaderFunction } from '@remix-run/node'
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from '@remix-run/react'
import { rootAuthLoader } from '@clerk/remix/ssr.server'
// Import ClerkApp
import { ClerkApp } from '@clerk/remix'
export const meta: MetaFunction = () => [
{
charset: 'utf-8',
title: 'New Remix App',
viewport: 'width=device-width,initial-scale=1',
},
]
export const loader: LoaderFunction = (args) => rootAuthLoader(args)
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
)
}
function App() {
return <Outlet />
}
export default ClerkApp(App, {
localization: {
unstable__errors: {
not_allowed_access:
'Send us an email if you want your corporate email domain allowlisted for access',
},
},
})
In Vue, pass the localization
prop to the integration.
import { createApp } from 'vue'
import App from './App.vue'
import { clerkPlugin } from '@clerk/vue'
const app = createApp(App)
app.use(clerkPlugin, {
localization: {
unstable__errors: {
not_allowed_access:
'Send us an email if you want your corporate email domain allowlisted for access',
},
},
})
app.mount('#app')
In Nuxt, pass the localization
prop to the integration.
export default defineNuxtConfig({
modules: ['@clerk/nuxt'],
clerk: {
localization: {
unstable__errors: {
not_allowed_access:
'Send us an email if you want your corporate email domain allowlisted for access',
},
},
},
})
Feedback
Last updated on