ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Java 提供的国际化支持

2022-05-31 11:31:53  阅读:232  来源: 互联网

标签:国际化 Java String Locale 支持 locale ResourceBundle return properties


目录

参考资料

  1. Intellij IDEA Resource Bundle

  2. Spring frame messagesource

  3. Spring MVC LocaleResolver

1. 缘由

以前学习 Java 的时候仅仅有这个概念,并没有深入了解。最近在开发 idea 插件,发现 CommonBundle 的父类 AbstractBundle 使用到了 java.util.ResourceBundle 。ResourceBundle 能够根据 Locale 从对应的消息文件(properties,xml)中读取消息。

1. 什么是国际化?

国际化(internationalization)是设计和制造容易适应不同区域要求的产品的一种方式,它要求从产品中抽离所有地域语言,国家/地区和文化相关的元素。换言之,应用程序的功能和代码设计考虑在不同地区运行的需要,其代码简化了不同本地版本的生产。开发这样的程序的过程,就称为国际化。

2. java.util.ResourceBundle

2.1 代码示例

  1. 新建 maven 项目,并在 src/main/resources 添加以下4个编码格式为 ISO-8859-1 的 properties 文件:
  • message.properties
  • message_en.properties
  • message_zh_CN.properties
  • message_zh_TW.properties

其内容分别是:
message.properties

welcome=这是默认欢迎信息!

message_en.properties

welcome=Welcome to my application !

message_zh_CN.properties

welcome=欢迎你来到我的应用 !

message_zh_TW.properties

welcome=歡迎來到我的應用!
  1. 编写 Locale 的工具类
import java.util.Locale;

/**
 * Locale  工具类
 */

public class LocaleUtil {

    /**
     * 根据 language tag 获取对应的 locale
     * @param languageTag  language tag (比如 zh-CN,zh,en 等)
     * @return locale
     */
    public static Locale localeByLanguageTag(String languageTag) {
        if(languageTag == null || languageTag.equals(" ") || languageTag.length() == 0) {
            return Locale.getDefault();
        }
        Locale locale;
        switch (languageTag.toLowerCase()) {
        case "zh":
            locale = Locale.CHINESE;
            break;
        case "zh-cn":
            locale = Locale.SIMPLIFIED_CHINESE;
            break;
        case "zh-tw":
            locale = Locale.TRADITIONAL_CHINESE;
            break;
        case "en":
            locale = Locale.ENGLISH;
            break;
        default:
            locale = Locale.ROOT;
            break;
        }
        return locale;
    }
}
  1. 编写获取消息类
import java.util.Locale;
import java.util.ResourceBundle;

/**
 * 该类主要用于 properties文件获取信息
 * @author black
 *
 */
public class MessageBundle {

    // 国际化文件名字
    private static final String MESSAGE_FILE_NAME="message";
    // JDK 提供 ResourceBundle 类用于支持国际化
    private ResourceBundle bundle;

    public MessageBundle(String languageTag) {
        // 1. 获取 locale
        Locale locale = LocaleUtil.localeByLanguageTag(languageTag);
        // 2. 实例化 ResourceBundle,即告诉 ResourceBundle 从哪个properties文件获取信息
        bundle = ResourceBundle.getBundle(MESSAGE_FILE_NAME, locale);
    }

    /**
     * 获取消息
     * @param key properties文件里的消息key
     * @return
     */
    public String getMessage(String key) {
        return bundle.getString(key);
    }
}

测试

  1. 编写测试类

import java.util.Scanner;

/**
 * 测试类
 * @author black
 *
 */
public class MessageBundleTest {

    public static void main(String[] args) {
        while (true) {
            System.out.println("-----------------------------------");
            // 1. 扫描输入的 language tag
            Scanner scanner = new Scanner(System.in);
            System.out.print("请输入Accept-Language:");
            String languageTag = scanner.next();
            // 2. 根据 language tag 获取对应的【欢迎信息】
            String welcome = getMessage(languageTag, "welcome");
            System.out.println("欢迎标语: " + welcome);
            System.out.println("-----------------------------------");
        }

    }

    private static String getMessage(String languageTag, String key) {
        MessageBundle messageBundle = new MessageBundle(languageTag);
        return messageBundle.getMessage(key);
    }
}

