如果你在 Spring Boot 里做过国际化,流程大概是这样的:配 MessageSource 的 Bean、写 ReloadableResourceBundleMessageSource、声明 LocaleResolver、注册 LocaleChangeInterceptor…五六步之后才能看到第一行翻译文本。

Solon 换了个方式。整个 i18n 模块围绕三个解析器、一个注解、一个工具类展开——没有 XML、没有拦截器注册、没有样板配置。

资源文件的约定

把文件放到 resources/i18n/ 下:

resources/i18n/messages.properties            # 默认语言(如中文)
resources/i18n/messages_en_US.properties      # 美式英语
resources/i18n/messages_ja_JP.properties      # 日文

格式是标准的 Java properties:

# messages_en_US.properties
login.title=Sign In
login.welcome=Welcome, {0}!
app.name=Solon Application

不需要 Bean 声明,不需要指定路径。Solon 自动发现。

三种解析器,一键切换

Solon 内置三个地区解析器。默认是 请求头解析器,但可以一行代码换掉。

1. Header 解析器(默认)

读取 Content-Language 请求头,降级到 Accept-Language

# app.yml — 无需任何配置

自定义头部名称:

@Configuration
public class I18nConfig {
    @Bean
    public LocaleResolver localeInit() {
        LocaleResolverHeader resolver = new LocaleResolverHeader();
        resolver.setHeaderName("lang");
        return resolver;
    }
}

从 cookie SOLON.LOCALE 读取语言:

@Configuration
public class I18nConfig {
    @Bean
    public LocaleResolver localeInit() {
        return new LocaleResolverCookie();
    }
}

3. Session 解析器

从 session 属性读取语言:

@Configuration
public class I18nConfig {
    @Bean
    public LocaleResolver localeInit() {
        return new LocaleResolverSession();
    }
}

4. 自定义解析器

如果三种都不够用,直接实现 LocaleResolver

@Component
public class QueryParamResolver implements LocaleResolver {
    @Override
    public Locale getLocale(Context ctx) {
        String lang = ctx.param("lang");
        if (lang != null) {
            return LocaleUtil.toLocale(lang);
        }
        return Locale.getDefault();
    }

    @Override
    public void setLocale(Context ctx, Locale locale) {
        ctx.setLocale(locale);
    }
}

三种读消息的方式

1. I18nUtil — 快捷工具

随处可用的静态方法:

// 从当前请求上下文(自动解析 locale)
String title = I18nUtil.getMessage("login.title");

// 指定 Locale
String welcome = I18nUtil.getMessage(locale, "login.welcome", new Object[]{"Alice"});

// 指定 Context
I18nUtil.getMessage(ctx, "app.name");

2. I18nService — 命名包访问

从特定语言包读取消息:

I18nService loginI18n = new I18nService("i18n.login");

@Mapping("/greet")
public String greet(Locale locale) {
    return loginI18n.get(locale, "login.welcome", new Object[]{"Alice"});
}

3. I18nBundle — 编程式操作

获取原始 bundle 做更复杂的操作:

I18nBundle bundle = I18nUtil.getBundle("i18n.messages", locale);
String value = bundle.get("login.title");
String formatted = bundle.getAndFormat("login.welcome", "Alice");

@I18n — 模板集成

给 Controller 加上 @I18n,所有模板自动获得 i18n 变量:

@I18n("i18n.login")
@Controller
public class LoginController {
    @Mapping("/login")
    public ModelAndView login() {
        return new ModelAndView("login.ftl");
    }
}

不指定包名则默认使用 i18n.messages

各模板引擎语法

Freemarker

${i18n["login.title"]}

${i18n.getAndFormat("login.welcome", user.name)}

Thymeleaf

登录

Beetl

${i18n["login.title"]}

分布式扩展

如果翻译内容来自配置中心或 CMS,实现 I18nBundleFactory

@Component
public class RemoteBundleFactory implements I18nBundleFactory {
    @Override
    public I18nBundle create(String bundleName, Locale locale) {
        Props props = getRemoteProps(bundleName, locale);
        return new I18nBundleImpl(props, locale);
    }
}

手动注册方式:

I18nUtil.setBundleFactory(new RemoteBundleFactory());

内置的 I18nBundleFactoryLocalresources/i18n/ 读取;工厂接口让你在不改业务代码的前提下换用 Redis、Nacos 或其他数据源。

诚实说三条边界

不含的:

  • 没有 MessageSource 层级体系(Solon 用扁平 bundle)
  • 没有开发模式热加载(重启生效,或自定义 I18nBundleFactory 实现热加载)
  • 没有内置的语言切换端点(切换逻辑留给 Resolver——如果要通过 URL 参数切换,写个自定义 Resolver 即可)

含的:

  • 60 秒内跑起国际化
  • 三种生产可用的解析器策略
  • 五种模板引擎的一等集成
  • 支持分布式配置的工厂模式
  • 零 XML、零拦截器、零仪式感

总结

Solon 的国际化模块延续了框架的哲学:约定优先,但需要时随时替换。一个依赖、一个注解用于模板、一个工具类用于编程访问、三个即插即用的解析器——覆盖了绝大多数真实场景。


原文地址: https://www.cveoy.top/t/topic/qHhF 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录