github:https://github.com/zhouyueyuedsf/easyintern
介绍
easyIntern是一款基于idea IDE的国际化插件,基于产品给出的excel国际化表单,灵活的对表单内的语言进行国际化的相关处理。主要特性如下
- 目前支持
<string>
标签和<string-array>
标签。 - 该插件会基于表单给定的值默认给出对应的
name
值,且支持对name
值和value
值的编辑 - 插件默认会对空格和&等特殊字符进行替换处理
- 针对文本替换问题,插件会在写入文件前比对单一项的相似度,自动提取出可疑的冲突的
name
和value
,让用户在页面进行编辑,当然你也可以直接生成新文件,复制粘贴,然后在studio的strings-xx
文件中编辑
easyintern源起
在国际词典中,经常会涉及到大量的新增国际化文本和修正后的国际化文本,常用的解决方案是用脚本语言去针对特定的场景编写脚本,缺点如下所示
- 一个脚本语言不能多处使用。
- 脚本一般没有特定的编辑功能
另外在github中,我也没能找到解决该类问题的项目。针对上述痛点,发现用idea插件模式可以很好的解决。只需做好一件事,用excel来编写国际化文案即可,且在插件在后续扩展中可以灵活的添加对各种格式的支持,甚至针对小白用户,调用自研的翻译引擎,形成一个自动化的国际化过程也很方便。
easyintern原理
UI使用idea提供的基于java swing封装后的api
读取excel数据使用org.apache.poi
的一套excel读取框架即可
用户可以在excel中灵活的指定有效数据区域和
<string-array>
标签所在区域,然后用上述读取框架转换成ui显示所要的数据
计算相似度底层使用的是文本的编辑距离,逻辑如下图所示
java与xml的转换用到的是JAXB的解析方式,举例如下
- 构建基于
strings.xml
的model
package com.youdao.model;
import javax.xml.bind.annotation.*;
import java.util.List;
/**
* @XmlRootElement(name = "resources") 根元素为<resources></resource>
* @XmlAccessorType 指定映射的字段类型
* XmlAccessType.FIELD:映射这个类中的所有字段到XML
* XmlAccessType.PROPERTY:映射这个类中的属性(get/set方法)到XML
* XmlAccessType.PUBLIC_MEMBER:将这个类中的所有public的field或property同时映射到XML(默认)
* XmlAccessType.NONE:不映射
*
* @XmlElement(name = "string") 映射根元素下的标签为<string>的子元素
* @XmlAttribute(name = "name") 映射元素下的属性为name到xml,<string name = "xxx"></string>
* @XmlValue 映射元素下的值,如<string name="">xxx</>,xxx表示该标签注释下的字段的值
*/
@XmlRootElement(name = "resources")
@XmlAccessorType(XmlAccessType.FIELD)
public class AndroidStringXmlModel {
@XmlElement(name = "string")
private List<StringMapModel> stringMapModelList;
@XmlElement(name = "string-array")
private List<StringArrayMapModel> stringArrayMapModels;
@XmlAccessorType(XmlAccessType.FIELD)
public static class StringMapModel {
@XmlAttribute(name = "name")
String name;
@XmlValue
String value;
@XmlAttribute(name = "tools:ignore")
String toolIgnore;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public String getToolIgnore() {
return toolIgnore;
}
public void setToolIgnore(String toolIgnore) {
this.toolIgnore = toolIgnore;
}
public void setValue(String value) {
this.value = value;
}
}
@XmlAccessorType(XmlAccessType.FIELD)
public static class StringArrayMapModel {
@XmlAttribute(name = "name")
String name;
List<String> item;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getItems() {
return item;
}
public void setItems(List<String> item) {
this.item = item;
}
}
public List<StringMapModel> getStringMapModelList() {
return stringMapModelList;
}
public void setStringMapModelList(List<StringMapModel> stringMapModelList) {
this.stringMapModelList = stringMapModelList;
}
public List<StringArrayMapModel> getStringArrayMapModels() {
return stringArrayMapModels;
}
public void setStringArrayMapModels(List<StringArrayMapModel> stringArrayMapModels) {
this.stringArrayMapModels = stringArrayMapModels;
}
public String getXmlnsTools() {
return xmlnsTools;
}
public void setXmlnsTools(String xmlnsTools) {
this.xmlnsTools = xmlnsTools;
}
}
- 输入
strings.xml
文件反序列化为java model
fun readStringsXmlByPath(path: String): AndroidStringXmlModel {
val mJaxb = JAXBContext.newInstance(AndroidStringXmlModel::class.java)
val unmarshaller = mJaxb.createUnmarshaller()
val stream = FileInputStream(path)
val model = unmarshaller.unmarshal(stream) as AndroidStringXmlModel
return model
}
- 输入java model,序列化为strings.xml
fun writeModelToXml(model: AndroidStringXmlModel?, path: String, append: kotlin.Boolean) {
// 获取JAXB的上下文环境,需要传入具体的 Java bean -> 这里使用Student
val context = JAXBContext.newInstance(AndroidStringXmlModel::class.java)
// 创建 Marshaller 实例
val marshaller = context.createMarshaller()
// 设置转换参数 -> 这里举例是告诉序列化器是否格式化输出
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE)
// 构建输出环境
val out = FileOutputStream(path, append)
// 将所需对象序列化 -> 该方法没有返回值
marshaller.marshal(model, out)
}
开发过程遇到的坑
-
java.lang.ClassCastException: com.sun.org.apache.xerces.internal.parsers.SAXParser cannot be cast to org.xml.sax.XMLReader
,思考后发现SAXParser类加载和XmlReader类加载时用到了两个不同ClassLoader
解决:搜索网络无果,自己找到的原因如下图所示:
- xml解析中需要处理特殊字符