商品价签切换语言时,金额里的分组符和小数符也会变化。中文页面上的一千二百九十九元,在德语区域习惯下会采用另一种数字写法。如果业务把格式化字符串保存成价格,下次切换区域再从字符串取回数值,就可能把分组符误认成小数符,让同一商品出现不同金额。

这套多语商品价签工具把金额保存为最小货币单位的整数,区域设置只参与展示。手机上可以查看单个价签,平板后续用于并排核对不同语言版本。开篇聚焦金额单位、数字格式化和页面状态三者的分工,为后续多语文案、长文本适配和导出检查建立一致的数据入口。

多语价签的金额与展示分离

一、先定义价格的数据契约

示例商品为轻量保温壶,价格记录保存商品身份、名称、币种和整数金额。人民币记录的一元对应一百分,因此一千二百九十九元保存为129900。展示时除以一百得到主要单位数值,原始记录保持不变。

字段样例含义
productIdflask-light稳定商品身份
title轻量保温壶当前显示名称
currencyCNY币种标识
minor129900以分为单位的整数金额
localede-DE当前数字展示区域

locale 属于展示状态,不属于商品价格本身。改变它不会执行换汇,也不会改变货币种类。本篇固定支持人民币和两位小数,后续接入其他币种时,应明确该币种的最小单位与小数位规则,不能把除以一百当成全部币种通用的公式。

// model/PriceRecord.ets
export interface PriceRecord {
  productId: string;
  title: string;
  currency: string;
  minor: number;
}

export function validatePrice(record: PriceRecord): void {
  if (record.productId.trim().length === 0 || record.currency !== 'CNY') {
    throw new Error('商品身份或币种不符合当前价签规则');
  }
  if (!Number.isSafeInteger(record.minor) ||
      record.minor < 0 || record.minor > 100000000) {
    throw new Error('金额必须为业务范围内的非负整数分');
  }
}

金额上限是一百万元,对应一亿分,是示例产品明确的业务约束,并非平台金额上限。safe integer 检查防止输入已经失去整数精度,业务上限进一步收紧取值范围。负数、非数值和小数分都在入口拒绝,格式化函数不承担修正错误金额的职责。

二、把数字格式化放到展示出口

数字格式化使用 Localization Kit 的 intl.NumberFormat,输入为数值与区域,输出是给用户阅读的字符串。华为数字格式化指南提供该模块的导入与格式化方式。这里选择普通十进制格式,并把币种作为独立文字展示,避免不同区域使用相同货币符号时产生歧义。

// model/PriceFormat.ets
import { intl } from '@kit.LocalizationKit';
import { PriceRecord, validatePrice } from './PriceRecord';

export function displayPrice(record: PriceRecord, locale: string): string {
  validatePrice(record);
  if (locale !== 'zh-CN' && locale !== 'de-DE') {
    throw new Error('暂不支持此展示区域');
  }
  const formatter: intl.NumberFormat = new intl.NumberFormat(locale, {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
    useGrouping: true
  });
  return formatter.format(record.minor / 100);
}

支持区域采用明确的允许列表,避免从任意输入拼出区域参数。格式化器对区域和选项敏感,如果后续建立缓存,键应包含区域、小数位、分组策略等全部影响展示的配置。只按语言缓存,会把同一语言下不同地区的格式错误地合并。

这段逻辑有意不使用 parseFloat,也不把逗号和点号机械互换。格式化结果是展示终点,不能作为数据回写入口。字符串中还可能存在空格或区域相关符号,依靠删除标点恢复金额会把显示习惯误当成存储协议。

三、切换区域采用成功后提交状态

切换按钮不能先修改区域标签,再等待价格转换成功。如果转换失败,页面会出现“德语区域配着上一份中文格式”的混合状态。正确的顺序是先准备候选结果,成功后一起提交区域与展示值,失败时保留之前的有效组合,并展示可理解的错误信息。

// pages/Index.ets
import { PriceRecord } from '../model/PriceRecord';
import { displayPrice } from '../model/PriceFormat';

@Entry
@Component
struct Index {
  private record: PriceRecord = {
    productId: 'flask-light', title: '轻量保温壶',
    currency: 'CNY', minor: 129900
  };
  @State locale: string = 'de-DE';
  @State amountText: string = '';
  @State errorText: string = '';

  aboutToAppear(): void {
    this.selectLocale(this.locale);
  }

  private selectLocale(next: string): void {
    try {
      const candidate: string = displayPrice(this.record, next);
      this.amountText = candidate;
      this.locale = next;
      this.errorText = '';
    } catch {
      this.errorText = '价签更新失败,请检查金额和展示区域';
    }
  }

