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 ptIf 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.
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 yetThat 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.