很荣幸也很开心地公布,EWeb4J 支持国际化了。并且有着它独特的味道。首先我们来看看最终的效果。

package test.i18n;

public class TestI18N {

	@BeforeClass
	public static void prepare() throws Exception {
		String err = EWeb4JConfig.start("start.eweb.xml");
		if (err != null){
			System.out.println(">>>EWeb4J Start Error --> " + err);
			System.exit(-1);
		}
	}

	@Test
	public void testVarable() throws Exception{
		Map map = Props.getMap("Message");

		Lang.set(Locale.US);
		Assert.assertEquals("Welcome to eweb4j !", map.get("welcome"));

		Lang.set(Locale.SIMPLIFIED_CHINESE);
		Assert.assertEquals("欢迎使用 eweb4j 框架 !", map.get("welcome"));
	}

}




PS:


Map map = Props.getMap("Message");
Lang.set(Locale.CHINESE);
map.get("welcome");
Lang.set(Locale.EN);
map.get("welcome");




[b][color=red]这个map获取之后,每次更改 locale,而不需要重新获取map,因为 map.get("key") 已经被代理了[/color][/b]



看到了么?还是使用之前版本的 Props 来获取键值。使用上来说没有任何需要重新学习的地方。当然,你可以看到有个设置国际化语言的代码段:


Lang.set(Locale.US);
Lang.set(Locale.SIMPLIFIED_CHINESE);




怎么样?跟Play有点像?wow, 不不不。Play是这么用的:Lang.set(""),它是设置一个字符串,而在这是设置一个标准的 java.util.Locale。



有人问,支持 web 访问(浏览器语言设置等等)自动识别本地语言来进行国际化支持么?


答案是: Yes !



看看这个HelloAction.



package org.eweb4j.crud;
import org.eweb4j.cache.Props;

public class HelloAction {

	public String doWelcome(){
		return Props.getMap("Message").get("welcome");
	}
}




是吧,真的跟上个版本一样调用的还是Props接口。



好了,在代码层面上就到这儿了,我们看看配置。



[*] 1.首先,start.xml多了一个配置


<locales>
    <locale language="en" country="US" />  
    <locale language="zh" country="CN" />
</locales>




PS:后面的 country 是可选的



[*] 2.其次,准备好你的 properties 文件,老规矩,在 start.xml:


<properties>
    <file id="Message" path="message.properties" />
</properties>




注意,这里的file的所有命名不需要跟国际化有任何关系。



[*] 3.虽然上面的配置文件里不需要写什么国际化语言,但是在你的文件系统里,你至少得准备好两份文件:


message_en_US.properties//或者message_en.properties
message_zh_CN.properties//或者message_zh.properties



PS:这种命名法跟 Java 的国际化标准是一致的。国家country可以不填写。



[*] 4.文件内容:


message_en.properties


#eweb4j last update 'framework' value
#Sat May 05 02:00:40 CST 2012
framework=eweb4j
welcome=Welcome to ${framework} !




message_zh_CN.properties


#eweb4j last update 'framework' value
#Sat May 05 02:00:40 CST 2012
framework=eweb4j
welcome=欢迎使用 ${framework} 框架 !




PS:你可以很放心的使用中文!!这些文件目录也可以随便放,不一定都放在ClassPath中,因为EWeb4J没有使用ResourceBundle来处理它们。



好了,大概就这么多。下面稍微再说说:



[*] EWeb4J 框架会自动的识别加载到你的国际化文件,而不需要显式的在配置文件指明,只需要指定一个BaseName即可。说到BaseName,还要说说Java国际化的内容,这里不详细说明,大概了解下,国际化资源文件命名规范:(优先级从上到下递增)


[*] baseName.properties//例如 message.properties 

 [*] baseName_language.properties//例如 message_zh.properties 

 [*] baseName_language_country.properties//例如 message_zh_CN.properties




更加详细的内容请查阅相关资料。



对于 EWeb4J 框架来说,只需要告诉它 baseName 即可。另外,这个 baseName.properties是否必须存在跟另外两个文件有关系,如果另外两个存在(一个或都存在),那这个 baseName.properties 就可以不存在,框架是不会自动生成的。否则,框架会自动生成。



最后发一下EWeb4J实现国际化的几个关键源码:



//代理类,在Props.java内部
	private static class MapProxy<K, V> implements Map<K,V>{
		private String id;

		private Map<K,V> map(){
			String _id = id+"_"+Lang.get().toString();
			if (props.containsKey(_id))
				return (Map<K, V>) props.get(_id);
			else{
				_id = id + "_" + Lang.get().getLanguage();
				if (props.containsKey(_id))
					return (Map<K, V>) props.get(_id);
			}

			return (Map<K, V>) props.get(id);
		}

		public MapProxy(String id){
			this.id = id;
		}

		public V get(Object key) {
			Map map = this.map();
			return (V) (map == null ? null : map.get(key));
		}
		//......
	}

