国际化

Codofly Template 使用 next-intl 提供完整的国际化支持,目前支持英文和简体中文。

next-intl 配置

基础配置

title="i18n.ts"
import { notFound } from 'next/navigation'
import { getRequestConfig } from 'next-intl/server'

// 支持的语言列表
export const locales = ['en', 'zh'] as const
export type Locale = typeof locales[number]

export default getRequestConfig(async ({ locale }) => {
  // 验证语言是否支持
  if (!locales.includes(locale as any)) notFound()

  return {
    messages: (await import(`./messages/${locale}.json`)).default
  }
})

中间件配置

title="middleware.ts"
import createMiddleware from 'next-intl/middleware'
import { locales } from './i18n'

export default createMiddleware({
  locales,
  defaultLocale: 'en',
  localePrefix: 'always'
})

export const config = {
  matcher: ['/((?!api|_next|_vercel|.*\\..*).*)']
}

翻译文件管理

英文翻译

title="messages/en.json"
{
  "Common": {
    "save": "Save",
    "cancel": "Cancel",
    "delete": "Delete",
    "edit": "Edit",
    "loading": "Loading...",
    "error": "An error occurred"
  },
  "Navigation": {
    "home": "Home",
    "dashboard": "Dashboard",
    "teams": "Teams",
    "settings": "Settings",
    "signIn": "Sign In",
    "signOut": "Sign Out"
  },
  "HomePage": {
    "title": "Build AI SaaS Apps Fast",
    "subtitle": "Everything you need to launch your AI-powered SaaS",
    "getStarted": "Get Started",
    "learnMore": "Learn More"
  },
  "Features": {
    "ai": {
      "title": "Multi-AI Integration",
      "description": "Support for OpenAI, Anthropic, and Google models"
    },
    "auth": {
      "title": "User Authentication",
      "description": "Complete auth system with social login"
    },
    "teams": {
      "title": "Team Collaboration",
      "description": "Built-in team management and sharing"
    }
  }
}

中文翻译

title="messages/zh.json"
{
  "Common": {
    "save": "保存",
    "cancel": "取消",
    "delete": "删除",
    "edit": "编辑",
    "loading": "加载中...",
    "error": "发生错误"
  },
  "Navigation": {
    "home": "首页",
    "dashboard": "仪表盘",
    "teams": "团队",
    "settings": "设置",
    "signIn": "登录",
    "signOut": "退出"
  },
  "HomePage": {
    "title": "快速构建 AI SaaS 应用",
    "subtitle": "启动 AI 驱动的 SaaS 所需的一切",
    "getStarted": "开始使用",
    "learnMore": "了解更多"
  },
  "Features": {
    "ai": {
      "title": "多 AI 模型集成",
      "description": "支持 OpenAI、Anthropic 和 Google 模型"
    },
    "auth": {
      "title": "用户认证",
      "description": "完整的认证系统,支持社交登录"
    },
    "teams": {
      "title": "团队协作",
      "description": "内置团队管理和共享功能"
    }
  }
}

多语言路由

根布局

title="app/[locale]/layout.tsx"
import { NextIntlClientProvider } from 'next-intl'
import { getMessages } from 'next-intl/server'
import { setRequestLocale } from 'next-intl/server'
import { locales } from '@/i18n'

export function generateStaticParams() {
  return locales.map((locale) => ({ locale }))
}

interface Props {
  children: React.ReactNode
  params: { locale: string }
}

export default async function LocaleLayout({
  children,
  params: { locale }
}: Props) {
  // 启用静态渲染
  setRequestLocale(locale)
  
  const messages = await getMessages()

  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  )
}

页面国际化

title="app/[locale]/page.tsx"
import { useTranslations } from 'next-intl'
import { setRequestLocale } from 'next-intl/server'

interface Props {
  params: { locale: string }
}

export default function HomePage({ params: { locale } }: Props) {
  setRequestLocale(locale)
  
  const t = useTranslations('HomePage')

  return (
    <div className="text-center">
      <h1 className="text-4xl font-bold">{t('title')}</h1>
      <p className="text-xl text-gray-600 mt-4">{t('subtitle')}</p>
      <div className="mt-8 space-x-4">
        <button className="bg-blue-500 text-white px-6 py-2 rounded">
          {t('getStarted')}
        </button>
        <button className="border border-gray-300 px-6 py-2 rounded">
          {t('learnMore')}
        </button>
      </div>
    </div>
  )
}

语言切换

语言切换组件

title="components/language-switcher.tsx"
'use client'

import { useLocale } from 'next-intl'
import { usePathname, useRouter } from 'next/navigation'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'

const languages = [
  { code: 'en', name: 'English', flag: '🇺🇸' },
  { code: 'zh', name: '简体中文', flag: '🇨🇳' }
]

export function LanguageSwitcher() {
  const locale = useLocale()
  const router = useRouter()
  const pathname = usePathname()

  const handleLanguageChange = (newLocale: string) => {
    // 移除当前语言前缀
    const pathWithoutLocale = pathname.replace(`/${locale}`, '') || '/'
    
    // 添加新语言前缀
    const newPath = `/${newLocale}${pathWithoutLocale}`
    
    router.push(newPath)
  }

  return (
    <Select value={locale} onValueChange={handleLanguageChange}>
      <SelectTrigger className="w-40">
        <SelectValue>
          {languages.find(lang => lang.code === locale)?.flag}{' '}
          {languages.find(lang => lang.code === locale)?.name}
        </SelectValue>
      </SelectTrigger>
      <SelectContent>
        {languages.map(language => (
          <SelectItem key={language.code} value={language.code}>
            {language.flag} {language.name}
          </SelectItem>
        ))}
      </SelectContent>
    </Select>
  )
}

