~/ ?.log $
返回文章列表
10 min read
更新于 2026年3月6日

我不知道的 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 中最常见的短文本,恰恰是膨胀最严重的。

实际例子:

英文德文法文膨胀率
SubmitAbsendenSoumettre33%-50%
SettingsEinstellungenParamètres50%-62%
OKOKOK0%
Log inAnmeldenSe connecter50%-100%
SearchSucheRechercher0%-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-leftpadding-righttext-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-leftmargin-inline-start
margin-rightmargin-inline-end
padding-leftpadding-inline-start
text-align: lefttext-align: start
left (position)inset-inline-start
border-leftborder-inline-start
float: leftfloat: 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> 元素的 dirlang 属性。配合 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 问题会在上线前就被消灭。


本系列其他文章:

相关主题:

share.ts

// 觉得这篇文章有帮助?

// 欢迎分享给更多人

export const  subscribe  =  "/rss.xml" ;