TL;DR: Paraglide.js is a compile-time internationalization library that extracts messages at build time, generating optimized per-locale bundles with zero runtime overhead. Unlike traditional i18n libraries (react-intl, i18next) that ship all translations to the client, Paraglide only ships the locale the user needs. This guide covers production patterns for multilingual SaaS serving content in English, German, and Chinese.
Why Paraglide?
| Feature | Paraglide.js | react-intl | i18next | react-i18next |
|---|---|---|---|---|
| Bundle size | ~0 KB (compiled) | ~30 KB | ~35 KB | ~40 KB |
| Runtime overhead | None (compile-time) | Message parsing | Runtime lookups | Runtime lookups |
| Type safety | ✅ Full (generated TS) | Partial | Partial | Partial |
| Tree-shakeable | ✅ (dead code elimination) | ❌ | ❌ | ❌ |
| Lazy loading | Built-in (per-locale split) | Manual | Manual | Manual |
| ICU messages | ✅ | ✅ | Partial | Partial |
| SSR compatible | ✅ | ✅ | ✅ | ✅ |
Setup
npm install @inlang/paraglide-js
npx paraglide-js init
// project.inlang/settings.json
{
"sourceLanguageTag": "en",
"languageTags": ["en", "zh", "de"],
"modules": ["https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@latest/dist/index.js"],
"plugin.inlang.messageFormat": {
"pathPattern": "./messages/{languageTag}.json"
}
}
Message Files
// messages/en.json
{
"$schema": "https://inlang.com/schema/inlang-message-format",
"app_title": "TanStack Ship",
"nav_home": "Home",
"nav_pricing": "Pricing",
"nav_docs": "Docs",
"hero_heading": "Build Your SaaS Faster",
"hero_subheading": "A production-ready SaaS starter with auth, billing, and multi-language support.",
"cta_start": "Get Started",
"cta_learn": "Learn More",
"footer_copyright": "© 2026 TanStack Ship. All rights reserved.",
"pricing_monthly": "Monthly",
"pricing_yearly": "Yearly",
"pricing_save": "Save {discount}%",
"welcome_back": "Welcome back, {name}!",
"items_count": "{count} {count, plural, one {item} other {items}}"
}
// messages/zh.json
{
"$schema": "https://inlang.com/schema/inlang-message-format",
"app_title": "TanStack Ship",
"nav_home": "首页",
"nav_pricing": "定价",
"nav_docs": "文档",
"hero_heading": "更快地构建您的 SaaS",
"hero_subheading": "一个生产就绪的 SaaS 启动套件,支持认证、计费和多语言。",
"cta_start": "开始使用",
"cta_learn": "了解更多",
"footer_copyright": "© 2026 TanStack Ship。保留所有权利。",
"pricing_monthly": "月付",
"pricing_yearly": "年付",
"pricing_save": "节省 {discount}%",
"welcome_back": "欢迎回来,{name}!",
"items_count": "{count} 个{count, plural, other {项目}}"
}
Usage in React Components
Basic Usage
// Generated by Paraglide at build time
import * as m from "../paraglide/messages"
function HeroSection() {
return (
<section className="hero">
<h1>{m.hero_heading()}</h1>
<p>{m.hero_subheading()}</p>
<div className="ctas">
<Button>{m.cta_start()}</Button>
<Button variant="secondary">{m.cta_learn()}</Button>
</div>
</section>
)
}
Parameters and Pluralization
function WelcomeBanner({ userName, itemCount }: { userName: string; itemCount: number }) {
return (
<div className="welcome">
<h2>{m.welcome_back({ name: userName })}</h2>
<p>{m.items_count({ count: itemCount })}</p>
{/* English: "Welcome back, Alice!" / "5 items" */}
{/* Chinese: "欢迎回来,Alice!" / "5 个项目" */}
</div>
)
}
Locale Switching
Using TanStack Router Search Params
// src/routes/__root.tsx
import { useRouter } from "@tanstack/react-router"
function LocaleSwitcher() {
const router = useRouter()
const switchLocale = async (locale: "en" | "zh" | "de") => {
// Set the locale for Paraglide
await setLocale(locale)
// Update the URL without reloading
router.navigate({
to: router.state.location.pathname,
search: { locale },
replace: true,
})
}
return (
<select onChange={(e) => switchLocale(e.target.value as any)}>
<option value="en">English</option>
<option value="zh">中文</option>
<option value="de">Deutsch</option>
</select>
)
}
Server-Side Locale Detection
// server/middleware/locale.ts
export const resolveLocale = createServerFn({ method: "GET" }).handler(
async ({ request, context }) => {
// Priority: URL param > cookie > Accept-Language header > default
const urlParam = new URL(request.url).searchParams.get("locale")
if (urlParam && ["en", "zh", "de"].includes(urlParam)) {
return urlParam
}
const cookie = request.headers.get("Cookie")
const cookieLocale = cookie?.match(/locale=(\w+)/)?.[1]
if (cookieLocale && ["en", "zh", "de"].includes(cookieLocale)) {
return cookieLocale
}
const acceptLanguage = request.headers.get("Accept-Language")
if (acceptLanguage?.startsWith("zh")) return "zh"
if (acceptLanguage?.startsWith("de")) return "de"
return "en" // Default
}
)
SEO Integration
hreflang Tags
// Generated from Paraglide's language tags
function HreflangTags() {
const locales = ["en", "zh", "de"]
return (
<>
{locales.map((locale) => (
<link
key={locale}
rel="alternate"
hrefLang={locale}
href={`https://tanstackship.com/${locale}`}
/>
))}
<link rel="alternate" hrefLang="x-default" href="https://tanstackship.com" />
</>
)
}
Locale-Specific Canonical URLs
// TanStack Router route configuration
export const Route = createFileRoute("/pricing")({
loader: async ({ context }) => {
const locale = context.locale ?? "en"
const messages = await loadMessages(locale)
return {
meta: {
title: messages.page_title_pricing,
description: messages.page_desc_pricing,
canonical: `https://tanstackship.com/${locale}/pricing`,
hreflang: ["en", "zh", "de"].map((l) => ({
lang: l,
url: `https://tanstackship.com/${l}/pricing`,
})),
},
}
},
})
Optimized Bundle Splitting
Paraglide's compile-time approach generates separate bundles per locale:
# Build output — each locale is independently cacheable
dist/
paraglide/
en.js # 3.2 KB — English messages
zh.js # 3.5 KB — Chinese messages
de.js # 3.4 KB — German messages
Only the user's selected locale is loaded:
// Dynamic import based on locale
// This is handled automatically by Paraglide at build time
import(`../paraglide/${locale}.js`)
Number and Date Formatting
function PriceDisplay({ amount, locale }: { amount: number; locale: string }) {
const formatter = new Intl.NumberFormat(locale, {
style: "currency",
currency: "USD",
})
return <span>{formatter.format(amount)}</span>
// en: "$29.00" | zh: "US$29.00" | de: "29,00 $"
}
function DateDisplay({ date, locale }: { date: Date; locale: string }) {
return (
<time dateTime={date.toISOString()}>
{new Intl.DateTimeFormat(locale, {
dateStyle: "long",
}).format(date)}
</time>
// en: "June 16, 2026" | zh: "2026年6月16日" | de: "16. Juni 2026"
)
}
Working with Translation Management
Inlang IDE Extension
Use the Inlang VS Code extension for inline message editing:
// In VS Code, hover over m.hero_heading() to see translations
// Click to edit directly in the IDE
function HeroSection() {
return (
<h1>{m.hero_heading()}</h1>
// Press Cmd+. to open translation editor
)
}
Export/Import for Translators
# Export all messages to CSV for translators
npx paraglide-js export --format csv
# Import translated messages
npx paraglide-js import translated_messages.csv
Production Checklist
- All user-facing strings use Paraglide message functions
- Locale switching does not trigger a full page reload
- Locale preference persisted in cookie and database
- hreflang tags set for all supported locales
- Canonical URLs include locale prefix
- Server-side locale detection handles Accept-Language header
- Number and date formatting use
IntlAPIs (not hardcoded) - Translation files have a review workflow
- Missing translations fall back to source language (English)
- RTL support checked if applicable (Arabic, Hebrew)
Conclusion
Paraglide.js represents the next generation of i18n for React applications. Its compile-time approach eliminates runtime overhead while providing full type safety and optimized bundle splitting. Combined with TanStack Start, locale detection and switching integrate naturally into the server function and routing architecture. For a broader perspective on entering global markets, see SaaS globalization strategy.
The key advantages for SaaS:
- Zero runtime cost — translations are compiled into the bundle
- Type safety — missing messages are caught at build time, not runtime
- Optimized delivery — users only download their locale's messages
- SEO ready — hreflang, canonical, and sitemap integration
For a complete multilingual SEO strategy covering hreflang, canonical URLs, and content localization across markets, see Multilingual SEO complete strategy and the hreflang tags ultimate guide.