只提供RESTful API的Java EE工程的多语言化

一个Java EE工程如果只提供RESTful API,多语言化还真是非常简单,虽然Servlet的WEB页面多语言化可能也并不复杂

一开始就上多语言,比半中途才上,可以省掉很多不必要的功夫

提供多语言的类是一个典型的多例模式(http://en.wikipedia.org/wiki/Multiton_pattern),阎宏的《Java与模式》中有一个Demo,但那个Demo的代码写得真叫一个烂,一堆语法错误不说,逻辑都写得莫名其妙的,网上的博客一大抄,导致这种有问题的代码广为流传,真是误人子弟

某工程半中途要上多语言,语言资源文件决定使用xml,另外语言不使用region和locale细分,语言只包括下面四种
ja (日语,locale_ja.xml)
en (英语,locale_en.xml)
zh_CN (简体中文,locale_zh_CN.xml)
zh_TW (繁体中文,locale_zh_TW.xml)

因此从字符串到java.util.Locale对象,首先就要做一次转换。

如果语言文件选择properties文件,文件读取将非常简单
选择xml文件则需要自己写处理逻辑,这方面网上的文章非常多

语言资源需要按照下面的写法组织,推荐保存为无BOM的UTF-8文档

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> 
<properties>
  <entry key="LoingFailed">登录失败,错误的用户名或密码。</entry>
</properties>

多语言类源代码,注意包的名字和资源文件路径
资源文件推荐放在工程的resources下,编译后会打包在一起

package info.kuyur.demo;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.Set;

public class LocaleLoader {
	public static final String LOCALE_EN = "en";
	public static final String LOCALE_JA = "ja";
	public static final String LOCALE_ZH_CN = "zh_CN";
	public static final String LOCALE_ZH_TW = "zh_TW";
	private static final String LOCALE_DEFAULT = "ja";
	private static final String LOCALE_PREFIX = "info/kuyur/locales/locale";
	private static final String MESSAGE_NOT_FOUND = "Message not found.";
	private static final String LOCALE_FILE_NOT_EXISTED = "Locale file {0}{1}{2}{3} not existed.";

	private static Map<String, LocaleLoader> instances = new HashMap<String, LocaleLoader>(4);
	private static XMLResourceBundleControl xmlResourceBundleControl = new XMLResourceBundleControl();

	private String locale;
	private ResourceBundle resourceBundle = null;

	private static class XMLResourceBundle extends ResourceBundle {
		private Properties props;
		XMLResourceBundle(InputStream stream) throws IOException {
			props = new Properties();
			props.loadFromXML(stream);
		}

		@Override
		protected Object handleGetObject(String key) {
			return props.getProperty(key);
		}

		@Override
		public Enumeration<String> getKeys() {
			Set<String> handleKeys = props.stringPropertyNames();
			return Collections.enumeration(handleKeys);
		}
	}

	private static class XMLResourceBundleControl extends ResourceBundle.Control {
		@Override
		public List<String> getFormats(String baseName) {
			return Collections.singletonList("xml");
		}

		@Override
		public ResourceBundle newBundle(String baseName, Locale locale, String format,
				ClassLoader loader, boolean reload)
		throws IllegalAccessException, InstantiationException, IOException {
			if ((baseName == null) || (locale == null) || (format == null) || (loader == null)) {
				throw new NullPointerException();
			}
			ResourceBundle bundle = null;
			if (format.equalsIgnoreCase("xml")) {
				String bundleName = this.toBundleName(baseName, locale);
				String resourceName = this.toResourceName(bundleName, format);
				URL url = loader.getResource(resourceName);
				if (url != null) {
					URLConnection connection = url.openConnection();
					if (connection != null) {
						if (reload) {
							connection.setUseCaches(false);
						}
						InputStream stream = connection.getInputStream();
						if (stream != null) {
							BufferedInputStream bis = new BufferedInputStream(stream);
							bundle = new XMLResourceBundle(bis);
							bis.close();
						}
					}
				}
			}
			return bundle;
		}
	}

	// can not be constructed by external
	private LocaleLoader(String locale) {
		this.locale = locale;
		Locale lo = toLocale(locale);
		try {
			resourceBundle = ResourceBundle.getBundle(LOCALE_PREFIX, lo, xmlResourceBundleControl);
		} catch (MissingResourceException e) {
			resourceBundle = null;
		}
	}

	/**
	 * Return a locale object which contains messages.
	 * If parameter is null or emtpy, will return default locale.  
	 * @param locale
	 * @return
	 */
	public synchronized static LocaleLoader getInstance(String locale) {
		locale = toSupportedLocale(locale);
		LocaleLoader instance = instances.get(locale);
		if (instance != null) {
			return instance;
		} else {
			instance = new LocaleLoader(locale);
			instances.put(locale, instance);
			return instance;
		}
	}

	public String getLocale() {
		return locale;
	}

	public String getString(String key) {
		if (resourceBundle == null) {
			return MessageFormat.format(LOCALE_FILE_NOT_EXISTED, LOCALE_PREFIX, "_", locale, ".xml");
		}
		try {
			return resourceBundle.getString(key);
		} catch (Exception e) {
			return MESSAGE_NOT_FOUND;
		}
	}

	private static String toSupportedLocale(String locale) {
		if (LOCALE_JA.equalsIgnoreCase(locale)) {
			return LOCALE_JA;
		}
		if (LOCALE_EN.equalsIgnoreCase(locale)) {
			return LOCALE_EN;
		}
		if (LOCALE_ZH_CN.equalsIgnoreCase(locale)) {
			return LOCALE_ZH_CN;
		}
		if (LOCALE_ZH_TW.equalsIgnoreCase(locale)) {
			return LOCALE_ZH_TW;
		}
		return LOCALE_DEFAULT;
	}

	private Locale toLocale(String locale) {
		if (LOCALE_JA.equals(locale)) {
			return Locale.JAPANESE;
		} else if (LOCALE_EN.equals(locale)) {
			return Locale.ENGLISH;
		} else if (LOCALE_ZH_CN.equals(locale)) {
			return Locale.SIMPLIFIED_CHINESE;
		} else if (LOCALE_ZH_TW.equals(locale)) {
			return Locale.TRADITIONAL_CHINESE;
		}
		return Locale.JAPANESE;
	}
}

这个类被设计成不会出错,即使语言文件不存在或者词条不存在
在代码中写死了支持的语言种类,但考虑到几乎不可能频繁增加新语言,这种写法还是可以接受的
(java.util.Locale中的静态Locale常量也是写死的)

测试用例:

package info.kuyur.demo;

import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;

import org.junit.BeforeClass;
import org.junit.Test;

public class LocaleLoaderTest {
	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
	}

	@Test
	public void testGetString() {
		LocaleLoader ja = LocaleLoader.getInstance(LocaleLoader.LOCALE_JA);
		assertTrue(LocaleLoader.LOCALE_JA.equals(ja.getLocale()));
		assertTrue("ja".equals(ja.getLocale()));
		System.out.println(ja.getString("LoingFailed"));
		System.out.println(ja.getString("hogehoge"));

		LocaleLoader en = LocaleLoader.getInstance("En");
		assertTrue(LocaleLoader.LOCALE_EN.equals(en.getLocale()));
		assertTrue("en".equals(en.getLocale()));
		System.out.println(en.getString("LoingFailed"));
		System.out.println(en.getString("hogehoge"));

		LocaleLoader zh_CN = LocaleLoader.getInstance("zH_cN");
		assertTrue(LocaleLoader.LOCALE_ZH_CN.equals(zh_CN.getLocale()));
		assertTrue("zh_CN".equals(zh_CN.getLocale()));
		System.out.println(zh_CN.getString("LoingFailed"));
		System.out.println(zh_CN.getString("hogehoge"));

		LocaleLoader zh_TW = LocaleLoader.getInstance("Zh_Tw");
		assertTrue(LocaleLoader.LOCALE_ZH_TW.equals(zh_TW.getLocale()));
		assertTrue("zh_TW".equals(zh_TW.getLocale()));
		System.out.println(zh_TW.getString("LoingFailed"));
		System.out.println(zh_TW.getString("hogehoge"));
	}

	@Test
	public void testIsSameObject() {
		LocaleLoader ja1 = LocaleLoader.getInstance(LocaleLoader.LOCALE_JA);
		LocaleLoader ja2 = LocaleLoader.getInstance(LocaleLoader.LOCALE_JA);
		assertSame(ja1, ja2);
	}

	@Test
	public void testDefault() {
		LocaleLoader ja = LocaleLoader.getInstance(LocaleLoader.LOCALE_JA);
		assertTrue(LocaleLoader.LOCALE_JA.equals(ja.getLocale()));

		LocaleLoader hogehoge = LocaleLoader.getInstance("hogehoge");
		assertTrue(LocaleLoader.LOCALE_JA.equals(hogehoge.getLocale()));

		assertSame(ja, hogehoge);
	}

	@Test
	public void testNullLocaleName() {
		LocaleLoader hogehoge = LocaleLoader.getInstance("");
		assertTrue(LocaleLoader.LOCALE_JA.equals(hogehoge.getLocale()));

		LocaleLoader gehogeho = LocaleLoader.getInstance(null);
		assertTrue(LocaleLoader.LOCALE_JA.equals(gehogeho.getLocale()));
	}
}

用法用例:
参考测试用例

本文目前尚无任何评论.

发表评论

XHTML: 您可以使用这些标签: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>
:wink: :-| :-x :twisted: :) 8-O :( :roll: :-P :oops: :-o :mrgreen: :lol: :idea: :-D :evil: :cry: 8) :arrow: :-? :?: :!: