---
title: Translations
description: Change c15t banner and dialog copy in a Svelte app with component
  text props or the provider's i18n option, and switch languages at runtime.
group: frameworks
lastModified: "2026-10-10T16:01:45+01:00"
---
## Where the copy comes from

The banner, dialog, widget, gate placeholder and IAB surfaces read their text
from one set of translations for the visitor's language. Which set depends on
the provider's `mode`:

|Mode|Copy comes from|Languages|
|--|--|--|
|`hosted()`|The backend's `/init` response, in the language the browser or server asked for, with the provider's `i18n` messages for that language on top|Every language the backend bundles: bg, cs, cy, da, de, el, en, es, et, fi, fr, ga, gu, he, hi, hr, hu, id, is, it, lb, lt, lv, mt, nb, nl, nn, pl, pt, rm, ro, sk, sl, sv and zh|
|`manifest()`|The manifest's translations over the base copy for the visitor's language, with the provider's `i18n` messages on top. Resolved on the server in SvelteKit, in the browser in Svelte.|English base copy is bundled; another language's base copy loads the first time a visitor needs it|
|`offline()`|The provider's `i18n` prop, merged over bundled English|English, plus any language you supply in full|

The browser bundle carries English only. Other languages arrive from the
backend, or from messages you pass.

## Change the copy for one banner

`ConsentBanner` takes `title`, `description`, `acceptButtonText`,
`rejectButtonText`, `customizeButtonText` and `dismissButtonText`. These props
win in every mode:

```svelte
<ConsentBanner
	title="Cookies on this site"
	rejectButtonText="Reject optional"
/>
```

The props replace the text in every language, so pass values in the page's
language, or compute them from your own i18n library.

## Change the copy site-wide

Pass `i18n` to the provider. `messages` holds partial translations per
language; the keys you leave out keep c15t's copy:

```ts title="src/lib/consent-i18n.ts"
import type { I18nConfig } from '@c15t/svelte';

// Pass as `i18n={i18n}` on ConsentProvider. Messages merge over the
// bundled English copy, so list only the keys you change.
export const i18n: Partial<I18nConfig> = {
	locale: 'en',
	messages: {
		en: {
			common: { customize: 'Choose cookies', rejectAll: 'Reject optional' },
			cookieBanner: { title: 'Your privacy on this site' },
		},
	},
};
```

`locale` selects the language the provider starts in, `en` by default.

In `hosted()` mode, the backend's copy for the visitor's language is the base,
and your `i18n` messages for that language replace it key by key. Keys you do
not list keep the backend's copy, including copy edited in your Inth project.
The same applies to the copy SvelteKit's `loadConsent` resolves on the
server. c15t picks the
messages for the exact language first, then for its primary language, so a
visitor resolved to `de-AT` gets your `de` messages when there is no `de-AT`
entry. Messages for other languages are not applied.

A key only overrides the backend when your text differs from c15t's built-in
wording for that language. So passing the stock bundles from
`@c15t/translations/all` to enable languages keeps backend edits visible,
while a key you actually reworded stays pinned in code. Core bundles only
English; for other languages, import `@c15t/translations/all` so c15t can
recognize its stock wording. A
self-hosted backend takes `customTranslations` and `i18n` in its
[configuration](/docs/self-host/api/configuration).

In `offline()` mode, `i18n` is the only source of copy, and it applies from the
first render.

## Common message keys

|Key|Where it shows|
|--|--|
|`cookieBanner.title`, `cookieBanner.description`|Choice banner heading and body.|
|`cookieBanner.noticeTitle`, `cookieBanner.noticeDescription`|Notice banner heading and body.|
|`common.acceptAll`, `common.rejectAll`, `common.customize`, `common.save`|Banner and dialog buttons.|
|`common.acknowledge`|The notice's dismiss button.|
|`common.securedBy`|The "Secured by" tag.|
|`consentManagerDialog.title`, `consentManagerDialog.description`|Preference dialog heading and body.|
|`consentTypes.<category>.title`, `consentTypes.<category>.description`|A category's name and description, such as `consentTypes.measurement.title`.|
|`consentGate.title`, `consentGate.actionButton`|The `ConsentGate` placeholder. `{category}` is replaced by the category title.|

[Copy and translations](/docs/customization/translations) covers wording for
each action and testing long labels.

## Switch the language

`getConsentManager().setLanguage(code)` switches the language and resolves the
policy again, so a hosted backend returns copy in the new language and your
`i18n` messages for that language apply on top. Call it
from your language switcher's event handler. The visitor's language otherwise
comes from the `Accept-Language` header, or from `overrides.language` on the
provider.

The root of the banner, dialog and widget sets `dir` from the active
language, so right-to-left languages such as Hebrew lay out correctly.

## Verify the copy

Open the page with the browser language set to each language you support.
The banner, the dialog's category names and the gate placeholder all use that
language. Then choose a narrow window and zoom to 200%: long labels wrap
without cutting off.
