Internationalization (i18n)
To internationalize (i18n) your app:
- Use the
onBeforeRoute()hook. - Update your
<Link>component.
1. onBeforeRoute()
// pages/+onBeforeRoute.js
// Environment: server & client
export { onBeforeRoute }
import { modifyUrl } from 'vike/modifyUrl'
function onBeforeRoute(pageContext) {
const { urlWithoutLocale, locale } = extractLocale(pageContext.urlParsed)
return {
pageContext: {
// Make locale available as pageContext.locale
locale,
// Vike's router will use pageContext.urlLogical instead of pageContext.urlOriginal and
// the locale is removed from pageContext.urlParsed
urlLogical: urlWithoutLocale
}
}
}
function extractLocale(url) {
const { pathname } = url
// Determine the locale, for example:
// /en-US/film/42 => en-US
// /de-DE/film/42 => de-DE
const locale = /* ... */
// Remove the locale, for example:
// /en-US/film/42 => /film/42
// /de-DE/film/42 => /film/42
const pathnameWithoutLocale = /* ... */
// Reconstruct full URL
const urlWithoutLocale = modifyUrl(url.href, { pathname: pathnameWithoutLocale })
return { locale, urlWithoutLocale }
}// pages/+onBeforeRoute.ts
// Environment: server & client
export { onBeforeRoute }
import type { PageContext } from 'vike/types'
import { modifyUrl } from 'vike/modifyUrl'
import type { Url } from 'vike/types'
function onBeforeRoute(pageContext: PageContext) {
const { urlWithoutLocale, locale } = extractLocale(pageContext.urlParsed)
return {
pageContext: {
// Make locale available as pageContext.locale
locale,
// Vike's router will use pageContext.urlLogical instead of pageContext.urlOriginal and
// the locale is removed from pageContext.urlParsed
urlLogical: urlWithoutLocale
}
}
}
function extractLocale(url: Url) {
const { pathname } = url
// Determine the locale, for example:
// /en-US/film/42 => en-US
// /de-DE/film/42 => de-DE
const locale = /* ... */
// Remove the locale, for example:
// /en-US/film/42 => /film/42
// /de-DE/film/42 => /film/42
const pathnameWithoutLocale = /* ... */
// Reconstruct full URL
const urlWithoutLocale = modifyUrl(url.href, { pathname: pathnameWithoutLocale })
return { locale, urlWithoutLocale }
}See also: API >
modifyUrl()
This only removes the locale from
pageContext.urlParsed: it doesn't modify the actual URL in the browser.
Upon rendering a page, onBeforeRoute() is the first hook that Vike calls. This means that
all other hooks can use pageContext.locale and the updated pageContext.urlParsed,
as well as all your UI components (with usePageContext()).
This technique also works with:
?lang=frquery parametersdomain.frdomain TLDsAccept-Language: fr-FRheadersThe
Accept-Languageheader can be used for redirecting the user to the right localized URL (e.g. URL/about+ HeaderAccept-Language: de-DE=> redirect to/de-DE/about). Once the user is redirected to a localized URL, you can use the technique described above.Using the
Accept-Languageheader to show different languages for the same URL is considered bad practice for SEO and UX. It's recommended to useAccept-Languageonly to redirect the user.
2. <Link>
Update your <Link> component, for example:
// components/Link.js
import { usePageContext } from 'vike-react/usePageContext' // or vike-vue / vike-solid
import { localeDefault } from '../i18n'
export function Link({ href, locale, ...props }) {
const pageContext = usePageContext()
locale = locale ?? pageContext.locale
if (locale !== localeDefault) {
href = '/' + locale + href
}
return <a href={href} {...props} />
}// components/Link.ts
import { usePageContext } from 'vike-react/usePageContext' // or vike-vue / vike-solid
import { localeDefault } from '../i18n'
export function Link({ href, locale, ...props }: { locale: string } & AnchorElementAttributes) {
const pageContext = usePageContext()
locale = locale ?? pageContext.locale
if (locale !== localeDefault) {
href = '/' + locale + href
}
return <a href={href} {...props} />
}Examples
- /examples/i18n/
- github.com/crummy/vite-ssr-i18n
vite-plugin-ssr was the previous name of Vike.
Pre-rendering
If you use pre-rendering then, in addition to defining onBeforeRoute(), you also need to
define the onPrerenderStart() hook:
// pages/+onPrerenderStart.js
// Environment: build-time
export { onPrerenderStart }
const locales = ['en-US', 'de-DE', 'fr-FR']
const localeDefault = 'en-US'
async function onPrerenderStart(prerenderContext) {
const pageContexts = []
prerenderContext.pageContexts.forEach((pageContext) => {
// Duplicate pageContext for each locale
locales.forEach((locale) => {
// Localize URL
let { urlOriginal } = pageContext
if (locale !== localeDefault) {
urlOriginal = `/${locale}${pageContext.urlOriginal}`
}
pageContexts.push({
...pageContext,
urlOriginal,
// Set pageContext.locale
locale
})
})
})
return {
prerenderContext: {
pageContexts
}
}
}// pages/+onPrerenderStart.ts
// Environment: build-time
export { onPrerenderStart }
import type { PrerenderContext, PageContextServer } from 'vike/types'
const locales = ['en-US', 'de-DE', 'fr-FR']
const localeDefault = 'en-US'
async function onPrerenderStart(prerenderContext: PrerenderContext) {
const pageContexts: PageContextServer[] = []
prerenderContext.pageContexts.forEach((pageContext) => {
// Duplicate pageContext for each locale
locales.forEach((locale) => {
// Localize URL
let { urlOriginal } = pageContext
if (locale !== localeDefault) {
urlOriginal = `/${locale}${pageContext.urlOriginal}`
}
pageContexts.push({
...pageContext,
urlOriginal,
// Set pageContext.locale
locale
})
})
})
return {
prerenderContext: {
pageContexts
}
}
}See /examples/i18n/ for an example using onPrerenderStart().
Your onBeforePrerenderStart() hooks (if you use any) return URLs without any locale (e.g. onBeforePrerenderStart() returning /product/42). Instead, it's your onPrerenderStart() hook that duplicates and modifies URLs for each locale (e.g. duplicating /product/42 into /en-US/product/42, /de-DE/product/42, /fr-FR/product/42).
// pages/product/+onBeforePrerenderStart.js
export { onBeforePrerenderStart }
async function onBeforePrerenderStart() {
const products = await Product.findAll()
const URLs = products.map(({ id }) => '/product/' + id)
// You don't add the locale here (it's your onPrerenderStart() hook that adds the locales)
return URLs
}// pages/product/+onBeforePrerenderStart.ts
export { onBeforePrerenderStart }
async function onBeforePrerenderStart(): Promise<string[]> {
const products = await Product.findAll()
const URLs: string[] = products.map(({ id }) => '/product/' + id)
// You don't add the locale here (it's your onPrerenderStart() hook that adds the locales)
return URLs
}Essentially, you use onBeforePrerenderStart() to determine URLs and/or load data, and use onPrerenderStart() to
manipulate localized URLs and set pageContext.locale.
onPrerenderStart()is a global hook you can define only once, whileonBeforePrerenderStart()is a per-page hook you can define multiple times.
Alternatively, if you need to load data that depends on localization, instead of onPrerenderStart() you can use
onBeforePrerenderStart() to localize pageContext.data:
// pages/product/+onBeforePrerenderStart.js
// This example doesn't use onPrerenderStart() but, instead,
// uses onBeforePrerenderStart() to duplicate and localize URLs and their pageContext
export { onBeforePrerenderStart }
async function onBeforePrerenderStart() {
// Load data
const products = await Product.findAll()
// Set pageContext + localize
const urlsWithPageContext = []
products.forEach((product) => {
;['en-US', 'de-DE', 'fr-FR'].forEach((locale) => {
urlsWithPageContext.push({
url: `/${locale}/product/${product.id}`,
pageContext: {
locale,
product,
data: {
product: {
name: product.name,
description: product.description,
price: product.price
// ...
}
}
}
})
})
})
return urlsWithPageContext
}// pages/product/+onBeforePrerenderStart.ts
// This example doesn't use onPrerenderStart() but, instead,
// uses onBeforePrerenderStart() to duplicate and localize URLs and their pageContext
export { onBeforePrerenderStart }
type PageContext = {
locale: string
data?: Record<string, unknown>
} & Record<string, unknown>
type Return = { url: string, pageContext: PageContext }[]
async function onBeforePrerenderStart(): Promise<Return> {
// Load data
const products = await Product.findAll()
// Set pageContext + localize
const urlsWithPageContext: Return = []
products.forEach(product => {
['en-US', 'de-DE', 'fr-FR'].forEach(locale => {
urlsWithPageContext.push({
url: `/${locale}/product/${product.id}`,
pageContext: {
locale,
product,
data: {
product: {
name: product.name,
description: product.description,
price: product.price,
// ...
}
}
}
})
})
})
return urlsWithPageContext
}You may still need to use onPrerenderStart() for localizing static pages that don't load data:
// pages/+onPrerenderStart.js
export { onPrerenderStart }
import assert from 'assert'
const locales = ['en-US', 'de-DE', 'fr-FR']
async function onPrerenderStart(prerenderContext) {
const pageContexts = []
prerenderContext.pageContexts.forEach((pageContext) => {
if (pageContext.locale) {
// Already localized by one of your onBeforePrerenderStart() hooks
assert(pageContext.urlOriginal.startsWith(`/${pagecontext.locale}/`))
pageContexts.push(pageContext)
} else {
// Duplicate pageContext for each locale
locales.forEach((locale) => {
// Localize URL and pageContext
pageContexts.push({
...pageContext,
urlOriginal: `/${locale}${pageContext.urlOriginal}`,
locale
})
})
}
})
return {
prerenderContext: {
pageContexts
}
}
}// pages/+onPrerenderStart.ts
export { onPrerenderStart }
import assert from 'assert'
import type { PrerenderContext, PageContextServer } from 'vike/types'
const locales = ['en-US', 'de-DE', 'fr-FR']
async function onPrerenderStart(prerenderContext: PrerenderContext) {
const pageContexts: PageContextServer[] = []
prerenderContext.pageContexts.forEach((pageContext) => {
if(pageContext.locale) {
// Already localized by one of your onBeforePrerenderStart() hooks
assert(pageContext.urlOriginal.startsWith(`/${pagecontext.locale}/`))
pageContexts.push(pageContext)
} else {
// Duplicate pageContext for each locale
locales.forEach((locale) => {
// Localize URL and pageContext
pageContexts.push({
...pageContext,
urlOriginal: `/${locale}${pageContext.urlOriginal}`,
locale
})
})
}
})
return {
prerenderContext: {
pageContexts
}
}
}lang
You can use the lang setting to define the value of the <html lang> attribute, see lang.
Split locale data
To reduce your client bundle sizes, you can load only the translation of the current language — instead of loading the translations of all languages at once.
For example by using +onBeforeRender.shared.js:
// pages/+onBeforeRender.shared.js
// Environment: server & client
export async function onBeforeRender(pageContext) {
pageContext.localeData = await import(`../translations/${pageContext.locale}.js`)
}// pages/+onBeforeRender.shared.ts
// Environment: server & client
import type { PageContext } from 'vike/types'
export async function onBeforeRender(pageContext: PageContext) {
pageContext.localeData = await import(`../translations/${pageContext.locale}.ts`)
}Alternatively, you can define a server-only +onBeforeRender.js hook (without .shared.js suffix) and add pageContext.localeData to passToClient.