ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

用AI生成多语言文本,提前排查UI溢出问题

用AI生成多语言文本,提前排查UI溢出问题

某次版本上线前,我例行检查后台的崩溃日志,意外看到一个德国用户在差评里放了一张截图:一个按钮上写着半截单词,后面跟着省略号,旁边还挤着三个换行的文字。乍一看是翻译没做完,点进去才知道是文案溢出。多语言环境下的文本溢出问题,绝对比大多数团队想象得普遍。很多团队会安排专人做多语言测试,但效率低、覆盖差、反馈还慢,等翻译公司回稿往往已经过去一两周了。我自己在几个跨语言应用里做过反复试验后,得出一个结论:与其等真实翻译,不如先用AI按约束自动生成多语言文本,结合国际化测试的基础流程,把溢出风险提前炸出来。

这篇文章要分享的,就是一套我自己在项目中跑通的做法——怎么用AI生成不同语言的高长度测试短语,怎么把生成的字符串注入到界面里,怎么判断元素是否真的溢出,以及这套方案到底能省多少事、踩过哪些坑。适合客户端开发、测试工程师、以及管国际化产品的同学参考,就算你的项目还没有完整多语言体系,看完也能直接落地一部分。

1. 语言膨胀率:一句"保存"在不同语言里差出多少个字符

先理解问题根源。中文界面文案往往很精炼,两三个字就能表达完一个动作。可一旦切到德语、芬兰语这类语言,同样的意思可能需要两倍以上的字符。这就是所谓"语言膨胀率":在相同语义下,不同语言所需字符数量的差异。

1.1 同一句话在不同语言里的真实差别

我做过一个小实验,找出之前项目里几个常见的UI短语,分别查了它们在几种语言里的表达长度。

英文原文中文德语芬兰语日语
Save保存SpeichernTallenna保存
Delete item删除项目Element löschenPoista kohde項目を削除
Terms of service服务条款NutzungsbedingungenPalvelun 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场景

一次合格的文本溢出测试,至少需要同时满足三个条件:

  1. 长度要够:字符串的字符数或渲染宽度要高于当前容器能承受的上限,通常我会要求生成英文原文长度的140%-180%。
  2. 语言特征要对:德语要有长复合词,芬兰语要有长尾缀,日语要包含适当比例的汉字和假名混排,法语要能产生连读导致的排版变化。
  3. 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 cleaned

3.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和人工配合着用,这套国际化测试流程才算真正闭环。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进