Paraglide.js 实战:现代化 React i18n 工作流

深入实践 Paraglide.js — 面向 React 的现代化编译时国际化方案 — 涵盖安装配置、消息提取、运行时切换、复数处理、日期格式化及与 TanStack Start 的集成。

Sam Rivera
Sam Rivera
June 16, 202612 min read
其他语言:Deutsch · English

TL;DR: Paraglide.js 是一款编译时国际化库,在构建阶段提取消息,生成针对各语言优化的包,零运行时开销。传统的 i18n 库(如 react-intl、i18next)会将所有翻译发送给客户端,而 Paraglide 只交付用户所需的语言包。本文涵盖面向多语言 SaaS 的生产级模式,涉及英语、德语和中文三种语言。


为什么选择 Paraglide?

功能Paraglide.jsreact-intli18nextreact-i18next
包体积~0 KB(编译后)~30 KB~35 KB~40 KB
运行时开销无(编译时)消息解析运行时查找运行时查找
类型安全✅ 完整(生成 TS)部分部分部分
Tree-shakeable✅(死代码消除)❌❌❌
懒加载内置(按语言分割)手动手动手动
ICU 消息✅✅部分部分
SSR 兼容✅✅✅✅

安装配置

bash
npm install @inlang/paraglide-js
npx paraglide-js init
typescript
// 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"
  }
}

消息文件

json
// 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}}"
}
json
// 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 {项目}}"
}

在 React 组件中使用

基础用法

typescript
// 构建时由 Paraglide 生成
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>
  )
}

参数与复数处理

typescript
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>
  )
}

语言切换

使用 TanStack Router 搜索参数

typescript
// src/routes/__root.tsx
import { useRouter } from "@tanstack/react-router"

function LocaleSwitcher() {
  const router = useRouter()

  const switchLocale = async (locale: "en" | "zh" | "de") => {
    // 设置 Paraglide 的语言
    await setLocale(locale)

    // 更新 URL 而不重新加载页面
    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>
  )
}

服务端语言检测

typescript
// server/middleware/locale.ts
export const resolveLocale = createServerFn({ method: "GET" }).handler(
  async ({ request, context }) => {
    // 优先级:URL 参数 > Cookie > Accept-Language 头 > 默认值
    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" // 默认语言
  }
)

SEO 集成

hreflang 标签

typescript
// 由 Paraglide 的语言标签生成
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" />
    </>
  )
}

各语言规范 URL

typescript
// TanStack Router 路由配置
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`,
        })),
      },
    }
  },
})

优化的包分割

Paraglide 的编译时方案按语言生成独立的包:

bash
# 构建产物 — 各语言独立缓存
dist/
  paraglide/
    en.js       # 3.2 KB — 英文消息
    zh.js       # 3.5 KB — 中文消息
    de.js       # 3.4 KB — 德语消息

仅加载用户选择的语言:

typescript
// 根据语言动态导入
// Paraglide 在构建时自动处理
import(`../paraglide/${locale}.js`)

数字与日期格式化

typescript
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"
  )
}

翻译管理

Inlang IDE 插件

使用 Inlang VS Code 插件进行内联消息编辑:

typescript
// 在 VS Code 中,将鼠标悬停在 m.hero_heading() 上即可查看翻译
// 点击即可直接在 IDE 中编辑

function HeroSection() {
  return (
    <h1>{m.hero_heading()}</h1>
    // 按 Cmd+. 打开翻译编辑器
  )
}

面向译员的导出/导入

bash
# 将所有消息导出为 CSV 供译员使用
npx paraglide-js export --format csv

# 导入已翻译的消息
npx paraglide-js import translated_messages.csv

生产环境检查清单

  • 所有面向用户的字符串均使用 Paraglide 消息函数
  • 语言切换不触发整页重新加载
  • 语言偏好持久化至 Cookie 和数据库
  • 所有支持语言均设置 hreflang 标签
  • 规范 URL 包含语言前缀
  • 服务端语言检测处理 Accept-Language 头
  • 数字和日期格式化使用 Intl API(非硬编码)
  • 翻译文件有审核工作流
  • 缺失翻译回退至源语言(英语)
  • 如适用,检查 RTL 支持(阿拉伯语、希伯来语)

结语

Paraglide.js 代表了 React 应用 i18n 的下一代方案。其编译时方案消除了运行时开销,同时提供完整的类型安全性和优化的包分割。结合 TanStack Start,语言检测与切换可自然融入服务端函数和路由架构中。关于进入全球市场的更广泛视角,请参阅 SaaS 全球化策略。

Paraglide 对 SaaS 的核心优势:

  1. 零运行时成本 — 翻译在构建时编译进包
  2. 类型安全 — 缺失消息在构建时被捕获,而非运行时
  3. 优化交付 — 用户仅下载其语言的消息
  4. SEO 就绪 — hreflang、规范 URL 和 sitemap 集成

关于涵盖 hreflang、规范 URL 及跨市场内容本地化的完整多语言 SEO 策略,请参阅 多语言 SEO 完整策略 和 hreflang 标签终极指南。