阿拉伯语版本看起来是坏的,但翻译本身没问题。侧边栏在左边,返回箭头指向左边,头像紧贴每张卡片的左缘:每个词都是阿拉伯语,每个盒子却还在英文版留下的位置上。文本方向是一个 HTML 属性,加上一组写在组件里的 CSS 决策,任何翻译目录文件都不承载它。globalize.now 是 AI 驱动的本地化基础设施,每次 Git 推送都会提取字符串并同步翻译目录。

为什么我的 Lovable 应用在阿拉伯语下仍然从左到右?

因为方向是文档和样式表的属性,而不是消息本身的属性。

你的翻译目录是由源字符串和译文字符串配成的键值对。一旦它们为 ar 编译完成、组件也渲染出来,变的只有文字,其他一切照旧。无论这些目录用的是 globalize.now 写入的 Lingui,还是 react-i18next,结果都一样。这两种格式都没有地方可以写“侧边栏要放到另一边”。

有两件事必须改。html 元素上的 dir 属性告诉浏览器行内轴朝哪个方向延伸。你的 CSS 要停止指定物理的某一侧,改为指定该轴的起点和终点。其余一切都由这两点推导出来。

给 Lovable 应用添加阿拉伯语后,到底哪些地方会出问题?

故障集中在几个可预见的地方,而且全都是样式问题,与文案无关。

  • 导航抽屉和侧边栏仍停在物理左侧。
  • 箭头、返回箭头和进度指示器指向了错误的方向。
  • 不对称的内边距把内容钉在错误的一侧,标签挤向一边。
  • 标题上的 text-left 让阿拉伯语一直靠在容器左侧。
  • 绝对定位的徽标和关闭按钮出现在读者预期位置的另一侧角落。
  • 用来标记当前列表项的左边框出现在尾侧,而不是首侧。
  • 阿拉伯语句子里嵌入的英文产品编码,在没有方向提示时会出现不可预期的重排。

这些都不是翻译缺陷,每一个都是写死在类名里的物理方向。

如何根据 locale 标签推导文本方向?

先问 locale 它用哪个方向,再把答案写到文档上。

Intl.Locale.prototype.getTextInfo() 会针对 locale 标签返回 ltr 或 rtl 的方向值。它于 2026 年 7 月进入 Baseline newly available 状态,也就是说只有各主流浏览器的当前版本支持,而你的用户实际使用的大多数浏览器并不支持。把下面的兜底方案当作实际生效的路径,把这个 API 当作将来替代兜底的方案。

// getTextInfo() is not yet in TypeScript's bundled Intl.Locale definitions.
declare global {
  namespace Intl {
    interface Locale {
      getTextInfo?(): { direction: "ltr" | "rtl" };
    }
  }
}

// Stopgap for runtimes without getTextInfo(). This set is the one place where
// adding a new right-to-left language still costs you a code change.
const RTL_FALLBACK = new Set([
  "ar", "he", "fa", "ur", "ps", "sd", "yi", "dv", "ckb", "ug", "nqo", "syr",
]);

export function directionFor(locale: string): "ltr" | "rtl" {
  let tag: Intl.Locale | undefined;
  try {
    tag = new Intl.Locale(locale);
  } catch {
    // Malformed tag. Fall through to the string path below.
  }

  const direction = tag?.getTextInfo?.().direction;
  if (direction === "ltr" || direction === "rtl") return direction;

  const language = tag?.language ?? locale.split(/[-_]/)[0].toLowerCase();
  return RTL_FALLBACK.has(language) ? "rtl" : "ltr";
}

这两处 try 都很关键。遇到格式错误的标签时,new Intl.Locale() 会抛出 RangeError,而在较旧的运行时里,getTextInfo() 根本不存在。只处理后一种情况是常见的 bug:从查询字符串传入像 ar_EG 这样的 locale,会让函数进入自己的恢复路径,然后又以未捕获错误的形式抛出来。

在应用已经知道当前 locale 的地方,同时设置这两个属性。Lovable 目前会生成两种结构:Vite 单页应用和服务端渲染的 TanStack Start 应用,所以请先确认你的项目实际生成的是哪一种。在 TanStack Start 中,它是根文档组件;在 Vite 中,则是挂载 i18n provider 的位置。

<html lang={locale} dir={directionFor(locale)}>

把 dir 放在 html 上,而不是某个包装用的 div 上。Portal、模态框和 toast 渲染在组件树之外,同样需要继承它。

为了支持从右到左,我需要修改哪些 Tailwind 类?

凡是指明物理方向的那些。它们对应的逻辑写法跟随行内轴,在 dir 变化时会自动翻转。

物理方向工具类逻辑方向替换作用
ml-4 / mr-4ms-4 / me-4margin-inline-start / margin-inline-end
pl-6 / pr-6ps-6 / pe-6padding-inline-start / padding-inline-end
left-0 / right-0start-0 / end-0inset-inline-start / inset-inline-end
text-left / text-righttext-start / text-end沿行内轴的文本对齐
border-l / border-rborder-s / border-e首侧 / 尾侧边框
rounded-l-lg / rounded-r-lgrounded-s-lg / rounded-e-lg首侧 / 尾侧圆角
float-left / float-rightfloat-start / float-end沿行内轴浮动
clear-left / clear-rightclear-start / clear-end沿行内轴清除浮动
scroll-ml-4 / scroll-mr-4scroll-ms-4 / scroll-me-4scroll-snap 外边距

