Localization
Ship a paywall in multiple languages by adding one file per locale — no registration, no wiring.
Ship a paywall in multiple languages by adding one file per locale. The filename is the locale, and the device picks which one renders — no registration, no wiring.
Add locales
Message catalogs live in messages/ directories, at the same two levels as components and assets:
superwall/
├── messages/ shared by every paywall
│ ├── en.ts
│ └── de.ts
└── paywalls/pro/
└── messages/ this paywall's own
├── en.ts
└── fr.tsEach file default-exports a nested object:
// paywalls/pro/messages/fr.ts
export default {
paywall: {
title: "Passez à Pro",
cta: "S'abonner · {price}",
perMonth: "{price} par mois, facturé annuellement",
},
} as const;A paywall's own catalog layers over the shared one — it overrides the keys it names and inherits the rest. A locale can exist in either layer or both.
If your fallback language isn't English, set it in config.ts:
localization: { defaultLocale: "en" },Use the strings — useTranslation()
const { t, locale, setLocale, locales } = useTranslation();
<h1>{t("paywall.title")}</h1>
<button>{price ? t("paywall.cta", { price }) : t("paywall.ctaBare")}</button>
<button aria-label={t("paywall.close")}>×</button>t(key, values?)— the translated string for the active locale. Interpolation is{name}in the catalog witht(key, { name: value })at the call site.locale— the active locale, resolved from the device. Resolution is specific-to-general:pt-BRmatches apt-BRcatalog first, thenpt, then the default locale.setLocale(locale)— override the device;setLocale(undefined)returns to auto-detection. This is for previews and tests — on device, the system setting is the truth.locales— every locale that has a catalog.
How fallbacks behave
- A key missing from the active locale falls back to the default locale per key — a partial translation stays usable while it's being finished.
- An unknown key renders as itself, so
t()never breaks. The flip side: key typos are invisible at runtime — nothing throws, the key just shows up on screen. Check your copy in the studio with its locale switcher. - Guard interpolations on the value existing, with a bare-key fallback — as in the CTA above. Never render "Subscribe · undefined".
The rules
- Never put a price in a catalog. Prices are localized by the store — the SDK delivers the right currency and format for the user's region. Interpolate them:
"Subscribe · {price}". See Products. - No language picker on device. The locale is the person's system setting; preview other locales with the studio's locale switcher.
- Copy expands. German runs long — size nothing to fit English.
- Product
periodandperiodlyvariables ("yearly" → "jährlich") localize automatically in 44 languages, independent of your catalogs. - A single-locale paywall needs none of this — plain strings in JSX are fine until the second locale arrives.
There is no plural engine — no ICU, no _one/_other suffixes. Write around plurals, or fork on the count yourself.
The localization example shows four locales, both catalog layers, and guarded interpolation.
How is this guide?