某次版本上线前,我例行检查后台的崩溃日志,意外看到一个德国用户在差评里放了一张截图:一个按钮上写着半截单词,后面跟着省略号,旁边还挤着三个换行的文字。乍一看是翻译没做完,点进去才知道是文案溢出。多语言环境下的文本溢出问题,绝对比大多数团队想象得普遍。很多团队会安排专人做多语言测试,但效率低、覆盖差、反馈还慢,等翻译公司回稿往往已经过去一两周了。我自己在几个跨语言应用里做过反复试验后,得出一个结论:与其等真实翻译,不如先用AI按约束自动生成多语言文本,结合国际化测试的基础流程,把溢出风险提前炸出来。
这篇文章要分享的,就是一套我自己在项目中跑通的做法——怎么用AI生成不同语言的高长度测试短语,怎么把生成的字符串注入到界面里,怎么判断元素是否真的溢出,以及这套方案到底能省多少事、踩过哪些坑。适合客户端开发、测试工程师、以及管国际化产品的同学参考,就算你的项目还没有完整多语言体系,看完也能直接落地一部分。
1. 语言膨胀率:一句"保存"在不同语言里差出多少个字符
先理解问题根源。中文界面文案往往很精炼,两三个字就能表达完一个动作。可一旦切到德语、芬兰语这类语言,同样的意思可能需要两倍以上的字符。这就是所谓"语言膨胀率":在相同语义下,不同语言所需字符数量的差异。
1.1 同一句话在不同语言里的真实差别
我做过一个小实验,找出之前项目里几个常见的UI短语,分别查了它们在几种语言里的表达长度。
| 英文原文 | 中文 | 德语 | 芬兰语 | 日语 |
|---|---|---|---|---|
| Save | 保存 | Speichern | Tallenna | 保存 |
| Delete item | 删除项目 | Element löschen | Poista kohde | 項目を削除 |
| Terms of service | 服务条款 | Nutzungsbedingungen | Palvelun käyttöehdot | 利用規約 |
注意"服务条款"这一类,德语直接拼成一个大长词,从11个字符干到24个字符;芬兰语的复合词更夸张,经常会整个句子只由一个词串构成。日语看着不长,但假名和汉字混排时,渲染宽度并不小,尤其是遇到片假名外来语,会突然冒出很长的音译词。
这种长度差异带来的结果非常直观:你按中文或英文设计的固定宽度按钮,在德语环境下一旦文案超过容器宽度,轻则截断、重则换行破坏布局。
1.2 字符数只是起点,渲染宽度才是终端
这里要特别提醒一个误区:不要只数字符数,前端最终看的是渲染宽度。同样是10个字符,"WWWWWWWWWW"和"iiiiiiiiii"宽度差出一倍,因为每个字形的advance width不同。
我在某个跨平台App里见过一个活生生例子:一个日期选择器的按钮,英文"Next"只有4个字符,德语"Fortsetzen"是10个字符,表面看只是多了6个字母,但激活系统字体后,德语的宽度是英文的1.9倍。按钮的padding一固定,文字直接顶到底部边框。所以在做国际化测试时,"字符数膨胀率"只能作为初步估算,更可靠的是直接测量渲染宽度,这点后面会详细讲。
1.3 为什么人工测试总是漏这些
如果你的团队没有以目标语言为母语的成员,人工测试基本只能靠想象"这句德语大概多长"。多数测试人员自己拼出来的短语都是常见短词,根本覆盖不到复合长词和真实口语里的长表达。还有一个很现实的问题:人脑生成的测试句天然偏向"合理表达",而UI文本恰恰需要的是"口语化但语义准确"的混合体,一个人很难同时产出十几个语言的靠谱测试句子。
AI在这里的价值就体现出来了:它不负责"翻译准确性",而是按约束条件快速采样出大量符合语言形态、长度分布够宽的短语,把过去需要翻译公司几周才能拿到的材料,压缩到几小时内。
2. 传统的多语言测试为什么总是"翻译完才知道布局出事"
在没有AI辅助之前,团队做多语言文本溢出测试通常有三条路:找翻译、人工造词、伪本地化。每条路都有它的局限。
2.1 找翻译公司的路子:周期长、反馈靠后
把全部文案发给翻译公司,等翻译稿回来,再手动替换进工程,跑一轮截图对比。一套流程走下来,两个星期算快的。
更难受的是,翻译公司交付的往往是"正确翻译",而不是"压力测试文本"。你拿到手的德语文案刚好是6个字符,但真实用户界面里可能偶尔冒出24个字符的复合词,这个Case完全测不到。
2.2 人工造长词:覆盖率和真实性不可兼得
有些同学会手动拼一些超长词,比如在原有单词后面加一堆重复形容词,硬凑出一个超长字符串。这种做法能测出布局会不会崩,但有两个致命问题:
- 拼出来的词不符合目标语言的基本规则,术语叫"非自然文本"。德语里合法的长词往往是由词干+词缀按规则复合而成,硬拼的数字串在字体渲染上不一定触发相同行为。
- 只能覆盖"长度"这一个维度,测不到特定语言的换行习惯、断词规则、标点悬挂等问题。
2.3 伪本地化能解决一半,但只有一半
伪本地化(pseudo-localization)是很多国际团队的前置测试手段。原理很简单:写一个脚本,把英文文本里的ASCII字符替换为带变音符的拉丁字符,比如"A"变成"À",同时额外扩展长度,模拟德法西等拉丁语系的文本长度。
我自己的经验里,伪本地化在"提前暴露宽度不足"上非常有效,尤其在开发早期,每天都能跑。它的局限也很明显:处理不了非拉丁语系。
比如日语没有空格分词,换行逻辑和英文完全不同;芬兰语有大量不同尾缀,伪本地化替换出来的字符串根本不对应真实形态;泰语这类语言甚至没有明确的词边界,断行规则依赖字典和音节判断。你伪本地化跑得再欢,也测不到真实语言的断行行为。
| 覆盖维度 | 人工造词 | 伪本地化 | AI生成 |
|---|---|---|---|
| 长字符覆盖 | 可以 | 可以 | 可以 |
| 真实语言结构 | 差 | 差 | 较好 |
| 非拉丁语系断词 | 基本覆盖不到 | 覆盖不了 | 可以定制 |
| 批量生成速度 | 极慢 | 快 | 快 |
| 语义合理性 | 一般 | 无意义 | 较好 |
所以我的结论是:AI生成不是用来推翻伪本地化的,而是用来补齐它够不到的那一半。尤其是那些需要真实语言结构、真实断词场景的测试目标,AI按约束生成是目前投入产出比最高的一条路。
3. 让AI按约束造文本:核心思路与具体Prompt
说了这么多问题,终于进入正题。AI生成多语言文本的整套逻辑并不复杂,关键在"按约束采样",而不是"让AI自由发挥"。
3.1 先定目标:长度、语言特征、UI场景
一次合格的文本溢出测试,至少需要同时满足三个条件:
- 长度要够:字符串的字符数或渲染宽度要高于当前容器能承受的上限,通常我会要求生成英文原文长度的140%-180%。
- 语言特征要对:德语要有长复合词,芬兰语要有长尾缀,日语要包含适当比例的汉字和假名混排,法语要能产生连读导致的排版变化。
- UI场景要明确:按钮、标题、通知、错误提示、列表项,不同场景的文案风格完全不一样。
我建议在开始生成之前,先把待测页面里所有需要检测的UI元素列一张表,标注元素类型、容器宽度、当前文案语言、字号。这样不仅方便后续测溢出,也能让AI生成时更有针对性。
3.2 Prompt模板与生成代码
我的做法是构造一个带JSON输出的系统级Prompt,每轮请求一个语言和一个UI场景,避免一次请求塞太多语言导致模型混用语言。
import json import openai # 这里只是示例,任何LLM SDK都可以 def build_prompt(lang, scene, max_chars): prompt = f""" 你是擅长移动端UI文案的专家。请为{scene}生成10条测试短语。 要求: 1. 语言必须是{lang}的母语者自然表达,不要翻译腔。 2. 每条短语的字符数控制在{max_chars}左右,允许上下浮动15%。 3. 语义覆盖:操作指令、状态提示、错误反馈、成功反馈、超长名称。 4. 尽量使用{lang}中长度偏长的自然表达,包括复合词、语境补充语或口语长句。 5. 不要加注释,不要写多余文字,直接输出JSON数组。 示例: ["...", "..."] """ return prompt def generate_test_strings(lang, scene, max_chars, count=10): prompt = build_prompt(lang, scene, max_chars) resp = openai.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}], temperature=0.7, ) texts = json.loads(resp.choices[0].message.content) return deduplicate_and_clean(texts)输出示例(德语、按钮场景)可能是这样:
[ "Speichern", "Weitere Einstellungen öffnen", "Zahlungsinformationen aktualisieren", "Bitte warten Sie einen Moment" ]这里的关键参数是temperature。我实测下来,生成测试文本需要一定随机性,temperature设在0.6到0.8之间比较合适,太低容易每轮生成高度相似的短语,太高则可能出现语言混乱。
3.3 为什么按"字符数"约束,而不是按"Token数"
这是最容易犯的错。大模型的对话单位是Token,但UI渲染看的是字符数和字形宽度,二者并不等价。
一个很现实的例子:同一段德语文本可能占20个Token,但渲染宽度只有80个像素;一段中文文本只有8个Token,却因为汉字是全角字形,宽度占了120像素。如果你在Prompt里指定的是"生成50个Token的句子",最终出来的字符串长度很可能不符合预期。
我在工具里直接写入"字符数在XX到XX之间",并附带一个后处理脚本:遍历生成的字符串,用 len() 过滤掉不满足长度范围的条目,再补生成缺失的数量。
def deduplicate_and_clean(texts): cleaned, seen = [], set() for t in texts: t = t.strip() if t and t not in seen: seen.add(t) cleaned.append(t) return cleaned3.4 本地规则 + LLM的组合拳
有些时候,单靠LLM生成的短语语义丰富,但缺少系统性的边界覆盖。我的做法是在LLM输出之外,再叠一层规则模板。
例如对德语,我会在词库里准备一组高活性词干,然后用规则把它们拼接成更长的复合词,强制覆盖"长词+上下文"的场景。用这套"本地规则+LLM"组合,可以同时得到两个方向的数据:LLM负责"自然但偏长",规则负责"极端但合法"。
prefixes = ["Zahlungsinformations", "Benachrichtigungseinstellungs", "Kontoverwaltungs"] suffixes = ["system", "prozess", "formular", "seite"]两个列表做笛卡尔积,再和LLM生成结果合并,等于是把"随机性"和"可控的边界压力"结合在了一起。这一步让最终测试集的覆盖效果好了很多,尤其是那些页面里几乎不会出现、但一旦出现就会把容器挤爆的长词。
4. 把测试文本灌进真实界面:三端注入与溢出判定方法
有了测试字符串,下一步是把它真正放到界面里跑。很多人做到这一步就卡住了——不知道怎么批量替换多语言文案。这里分享三种平台的注入方式。
4.1 Web端:检索替换 JSON 文件 + 运行时断言
Web端做国际化一般会有一个语言资源文件,比如 i18n/zh-CN.json。解法很简单:写一个脚本,读取英文原始文案,用刚才生成的对应语言字符串替换掉目标语言文件里的值,然后启动本地开发服务器。
替代完成之后,还需要用一组JS断言来检测溢出。
function isOverflowing(el, tolerance = 2) { return el.scrollWidth > el.clientWidth + tolerance; } document.querySelectorAll('[data-i18n]').forEach(el => { if (isOverflowing(el)) { console.warn(`[i18n-overflow] ${el.dataset.i18n}`, el); } });这里我经验是用2像素的容差,因为现代浏览器的亚像素渲染偶尔会产生微小的四舍五入误差。设成0的话会有一堆假正报,设太大又会漏掉真实问题。
4.2 Android:values-xx 批量替换
Android的多语言资源文件都在 res/values-xx/strings.xml 里。我的做法是写一个Python脚本,用ElementTree直接改XML节点值,把测试字符串写进对应语言,同时保留原英文文案作为key。
<string name="btn_save">Speichern</string> <string name="btn_save">Tallenna</string>替换到目标语言文件夹后,用模拟器或者真机切换系统语言,就能在真实渲染流程里看到效果。Android上特别要注意一个点:TextView默认有ellipsize属性,如果不设置成end或marquee,溢出时它可能会自动截断,你根本察觉不到问题。所以检测溢出时,最好先临时把ellipsize改成none,强制让文字撑出容器边界,才能测出真实宽度。
4.3 iOS:模拟器语言切换 + boundingRect 计算
iOS端更直接,把Localizable.strings里的value替换成目标语言,然后切模拟器语言重启,就能跑出真实Layout。
另外,我不建议完全依赖模拟器截图肉眼看。更好的方式是基于CoreText的 boundingRect 方法做离线预判:
let text = "Zahlungsinformationen aktualisieren" let font = UIFont.systemFont(ofSize: 16) let width = text.size(withAttributes: [.font: font]).width if width > containerWidth { print("⚠️ overflow: \(text), width=\(width)") }这套预判脚本不需要启动App就能批量检测几百条文案,是早期最廉价的问题发现手段。但要注意,离线计算用的是系统字体默认度量,实际渲染时如果用了自定义字重或字间距,数值会有偏差,最终仍要落到真机截图确认一版。
4.4 溢出判定:别只靠肉眼截图
坦白说,依赖QA肉眼一张图一张图对,效率太低且容易疲劳。我在落地这套流程时,给团队配了三条判定通道:
| 判定方式 | 适用阶段 | 说明 |
|---|---|---|
| 离线宽度计算 | 生成阶段 | 快速过滤超长文本 |
| 运行时尺寸断言 | 渲染阶段 | scrollWidth/clientWidth比对 |
| 截图视觉回归 | 最终验收 | 比对基线快照,像素级diff |
视觉回归可以引入常见的快照测试工具,在替换测试字符串之后对关键页面截图,把截图与预设的正常状态做像素比对,超出阈值就报警。阈值我通常设在1%到2%之间的像素比例,这样既能探测到明显的溢出,又能忽略抗锯齿带来的亚像素噪声。
5. 实战结果与坑:一场覆盖12种语言的溢出排查
最后分享一下我在某个跨平台客户端里的实际测试数据,以及那些"文档里不会写"的坑。
5.1 一次真实迭代的测试效果
当时我们用AI生成器跑了12种语言、230条测试短语,覆盖登录、支付、设置、列表、推送通知五个典型页面。全套流程大概花了一天时间,其中真正耗时的反而是把字符串灌进项目环境。
问题检出结果如下表:
| 问题类型 | 数量 | 典型案例 |
|---|---|---|
| 按钮宽度不足 | 7 | 德语"Fortsetzen"在窄按钮上被截断 |
| 列表行高不够 | 3 | 芬兰语长复合词导致行高撑破 |
| 换行破坏布局 | 2 | 法语长词在卡片中间换行 |
| 固定宽度容器溢出 | 1 | 日文片假名外来语在tab上溢出 |
13个问题,放在过去流程里基本要等到翻译公司回稿后的第二轮测试才能发现,而且大概率只能发现其中一半。
5.2 LLM在生成时的几个魔幻行为
AI不是完美的,用的时候有几个坑我实际踩过,提醒一下。
第一,它偶尔会用中文回复你。明明要求生成德语,前几条还是德语,第三条突然冒出一句中文短语。这多半是模型认为自己需要"解释"而不是"执行"。我的对策是在Prompt最后强制加一句"只返回JSON数组,不允许任何解释文本",并在后处理脚本里过滤掉含有常见中文标点的条目。
第二,生成重复内容的概率不低。尤其当每轮请求数量超过5条时,前两条和后一条经常高度重复。所以我做了全局去重,同时引入随机种子,让每轮的内容有更大差异。
第三,最坑的是"一本正经的错误"。比如让它生成波兰语时,它可能会生成"西班牙语式"的句子。这可能和训练语料中波兰语占比偏低有关。解决办法:不要跨语言一次性生成太多种语言,每种语言单独请求,并在Prompt里附带一个该语言的短示例。示例带来的收益非常明显。
第四,绝对不要拿LLM生成的测试文本去"冒充翻译"直接上生产环境。它的定位是压测数据,不是交付文本。这个边界一定要守住,否则审校环节会出大问题。
5.3 把测试字符串沉淀成资产库
最后分享一个长期价值点:我建议把每次AI生成出来、清洗过的字符串集,按语言、场景、长度区间分类存成模型文件。这样每个迭代都能复用,还能在UI改动后立刻跑一遍回归。
我自己的库已经积累了三轮迭代,每次新增页面或者调整组件宽度,直接把这些字符串灌进去跑断言,十分钟就能出报告。这个资产积累起来以后,测试投入越来越小,反而比做一次"一次性大检查"更划算。
另外一个小建议:AI生成跑完后,依然建议找母语者做一轮终检。AI能帮你把布局问题提前筛掉,但真实的语感、专业术语、俚语边界,仍然需要人来把最后一道关。AI和人工配合着用,这套国际化测试流程才算真正闭环。