Tailwind 的 margin 文档直接展示了效果:ms-8 和 me-8 分别渲染在 dir="ltr" 和 dir="rtl" 容器中时,会落在相反的两侧。逻辑属性工具类从 Tailwind v3.3 开始提供,所以维护时间较长的老项目可能还用不上。

有两个工具类不能只改名,还得再仔细看一遍。space-x-* 和 divide-x-* 会在同级元素之间添加间距和分隔线,所以在反向排列的行里,它们会落在每个子元素的错误一侧。Tailwind 正是为这种情况提供了 space-x-reverse 和 divide-x-reverse。mt-* 和 pb-* 这类垂直方向的工具类则无需处理,因为方向只作用于行内轴。

逻辑属性有哪些情况解决不了?

凡是把方向编码在盒模型之外的东西都不行,这正是 rtl: 和 ltr: 变体派上用场的地方。

<ChevronRight className="rtl:rotate-180" />
<div className="bg-[url(/hero.svg)] bg-left rtl:bg-right" />
<div className="shadow-[4px_0_8px_rgba(0,0,0,.15)] rtl:shadow-[-4px_0_8px_rgba(0,0,0,.15)]" />

transform、背景位置、阴影偏移、渐变角度、轮播的滚动计算,以及任何表示“前进”的图标,都属于这一类。这份清单要尽量短。如果几乎每个元素都要写 rtl:,说明底层样式仍是物理方向的,应该改成逻辑写法。

用户生成内容需要单独处理,而且分两种情况。对于语言不受你控制的整块内容,dir="auto" 能让浏览器根据第一个强方向字符来确定该区块的基础方向。

<p dir="auto">{review.body}</p>

对于已有方向的句子中夹杂的外文片段,比如阿拉伯语正文里的英文 SKU,dir="auto" 不起作用,因为它设置的是段落的基础方向,而不是隔离一段行内内容。这种情况要改为把该片段包裹起来。

<bdi>{product.sku}</bdi>

为什么翻译插件无法替你翻转布局?

因为插件操作的是渲染后页面里的文本节点,而你的方向问题出在生成这个页面的样式里。

运行时服务可以在英文版渲染完成后,向文档注入 dir="rtl" 并翻译可见字符串。但它无法深入你的组件,把 ml-4 改成 ms-4,因为 ml-4 早已编译成样式表里的 margin-left,而这份样式表不归服务所有。最终得到的是:阿拉伯语文本,套着从左到右的布局框架。

还有第二重代价。部分 Lovable 应用使用的运行时翻译服务 Lovalingo 已宣布,将于 2026 年 8 月 31 日 23:59(CEST)停止运营。其迁移指南建议客户改用项目自有的国际化方案,并在移除前重新验证 locale 路由、canonical URL 和 hreflang。在供应商产品内完成的布局工作,会随供应商一起消失;而你自己仓库里的逻辑工具类和 dir 属性不会。我们的 Lovalingo 对比 在字符串层面表达了同样的观点。

如何防止下一次 Lovable 提示词把修复冲掉?

转换一次,再让物理工具类在 review 中一目了然。

  1. 安装工作流。在 Lovable 工作区添加 lovable-i18n Skill,然后提示 Lovable 配置 i18n。在编辑器之外,安装命令是 npx skills add globalize-now/globalize-skills,加上 --all 即可为所有 Agent 安装。
  2. 使用上面的 directionFor,根据 locale 在 html 元素上设置文字方向。
  3. 把组件中的物理工具类全部转换为逻辑工具类,从包裹页面的最外层组件开始。
  4. 仅为 transform、背景、阴影和带方向的图标添加 rtl: 变体,并对行内的外语文字片段使用 <bdi>。
  5. 在 review 中加一道 grep 检查,让新生成的物理工具类在合并前就报错失败。
rg -n '\b(ml|mr|pl|pr|scroll-m[lr])-|\b(float|clear|text)-(left|right)\b|\bborder-[lr]\b|\brounded-[lr]-|\b(left|right)-[0-9]' src/

第五步才是真正守住成果的一步。Lovable 每次提示词都会重新生成组件,要求模型添加设置面板时,它会习惯性地写出 ml-4,因为这些模型训练时见到的 React 代码绝大多数都用物理工具类。让 PR 检查直接失败,比三周后在阿拉伯语截图里才发现问题要省事得多。

字符串这半边的流程可以自动运转。globalize.now 从 Lovable 生成的代码中提取新增的硬编码字符串,写入你的翻译目录,并在每次 Git 推送时同步,所以你继续写提示词的同时,阿拉伯语版本始终保持最新。机制与 Vite 指南 和 TanStack Start 指南 中介绍的相同,这也正是 翻译不再随每次推送而失效 的原因。

添加阿拉伯语不产生额外费用。Starter 套餐每个工作区每月 €20,含 €20 翻译额度(约 200,000 词),超出后按相同的单字符费率继续计费,不按席位收费,也不按语言收费,从右到左的语言同样包含在内。更多配置说明见 Lovable 集成页面,工程细节见 开发者页面,非技术背景的构建者可以走 vibe coder 页面 上更简短的路径。

改一次布局,从此一劳永逸

文字方向是仓库只需决定一次的事。字符串则每次写提示词都在变,而这部分可以自动运转。

globalize.now 把硬编码的应用文案转换为可直接翻译的语言文件,并在你持续发布时保持同步更新。

免费试用 globalize.now