TL;DR: 为每位访客精准检测语言需要叠加浏览器头信息、地理位置、已保存的用户偏好及 URL 信号。本指南涵盖检测策略、降级链路与持久化方案。更多模式见 tanstackship.com。
引言
向用户展示错误的语言,是流失国际访客最快的方式之一。一位德国用户进入纯英文页面后的跳出率高达 70%。有效的语言检测需要多层级策略配合合理的降级方案。如需了解 i18n 架构模式概述,请先阅读 SaaS 应用 i18n:架构模式对比。
检测层级策略
| 优先级 | 来源 | 工作方式 | 可靠性 |
|---|---|---|---|
| 1 | URL 路径/语言前缀 | /de/dashboard | 最高 |
| 2 | 用户偏好(已保存) | Cookie、localStorage、数据库 | 高 |
| 3 | 浏览器 Accept-Language | navigator.languages | 中 |
| 4 | Geo IP 地理位置 | Cloudflare、MaxMind | 中 |
| 5 | 默认语言环境 | 可配置的降级方案 | 最低 |
基于 URL 的检测(最高优先级)
// TanStack Router: 从 URL 路径获取 locale
import { createRootRoute, createRouter, redirect } from '@tanstack/react-router'
const SUPPORTED_LOCALES = ['en', 'de', 'zh', 'ja', 'ar']
const DEFAULT_LOCALE = 'en'
// 带有 locale 参数的根路由
const rootRoute = createRootRoute({
beforeLoad: ({ params, location }) => {
const locale = params.locale
if (!locale || !SUPPORTED_LOCALES.includes(locale)) {
// 检测并重定向
const detected = detectUserLocale()
throw redirect({
to: `/${detected}${location.pathname}`,
replace: true,
})
}
// 为应用设置 locale
setLocale(locale)
},
})
浏览器语言检测(中间件)
// Cloudflare Worker: Accept-Language 解析
function parseAcceptLanguage(header: string | null): string[] {
if (!header) return [DEFAULT_LOCALE]
return header
.split(',')
.map(entry => {
const [lang, quality = 'q=1'] = entry.trim().split(';')
const q = parseFloat(quality.replace('q=', '')) || 1
const locale = lang.split('-')[0] // 'en-US' → 'en'
return { locale, q }
})
.sort((a, b) => b.q - a.q)
.map(entry => entry.locale)
.filter(locale => SUPPORTED_LOCALES.includes(locale))
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url)
const pathLocale = url.pathname.split('/')[1]
if (SUPPORTED_LOCALES.includes(pathLocale)) {
return env.ASSETS.fetch(request)
}
// 从 Accept-Language 检测
const acceptLang = request.headers.get('Accept-Language')
const preferredLocales = parseAcceptLanguage(acceptLang)
const detectedLocale = preferredLocales[0] || 'en'
// 重定向到检测到的 locale 路径
return Response.redirect(`${url.origin}/${detectedLocale}${url.pathname}`, 302)
}
}
用户偏好持久化
// React: 保存并恢复语言偏好
import { setLocale } from '~/paraglide/runtime.js'
function LanguageSwitcher() {
const switchLanguage = (locale: string) => {
// 保存偏好
localStorage.setItem('preferred-locale', locale)
document.cookie = `locale=${locale}; path=/; max-age=${60 * 60 * 24 * 365}`
// 切换 locale(Paraglide.js)
setLocale(locale)
// 无需全量刷新更新 URL
const url = new URL(window.location.href)
url.pathname = url.pathname.replace(/^\/[a-z]{2}/, `/${locale}`)
window.history.pushState({}, '', url)
}
return (
<select onChange={(e) => switchLanguage(e.target.value)}>
<option value="en">English</option>
<option value="de">Deutsch</option>
<option value="zh">中文</option>
<option value="ja">日本語</option>
<option value="ar">العربية</option>
</select>
)
}
检测决策流程
访客到达
↓
URL 中是否有 locale? ──是──→ 使用 URL locale
否
↓
是否有已保存的偏好? ──是──→ 使用已保存的 locale
否
↓
检查 Accept-Language
↓
locale 是否受支持? ──是──→ 使用浏览器 locale
否
↓
Geo IP 检测
↓
使用默认值(en) ──────→ 醒目展示语言切换器
结语
分层构建检测策略:URL 路径优先,已保存偏好其次,浏览器头信息第三,地理位置第四。无论检测多精准,都要始终展示醒目的语言切换器——自动检测不应成为切换语言的唯一方式。将用户选择缓存在 Cookie 和 localStorage 中,以实现跨会话持久化。
如需了解语言检测的 SEO 方面,请参阅 hreflang 标签终极指南。如需构建特定语言的落地页,构建语言特定落地页策略 涵盖了相关方法。如需跨语言运行实验,请查看 多语言 A/B 测试指南。