//在获取缓存的时候,判断该ID是否拥有国际化资源文件,若有,使用代理
public static Map<String, String> getMap(String id) {
    if (i18nIds.contains(id))
        return new MapProxy<String, String>(id);

    return props.get(id);
}

// 读取properties的全部信息
public static synchronized String readProperties(Prop f, boolean isCreate)throws IOException {
		if (f == null || f.getPath().length() == 0)
			return null;

		String id = f.getId();
		String path = f.getPath();
		ConfigBean cb = (ConfigBean) SingleBeanCache.get(ConfigBean.class);
		I18N i18n = cb.getLocales();

		final String sufPro = ".properties";

		if (i18n != null){
			for (Locale l : i18n.getLocale()){

				String suffix1 = "_"+l.getLanguage()+"_"+l.getCountry();
				String tmpPath1 = path.replace(sufPro, suffix1 + sufPro);
				i18nIds.add(id);
				if (FileUtil.exists(ConfigConstant.CONFIG_BASE_PATH+ tmpPath1)){
					Prop p = new Prop();
					p.setGlobal("false");
					p.setId(id + suffix1 );
					p.setPath(tmpPath1);
					readProperties(p, false);//递归,把国际化文件内容加载仅缓存
					isCreate = false;// 如果存在国际化文件,那么默认的文件允许不存在
					continue;
				}

				String suffix2 = "_"+l.getLanguage() ;
				String tmpPath2 = path.replace(sufPro, suffix2 + sufPro);

				if (FileUtil.exists(ConfigConstant.CONFIG_BASE_PATH+ tmpPath2)){
					Prop p = new Prop();
					p.setGlobal("false");
					p.setId( id + suffix2 );
					p.setPath( tmpPath2 );
					readProperties(p, false);//递归,把国际化文件内容加载仅缓存
					isCreate = false;// 如果存在国际化文件,那么默认的文件允许不存在
					continue;
				}
			}
		}

//............
}




[color=red] !!正是因为这个代理类 MapProxy 的存在,所以在使用 Props.getMap("Message") 的 get("welcome") 的时候才会去找到匹配的国际化资源信息。这个很重要!![/color]



package org.eweb4j.i18n;

import java.util.List;
import java.util.Locale;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eweb4j.config.ConfigConstant;
import org.eweb4j.config.Log;
import org.eweb4j.config.LogFactory;
import org.eweb4j.config.bean.I18N;
import org.eweb4j.mvc.Context;
import org.eweb4j.mvc.MVC;
import org.eweb4j.mvc.MVCCons;


/**
 * EWeb4j 国际化支持
 * @author weiwei
 *
 */
public class Lang {

	private static Log log = LogFactory.getMVCLogger(Lang.class);

	private static ThreadLocal<Locale> current = new ThreadLocal<Locale>();

	public static Locale get(){

		Locale locale = current.get();
		if (locale == null){
			Context ctx = MVC.current.get();
			if (ctx != null){
				HttpServletRequest req = ctx.getRequest();
				HttpServletResponse res = ctx.getResponse();
				if (req != null){
					resolve(req, res);
				}else{
					setDefaultLocale();
				}
			}else{
				setDefaultLocale();
			}

			locale = current.get();
		}

		return locale;
	}

	private static void resolve(HttpServletRequest req, HttpServletResponse res) {
		Cookie[] cookies = req.getCookies();
		if (cookies != null){
			for (Cookie cookie : cookies){
				if (MVCCons.COOKIE_KEY_LOCALE.equals(cookie.getName())){
					String loc = cookie.getValue();
					if (loc == null)
						continue;

					if (loc.indexOf("_") > 0){
						String[] locs = loc.split("_");
						if (set(new Locale(locs[0], locs[1]))){
							return ;
						}
					}

					if (set(new Locale(loc)))
						return;
				}
			}
		}

		Locale locale = req.getLocale();

		set(locale);

		res.addCookie(new Cookie(MVCCons.COOKIE_KEY_LOCALE, locale.toString()));
	}

	public static boolean set(Locale locale){
		if (I18N.get().contains(locale)){
			log.debug("Locale is set -> %s", locale.toString());
			current.set(locale);
			return true;
		}

		log.warn("Locale %s is not defined in your %s > i18n", locale, ConfigConstant.START_FILE_NAME);
		return false;
	}

	public static void clear(){
		current.remove();
	}

	public static void change(Locale locale){
		if (get() == null){
			if (set(locale))
				MVC.current.get().getResponse().addCookie(new Cookie(MVCCons.COOKIE_KEY_LOCALE, locale.toString()));
		}else{
			if (!get().equals(locale)){
				if (set(locale))
					MVC.current.get().getResponse().addCookie(new Cookie(MVCCons.COOKIE_KEY_LOCALE, locale.toString()));
			}
		}
	}

	private static void setDefaultLocale() {
		List<org.eweb4j.config.bean.Locale> locales = I18N.get().getLocale();
		if (locales == null || locales.isEmpty()){
			set(Locale.getDefault());
		} else{
			set(new Locale(locales.get(0).getLanguage(), locales.get(0).getCountry()));
		}
	}
}