运行测试,控制台输出:

-----------------------------------
请输入Accept-Language:default
欢迎标语: 这是默认欢迎信息!
-----------------------------------
-----------------------------------
请输入Accept-Language:en
欢迎标语: Welcome to my application !
-----------------------------------
-----------------------------------
请输入Accept-Language:zh-CN
欢迎标语: 欢迎你来到我的应用 !
-----------------------------------
-----------------------------------
请输入Accept-Language:zh-TW
欢迎标语: 歡迎來到我的應用!
-----------------------------------

局限性

  1. windos操作系统下 properties 文件编码必须是 ISO-8859-1,如果改为 utf-8 会产生乱码。

3. Spring 的国际化支持

3.1 MessageSource 消息源

Spring 提供了 org.springframework.context.MessageSource 接口,是解析消息的策略接口,支持消息的参数化和国际化。

Spring 还提供了2个开箱即用的实现类:

  • org.springframework.context.support.ResourceBundleMessageSource(基于 java.util.ResourceBundle 构建的)
  • org.springframework.context.support.ReloadableResourceBundleMessageSource(高度可配置化,可重新加载 message definitions)

ApplicationContext 接口继承了 MessageSource 接口,说明 Spring 容器具有消息参数化和国际化的能力。

接口继承结构:
image

3.1.1 ResourceBundleMessageSource

ResourceBundleMessageSource 依赖于 java.util.ResourceBundle,结合JDK java.text.MessageFormat 提供的标准消息解析。

由于java.util.ResourceBundle 则是根据 Locale 读取某个 properties 文件,所以 ResourceBundleMessageSource 的信息源就是国际化消息 properties 文件。

那么 ResourceBundleMessageSource 怎么知道读取哪些 properties 文件呢?

答:其父类 AbstractResourceBasedMessageSource 的 setBasenames(String... basenames) 设置的 (原理是通过 Spring 容器的 Setter 注入实现的)

<beans>
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>format</value>
                <value>exceptions</value>
                <value>windows</value>
            </list>
        </property>
    </bean>
</beans>

