Internationalization (i18n)

You can internationalize (i18n) your app by using the onBeforeRoute() hook.

// /renderer/+onBeforeRoute.js
 
export { onBeforeRoute }
 
import type { Url } from 'vike/types'
 
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
      urlLogical: urlWithoutLocale
    }
  }
}
 
// Or use a library instead of implementing your own locale retrieval mechanism
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 { origin, searchOriginal, hashOriginal } = url
  const urlWithoutLocale = `${origin || ''}${pathnameWithoutLocale}${searchOriginal || ''}${hashOriginal || ''}`
 
  return { locale, urlWithoutLocale }
}

Upon rendering a page, onBeforeRoute() is the first hook that Vike calls, which means that the rest of your app doesn't have to deal with localized URLs anymore and you can simply use pageContext.locale instead.

See API > usePageContext() for being able to access pageContext.locale in any React/Vue component.

This technique also works with:

  • ?lang=fr query parameters

  • domain.fr domain TLDs

  • Accept-Language: fr-FR headers

    The Accept-Language header can be used for redirecting the user to the right localized URL (e.g. URL /about + Header Accept-Language: de-DE => redirect to /de-DE/about). Once the user is redirected to a localized URL, you can use the technique described above. You can perform the redirection by using your server (e.g. Express.js) independently of Vike.

    Using the Accept-Language header to show different languages for the same URL is considered bad practice for both SEO and UX reasons. It's recommended to use Accept-Language only to redirect the user.

Examples

Pre-rendering

If you use pre-rendering then, in addition to defining onBeforeRoute(), you also need to define the onPrerenderStart() hook:

// /renderer/+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, while onBeforePrerenderStart() 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:

// /renderer/+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.

See also