如何從單一 JSON 來源生成本地化價格表
Generating Localised Price Tables From One JSON Source
將價格和方案資料集中在單一 JSON 來源,再按語系生成價格表,可避免不同語言版本出現不一致的價格資訊。各貨幣價格明確設定,並以 Intl.NumberFormat 按當地格式顯示;測試則檢查每個語系和方案是否缺少價格或翻譯鍵。
A pricing page that exists in six languages usually ends up with six slightly different price tables. Someone updates the English plan limits, forgets the Japanese page, and now an AI assistant or a customer quotes the wrong number. The fix is to treat prices as data and render every language version from one source.
One source of truth
{
"currency_by_locale": {"en": "USD", "de": "EUR", "ja": "JPY", "pt-BR": "BRL"},
"plans": [
{"id": "starter", "price": {"USD": 29, "EUR": 27, "JPY": 4300, "BRL": 149},
"seats": 3, "features": ["chat", "reports"]},
{"id": "growth", "price": {"USD": 99, "EUR": 92, "JPY": 14800, "BRL": 499},
"seats": 10, "features": ["chat", "reports", "api"]}
],
"updated": "2026-10-31"
}
Prices per currency are set explicitly rather than converted on the fly. Rounded local prices look intentional, and you avoid tables that change every time an exchange rate moves.
Rendering with Intl
import data from "./pricing.json" assert { type: "json" };
import t from "./i18n.json" assert { type: "json" };
export function renderTable(locale) {
const cur = data.currency_by_locale[locale];
const fmt = new Intl.NumberFormat(locale, { style: "currency", currency: cur,
maximumFractionDigits: cur === "JPY" ? 0 : 2 });
const rows = data.plans.map(p => `
<tr><th>${t[locale].plans[p.id]}</th>
<td>${fmt.format(p.price[cur])} / ${t[locale].month}</td>
<td>${p.seats} ${t[locale].seats}</td>
<td>${p.features.map(f => t[locale].features[f]).join(", ")}</td></tr>`);
return `<table>${rows.join("")}</table>
<p>${t[locale].updated}: ${new Date(data.updated).toLocaleDateString(locale)}</p>`;
}
Intl.NumberFormat handles symbol position, separators and spacing, which differ more than people expect: €27,00 in some locales, 27,00 € in German, R$ 149,00 in Brazil.
Fail the build on missing data
Add a test that loops over every locale and plan and fails if a price or translation key is missing. It is the cheapest way to stop a half-updated table from shipping.
for (const loc of Object.keys(data.currency_by_locale))
for (const p of data.plans) {
if (p.price[data.currency_by_locale[loc]] == null) throw new Error(`${loc}:${p.id}`);
if (!t[loc]?.plans?.[p.id]) throw new Error(`missing label ${loc}:${p.id}`);
}
Why it matters beyond tidiness
Consistent, dated prices across language versions make your pricing page safer to quote. Search engines and AI assistants that read several versions of the page see the same facts, and buyers comparing currencies see a deliberate policy rather than a mistake.
Translation tooling is the other half of this workflow; this multilingual website translation tools comparison covers how teams split work between machine translation, plugins and human review.
Published by PandaWM, an overseas marketing team based in Hong Kong.
來源:DEV Community · dev.to