1. 简介

本文将介绍如何结合 Thymeleaf,根据用户的区域设置(Locale)动态格式化货币金额。这在国际化(i18n)场景中非常实用,比如多语言电商平台或金融类系统,避免出现 $1000 被德国用户看到的尴尬场面(他们更习惯 1.000,00 €)。

Thymeleaf 提供了开箱即用的表达式工具类,能轻松实现本地化货币展示,无需手动拼接符号或处理小数位。

2. Maven 依赖

首先引入 Spring Boot 的 Thymeleaf 启动器依赖:

<dependency>
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-thymeleaf</artifactId> 
    <version>3.1.5</version>
</dependency>

✅ 这个依赖会自动包含 Thymeleaf 核心库以及与 Spring 集成所需的组件,无需额外引入。

3. 项目搭建

我们构建一个简单的 Spring Web 应用,根据请求头中的 Accept-Language 返回对应地区的货币格式。

3.1 Thymeleaf 模板

resources/templates/currencies/ 目录下创建 currencies.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Currency table</title>
    </head>
    <body>
        <!-- 内容将在后续步骤填充 -->
    </body>
</html>

3.2 控制器

创建处理请求的控制器:

@Controller
public class CurrenciesController {

    @GetMapping("/currency")
    public String exchange(
        @RequestParam(value = "amount", required = false) String amount,
        @RequestParam(value = "amountList", required = false) List<String> amountList,
        Locale locale) {
        // 实际场景中可将数据放入 model,这里简化处理
        return "currencies/currencies";
    }
}

⚠️ 注意:Locale locale 参数会自动由 Spring 根据 Accept-Language 请求头注入。

4. 货币格式化实战

Thymeleaf 的 #numbers 工具类是本节核心,支持多种本地化数字格式化能力。

4.1 单个货币格式化

使用 #numbers.formatCurrency() 方法,自动根据当前 Locale 格式化金额:

<p th:text="${#numbers.formatCurrency(param.amount)}"></p>
  • param.amount 是 URL 参数传入的金额字符串
  • 格式化结果包含货币符号、千分位分隔符、保留两位小数

✅ 示例测试(美国地区):

@Test
public void whenCallCurrencyWithUSALocale_ThenReturnProperCurrency() throws Exception {
    mockMvc.perform(MockMvcRequestBuilders.get("/currency")
      .header("Accept-Language", "en-US")
      .param("amount", "10032.5"))
      .andExpect(status().isOk())
      .andExpect(content().string(containsString("$10,032.50")));
}

输出:$10,032.50 —— 美元符号 + 逗号分隔 + 小数点

4.2 货币数组格式化

#numbers 还支持直接格式化金额列表,调用 listFormatCurrency()

<p th:text="${#numbers.listFormatCurrency(param.amountList)}"></p>

更新控制器以接收列表参数(Spring 自动绑定):

@GetMapping("/currency")
public String exchange(
    @RequestParam(value = "amount", required = false) String amount,
    @RequestParam(value = "amountList", required = false) List<String> amountList,
    Locale locale) {
    return "currencies/currencies";
}

✅ 示例测试(英国地区):

@Test
public void whenCallCurrencyWithUkLocaleWithArrays_ThenReturnLocaleCurrencies() throws Exception {
    mockMvc.perform(MockMvcRequestBuilders.get("/currency")
      .header("Accept-Language", "en-GB")
      .param("amountList", "10", "20", "30"))
      .andExpect(status().isOk())
      .andExpect(content().string(containsString("£10.00, £20.00, £30.00")));
}

输出:£10.00, £20.00, £30.00 —— 英镑符号,逗号分隔各项

4.3 去除末尾零(.00)

有时整数金额不需要显示 .00,可通过 #strings.replace 简单处理:

<p th:text="${#strings.replace(#numbers.formatCurrency(param.amount), '.00', '')}"></p>

✅ 示例测试(美国地区,整数):

@Test
public void whenCallCurrencyWithUSALocaleWithoutDecimal_ThenReturnCurrencyWithoutTrailingZeros()
  throws Exception {
    mockMvc.perform(MockMvcRequestBuilders.get("/currency")
      .header("Accept-Language", "en-US")
      .param("amount", "10032"))
      .andExpect(status().isOk())
      .andExpect(content().string(containsString("$10,032")));
}

输出:$10,032 —— 干净利落,无多余小数位

⚠️ 注意:此方法有局限,如金额为 100.50 会被错误替换为 100.5,实际应保留一位小数。更严谨的做法是先判断是否为整数,或使用自定义 Dialect

4.4 自定义小数分隔符

某些地区(如德语区)使用逗号作为小数点。虽然 formatCurrency 会自动适配,但若需手动控制,可用 formatDecimal

<p th:text="${#numbers.formatDecimal(param.amount, 1, 2, 'COMMA')}"></p>

参数说明:

  • param.amount: 原始数值
  • 1: 整数部分最小位数
  • 2: 小数部分固定位数
  • 'COMMA': 小数点用逗号表示

✅ 示例测试:

@Test
public void whenCallCurrencyWithUSALocale_ThenReturnReplacedDecimalPoint() throws Exception {
    mockMvc.perform(MockMvcRequestBuilders.get("/currency")
      .header("Accept-Language", "en-US")
      .param("amount", "1.5"))
      .andExpect(status().isOk())
      .andExpect(content().string(containsString("1,5")));
}

输出:1,5 —— 注意这不是千分位,而是小数点被替换为逗号

⚠️ 踩坑提醒:formatDecimal 不带货币符号,仅格式化数字。如需符号,需手动拼接或仍使用 formatCurrency

5. 总结

通过 Thymeleaf 的 #numbers 工具类,我们能以声明式方式实现货币的本地化展示,核心要点如下:

  • ✅ 使用 #numbers.formatCurrency() 自动适配 Locale
  • listFormatCurrency() 支持金额列表批量格式化
  • ✅ 结合 #strings.replace 可简单去除 .00
  • formatDecimal 提供更细粒度的数字格式控制

整套方案简单粗暴,适合大多数国际化场景。对于复杂需求(如动态币种、精度控制),建议封装为自定义 Utility 或 Thymeleaf Dialect

示例代码已托管至 GitHub: https://github.com/john-doe/spring-thymeleaf-currency-demo


原始标题:Formatting Currencies in Spring Using Thymeleaf

« 上一篇: Java 14 Record 关键字
» 下一篇: Java周报,334期