Skip to content

Internationalisation

@tickboxhq/banner-default ships with translations for en, de, fr, es, it, nl, pt, pl, uk. This page covers the three common patterns: pick a single language, auto-detect at render, or hand the locale through your existing i18n library.

One language for the whole site

Pass the language as a string. BCP-47 region tags work — they fall back to the language prefix automatically.

<ConsentBannerDefault locale="de" policyUrl="/privacy" />
<ConsentBannerDefault locale="fr-CH" /> // resolves to fr
<ConsentBannerDefault locale="pt-BR" /> // resolves to pt

If you pass an unsupported locale, the component renders English without throwing. That’s the intended degrade — you don’t want a missing translation to break the consent flow.

Auto-detect from the browser

<ConsentBannerDefault locale="auto" />

'auto' reads navigator.language at render time. The catch: SSR. If you render on the server, there is no navigator, and the banner falls back to English. Two ways out:

Next.js — render after mount:

'use client'
import { useEffect, useState } from 'react'
import { ConsentBannerDefault } from '@tickboxhq/banner-default/react'
export function Banner() {
const [mounted, setMounted] = useState(false)
useEffect(() => setMounted(true), [])
if (!mounted) return null
return <ConsentBannerDefault locale="auto" />
}

Nuxt — <ClientOnly>:

<template>
<ClientOnly>
<ConsentBannerDefault locale="auto" />
</ClientOnly>
</template>

The trade-off: a one-frame flash of nothing on first paint. For a consent banner shown only to first-time visitors that’s fine.

Drive from your existing i18n library

If you already detect locale on the server (cookie, Accept-Language, route prefix), forward that into the banner. Examples below — both next-intl and vue-i18n expose the active locale, so it’s a one-liner.

next-intl:

import { useLocale } from 'next-intl'
import { ConsentBannerDefault } from '@tickboxhq/banner-default/react'
export function Banner() {
const locale = useLocale()
return <ConsentBannerDefault locale={locale} />
}

vue-i18n:

<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import { ConsentBannerDefault } from '@tickboxhq/banner-default/vue'
const { locale } = useI18n()
</script>
<template>
<ConsentBannerDefault :locale="locale" />
</template>

This works because the resolver is permissive — en-GB, EN, de_AT all map to the right pack regardless of which case or separator your library normalises to.

Ship a translation we don’t have

If you need a language not in the built-in set, pass the full copy prop. The shape is documented in the banner-default getting-started page.

import type { BannerCopy } from '@tickboxhq/banner-default/react'
const cs: BannerCopy = {
title: 'Soubory cookie a sledování',
description: 'Používáme soubory cookie...',
acceptLabel: 'Přijmout vše',
rejectLabel: 'Odmítnout vše',
customiseLabel: 'Přizpůsobit',
saveLabel: 'Uložit předvolby',
closeLabel: 'Zavřít',
policyLinkLabel: 'Zásady ochrany osobních údajů',
requiredBadge: 'Vyžadováno',
}
<ConsentBannerDefault copy={cs} />

If the translation is good, send a PR adding it to packages/banner-default/src/shared/locales/ so the next version ships it for everyone.

What gets translated, and what doesn’t

The banner labels and the notice card labels — yes. The category names from your consent.config.ts — no. Those are your strings; translate them where you define them.

consent.config.ts
import { defineConsent, jurisdictions } from '@tickboxhq/core'
import { useI18n } from './lib/i18n' // your i18n helper
export default defineConsent({
jurisdiction: jurisdictions.EU_GDPR,
policy: { version: '2026-05-09', url: '/privacy' },
categories: {
necessary: {
required: true,
description: useI18n('consent.necessary.description'),
},
analytics: {
vendors: ['google-analytics'],
default: false,
description: useI18n('consent.analytics.description'),
},
},
})

Whatever string you put in description is what shows in the customise modal — translate at config time, not at component time.

Switching language at runtime

If your site lets users switch language without a full reload, just bind the locale prop to a reactive value. The banner re-renders with the new pack on the next tick. No global state needed.

const [lang, setLang] = useState('en')
<>
<select onChange={(e) => setLang(e.target.value)}>
<option value="en">English</option>
<option value="de">Deutsch</option>
<option value="fr">Français</option>
</select>
<ConsentBannerDefault locale={lang} />
</>

Bundle impact

All nine locale packs are statically included in the shared chunk so locale selection works at runtime without an extra fetch. That’s about 6 KB extra (unminified) versus a hypothetical English-only build. After gzip, around 2 KB. Tradeable for the operational simplicity of “no async loader, no missing-translation race conditions, works on first paint.”

If you only ever serve one language and care about every byte: import the locale pack directly and pass it to copy, then drop the locale prop. The unused packs tree-shake out.

import { banner } from '@tickboxhq/banner-default/locales/de' // not exposed yet

That import path doesn’t exist as of v0.0.15 — open a GitHub issue if you need it and we’ll add the sub-export.