被漏掉的一个结构化数据字段,决定巴西用户看到的货币和国家

漏掉的通常是 offers 里那个 priceCurrency 的值,以及跟它一起被复制过来的 addressCountry。页面换成葡萄牙语,这两个值不会自己跟着变;JSON-LD 里还写着 USD 和 US,巴西用户看到的价格和国家就仍然指向美国。inLanguage 常常和它们一起被漏掉,但真正让人在下单前停住的那一个,是货币。

凌晨一点多,桌面上开着两个标签页,一个是英文版商品页,一个是它的巴西葡语版。我把两段 JSON-LD 复制进同一个编辑器里,上下叠着往下滚。四十来行,两段几乎一模一样:url 没变,name 没变,offers 里的 priceCurrency 也没变。笔记本风扇在转,屏幕上是两块长得完全相同的代码,而我刚刚花了一整个下午把页面正文从英语改成了葡萄牙语。

那天之后我改了做法:不再用「整块复制、顺手改几处」的方式处理多语言页面的结构化数据,而是把这段 JSON-LD 当成一份逐字段的清单,一个值一个值地问过去。下面就是这份清单,用的还是那天并排放着的那两段代码。

先说判断标准:这个值是跟着谁走的

只有一句话:这个值是跟着用户走的,还是跟着商品走的。

语言、货币、国家、可用区域、页面 URL,是跟着用户走的,每多一个语言版本就要重写一遍。SKU、品牌、型号、图片,是跟着商品走的,原样复制过去没问题。

模板之所以会出错,恰恰是因为它把这两类值写在了同一个文件里,复制的时候一起搬走了。

跟着用户走的值,一个都不能留

inLanguage

写 pt-BR。两个小写字母、一个连字符、两个大写字母,这是语言标签的写法,不是 pt,也不是 pt_BR。留空不等于「让系统自己判断」——判断依据可能在 URL 上,也可能在页面其它信号上,你看不到,也就控制不了。写死它是最便宜的一种确定性。

如果同一门语言你有多个地区版本,地区码就是唯一的区分手段。这时候它不只是「声明一下语言」,它是在告诉系统这两个页面不是同一件事。另外,它最好和 hreflang 里的声明方向一致,两处各说各话的时候,你很难判断最后哪一边被采信。

name 和 description

必须改,而且改完之后要和页面上看得见的文字对得上。结构化数据不是页面的补充说明,它是同一件事的另一种说法。页面上印着葡萄牙语的商品名,markup 里还是英文,这不是「翻译还没做完」,这是页面在自我矛盾。

offers.price 和 priceCurrency

这是被漏得最多的一个。priceCurrency 写 BRL,用 ISO 的三字母代码,大写,不要写 R$,也不要写小写 brl。

price 这里有个坑值得单独说:巴西页面上显示的价格是 R$ 1.234,56 这种格式,逗号当小数点、句点当千分位。很多人顺手就把屏幕上那串字符复制进 JSON-LD。不行,markup 里的 price 必须是 1234.56 这种机器读的形式。显示格式和声明格式是两套东西,这个区别和语言无关,但往往是你做葡萄牙语页面时第一次撞上它。

还有一点:price 要跟页面上真实显示的价格一致。页面写 199,markup 里写 149,这不是「价格还没来得及更新」,这是错误声明。

addressCountry 和 PostalAddress

如果这段模板里有 publisher、provider 或卖家的 PostalAddress,里面的 addressCountry 要写成 BR 两个字母,不是 Brasil,也不是 Brazil。巴西地址的结构是街道、门牌号、城市、州的两字母缩写、八位 CEP;填 postalCode 时可以用 01310-100 这种带连字符的形式,也可以纯数字,但别留着美国模板那套州缩写加五位 ZIP。

areaServed 和 shippingDestination

要改。如果这段 schema 描述的是服务范围,或者在配送信息里声明了可达区域,那里面的 addressCountry 同样要是 BR。留着 US,等于在说这东西只在别处可用。这个字段安静得很,安静到很多人根本不知道自己模板里有它。

url、@id、mainEntityOfPage

必须改,并且要和这个页面的葡萄牙语 URL 对得上。url 指向英文版,整段 markup 就在否定自己。

跟着商品走的值,可以原样留着

@context 和 @type

不用动。@context 永远是 schema.org 那个地址,@type 是 Product 就还是 Product,语言和类型没有关系。很多人是从这里开始怀疑自己的,这一步其实是安全的。

availability、itemCondition 这类枚举值

不要翻译。InStock 就是 InStock,它是词表里的固定值,不是给人读的自然语言。把它翻成葡萄牙语反而会让校验出问题。

sku、brand、gtin、image

一般不用改。SKU 和品牌不随语言变化。图片复不复用是你自己的决定——如果巴西版本换了另一张主图,image 跟着换,但那是内容决定,不是本地化要求。

aggregateRating

数字本身不用改,但前提是葡萄牙语页面上真的看得见评分和评价。如果评论只有英文站有、葡语页面没有,这块就不该出现。

校验器会告诉你的,和不会告诉你的

校验器能告诉你语法对不对、必填字段在不在、结构层级有没有断。它不会告诉你 priceCurrency 应该是 BRL。一段字段齐全、结构完整的 markup,可以同时是完全错的——它校验的是形状,不是事实。

结构化数据的规矩不只是「有就行」,而是必须和页面上看得见的内容对得上。对不上,代价不只是拿不到好的展示形式,还可能被当成误导性标记处理。这就是为什么我一直把「看起来对」和「对」分开当两件事。

还有一点得说清楚:字段全部正确,也只是让这个页面具备资格。那些富媒体样式会不会出现,取决于查询本身和站点的整体情况,这两件事不在你能直接控制的范围内。所以别用一个语言版本的表现,去反推另一个版本做得对不对。

我现在按什么顺序做

先把页面上要显示的东西定死:葡萄牙语的价格、地址、可配送区域、页面 URL。然后再回头写 JSON-LD,让 markup 去追页面,而不是先写 markup、再让页面去凑。

如果这段 JSON-LD 是模板层自动输出的,要改的位置通常在模板里,不在某一个页面上;改完记得回到渲染结果里确认一遍,看有没有别的地方又把它覆盖回去了。顺序反过来的时候,我总会漏掉一个值。而漏掉的那一个,通常就是用户在下单前会看到的那一个。

本文仅为教育性信息,不构成专业意见;在依据文中内容采取任何行动前,请先咨询相关领域的专业人士。