  build() {
    Column({ space: 20 }) {
      Text('多语商品价签').fontSize(24)
      Text(this.record.title).fontSize(20)
      Text(this.record.currency + ' ' + this.amountText).fontSize(32)
      Row({ space: 12 }) {
        Button('zh-CN').onClick(() => { this.selectLocale('zh-CN'); })
        Button('de-DE').onClick(() => { this.selectLocale('de-DE'); })
      }
      Text('当前区域 ' + this.locale)
      Text('原始金额 ' + this.record.minor + ' 分')
      Text('语言切换不改变金额')
      Text(this.errorText).fontColor('#A63020')
    }
    .width('100%')
    .padding(24)
  }
}

页面初始化也走同一个提交方法,避免首次进入和后续点击采用两套格式化规则。错误信息不直接拼接异常对象,防止把底层信息带给用户;排查时可以在开发环境记录错误类型、区域和商品身份。价格数据是否可以写入诊断信息,应结合业务权限决定。

开发环境中的本地化格式化函数与价签预览

本篇使用基础 ArkUI 页面与本地化模块,不增加网络请求。格式化数字并不要求读取用户联系方式或位置信息。区域由页面显式选择,后续如果跟随系统区域,还需要处理配置变化时的重新计算,不能把页面首次进入时的格式化结果永久保存。

四、展示精度与计算精度不要混为一谈

两位小数让129900分展示为完整货币金额,但它并没有替业务定义优惠、税费或结算规则。多个价签求和应基于整数分完成,再对总额进行展示;不能把每个格式化结果重新解析并相加。涉及比例运算时,应在金额计算层明确定义舍入位置和舍入方法。

当用户输入带小数的价格时,也不宜直接用浮点数乘一百再存入整数列。更稳妥的后续方案是接收受约束的十进制文本,拆分整数与小数部分,校验位数后构造整数分。本篇没有开放价格编辑入口,因此数据始终来自前述整数契约。

这项分层也有助于多人核对:负责商品资料的人查看原始金额与币种,负责语言展示的人检查数字格式和长文案,两个角色引用同一商品身份。展示上的标点差异不会被记录成价格变更,真正的金额调整则必须进入独立的数据修订流程。

布局适配还需要照顾较长的数字字符串。价签区域应预留币种文字与金额之间的间距,并允许商品名称独立换行;不宜为维持一行展示而持续缩小字号。窄屏展示单张完整价签,宽屏再增加对照列,金额契约始终保持一致。若开发者采用固定宽度容器,至少要把业务允许的最大金额纳入预览样本。

另一个容易漏掉的场景是商品记录更新。当前页面的记录是固定样本,后续接入数据源后,应在完整记录通过校验后替换旧记录,再重新生成展示值。如果先更新名称、随后才更新金额,就可能短暂把新商品名称与旧价格放在一起。商品身份和金额修订号应当随同一份快照提交,区域切换则继续只影响快照的展示。

数字区域与应用界面语言还要分别管理。本篇按钮改变的是数字展示区域,所以商品名称和界面提示仍保留中文;后续文案资源按语言键加载时,也不会改变整数金额。当某种语言的商品名称缺失,可以使用明确的回退语言,但价签仍应显示实际采用的数字区域,避免把语言资源回退和金额格式回退混成一条隐含规则。

五、围绕不变量检查区域切换

检查不能只比对屏幕上的一行文字,还应检查切换前后数据是否保持不变。商品身份、币种和整数金额构成不变量,区域和数字字符串则允许变化。若发现价格变化,应先追踪记录来源,而不是在组件样式层寻找原因。

检查样本检查目标
129900分,切换两个区域原始金额保持129900,分组与小数符按区域变化
零分展示完整两位小数,不显示空值
一分展示主要单位的小数部分,不丢失末位
负数或非整数分数据校验拒绝
不支持的区域保留上一份有效价签并提示

手机上的德语区域数字格式与原始金额

开发时记录的有效区域与格式化输出应当配对检查。肉眼看起来相近的数字可能使用不同字符,自动核对应比较结构化数据与指定区域结果;用于归档的截图则应同时包含商品身份或可追溯上下文,防止脱离数据来源单独流转。

后续宽屏多语言对照将复用 PriceRecord,分别生成不同区域的展示行,不创建多个可编辑价格副本。这样一份金额可以支撑多种阅读习惯,也能让编辑、计算与展示各自拥有清晰的职责。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