next-intl localePrefix:默认语言不显示前缀

2 min read
Zekari
Next.jsi18nnext-intlURL 设计

URL 中的每个字符都是认知负担。

当你访问 example.com/ 时,系统提供英语内容。当你访问 example.com/zh 时,系统提供中文内容。这看起来很自然。但如果根路径重定向到 example.com/en,会发生什么?

用户看到 /en 时,大脑会产生疑问:「为什么要标记?」标记意味着特殊性。标记意味着这不是常态。

默认语言不应被标记。因为它本身就是「常态」。

localePrefix 的三种选择

next-intl 提供三种 URL 前缀策略:

'always' 强制所有语言都显示前缀。访问根路径会重定向到 /en。这种做法将默认语言也视为「特殊情况」。

'as-needed' 只为非默认语言添加前缀。英语用户看到 /,中文用户看到 /zh。这是默认值。

'never' 完全不显示语言前缀。语言通过 cookie 或其他机制判断。URL 保持不变。

每种选择背后都有不同的假设。

'always' 假设所有语言地位平等,应该一致标记。这在多语言完全对等的场景下有意义,比如政府网站。

'as-needed' 假设存在默认语言,其他语言是「变体」。这符合大多数产品的现实:英语是默认,其他语言是支持。

'never' 假设语言切换是会话状态,不应污染 URL。这在某些应用中合理,但牺牲了 URL 的可分享性。

为什么 'as-needed' 是合理默认值

URL 的本质是定位符。它应该准确指向资源,同时保持简洁。

当默认语言是英语时,/ 本身就已经定位到英语内容。添加 /en 没有增加任何信息,只是增加了噪音。

这不仅是审美问题。更长的 URL 意味着:

  • 更难记忆
  • 更难输入
  • 更占空间(在分享时)
  • 更多的认知负担(「为什么要加 /en?」)

'as-needed' 策略体现了「只在必要时显现」的设计哲学。当用户需要非默认语言时,前缀才出现。这个前缀是有意义的标记,它说:「这不是默认的,这是中文版本。」

中间件的角色

import createMiddleware from 'next-intl/middleware';

const intlMiddleware = createMiddleware({
  locales: ['en', 'zh', 'ja'],
  defaultLocale: 'en',
  localePrefix: 'as-needed'
});

export default intlMiddleware;

中间件做了什么?

它观察请求的路径。如果是 /,它设置内部 locale 为 'en',但不修改 URL。如果是 /zh,它设置内部 locale 为 'zh',URL 保持 /zh

有人会问:「这不也是某种重定向吗?」

不是。重定向是 HTTP 3xx 响应,告诉浏览器「去另一个地址」。中间件只是在服务器内部决定用哪个语言渲染页面。用户的地址栏不会改变。

这是关键区别。URL 是用户看到的真实地址,内部 locale 是服务器的渲染决策。两者不应混淆。

有人认为「不重定向」意味着「完全不处理语言」。这是误解。

中间件必须参与。它需要从 URL 中提取语言信息(或者从默认值推断),然后传递给渲染系统。

「不重定向」指的是不改变 URL,而非不处理语言。

何时应该使用 'always'

如果所有语言真的完全平等,'always' 是合理的。

联合国网站可能需要这样。六种官方语言,没有「默认」之说。每种语言都应该有明确的 /en, /zh, /fr 前缀。

但大多数产品不是这样。大多数产品有主要用户群,有默认语言,有「其他」语言。

强制标记默认语言会产生一个问题:它让默认语言看起来也是「选择之一」。这在心理上是微妙的。当用户看到 /en 时,会隐约觉得「可能还有其他选择」。

这在某些场景下是好事(鼓励探索其他语言)。但在另一些场景下是干扰(用户只想要默认体验)。

何时应该使用 'never'

如果语言是会话状态,不应该固化在 URL 中,'never' 合适。

典型场景:个性化应用。用户登录后,系统根据用户设置显示语言。无论访问哪个页面,语言都跟随用户。

但这牺牲了什么?分享。

当用户分享 example.com/article/123 给朋友时,朋友看到的可能是不同语言。因为 URL 中没有语言信息,系统只能根据接收者的 cookie 或浏览器设置判断。

如果分享是重要功能,'never' 不合适。

URL 设计的本质

URL 不只是技术实现细节。它是用户与系统的契约。

用户看到 / 时,期待这是「最基本的入口」。用户看到 /zh 时,理解这是「中文变体」。用户看到 /en 时,会困惑:「为什么默认语言也需要标记?」

设计 URL 时,要问的不是「技术上能不能这样做」,而是「这个 URL 传达了什么信息」。

'as-needed' 传达的信息是:默认语言是基线,其他语言是扩展。这符合大多数产品的现实。

'always' 传达的信息是:所有语言平等,没有默认。这适合特定场景。

'never' 传达的信息是:语言不是资源的一部分,而是会话状态。这适合另一些场景。

配置的清晰性

即使 'as-needed' 是默认值,显式声明仍然有价值:

const intlMiddleware = createMiddleware({
  locales: ['en', 'zh', 'ja'],
  defaultLocale: 'en',
  localePrefix: 'as-needed' // 显式声明意图
});

这不是冗余。这是确定性。

代码的读者(包括未来的你)不需要记住「默认值是什么」。配置本身说明了意图。

最后

简洁性不是目的,而是结果。

当你理解「默认语言不需要标记」时,/ 自然比 /en 更合适。

当你理解「标记意味着特殊性」时,只为非默认语言添加前缀自然是正确的。

URL 设计是认知设计。每个字符都在传达信息。去掉不必要的字符,不是为了省字节,而是为了减少噪音。

这不是技术问题。这是沟通问题。

Related Posts

Articles you might also find interesting

next-intl 的服务端与客户端协同机制

3 min read

理解 next-intl 如何在 Next.js App Router 中协调服务端渲染和客户端交互,以及为什么需要显式设置 locale。

Next.jsi18n
Read More

动态元数据生成:让机器读懂你的页面

2 min read

generateMetadata 不只是填写表单。它决定了搜索引擎、社交平台、AI 系统如何理解和呈现你的内容。

Next.jsSEO
Read More

继承基础配置

2 min read

配置不需要重复书写。继承机制让每个层次只表达自己的差异。

TypeScript配置管理
Read More

Purikura的页面系统

3 min read

通过五层分层继承复用架构,实现零代码修改的页面生成系统。从类型定义到页面渲染,每一层专注单一职责,实现真正的数据驱动开发。

架构设计React
Read More

Google Fonts 官方集成

2 min read

Next.js 提供了 next/font 模块,让字体加载变得简单且性能优化。Google Fonts 是最直接的商用免费字体选择。

Next.js字体
Read More

让错误浮现

1 min read

Next.js 构建悬挂问题的根源不在工具,而在掩盖。严格类型检查不是负担,而是质量的守护者。

TypeScriptNext.js
Read More

减少 Next.js 启动时的工作量

2 min read

开发服务器启动缓慢不是偶然。它在做的事太多了。

Next.js性能优化
Read More

用静态导出控制视口

2 min read

Next.js 中的视口配置通过静态导出模式定义页面初始状态,理解其背后的设计约束能够更好地控制用户体验边界。

Next.js视口
Read More