上面 xml 配置的含义是 ResourceBundleMessageSource 将会从 format.properties("format_en.properties",format.xml", "format_en.xml") ,exceptions.properties("exceptions_en.properties",exceptions.xml", "exceptions_en.xml") 和 windows.properties("windows_en.properties",windows.xml", "windows_en.xml") 文件中读取 message。

那么 ResourceBundleMessageSource 怎么解决 properties 文件的编码呢?
答:对 ResourceBundle.Control 提供了定制化实现,即 MessageSourceControl 类。MessageSourceControl 类代码:

private volatile MessageSourceControl control = new MessageSourceControl();
	/**
	 * Custom implementation of {@code ResourceBundle.Control}, adding support
	 * for custom file encodings, deactivating the fallback to the system locale
	 * and activating ResourceBundle's native cache, if desired.
	 */
	private class MessageSourceControl extends ResourceBundle.Control {

		@Override
		@Nullable
		public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
				throws IllegalAccessException, InstantiationException, IOException {

			// Special handling of default encoding
			if (format.equals("java.properties")) {
				String bundleName = toBundleName(baseName, locale);
				final String resourceName = toResourceName(bundleName, "properties");
				final ClassLoader classLoader = loader;
				final boolean reloadFlag = reload;
				InputStream inputStream;
				try {
					inputStream = AccessController.doPrivileged((PrivilegedExceptionAction<InputStream>) () -> {
						InputStream is = null;
						if (reloadFlag) {
							URL url = classLoader.getResource(resourceName);
							if (url != null) {
								URLConnection connection = url.openConnection();
								if (connection != null) {
									connection.setUseCaches(false);
									is = connection.getInputStream();
								}
							}
						}
						else {
							is = classLoader.getResourceAsStream(resourceName);
						}
						return is;
					});
				}
				catch (PrivilegedActionException ex) {
					throw (IOException) ex.getException();
				}
				if (inputStream != null) {
					String encoding = getDefaultEncoding();
					if (encoding != null) {
						try (InputStreamReader bundleReader = new InputStreamReader(inputStream, encoding)) {
							return loadBundle(bundleReader);
						}
					}
					else {
						try (InputStream bundleStream = inputStream) {
							return loadBundle(bundleStream);
						}
					}
				}
				else {
					return null;
				}
			}
			else {
				// Delegate handling of "java.class" format to standard Control
				return super.newBundle(baseName, locale, format, loader, reload);
			}
		}

		@Override
		@Nullable
		public Locale getFallbackLocale(String baseName, Locale locale) {
			Locale defaultLocale = getDefaultLocale();
			return (defaultLocale != null && !defaultLocale.equals(locale) ? defaultLocale : null);
		}

		@Override
		public long getTimeToLive(String baseName, Locale locale) {
			long cacheMillis = getCacheMillis();
			return (cacheMillis >= 0 ? cacheMillis : super.getTimeToLive(baseName, locale));
		}

		@Override
		public boolean needsReload(
				String baseName, Locale locale, String format, ClassLoader loader, ResourceBundle bundle, long loadTime) {

			if (super.needsReload(baseName, locale, format, loader, bundle, loadTime)) {
				cachedBundleMessageFormats.remove(bundle);
				return true;
			}
			else {
				return false;
			}
		}
	}

其中:

// encoding 可通过 AbstractResourceBasedMessageSource#setDefaultEncoding 方法修改文件编码
String encoding = getDefaultEncoding();
if (encoding != null) {
   // 使用指定文件编码,读取文件流,并构建
	try (InputStreamReader bundleReader = new InputStreamReader(inputStream, encoding)) {
 	// 返回 new PropertyResourceBundle(inputStream);
	return loadBundle(bundleReader);
	}
}

3.1.2 ReloadableResourceBundleMessageSource

ReloadableResourceBundleMessageSource 使用 Properties 保存从文件中读取到的消息,支持消息的重新加载。

根据 basename + Locale 判断读取哪个文件。

文件编码可使用 ReloadableResourceBundleMessageSource#setFileEncodings 指定:

/**
 * fileEncodings 内容:文件名=文件编码
 * 针对不同的文件灵活配置其编码
 */
public void setFileEncodings(Properties fileEncodings) {
		this.fileEncodings = fileEncodings;
	}

当然,如果没有设置 fileEncodings,那么可以通过 AbstractResourceBasedMessageSource#setDefaultEncoding 指定默认编码。

3.2 LocaleResolver 解析获取 Locale

DispatcherServlet 允许通过客户端的 Locale对象 (Locale对象表示特定的地理、政治或文化区域) 自动处理消息。这个过程由 LocaleResolver 对象完成的。

Locale 处理器和拦截器都位于 org.springframework.web.servlet.i18n 包中。

LocaleContextResolver 接口提供了 resolveLocaleContext 方法可解析获取 Locale 和 TimeZone 信息。

Spring 提供的 locale resolvers 如下:

  • CookieLocaleResolver(实现 ResolveLocaleContext 接口)
  • FixedLocaleResolver(实现 ResolveLocaleContext 接口)
  • SessionLocaleResolver(实现 ResolveLocaleContext 接口)
  • AcceptHeaderLocaleResolver

CookieLocaleResolver
检查 Cookie 是否有 TimeZone 和 Locale 信息。

FixedLocaleResolver
固定 TimeZone 和 Locale 信息。提供AbstractLocaleResolver#setDefaultLocale 和 AbstractLocaleContextResolver# setDefaultTimeZone
进行设定。

SessionLocaleResolver
从本次请求对应的 session 中检索 TimeZone 和 Locale 信息。

AcceptHeaderLocaleResolver
AcceptHeaderLocaleResolver 解析请求头的 accept-language 属性 获取Locale。
如下:
image
Locale 拦截器
使用 LocaleChangeInterceptor 可以对某个 HandlerMapping 改变其 Locale。

LocaleResolver 实现及继承结构图
image

3.3 总结

Spring 对国际化的支持:

Spring 使用 LocaleResolver 解析获取客户端的 Locale,MessageSource 使用此 Locale 决定从哪个文件中读取消息。

标签:国际化,Java,String,Locale,支持,locale,ResourceBundle,return,properties
来源: https://www.cnblogs.com/lihw-study/p/16327107.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有