Internationalization
Render Policy in French, German, Dutch, or Spanish
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:
// 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:
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, andvalidate-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
Errormessages 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.
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 — the full
defineConfigreference, including howlocaleinteracts witheffectiveDateandjurisdictions. - Adding a locale — internal contributor guide for adding new languages.