我不知道的 i18next(04)— 文本膨胀与 RTL 适配
很多人以为国际化就是"把文本换成另一种语言",做完翻译就完事了。但实际上,翻译只是国际化的起点,UI 适配才是真正的战场。同样一个按钮,英文 "Submit" 占 6 个字符,德文 "Absenden" 占 8 个,阿拉伯文还要从右往左排列——这些差异如果不提前处理,上线后就是…
很多人以为国际化就是”把文本换成另一种语言”,做完翻译就完事了。但实际上,翻译只是国际化的起点,UI 适配才是真正的战场。同样一个按钮,英文 “Submit” 占 6 个字符,德文 “Absenden” 占 8 个,阿拉伯文还要从右往左排列——这些差异如果不提前处理,上线后就是一屏的布局错乱。
一、文本膨胀:不只是”字变多了”
文本膨胀(Text Expansion)指的是同一段内容翻译成不同语言后长度发生变化。IBM 的全球化指南给出过一组参考数据:
| 英文原文长度 | 翻译后膨胀率 |
|---|---|
| 1-10 个字符 | 100%-200% |
| 11-20 个字符 | 80%-100% |
| 21-30 个字符 | 60%-80% |
| 31-70 个字符 | 40%-60% |
| 70+ 个字符 | 30%-40% |
文本越短,膨胀率越高。 这意味着按钮、标签、菜单项这些 UI 中最常见的短文本,恰恰是膨胀最严重的。
实际例子:
| 英文 | 德文 | 法文 | 膨胀率 |
|---|---|---|---|
| Submit | Absenden | Soumettre | 33%-50% |
| Settings | Einstellungen | Paramètres | 50%-62% |
| OK | OK | OK | 0% |
| Log in | Anmelden | Se connecter | 50%-100% |
| Search | Suche | Rechercher | 0%-83% |
问题的关键在于——不能只用英文测 UI。在英文下完美的布局,换成德文可能按钮文字溢出、换成中文可能留下一大片空白。
二、CSS 弹性策略
处理文本膨胀的核心思路:不要给文本固定宽度,让容器自适应内容。
(1)按钮和标签:用 padding 代替固定宽度
/* 错误:固定宽度,德文会溢出 */
.btn {
width: 80px;
}
/* 正确:用 padding + min-width 保证弹性 */
.btn {
min-width: 80px;
padding: 8px 16px;
white-space: nowrap;
}
这段代码的区别在于:固定宽度的按钮在文本膨胀时会溢出或截断,而 min-width + padding 的组合允许按钮根据内容自动增长。
(2)导航栏:Flexbox 自动分配
.nav {
display: flex;
gap: 8px;
}
.nav-item {
flex: 0 1 auto; /* 不放大,可缩小,按内容定宽 */
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
flex: 0 1 auto 让每个导航项按内容大小排列,空间不足时允许缩小并显示省略号。
(3)最后的兜底:截断 + Tooltip
当容器确实无法容纳膨胀后的文本时,截断配合 Tooltip 是合理的降级方案:
function TruncatedText({ i18nKey }) {
const { t } = useTranslation();
const text = t(i18nKey);
return (
<span className="truncated" title={text}>
{text}
</span>
);
}
.truncated {
display: inline-block;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
关键在于 title 属性用完整文本,这样鼠标悬停时用户能看到全文。
三、RTL 适配:不只是”文字从右往左”
RTL(Right-to-Left)语言包括阿拉伯语、希伯来语、波斯语等。很多人以为 RTL 只是文字方向变了,但实际上整个布局都需要水平翻转:导航栏、图标方向、外边距、内边距——所有与”左右”相关的属性都要镜像。
i18next 的方向检测
i18next 内置了语言方向的检测方法:
i18next.dir(); // 返回当前语言的方向:'ltr' 或 'rtl'
i18next.dir('ar'); // 返回指定语言的方向:'rtl'
i18next.dir('en'); // 'ltr'
在 React 中,通常在顶层组件设置 dir 属性:
function App() {
const { i18n } = useTranslation();
return (
<div dir={i18n.dir()} className="app">
{/* 整个应用的内容 */}
</div>
);
}
这一步很多人做了。但只设置 dir 属性远远不够——CSS 中所有涉及左右方向的属性都需要处理。
CSS 逻辑属性:一套代码适配双向
传统的 CSS 物理属性(margin-left、padding-right、text-align: left)在 RTL 下不会自动翻转。CSS 逻辑属性(Logical Properties)解决了这个问题:
/* 物理属性:LTR 下正确,RTL 下错误 */
.sidebar {
margin-left: 20px;
padding-right: 16px;
text-align: left;
border-left: 2px solid #ccc;
}
/* 逻辑属性:LTR 和 RTL 都正确 */
.sidebar {
margin-inline-start: 20px;
padding-inline-end: 16px;
text-align: start;
border-inline-start: 2px solid #ccc;
}
说白了,inline-start 在 LTR 模式下等于 left,在 RTL 模式下等于 right。只要全程使用逻辑属性,RTL 适配就是零成本的——不需要写任何 [dir='rtl'] 的覆盖规则。
逻辑属性的对应关系:
| 物理属性 | 逻辑属性 |
|---|---|
margin-left | margin-inline-start |
margin-right | margin-inline-end |
padding-left | padding-inline-start |
text-align: left | text-align: start |
left (position) | inset-inline-start |
border-left | border-inline-start |
float: left | float: inline-start |
不要忘记的细节:图标方向
箭头、导航图标等方向性图标在 RTL 下也需要翻转:
[dir='rtl'] .icon-arrow {
transform: scaleX(-1);
}
但注意,不是所有图标都要翻转。搜索图标、关闭按钮(X)、播放/暂停按钮这些没有方向性的图标应该保持不变。经验法则是:只翻转表示方向的图标(箭头、返回、前进等)。
四、i18next 与方向切换的联动
在 SPA 中,用户可能在运行时切换语言。如果从英语切换到阿拉伯语,需要同步更新整个页面的方向。
i18next.on('languageChanged', (lng) => {
document.documentElement.dir = i18next.dir(lng);
document.documentElement.lang = lng;
});
这段代码监听语言变更事件,自动更新 <html> 元素的 dir 和 lang 属性。配合 CSS 逻辑属性,整个页面布局会自动适应新的方向。
这里有一个很多人会忽略的细节——如果项目用了 CSS-in-JS(如 styled-components 或 Emotion),dir 属性的变化不会触发组件重新渲染。需要在 theme 中包含方向信息,确保样式能响应方向变化:
const theme = {
direction: i18next.dir(),
// ...
};
// 在 ThemeProvider 中传入
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>;
五、预防优于修复:开发阶段的检测手段
与其上线后发现布局崩了再修,不如在开发阶段提前发现问题。
(1)用伪语言测试膨胀
i18next 的 appendNamespaceToCIMode 配置可以帮助识别未翻译的文本,但要测试膨胀效果,可以用一个自定义的伪翻译插件:
const pseudoLocalize = {
type: 'postProcessor',
name: 'pseudo',
process(value) {
return value.replace(/[a-zA-Z]/g, (c) => {
const map = { a: 'àá', e: 'éè', o: 'öô', u: 'üû' };
return (map[c.toLowerCase()] || c) + c;
});
},
};
i18next.use(pseudoLocalize).init({
postProcess: ['pseudo'],
// ...
});
这段代码将每个英文字母替换为带变音符号的双字符,模拟约 100% 的文本膨胀。在这种极端条件下还能正常显示的布局,就能扛住真实翻译的膨胀。
(2)用 Chrome 开发者工具强制 RTL
不需要配置阿拉伯语翻译,直接在控制台执行:
document.documentElement.dir = 'rtl';
就能立即预览整个页面的 RTL 效果。如果用了 CSS 逻辑属性,布局应该自动翻转;如果没有,就能一眼看到哪些地方需要修改。
六、总结
国际化不等于翻译。翻译解决的是”说什么”,文本膨胀和 RTL 适配解决的是”怎么放”。如果你只记住一句话:CSS 逻辑属性 + 弹性布局是 RTL 适配的最优解,伪语言测试是文本膨胀的预防手段。把这两件事融入开发流程,国际化的 UI 问题会在上线前就被消灭。
本系列其他文章:
- 上一篇:复数规则与 CLDR 标准
相关主题:
- 如果你对 i18next 的资源管理机制感兴趣,可以看:命名空间与资源加载
- 如果你对翻译文本中嵌入 JSX 感兴趣,可以看:Trans 组件的边界与陷阱