2

我正在做一个 Spring Web 应用程序。

我需要支持一些语言环境,例如 ko(韩语)、ru(俄语)、en(英语)等。

我可以通过 RequestContextUtils.getLocale(request) 或 LocaleChangeInterceptor 等方式从浏览器中捕获语言环境。

但是,浏览器的语言环境可能不是我的网络应用程序支持的。我必须将其解析为壁橱或默认语言环境。

基本上,我需要知道如何在给定浏览器的语言环境和一些语言环境值(如 ko、ru 和 en)的情况下获取已解析的语言环境。

我的理解是 Spring 有这样的语言环境解析代码,因为它能够在给定浏览器语言环境的情况下找到正确的资源包。我希望重用 Spring 的代码进行语言环境解析,但不知道该怎么做。请注意,这个问题与查找浏览器的语言环境或显示正确的消息无关。

编辑

根据我跟踪 Spring 的代码,Spring 似乎依赖 JDK 来找到确切或最接近的语言环境。我刚刚发现了这一点,并认为这就是我想要的:

资源包查找顺序 https://sites.google.com/site/openjdklocale/design-notes/resource-bundle-lookup-order

请注意,我不需要找到正确的资源包。我只需要获取现有 JDK 代码返回的语言环境,给定一个有问题的语言环境和一些已知的语言环境。所以我希望重用现有JDK的查找代码。任何想法?

我正在使用 JDK 7。


感谢您的帮助和意见!

问候。

4

2 回答 2

5

简短的回答

您是否查看过官方文档(第17.8 章使用语言环境)?您需要配置LocaleResolver,可能还需要一个LocaleChangeInterceptor(或自己编写)。

关于 Spring 工作原理的更长描述

请注意,解析客户端的语言环境与获取正确的资源包是不同的任务。

  • Spring 用于LocaleResolver获取或设置当前语言环境。有几种不同策略的实现LocaleResolver
    • FixedLocaleResolver- 总是将语言环境解析为预定义的值(不能设置不同的语言环境)
    • SessionLocaleResolver- 在特殊键下存储和解析区域设置以在会话上存储值
    • AcceptHeaderLocaleResolver- 这是实际尝试从浏览器获取语言环境的解析器(不能设置不同的语言环境)
    • CookieLocaleResolver- 存储和解析区域设置为存储在浏览器中的值

LocaleResolver用于填充LocaleContextHolder(顺便说一句。这是您应该从中获取语言环境的类)。

还有第二种机制LocaleChangeInterceptor,可以LocaleResolver根据用户请求参数通过您选择的方式设置语言环境。

现在,此基础架构与您的资源包(messages.properties、messages_en.properties、...)以及用于解析消息的机制无关。以下示例将说明原因。

示例场景

  • 假设您的资源包是:
    • messages.properties- 带有ru消息(默认消息)
    • messages_ko.properties- 带有ko消息
  • 假设您已经配置SessionLocaleResolver了默认语言环境ru
  • 并假设您已配置LocaleChangeInterceptor

