Skip to main content
Docs

Localization prop (experimental)

Warning

This feature is currently experimental and may not behave as expected. If you encounter any issues, contact support with as much detail as possible.

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 nameLanguage tag (BCP 47)Key
Arabic (Saudi)ar-SAarSA
Belarusbe-BYbeBY
Bulgarianbg-BGbgBG
Catalanca-EScaES
Chinese (Simplified)zh-CNzhCN
Chinese (Traditional)zh-TWzhTW
Croatianhr-HRhrHR
Czechcs-CZcsCZ
Danishda-DKdaDK
Dutchnl-BEnlBE
Dutchnl-NLnlNL
English (GB)en-GBenGB
English (US)en-USenUS
Finnishfi-FIfiFI
Frenchfr-FRfrFR
Germande-DEdeDE
Greekel-GRelGR
Hebrewhe-ILheIL
Hungarianhu-HUhuHU
Icelandicis-ISisIS
Italianit-ITitIT
Indonesianid-IDidID
Japaneseja-JPjaJP
Koreanko-KRkoKR
Mongolianmn-MNmnMN
Norwegiannb-NOnbNO
Polishpl-PLplPL
Portuguese (BR)pt-BRptBR
Portuguese (PT)pt-PTptPT
Romanianro-ROroRO
Russianru-RUruRU
Serbiansr-RSsrRS
Slovaksk-SKskSK
Spanishes-ESesES
Spanish (Mexico)es-MXesMX
Spanish (Uruguay)es-UYesUY
Swedishsv-SEsvSE
Thaith-THthTH
Turkishtr-TRtrTR
Ukrainianuk-UAukUA
Vietnamesevi-VNviVN

Usage

Caution

The localizations will only update the text in the Clerk components used in your application. The hosted Clerk Account Portal will remain in English.

To get started, install the @clerk/localizations package.

terminal
npm install @clerk/localizations
terminal
yarn add @clerk/localizations
terminal
pnpm add @clerk/localizations
terminal
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>.

app/layout.tsx
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>
  )
}
_app.tsx
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
astro.config.mjs
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.

main.js
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)
}
index.html
<!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.

app/root.tsx
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,
})
src/main.ts
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')
nuxt.config.ts
// 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:

  1. Fork the https://github.com/clerk/javascript/ repo.
  2. Clone it locally to edit it.
  3. Review Clerk's Contributing guide.
  4. If you are updating an existing localization locate the file in packages/localizations/src
  5. 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 be fr-CA.ts.
  6. Go through the file and edit the entries.
  7. If you are adding a new localization, add the language to the packages/localizations/src/index.ts file.
  8. 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.

app/layout.tsx
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.

astro.config.mjs
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.

main.js
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)
}
index.html
<!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.

app/root.tsx
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.

src/main.ts
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.

nuxt.config.ts
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.

app/layout.tsx
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.

astro.config.mjs
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.

main.js
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)
}
index.html
<!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.

app/root.tsx
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.

src/main.ts
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.

nuxt.config.ts
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.

app/layout.tsx
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>
  )
}
_app.tsx
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.

astro.config.mjs
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.

main.js
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)
}
index.html
<!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.

app/root.tsx
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.

src/main.ts
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.

nuxt.config.ts
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

What did you think of this content?

Last updated on