JFINAL国际化问题。

// 生成 Res
Res res = I18n.use(this.getBaseName(), localLanguage);

try {
    //  ThreadLocal 方案,给 Service 用
    I18nContext.set(res);

    // 视图层需要 _res 的话,保持你现在的时机
    if (!this.isSwitchView){
        c.setAttr(this.getResName(), res);
    }

    // invoke 前后都打印一下
    Res before = (Res) c.getAttr("_res");
    System.out.println("[i18n] BEFORE invoke _res=" + (before==null?null:before.get("button.importing")));

    inv.invoke();

    Res after = (Res) c.getAttr("_res");
    System.out.println("[i18n] AFTER  invoke _res=" + (after==null?null:after.get("button.importing")));
    System.out.println("[i18n] SAME OBJ? " + (before == after));


    if (this.isSwitchView) {
        this.switchView(localLanguage, c);
    }
} finally {
    // 避免线程复用导致串语言
    I18nContext.clear();
}

我有一个国际化代码是这样的,我想在control层也取到国际化语言,然后我就在invoke之前

c.setAttr(this.getResName(), res);

但是有个问题前端渲染界面的

TemplateRender
public void render() {
    this.response.setContentType(this.getContentType());
    Map<Object, Object> data = new HashMap();
    Enumeration<String> attrs = this.request.getAttributeNames();

    while(attrs.hasMoreElements()) {
        String attrName = (String)attrs.nextElement();
        data.put(attrName, this.request.getAttribute(attrName));
    }

    try {
        OutputStream os = this.response.getOutputStream();
        engine.getTemplate(this.view).render(data, os);
    } catch (Exception var4) {
        throw new RenderException(var4);
    }
}

这段render方法里面吧Request里面的请求参数都放到data里面,这里也有一个国际化参数_res,他取到的值是旧的(用户第一次登录选择英语,第二次登录选择中文,这个_res的值就是英语),这是什么原因呢?


解决是解决了,把serAttr放到invoke之后就正常了,我想知道,是谁改了这个参数,不是invoke之前设置了,之后肯定也能用吗??

评论区

hb963724769

2025-10-09 14:56

找到原因了,因为使用率CustomerCacheInterceptor,menu会缓存页面信息。
@Before(CustomerCacheInterceptor.class)
@CacheName("getMenu")
public void menu() {
}


protected void useCacheDataAndRender(Map cacheData, Controller controller) {
HttpServletRequest request = controller.getRequest();
Set> set = cacheData.entrySet();
Iterator> it = set.iterator();

while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
request.setAttribute((String)entry.getKey(), entry.getValue());
}

request.removeAttribute("_renderKey");
RenderInfo renderInfo = (RenderInfo)cacheData.get("_renderKey");
if (renderInfo != null) {
controller.render(renderInfo.createRender());
}

}
CustomerCacheInterceptor程序将页面缓存后,如果命中缓存会从缓存中取得信息。并覆盖掉res的国际化信息。这就导致他缓存的界面是第一次请求界面的信息,用户选的什么语言就是什么语言。另外这个还是不分用户的,其他用户进来时候取出来的语言也会是这个第一次缓存的语言数据。

当把c.setAttr(this.getResName(), res);放在了invoke之后,就表示他会执行完其他所有拦截器之后,从新把attr设置进去。

然后在TemplateRender会从新从Request里面取得新的res。

PS:这是作者有意这么控制的还是无意的啊。 I18nInterceptor故意吧c.setAttr(this.getResName(), res);放在invoke之后,然后TemplateRender,又故意从this.request.getAttributeNames();取得设置的国际化参数,再使用render方法实现的国际化。

算是国际化里面一个巨难查的隐藏BUG?

JFinal

2025-10-09 16:08

后端国际化很少用,现在国际化是玩的纯前端,比较省事

杜福忠

2025-10-09 16:15

@hb963724769 CustomerCacheInterceptor 是你们项目自定义的拦截器类,JF内置的是CacheInterceptor类,不过我看代码是差不多的。
你在拦截器里面request.removeAttribute后面,
加一行代码 request.setAttribute("_res", controller.getAttr("_res"));
意思就是不使用缓存的旧res对象,一直使用指定的res对象即可。

杜福忠

2025-10-09 16:27

@hb963724769 Controller的action缓存功能我不喜欢用,更喜欢在Service里面对需要的数据进行缓存取值,更方便管理和后续业务维护,并且性能基本和action缓存没太大差别。

lyh061619

2025-10-10 11:47

@JFinal 老大,前端做国际化后,后端还是需要国际化的支持,比如接口信息提示、拦截校验提示等这些仍然需要后端国际化处理。

热门反馈

扫码入社