场景I - 第一次请求:

  1. 用户向应用程序发出第一个请求
  2. 一旦请求到达 Spring DispatcherServlet,它就会查询LocaleResolver以获取请求的语言环境
  3. 会话上没有设置语言环境,因此语言环境被解析为ru(默认)
  4. ...处理程序的东西...
  5. 现在您正在渲染网页并且您想使用<spring:message>标签...
  6. 该标签尝试使用带有请求语言环境MessageSource的预配置( ) 来解析翻译代码(这是由您的解析器解析的)。ResourceBundleMessageSource
  7. 消息源尝试加载messages_ru.properties不存在的翻译代码,因此它移动到更通用的文件messages.properties(“意外”包含您的默认语言 - ru
  8. 用户获取他的俄语页面

场景 II - 用户单击链接将其语言更改为ko

  1. 使用查询参数发出第二个请求locale=ko
  2. DispatcherServlet将请求语言环境解析为ru(这是您的语言环境解析器返回的内容)
  3. 在将请求移交给您的处理程序之前,它会通过LocaleChangeInterceptor处理程序拦截器。
  4. LocaleChangeInterceptor检测locale查询参数并调用setLocale您的方法LocaleResolver,这会导致更改请求区域设置并在会话中存储新的区域设置以供将来请求。
  5. ...处理程序的东西...
  6. ...查看内容...
  7. 现在正在使用语言环境<spring:message>调用。MessageSourceko
  8. 消息源尝试加载messages_ko.properties并成功。
  9. 用户以韩语获取他的页面

场景 III - 用户尝试更改为无效的语言环境:

  1. 用户使用查询参数发出请求locale=en
  2. ...调度员的东西ko...(区域设置从会话中解析)
  3. 处理程序拦截器将语言环境更改为en(这也将存储在会话中)
  4. ...处理程序的东西...
  5. ...查看内容...
  6. 现在正在使用语言环境<spring:message>调用。MessageSourceen
  7. 消息源尝试加载messages_en.properties不存在的文件,因此它移动到更通用的文件messages.properties并将消息转换为ru,即使您将请求区域设置为en
  8. 用户获取他的俄语页面

概括

现在最后一个例子可能是困扰你的 - 没有检查是否支持用户选择的语言环境。如果您不想让用户切换到不受支持的语言环境,那么您需要继承一些LocaleResolver或编写自己的LocaleChangeInterceptor.

于 2013-06-02T09:13:25.027 回答
1

完全重用

要重用 JDK 逻辑,您可以在类路径中为每个已知语言环境(例如 test_fr_CA.properties、test_fr.properties、test_en_US.properties、test_en.properties、test.properties)创建一个属性文件。如果您希望能够与之匹配,请不要忘记根区域设置 (test.properties)。然后只需为有问题的语言环境创建一个资源包并检查它以查看实际使用的语言环境。

ResourceBundle rb = ResourceBundle.getBundle("test", Locale.FRENCH);
System.out.println("Locale used is:"+rb.getLocale().toString());

这些文件可以在测试后动态创建和清理。

高级代码复制,低级重​​用

您可以复制java.util.ResourceBundle.getBundleImpl(...) 中的高级代码。这基本上是通过在有问题的语言环境上重用java.util.ResourceBundle.Control.getCandidateLocales(...)的候选语言环境列表中寻找匹配项(使用您自己的匹配逻辑,如 equal toString() 表示)。如果没有匹配项,您可以通过重用java.util.ResourceBundle.Control.getFallbackLocale(...)来获得相关语言环境的下一个备用语言环境对于每个后备语言环境,您尝试匹配其候选列表中的语言环境,在循环中重复后备,直到没有后备语言环境。请注意,根区域设置将是每个候选列表中的最后一个候选区域,但除非您已用尽所有后备区域设置,否则应跳过它。

此方法不需要创建文件。您在 getCandidateLocales(...) 中使用不存在的 baseName 并获取 FallbackLocale(...) 调用,并将每个候选语言环境与您的已知语言环境列表进行比较以寻找匹配项。

一个简单的例子如下:

    ResourceBundle.Control rbControl = ResourceBundle.Control.getControl(ResourceBundle.Control.FORMAT_PROPERTIES);
    Locale localeInQuestion = Locale.CHINA;
    List<Locale> knownLocales = Arrays.asList(new Locale[] {Locale.CANADA_FRENCH, Locale.FRENCH, Locale.US, Locale.UK, Locale.ENGLISH, Locale.ROOT});
    String nonExistentBaseName = "bogus";

    Locale matchingLocale = null;
    Boolean reachedRootLocaleMatch = false;

    outerloop:
    for (Locale targetLocale = localeInQuestion;
            targetLocale != null;
            targetLocale = rbControl.getFallbackLocale(nonExistentBaseName, targetLocale)) {
        List<Locale> candidateLocales = rbControl.getCandidateLocales(nonExistentBaseName, targetLocale);
        for (Iterator iterator = candidateLocales.iterator(); iterator.hasNext();) {
            Locale currentCandidateLocale = (Locale) iterator.next();
            if (knownLocales.contains(currentCandidateLocale)) {
                if (currentCandidateLocale.equals(Locale.ROOT)) {
                    reachedRootLocaleMatch = true;
                }
                else {
                    matchingLocale = currentCandidateLocale;
                    break outerloop;
                }
            }
        }
    }

    if (matchingLocale == null && reachedRootLocaleMatch) {
        matchingLocale = Locale.ROOT;
    }

    if (matchingLocale != null) {
        System.out.println("The matching locale is: "+matchingLocale.toString());
    }
    else {
        System.out.println("There was no matching locale");
    }
于 2013-06-26T18:26:21.727 回答