导航栏集成

title="components/navbar.tsx"
import { useTranslations } from 'next-intl'
import { LanguageSwitcher } from './language-switcher'
import { Link } from '@/navigation'

export function Navbar() {
  const t = useTranslations('Navigation')

  return (
    <nav className="flex items-center justify-between p-4">
      <div className="flex space-x-6">
        <Link href="/">{t('home')}</Link>
        <Link href="/dashboard">{t('dashboard')}</Link>
        <Link href="/teams">{t('teams')}</Link>
      </div>
      
      <div className="flex items-center space-x-4">
        <LanguageSwitcher />
        <Link href="/auth/signin">{t('signIn')}</Link>
      </div>
    </nav>
  )
}

内容本地化

日期和时间

title="lib/date-formatter.ts"
import { useLocale } from 'next-intl'

export function useDateFormatter() {
  const locale = useLocale()

  const formatDate = (date: Date) => {
    return new Intl.DateTimeFormat(locale, {
      year: 'numeric',
      month: 'long',
      day: 'numeric'
    }).format(date)
  }

  const formatDateTime = (date: Date) => {
    return new Intl.DateTimeFormat(locale, {
      year: 'numeric',
      month: 'short',
      day: 'numeric',
      hour: '2-digit',
      minute: '2-digit'
    }).format(date)
  }

  return { formatDate, formatDateTime }
}

数字格式化

title="components/price-display.tsx"
import { useLocale } from 'next-intl'

interface PriceDisplayProps {
  amount: number
  currency?: string
}

export function PriceDisplay({ amount, currency = 'USD' }: PriceDisplayProps) {
  const locale = useLocale()

  const formatPrice = (amount: number, currency: string) => {
    return new Intl.NumberFormat(locale, {
      style: 'currency',
      currency: currency
    }).format(amount)
  }

  return <span>{formatPrice(amount, currency)}</span>
}

复数处理

title="components/message-count.tsx"
import { useTranslations } from 'next-intl'

interface MessageCountProps {
  count: number
}

export function MessageCount({ count }: MessageCountProps) {
  const t = useTranslations('Messages')

  return (
    <span>
      {t('count', { count })}
    </span>
  )
}

对应的翻译文件:

{
  "Messages": {
    "count": {
      "0": "No messages",
      "1": "1 message", 
      "other": "{count} messages"
    }
  }
}

动态导入翻译

按需加载翻译

title="lib/translations.ts"
export async function loadTranslations(locale: string, namespace: string) {
  try {
    const messages = await import(`@/messages/${locale}/${namespace}.json`)
    return messages.default
  } catch (error) {
    console.warn(`Failed to load ${namespace} translations for ${locale}`)
    return {}
  }
}

分离翻译文件

messages/
├── en/
│   ├── common.json
│   ├── navigation.json
│   ├── auth.json
│   └── dashboard.json
└── zh/
    ├── common.json
    ├── navigation.json
    ├── auth.json
    └── dashboard.json

添加新语言

步骤

  1. 添加语言到配置
title="i18n.ts"
export const locales = ['en', 'zh', 'ja'] as const // 添加日语
  1. 创建翻译文件
title="messages/ja.json"
{
  "Common": {
    "save": "保存",
    "cancel": "キャンセル",
    "delete": "削除"
  }
}
  1. 更新语言切换器
title="components/language-switcher.tsx"
const languages = [
  { code: 'en', name: 'English', flag: '🇺🇸' },
  { code: 'zh', name: '简体中文', flag: '🇨🇳' },
  { code: 'ja', name: '日本語', flag: '🇯🇵' } // 添加日语
]

服务端组件翻译

API 响应本地化

title="app/api/messages/route.ts"
import { getTranslations } from 'next-intl/server'

export async function POST(request: Request) {
  const { locale } = await request.json()
  const t = await getTranslations({ locale, namespace: 'API' })

  try {
    // 处理逻辑
    return NextResponse.json({
      message: t('success')
    })
  } catch (error) {
    return NextResponse.json({
      error: t('error')
    }, { status: 500 })
  }
}

最佳实践

1. 翻译键命名

// ✅ 好的命名
"Auth.SignIn.title"
"Dashboard.Stats.users"
"Teams.Members.invite"

// ❌ 避免的命名
"text1"
"message"
"button"

2. 翻译文件结构

{
  "ComponentName": {
    "title": "标题",
    "description": "描述",
    "actions": {
      "save": "保存",
      "cancel": "取消"
    }
  }
}

3. 处理缺失翻译

title="lib/fallback-translations.ts"
export function withFallback(key: string, fallback: string) {
  const t = useTranslations()
  
  try {
    return t(key)
  } catch {
    return fallback
  }
}

性能优化

翻译预加载

title="app/[locale]/loading.tsx"
import { getMessages } from 'next-intl/server'

export default async function Loading({ params: { locale } }) {
  // 预加载翻译
  await getMessages()
  
  return <div>Loading...</div>
}

静态生成

title="next.config.js"
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    ppr: true, // 启用部分预渲染
  },
}

module.exports = nextConfig

查看 next-intl 文档 了解更多高级功能。