`

一个i18n的方案请人拍砖

阅读更多
长久以前做18n一直靠Strurts的resource bundler的方法,从properties里读取一个个key的值来对应显示正确的语言文字,对于大部分场景这都是满足的。

但是对于有些情况,Resource bundler就不一定适合,比说产品的名称,一个很简单的例子,我的产品中文叫“钢笔,铅笔”,英文叫“pen,pencil",在搜索场景中,国内客户输入的就是“笔”--他想要钢笔铅笔的报价,国外用户可能输入“pen"--他只要钢笔的价格。那么resource bundler在这种情景下就有点力不从心。---可能方案,parser对应的properties,对应line的key读出来,然后读数据库,取信息....(也许luncene有更好的方案,不过偶不知道)。

因此我的初步解决方法是,将各国语言都存储到数据库中去,利用java 5.0的annotation标注其适用的field,例子如下。

//POJO 在hibernate mapping 是field不是properties
public class Major implements Serializable {
	@DocumentId
	private int id = 0;
	@SuppressWarnings("unused")
	@Field(name="subject",index = Index.TOKENIZED, store = Store.NO)
	@Localization(language = "en", country = "US", acquiescence = true)
	private String subject_english = StringUtils.EMPTY;
	@SuppressWarnings("unused")
	@Field(name="subject",index = Index.TOKENIZED, store = Store.NO)
	@Localization(language = "zh", country = "CN")
	private String subject_chinese = StringUtils.EMPTY;
        
        // get/set Id 从略

          public String getSubject(){
            return Translator.translate(this, CurrentUser.getLocale());
        }

        public void setSubject(String subject){

        }
         
}



Localization是个简单的annotation标签,它有四个参数,前面三个分别是langauge,country和variant,于java.util.Locale的构造参数是一样的,用于构造一个可比较的locale变量,第四个参数acquiescence用于指定某个field是否为默认显示。(default被java给占用了)。

无论我们指定了多少种语言的field,默认暴露出来的就只有subject一个属性,在getSubject中,我写了一个简单的Translator来parser匹配当前locale的field. CurrentUser是个辅助类,从ServletContext中读当前locale,如果用Spring的话,可以直接wrap
LocaleContextHolder.getLocale();


接下来是Translator的内容,并未做什么太多事情

import java.util.Locale;
import java.lang.reflect.Field;
import org.apache.commons.lang.StringUtils;

public class Translator {

	public static String translate(Object object, Locale locale)
			throws IllegalAccessException {
		String result = StringUtils.EMPTY;
		for (Field field : object.getClass().getFields()) {
			field.setAccessible(true);
			Localization i18n = (Localization) field
					.getAnnotation(Localization.class);
			// construct a local for compare
			if (locale.equals(new Locale(i18n.language(), i18n.country(), i18n
					.variant()))) {
				result = field.get(object).toString();
				break;
			}

			// set a default value if no found
			if (i18n.acquiescence() && StringUtils.isEmpty(result))
				result = field.get(object).toString();
		}
		return result;
	}
}


利用了反射读每一个field的标签,找到适合的值返回而已。

这个方案好处是,对搜索是透明的,无须干预后台生成索引,也无须干预hibernate search的搜索过程,理论上可以满足前面提出的应用场景,请大家拍砖。
分享到:
评论
28 楼 ray_linn 2009-08-31  
呵呵,居然想起这个帖子了, 先把应用场景说清楚,这样比较好讨论:

比如我的站点是电器公司的,客户关心冰箱,除了品牌,价格外,最注重的就是它的功能,有许多条款确实不需要做i18n,比如电耗、容积、这些是每台冰箱都有的东西,可以用一个个field来表示,但是以后的产品可能有新的功能,这个就没办法通过增加field来是实现了,因此留了个descript来容纳这些东西:

比如 冰箱A:  WLAN 下载 菜谱 光合 保鲜
比如 冰箱B:  臭氧 异味清除 光合 保鲜
     冰箱C:  真空保存 臭氧杀菌

这里这些关键词是我人为抽取出来的,这个设计是为了 对这些description做一个i18n的搜索用的:

比如输入“臭氧", 冰箱B和C应该被显示出来,而英国人输入ozone,得到是一致的结果,这里利用了hibernate search,来自动为description分词,索引。


简单一句话,这个方案是针对输入无法预测的string字段的一个解决办法。
27 楼 niuky 2009-02-13  
<p>你厉害</p>
26 楼 niuky 2009-02-13  
public class helloWorld{

}
25 楼 qaz1234 2009-02-13  
明显是个应用问题,非要套上I18N的枷锁。
I18N的概念的初衷是为了解决,相同的功能在不同地区及语言环境的显示问题。
非要使用这个思路解决产品本身功能性问题,就不是特别合适。既不方便,也很难优化提高效率。
有个朋友说的很对,东方和西方在很多思想上是不一致的。 只有却定功能的前提下,用另一种语言让特定的用户使用而已,而非解决软件的操作方法和查询结果的多寡问题。 就算你费劲的让他输入特定语言查到了特定解决,但他使用软件的操作方法和思维习惯你能兼顾的了吗?
如果你的方案没办法解决根本问题,就不要在一个“就和”的方案上走下去。走的越远,一旦碰壁,就需要退的更远。

总之,I18N很难承受内容管理之重。
24 楼 xixix2004 2008-11-25  
jacklondon 写道
jacktom 写道
做i18居然用数据库,明显的把问题复杂化嘛。影响服务器的性能姑且不说,“笔”的事例我觉得跟业务逻辑的细分有关。

那么请大侠回答上面的问题:如果你的系统正式运行一段时间后,增加新产品,或者产品更改名称,当你把产品名称放在 resource 文件中,怎么处理?



产品名称有必要放在resource文件中吗。。真的是简单问题复杂化。。。
23 楼 jacktom 2008-11-22  
做i18居然用数据库,明显的把问题复杂化嘛。影响服务器的性能姑且不说,“笔”的事例我觉得跟业务逻辑的细分有关。
22 楼 hellodesigner 2008-11-04  
    楼主你的需求想通过国际化来实现,思路似乎不对。国际化,并不适用搜索。国际化更加适应这样的场景:根据用户的使用环境中来决定显示的文本,这种映射关系是程序预先定义好的,并且这种关系只能是单向的(key-->value)。

    如果要实现你的需求,使用lucene或者其他的映射方式还是比较实际一点。
21 楼 kenken0y 2008-11-03  
这是个比较复杂的需求,compiere(一个开源的erp)中是使用专门一个表存多语言数据的,取数据的时候根据需要的语言用sql取出相应的数据,osCommerce中也有类似的实现
用hibernate的话,hmmm,可能就不能直接简单的load,get,saveOrUpdate了
20 楼 dazuiba 2008-11-03  
我认为这个问题已经脱离I18N的范畴了。

用户输入一个英文,你先将其翻译成中文,然后返回给他搜索结果。

这显然是属于机器翻译的范畴了。

两个方案,1 使用现有的翻译api或者字典
         2 人工建立一个翻译词库

你现在的方式其实就是方案2。

这个翻译的问题解决了,问题还没完---你还得给他返回搜索结果。

这个问题同样也有两个方案:
1 对用户输入进行翻译
2 在索引的数据上,添加对应的英文关键词。

你在用的是1 ,1修改了用户的输入,有时显得很笨拙。
其实2也很简单,你为luccnce的索引加上一个keyword字段,然后将中文英文都填进去即可。
19 楼 laozhijia 2008-11-01  
个人认为i18n这种东西根本没用,从没在什么较大的网站上看到中文站和英文站用同一套系统的.中国人对网站的使用习惯和国外的人是完全不一样的.
18 楼 sdh5724 2008-11-01  
对输入进行机器翻译。。。。。实际上, GOOGLE在SEARCH结果好像能做一定的输入翻译, 比如, 输入 apple, 能找到苹果。
17 楼 ray_linn 2008-11-01  
nihongye 写道
ray_linn 写道
这样暴露出来的的ProductInfo就会以默认的locale显示。

你是觉得product.getInfo(locale).getName()这样的调用相比product.getName(locale)不够好?


我是觉得多用了一个字段存储locale,似乎浪费了,所以改进点就是把locale变成了field上的annotation,关系也从1对多,变成1对1 --这似乎更符合实际情况.

这个方案主要是配合Hibernate Search, 让多语言的字段生成索引并对应到同一个document ID, 我一直在思考前面的先进提出的list resouce bundle的方案,还没弄明白如何让resource bundle里的东西生成索引.
16 楼 风花雪月饼 2008-11-01  
呃。国际化应该是界面上的Label的翻译吧。里面存的数据如果是要分多国访问的那应该是有不同记录的,汇率问题嘛。

PS:关于Lucene,可以在Doc中创建一个Field,其中存入:铅笔,pen,pencil
搜索的时候顺便搜索这个字段。
15 楼 nihongye 2008-11-01  
ray_linn 写道
这样暴露出来的的ProductInfo就会以默认的locale显示。

你是觉得product.getInfo(locale).getName()这样的调用相比product.getName(locale)不够好?
14 楼 fyting 2008-11-01  
实在是没看懂,按照不同的locale/language建立不同的索引,搜索的时候按locale定位索引不就行了?
13 楼 ray_linn 2008-11-01  
nihongye 写道
我们是这样干的,其实就是one-to-many的关系
Product{
    List<ProductInfo> infos;  
    ProductInfo getInfo(Locale locale);
}
ProductInfo{
   private String locale;
   private Stirng name;
   private String ....
}
但其实ResouceBundle也可以查询,先从那些Properties找出key来,然后再用key在数据库查询。



我要改进的其实就是你的一对多关系,改为一对一关系, 这样暴露出来的的ProductInfo就会以默认的locale显示。.
12 楼 nihongye 2008-11-01  
我们是这样干的,其实就是one-to-many的关系
Product{
    List<ProductInfo> infos;  
    ProductInfo getInfo(Locale locale);
}
ProductInfo{
   private String locale;
   private Stirng name;
   private String ....
}
但其实ResouceBundle也可以查询,先从那些Properties找出key来,然后再用key在数据库查询。
11 楼 yunhaifeiwu 2008-11-01  


i18n :主要是实现软件多国语言版本问题;

重点在软件界面显示上,可以显示多国语言文字.

而不是在其他.注意讨论问题的范围.
10 楼 yunhaifeiwu 2008-11-01  


地区决定了是用 ProgramResources_ch_ZH,还是用 ProgramResources_en_US

不管你的Fruit.id ,还是Fruit.name,还是Fruit.price ,还是Fuit.description;  都被捆绑在相应的key上了.

从中看出

当地区确定后,Fruit属性与 具体的ProgramResources类型对象的 list一一对应.
如:在中国,是苹果 ,决定了用的是 ProgramResources_ch_ZH对象;
苹果是存在Fruit.name中,而Fruit.name 与ProgramResources_ch_ZH中对应的
key (如"Fruit.name")相对应, 从而能从Fruit.name存储的内容找到
它在ListResourceBundle中的存储位置.

如果你要想从Fruit的name找到其他属性.
由于ListResourceBundle的存储单位,实质上是一个Map,
因此,你要在Map的key命名上,与你的Fruit要一一对应,这样在程序中你就能通过编吗实现.
如:fruit ,在map的key命名上,可这样:fruit_id;fruit_name;fruit_price,
fruit_description.

当用户输入苹果,想要price,程序根据确定的地区,找到ListResourceBundle的对象,寻找fruit_price内容.


对于你 "又及,Descritpion可能为TEXT类型的i18n文档, 要Search出description中带有某些字眼的POJO出来"

这可是其他方面的事了.不是用一个什么注释,用几个关系表,能解决的.

举个例子:

如果有人输个中文字,就找中国价格,如果输人英文字找英国的价格,

而有个中国人,输了英文,想找到英国的价格,而他看不懂啊,需要是汉字显示英国
地区的价格.

想是这个中国人想看全世界的价格信息,而返回的全是中文,这可怎么办?


狂汗! 你可找找qq2009的开发团队.再找金山词霸开发团队,他们如何解决这类问题的.




9 楼 ray_linn 2008-10-31  
谢谢你的分享,让我开了眼界.

不过我想你可能有点误会我要东西,我的要求是---

知道某个产品的某个i18n的名字,比如中文"苹果",英文"apple",能够回溯回它在数据库中的id,从而拿到他在数据库中的i18n资料,比如产地/价格/品种.

我不知道你的方案如何实现第一步,即从"苹果" "apple" 回溯回 id.

比如 中文资料有"国光苹果","红富士苹果","香蕉苹果","象牙芒果" 等等, 我可以通过搜索苹果,得到前三者的产地/价格/品种吗?


public class Fruit{

  public long id=0;
  public String name=null;
  public double price=0.0;
  public String description=null;
}



又及,Descritpion可能为TEXT类型的i18n文档, 要Search出description中带有某些字眼的POJO出来,不知道用resouce builder怎么实现?

相关推荐

Global site tag (gtag.js) - Google Analytics