You need to enable JavaScript to run this app. ICU4j如何通过NumberFormatter覆盖货币符号或添加自定义货币
优惠活动
大模型
产品
解决方案
定价
更多

ICU4j如何通过NumberFormatter覆盖货币符号或添加自定义货币

问题

我需要自定义覆盖货币符号。举个例子,en locale下使用货币代码BGN时,无论我选择Narrow、Short还是VARIANT参数,ICU都只会返回BGN 0.67,但我期望得到лв 0.67。除此之外我们还有其他需要覆盖货币符号的场景。

com.ibm.icu.util.Currency没有提供任何可实现覆盖的set方法。Numberformatter在为数字追加货币符号时,会调用Currency.java中的getName方法获取货币符号:

public String getName(ULocale locale, int nameStyle, boolean[] isChoiceFormat) {
  ...
  CurrencyDisplayNames names = CurrencyDisplayNames.getInstance(locale);
  switch (nameStyle) {
    case SYMBOL_NAME:
      return names.getSymbol(subType);
  ...
  }
  ...
}

CurrencyDisplayNames内置了预定义数据,也没有提供任何覆盖方法。
我发现Numberformat可以通过Numberformat.getCurrencyInstance()覆盖货币符号,但ICU建议新用户使用Numberformatter,请问我该如何实现符号覆盖,或者其他自定义需求比如在指定locale下将美元符号放在数字右侧?(ICU输出的大部分格式都符合我们的需求,我们仅需要针对某一locale下的特定货币做自定义调整)


解决方案

针对你的需求有两种轻量的实现方案,不需要修改ICU的底层源码:

方案1:自定义前后缀拼接(适合自定义规则少的场景)

核心思路是通过NumberFormatter的affixExclude参数排除默认的货币符号,拿到纯数字格式化结果后,再根据你预设的自定义规则拼接货币符号,调整前后位置:

import com.ibm.icu.number.CurrencyAffixExclude;
import com.ibm.icu.number.NumberFormatter;
import com.ibm.icu.util.Currency;
import com.ibm.icu.util.ULocale;
import java.util.HashMap;
import java.util.Map;

public class CustomCurrencyFormatter {
    // 自定义规则配置:key1=locale,key2=货币代码,值数组[前缀, 后缀]
    private static final Map<ULocale, Map<String, String[]>> CUSTOM_RULES = new HashMap<>();
    static {
        // 示例配置:en locale下BGN前缀加лв,USD后缀加$
        CUSTOM_RULES.put(ULocale.ENGLISH, Map.of(
            "BGN", new String[]{"лв ", ""},
            "USD", new String[]{"", " $"}
        ));
    }

    public static String format(double amount, ULocale locale, String currencyCode) {
        Currency currency = Currency.getInstance(currencyCode);
        // 先判断是否有自定义规则
        if (CUSTOM_RULES.containsKey(locale) && CUSTOM_RULES.get(locale).containsKey(currencyCode)) {
            String[] affix = CUSTOM_RULES.get(locale).get(currencyCode);
            // 获取不带货币符号的数字格式化结果
            String formattedNum = NumberFormatter.forLocale(locale)
                .unit(currency)
                .affixExclude(CurrencyAffixExclude.CURRENCY)
                .format(amount)
                .toString();
            return affix[0] + formattedNum + affix[1];
        }
        // 无自定义规则直接返回默认格式化结果
        return NumberFormatter.forLocale(locale)
            .unit(currency)
            .format(amount)
            .toString();
    }

    public static void main(String[] args) {
        // 测试BGN的场景,输出:лв 0.67
        System.out.println(format(0.67, ULocale.ENGLISH, "BGN"));
        // 测试美元后置场景,输出:123.45 $
        System.out.println(format(123.45, ULocale.ENGLISH, "USD"));
    }
}

方案2:SPI注册自定义CurrencyDisplayNames(适合自定义规则多、无侵入场景)

ICU支持通过SPI扩展CurrencyDisplayNamesProvider来覆盖默认的货币符号配置,原有NumberFormatter的调用代码不需要做任何修改就可以自动应用自定义规则:

  1. 自定义Provider实现类
import com.ibm.icu.util.CurrencyDisplayNames;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.spi.CurrencyDisplayNamesProvider;
import java.util.Collections;
import java.util.Locale;
import java.util.Set;

public class CustomCurrencyDisplayNamesProvider extends CurrencyDisplayNamesProvider {
    @Override
    public CurrencyDisplayNames getInstance(ULocale locale) {
        // 只处理需要自定义的locale,其他返回null走默认逻辑
        if (!ULocale.ENGLISH.equals(locale)) {
            return null;
        }
        return new CurrencyDisplayNames() {
            @Override
            public String getSymbol(String currencyCode) {
                // 自定义BGN的符号
                if ("BGN".equals(currencyCode)) {
                    return "лв";
                }
                // 其他货币返回默认符号
                return CurrencyDisplayNames.getInstance(locale, false).getSymbol(currencyCode);
            }

            // 其他方法直接代理默认实现即可
            @Override
            public String getName(String currencyCode) {
                return CurrencyDisplayNames.getInstance(locale, false).getName(currencyCode);
            }
            @Override
            public String getPluralName(String currencyCode, String pluralKey) {
                return CurrencyDisplayNames.getInstance(locale, false).getPluralName(currencyCode, pluralKey);
            }
            @Override
            public Set<String> keySet() {
                return Collections.emptySet();
            }
            @Override
            public ULocale getULocale() {
                return locale;
            }
        };
    }

    @Override
    public Set<Locale> getAvailableLocales() {
        return Collections.singleton(ULocale.ENGLISH.toLocale());
    }
}
  1. 注册SPI:在项目的META-INF/services目录下新建文件com.ibm.icu.util.spi.CurrencyDisplayNamesProvider,写入你的实现类全限定名(比如com.yourpackage.CustomCurrencyDisplayNamesProvider)
  2. 原有代码直接调用NumberFormatter即可,输出会自动应用自定义符号。

内容的提问来源于stack exchange,提问作者sam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.11 14:06:02