Internationalization (i18n)
To internationalize (i18n) your app:
- Use the
onBeforeRoute()
hook. - Update your
<Link>
component.
1. onBeforeRoute()
// pages/+onBeforeRoute.ts
import { modifyUrl } from 'vike/modifyUrl'
import type { Url } from 'vike/types'
export 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: 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=fr
query parametersdomain.fr
domain TLDsAccept-Language: fr-FR
headersThe
Accept-Language
header 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-Language
header to show different languages for the same URL is considered bad practice for SEO and UX. It's recommended to useAccept-Language
only to redirect the user.
2. <Link>
Update your <Link>
component, for example:
// components/Link.jsx
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} />
}
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
export { onPrerenderStart }
const locales = ['en-US', 'de-DE', 'fr-FR']
const localeDefault = 'en-US'
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
}
}
}
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
}
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
}
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']
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
}
}
}
lang
You can use the lang
setting to define the value of the <html lang>
attribute, see lang
.