---
title: Internationalization
description: Render Policy in French, German, Dutch, or Spanish
---

> **PolicyStack V1** — current documentation. [Supported capabilities and limitations](https://policystack.dev/docs/reference/support).

Policy emits around 125 strings into every compiled policy — headings, table headers, GDPR/CCPA boilerplate, formatted dates. Set a `locale` and those strings switch language. Your company name, processing purposes, retention text, and third-party descriptions pass through as you wrote them.

## Supported locales

| Locale  | Tag  | Status        |
| ------- | ---- | ------------- |
| English | `en` | Ships in v1.0 |
| French  | `fr` | Ships in v1.0 |
| German  | `de` | Ships in v1.0 |
| Dutch   | `nl` | Ships in v1.0 |
| Spanish | `es` | Ships in v1.0 |

English is the ground truth — every other dictionary is checked against it at compile time via the `Dictionary` type, so a missing key fails `tsc` rather than silently falling back to English at runtime.

## Setting a locale on the config

Pass `locale` to `defineConfig` and every emitted string in both the privacy and cookie policies renders in that language:

```ts
// policystack.ts
import { ContractPrerequisite, defineConfig, LegalBases } from "@policystack/sdk";

export default defineConfig({
	company: {
		name: "Acme, Inc.",
		legalName: "Acme, Inc.",
		address: "123 Main St, San Francisco, CA",
		contact: { email: "privacy@acme.com" },
	},
	effectiveDate: "2026-01-01",
	jurisdictions: ["eea"],
	locale: "fr",
	data: {
		collected: {
			"Account Information": ["Name", "Email"],
		},
		context: {
			"Account Information": {
				purpose: "To authenticate users and send service notifications.",
				lawfulBasis: LegalBases.Contract,
				retention: "Until account deletion",
				provision: ContractPrerequisite("We cannot operate your account."),
			},
		},
	},
});
```

`locale` is the rendering language; it is independent of `jurisdictions` (which is the regulatory surface). `locale` is optional and defaults to `"en"`.

## Per-render override (React)

`<PrivacyPolicy />` and `<CookiePolicy />` accept a `locale` prop that overrides `config.locale` at render time. The same config can drive multiple languages side-by-side:

```tsx
import { PolicyStack } from "@policystack/react/provider";
import { PrivacyPolicy } from "@policystack/react/policy";
import policy from "@/policy";

export function PrivacyPolicyPage() {
	return (
		<PolicyStack config={policy}>
			<PrivacyPolicy /> {/* uses config.locale */}
			<PrivacyPolicy locale="fr" /> {/* override → French */}
		</PolicyStack>
	);
}
```

Useful for multilingual sites that want a language switcher, or for serving an English fallback alongside a regional translation.

## Dates

`effectiveDate` renders through `Intl.DateTimeFormat` with the locale's BCP-47 tag (`en-US`, `fr-FR`, `de-DE`, `nl-NL`, `es-ES`), pinned to UTC so the same input produces the same output across build servers in any timezone. The string `"2026-01-01"` becomes:

- English — `January 1, 2026`
- French — `1 janvier 2026`
- German — `1. Januar 2026`
- Dutch — `1 januari 2026`
- Spanish — `1 de enero de 2026`

## What does not translate

A handful of strings stay English by design — they're not user-facing policy content:

- **`reason:` audit metadata** on heading nodes (e.g. `"Required by GDPR Article 13(1)(c)"`) — threaded into the document tree for compliance tooling, not rendered to end users.
- **Validation messages** from `validate.ts`, `validate-config.ts`, and `validate-cookie.ts` — surface in build logs to the developer integrating Policy, not in the published document.
- **Section IDs** (`"introduction"`, `"data-collected"`, …) — stable identifiers used by tests and framework integrations to target sections.
- **Renderer format output** (markdown syntax, HTML tags, PDF bullet glyphs) — already locale-agnostic.
- **Internal `Error` messages** thrown when configs are malformed — developer-facing.

## Versions are per-locale

`locale` feeds into both `computePrivacyVersion` and `computeCookieVersion`, so a French build and an English build of the same config produce distinct 8-character version hashes. This is intentional: the Consent bridge re-prompts consent when the cookie-policy version changes, so users see a fresh prompt the first time they're served a different-language policy. See the [Consent docs](https://policystack.dev/docs/consent).

## Compliance caveat

The translated GDPR/CCPA/UK-GDPR boilerplate is first-pass legal text. Have a native-speaking compliance reviewer or counsel sign off on the rendered output before relying on a non-English locale in production — the same posture as Policy's English output, where the policy is a document, not legal advice.

## See also

- [Configuration](https://policystack.dev/docs/policy/configuration) — the full `defineConfig` reference, including how `locale` interacts with `effectiveDate` and `jurisdictions`.
- [Adding a locale](https://github.com/jamiedavenport/policystack/blob/main/packages/core/src/i18n/README.md) — internal contributor guide for adding new languages.
