安卓Res国际化文字资源转换AndroidResTranslator

AndroidResTranslator

介绍

AndroidResTranslator是使用Java/IDEA写的一个安卓多国语言转换控制台程序。主要功能包括:

  • 一键翻译:以某一种语言转另一种语言,支持批量转其他语言
  • 支持配置多种翻译api:防止A厂商的翻译api在不支持翻译时,可以使用B厂商进行翻译
  • 转xls表格:根据string的name属性作为key整合所有xml语言文件转换xls表格形式
  • xls转xml:翻译者将xls翻译之后需要写入xml当中后,程序根据key-value对比并替换xml内容
  • 多种翻译类型:支持不同类型的翻译:
    • 全量翻译(根据源语言的文字翻译并全部替换掉目标xml语言文件当中的所有值
    • 填充翻译(根据源语言文件的k-v值与目标语言文件当中的k-v值进行对比,若目标语言文件当中不包含源语言当中的key值,则将源语言的此key值的string进行翻译并添加到目标语言文件当中)

语言转换步骤

第一步:在as当中创建需要翻译的国家的语言文件

在此举例使用的是小日子语言,源语言为英语。
源语言内容:
在这里插入图片描述
新建语言:
在这里插入图片描述
在这里插入图片描述

注意:这个步骤并不是必须的。以上的工作只不过是使用as创建了一个 values-ja 文件夹和一个 strings.xml 文件。若你清楚的知道想要创建的目标语言简写,可以直接在res下以此对应规则创建对应语言文件夹和文件,工程依然认同此翻译文件。

第二步:你随意选择一个地方创建一个文件夹,并将所有 values 开头的文件夹copy一份到你新创建的文件夹当中

在这里插入图片描述

注意:请不要将values-night文件粘贴进去。翻译仅支持语言文件。

第三步:将AndroidResTranslator项目down到本地

查看Gitee:AndroidResTranslator

在这里插入图片描述

注意:默认的values文件夹的语言为en。你可以自行在程序当中更改。

第四步:配置后运行

注意:在进行此步前需要对程序当中的翻译厂商进行配置,当前流程是基于配置完成后进行的步骤。未进行配置前程序无法运行。具体配置见后面章节:关于配置厂商

查看Dom4j.java文件。main方法当中,仅调用了一个方法:

    public static void main(String[] args) {
        Dom4j dom4j = new Dom4j();
        dom4j.translationEntireValueFolder("C:\\Users\\hp\\Desktop\\values", "en", new FilledTranslationConsumer());
    }
    /**
     * 全量翻译(但不准确,取决于 translationType 参数
     * 逻辑是:给一个安卓res文件夹,程序获取文件夹下所有“values-xx”文件夹,解析“values-xx”文件夹下所有string.xml。执行翻译逻辑
     *
     * @param valueFolderPath 安卓res文件夹
     * @param sourceCountry   源语言国家简写。如英语为“en”。翻译时是根据“values-xx”文件夹命名的国家简写识别的。如果“value”文件夹没有国家后缀,默认为英语en
     * @param translationType 执行翻译的实际类。不同实现执行的逻辑不一样
     */
    public void translationEntireValueFolder(String valueFolderPath, String sourceCountry, TranslationConsumer translationType) {
    	//...
    }

你需要替换源程序当中translationEntireValueFolder当中的参数值。

一参:是从安卓程序当中copy文件到新建的文件夹的文件地址
二参:是想要从A语言翻译到B语言或其他语言,A语言的语言缩写
三参:翻译类型。稍后我们会讲到,暂且使用写好的FilledTranslationConsumer

然后点击运行即可。
可以在控制台等待程序运行结束
在这里插入图片描述
程序运行结束会在程序根目录下出现一个新的名为values文件夹,此文件夹内包含了所有的翻译结果。无论是单对单或是单对多翻译结果。

将这个生成的文件夹粘贴回安卓程序资源文件夹内即可。注意as提示文件更改时要点击替换文件

注意:建议在粘贴前检查一遍翻译结果文件。由于是程序是对厂商Api调用翻译的原因,不可避免地受到网络超时、厂商Api参数更改等其他问题。可以查看控制台Log是否出现“未翻译成功”相关字样。

实际上,生成的语言文件取决于程序读取的文件夹内以values开头的文件数量。即,有多少个原始的values文件,即生成对应名称与数量的翻译文件。

翻译类型

以上演示的是 填充式 的翻译:即,在res/vlaues(也即默认是 values-en)文件夹内的 strings.xml 文件,以所有string条目的name属性作为key,在进行翻译时,翻译生成的文件内若不包含此key的string条目,则进行翻译并添加到翻译文件内。因此它被称作“填充式”翻译。

除“填充式”翻译以外,还有 替换式 翻译。即无论生成的翻译文件内是否包含对应key值的string条目,都将删除此条目后,将源语言的文字进行翻译后重新填充到生成的文件当中。

填充式翻译:

本例当中含有两个语言文件:

  • 源语言文件values/strings.xml(英语 左)
  • 目标翻译文件values-ja/strings.xml(日语 右)

在这里插入图片描述
很明显,values-ja/strings.xml文件内相对values/strings.xml少了name为:tipshello 的两条string。

回到Dom4j.java程序,在程序当中translationEntireValueFolder的第三个参数使用的是FilledTranslationConsumer

    public static void main(String[] args) {
        Dom4j dom4j = new Dom4j();
        dom4j.translationEntireValueFolder("C:\\Users\\hp\\Desktop\\values", "en", new FilledTranslationConsumer());
    }

FilledTranslationConsumer 代表的是 填充式翻译

运行程序,我们来看结果:
在这里插入图片描述
控制台显示仅翻译了两条values-ja/strings.xmlvalues\strings.xml当中没有的string条目将会翻译后填充到文件当中。


我们再尝试替换式翻译

替换式翻译:

在这里插入图片描述
我们看到:在values-ja文件夹下的strings.xml的内容包含了错误的英语部分,但两者包含相同name属性的string条目。回到程序,我们使用AllReplaceTranslationConsumer

    public static void main(String[] args) {
        Dom4j dom4j = new Dom4j();
        dom4j.translationEntireValueFolder("C:\\Users\\hp\\Desktop\\values", "en", new AllReplaceTranslationConsumer());
    }

运行程序,看看结果:
在这里插入图片描述
所有的string都重新翻译并且替换了。

关于配置厂商

上面的例子全部都是基于 配置完毕 才可以运行的。如果你没有进行配置,程序无法进行翻译。

翻译配置适配了一部分主流厂商,目前程序当中含有的厂商:

当然,你如果有更好的选择,你也可以自行扩充翻译程序。目前先尝试配置我们的第一个翻译厂商:lingvanex

声明:本人并非给此翻译厂商打广告。此厂商配置仅作为教程流程示例,读者是否使用不做干预。

为什么使用lingvanex:在我尝试写这个程序时,浏览器在我看了几个翻译厂商都觉得各有不满意的地方,特别是在接入时对ak、sk等安全验证参数进行加密请求非常繁琐(这点在后面章节 假如当前使用的厂商不支持我想要翻译的语言很突出)。当时注册lingvanex控制台账号,甚至连注册的邮箱都无需验证,并且api的请求也只需要一个token参数。因此,这个程序第一个翻译厂商就是lingvanex。

尝试注册账号

账号注册仅需要一个邮箱,邮箱也无需验证。

在遇到让你填写国家和zip-code(邮编)的时候,随便填几个数字就行了,也不会进行验证。然后让你填信用卡号,直接原地返回键就行。账号就注册完成了。
在这里插入图片描述

填入token

复制你的 Api Key,回到程序,在程序根目录下找到 config.properties 文件。把拿到的Api Key放进去就行了。
在这里插入图片描述
恭喜,到此,配置就已经完成了。


假如翻译显示“未找到有效的服务器进行翻译”

2024/09/10 由于架构更新,此情况只会出现在继承了MapLangSupportHandler的翻译类当中。

假如你需要新增西班牙语。但是程序显示未找到有效的服务器进行翻译

java.lang.Exception: 未找到有效的服务器进行翻译 ---》 es
	at org.example.translation.TranslationManager.translation(TranslationManager.java:60)
	at org.example.translation.AbsTranslationConsumer.onElementNeedTranslation(AbsTranslationConsumer.java:221)
	at org.example.translation.AbsTranslationConsumer.accept(AbsTranslationConsumer.java:39)
	at org.example.dom4j.Dom4j.translationEntireValueFolder(Dom4j.java:127)
	at org.example.dom4j.Dom4j.main(Dom4j.java:35)

那么你需要在lingvanex翻译语言支持列表当中找到 Spanish :

注意 这取决于你使用的翻译类是属于哪一个导致的。由于目前程序当中仅有一个继承于MapLangSupportHandler类,可配置项仅为LingvanexTranslation.java。此后若您扩展此类,请注意配置此映射Map。

在这里插入图片描述

回到程序,在LingvanexTranslation.java文件找到initSupportComparisonTable方法:

    @Override
    protected void initSupportComparisonTable(Map<String, String> countryComparisonTable) {
        countryComparisonTable.put("en", "en_US");
        countryComparisonTable.put("cs", "cs_CZ");
        countryComparisonTable.put("de", "de_DE");
        countryComparisonTable.put("el", "el_GR");
        countryComparisonTable.put("fi", "fi_FI");
        countryComparisonTable.put("fr", "fr_CA");
        countryComparisonTable.put("hr", "hr_HR");
        countryComparisonTable.put("in", "id_ID");
        countryComparisonTable.put("it", "it_IT");
        countryComparisonTable.put("ja", "ja_JP");
        countryComparisonTable.put("ko", "ko_KR");
        countryComparisonTable.put("nl", "nl_NL");
        countryComparisonTable.put("pt", "pt_PT");
        countryComparisonTable.put("ro-rRO", "ro_RO");
        countryComparisonTable.put("ru", "ru_RU");
        countryComparisonTable.put("sl", "sl_SI");
        countryComparisonTable.put("sr", "sr-Cyrl_RS");
        countryComparisonTable.put("zh", "zh-Hans_CN");
        countryComparisonTable.put("zh-rHK", "zh-Hant_TW");
        countryComparisonTable.put("zh-rMO", "zh-Hant_TW");
        countryComparisonTable.put("zh-rTW", "zh-Hant_TW");
        //添加的部分
        countryComparisonTable.put("es", "es_ES");
    }

填入:

        countryComparisonTable.put("es", "es_ES");

第一个参数是安卓程序语言文件名称的values-xx文件在 - 后的后缀
第二个参数是刚刚在lingvanex语言支持网站查到的Language-code参数
写入完成后重新启动程序即可。

为什么要这么做?我们不能确定厂商是否支持对应翻译国家语言。因此,所有支持翻译的列表需要查询后填入。还有一个原因就是,安卓程序多语言的语言缩写是国际通用的,例如 中文为zh、英语为en、小日子语为ja。但对应部分翻译api来说,比如正在用的 lingvanex 的语言支持列表,其Language-code并不对应正常写法,如 中文为zh-Hans_CN、英语为en_US。对于api的参数请求上也需要对应的转换配置,于是这个map承担了这种对应的映射工作。


假如当前使用的厂商不支持我想要翻译的语言

在浏览器就可以查到很多翻译厂商,如果程序当中已经有对应厂商的翻译类,那和上一步基本一致,只需要新增对应支持的语言列表就行了。

但是如果程序当中不包含你查询到的厂商,那就稍微麻烦一点。需要你自己去适配对应厂商的接口。

以下流程是实操流程,接入失败并不是剧本

注册新厂商的账号、查看翻译API、查看支持的语言列表

比如我百度到一堆翻译厂商,其中 火山翻译 还没有接入,假设你就死活认定需要这个厂商,那就先进网站注册个账号,然后实名认证,然后开通控制台,然后查接入文档,账号注册之类的按照人家指引即可。

编写实体翻译类

先建个火山翻译的包,把翻译实体类写上:
翻译实体类继承于LangSupportHandler
在这里插入图片描述
目前这个类除了继承的两个方法空空如也。
initSupportComparisonTable方法当中填入的是支持翻译语言的列表,这个要查了他们文档才晓得,先不搞。
requestTranslation方法是实际请求服务器翻译的方法,那么我们直接火山翻译接口文档:

在这里插入图片描述
非常好。字都认识,但是一句话都没看懂。
于是找到他们的示例代码。cv大法。

在这里插入图片描述
在这里插入图片描述
复制粘贴的老毛病了,百度出来这玩意是java17的玩意。于是原地改java版本:(我额外瞄了一眼issue,果然有人骂作者。笑死,java8永远不变)

在这里插入图片描述
根据示例代码整合一下:

package org.example.huoshan_translation;

import com.google.gson.Gson;
import okhttp3.*;
import org.example.translation.LangSupportHandler;
import org.example.translation.PropValueSupplier;
import org.example.translation.TranslationPostBean;
import org.example.translation.TranslationResponseBean;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;

public class HuoshanTranslation extends LangSupportHandler {

    private String SecretAccessKey;
    private String AccessKeyID;
    // 请求地址
    private String endpoint = "iam.volcengineapi.com";
    private String path = "/"; // 路径,不包含 Query// 请求接口信息
    private String service = "iam";
    private String region = "cn-beijing";
    private String schema = "https";
    private final Sign sign;
    private final OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .build();
    private final Gson gson = new Gson();

    public HuoshanTranslation(PropValueSupplier propValueSupplier) {
        super(propValueSupplier);
        SecretAccessKey = propValueSupplier.get("huoshan.secretAccessKey");
        AccessKeyID = propValueSupplier.get("huoshan.accessKeyID");
        sign = new Sign(region, service, schema, endpoint, path, AccessKeyID, SecretAccessKey);
    }

    @Override
    protected void initSupportComparisonTable(Map<String, String> countryComparisonTable) {
        countryComparisonTable.put("ja", "ja");
    }

    @Override
    public TranslationResponseBean requestTranslation(TranslationPostBean postBean) {
        String action = "ListPolicies";
        String version = "2018-01-01";

        Date date = new Date();
        HashMap<String, String> queryMap = new HashMap<>() {{
            put("Limit", "1");
        }};
        byte[] body = null;
        if (body == null) {
            body = new byte[0];
        }
        String xContentSha256 = null;
        try {
            xContentSha256 = Sign.hashSHA256(body);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
        sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
        String xDate = sdf.format(date);
        String shortXDate = xDate.substring(0, 8);
        String contentType = "application/x-www-form-urlencoded";

        String signHeader = "host;x-date;x-content-sha256;content-type";


        SortedMap<String, String> realQueryList = new TreeMap<>(queryMap);
        realQueryList.put("Action", action);
        realQueryList.put("Version", version);
        StringBuilder querySB = new StringBuilder();
        for (String key : realQueryList.keySet()) {
            querySB.append(sign.signStringEncoder(key)).append("=").append(sign.signStringEncoder(realQueryList.get(key))).append("&");
        }
        querySB.deleteCharAt(querySB.length() - 1);

        String canonicalStringBuilder = "POST" + "\n" + path + "\n" + querySB + "\n" +
                "host:" + endpoint + "\n" +
                "x-date:" + xDate + "\n" +
                "x-content-sha256:" + xContentSha256 + "\n" +
                "content-type:" + contentType + "\n" +
                "\n" +
                signHeader + "\n" +
                xContentSha256;

        String hashcanonicalString = null;
        try {
            hashcanonicalString = Sign.hashSHA256(canonicalStringBuilder.getBytes());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        String credentialScope = shortXDate + "/" + region + "/" + service + "/request";
        String signString = "HMAC-SHA256" + "\n" + xDate + "\n" + credentialScope + "\n" + hashcanonicalString;

        byte[] signKey = new byte[0];
        try {
            signKey = sign.genSigningSecretKeyV4(SecretAccessKey, shortXDate, region, service);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        String signature = null;
        try {
            signature = HexFormat.of().formatHex(Sign.hmacSHA256(signKey, signString));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        HashMap<String, String> postBody = new HashMap<>();
        postBody.put("SourceLanguage", postBean.getSourceCountry());
        postBody.put("TargetLanguage", postBean.getTargetCountry());
        postBody.put("TextList", postBean.getSourceString());

        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), gson.toJson(postBody));
        Request request = new Request.Builder()
                .url(schema + "://" + endpoint + path + "?" + querySB)
                .header("Host", endpoint)
                .header("X-Date", xDate)
                .header("X-Content-Sha256", xContentSha256)
                .header("Content-Type", contentType)
                .header("Authorization", "HMAC-SHA256" +
                        " Credential=" + AccessKeyID + "/" + credentialScope +
                        ", SignedHeaders=" + signHeader +
                        ", Signature=" + signature)
                .post(requestBody)
                .build();
        try {
            Response execute = okHttpClient.newCall(request).execute();
            String responseStr = execute.body().string();
            RespBean respBean = gson.fromJson(responseStr, RespBean.class);
            if (respBean != null && respBean.getResponseMetadata().getError() == null && !respBean.getTranslationList().isEmpty()) {
                return new TranslationResponseBean()
                        .setSourceCountry(respBean.getTranslationList().get(0).getDetectedSourceLanguage())
                        .setSourceString(postBean.getSourceString())
                        .setTargetCountry(postBean.getTargetCountry())
                        .setTargetResult(respBean.getTranslationList().get(0).getTranslation());
            } else {
                RuntimeException runtimeException = new RuntimeException("server response:" + responseStr +
                        "\nSourceCountry:" + postBean.getSourceCountry() +
                        "\nTargetCountry:" + postBean.getTargetCountry() +
                        "\nContent:" + postBean.getSourceString());
                runtimeException.printStackTrace();
                translation(postBean);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new TranslationResponseBean()
                .setTargetResult("未完成翻译")
                .setSourceString(postBean.sourceString)
                .setSourceCountry(postBean.sourceCountry)
                .setTargetCountry(postBean.targetCountry);
    }
}

写半天结果运行之后服务器报错。我尝试直接运行复制过来的Sign.java。还是报错。我谢谢你,火山。

{"ResponseMetadata":{"RequestId":"202408081750047AEBBA99E7E1594EEECF","Action":"ListPolicies","Version":"2024-08-08","Error":{"CodeN":100024,"Code":"InvalidAuthorization","Message":"Invalid 'Authorization' header, Pls check your authorization header."}}}

Process finished with exit code 0

那没办法了,只能用人家的SDK了(我们java厨师比较潦草哈:

在这里插入图片描述
先找到readme瞄一眼,雀氏挺不错的:

在这里插入图片描述

由于人家仓库示例是maven,咱项目是gradle。得maven转gradle哈。按照人家说的,加个依赖:

dependencies {
// https://mvnrepository.com/artifact/net.sourceforge.jexcelapi/jxl
    implementation 'net.sourceforge.jexcelapi:jxl:2.6.12'
// https://mvnrepository.com/artifact/dom4j/dom4j
    implementation 'dom4j:dom4j:1.6.1'
// https://mvnrepository.com/artifact/com.tencentcloudapi/tencentcloud-sdk-java
    implementation 'com.tencentcloudapi:tencentcloud-sdk-java:3.1.1008'
// https://mvnrepository.com/artifact/com.google.code.gson/gson
    implementation 'com.google.code.gson:gson:2.10'
// https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
    implementation 'com.squareup.okhttp3:okhttp:4.12.0'
    //火山翻译sdk
    implementation 'com.volcengine:volc-sdk-java:1.0.177'

    testImplementation platform('org.junit:junit-bom:5.9.1')
    testImplementation 'org.junit.jupiter:junit-jupiter'
}

写完记得sync一下。
又到了写代码环节,但是这次比较简单,毕竟SDK都用上了,按照人家说的做就行。

public class HuoshanTranslation extends LangSupportHandler {

    private String SecretAccessKey;
    private String AccessKeyID;
    private final ITranslateService translateService = TranslateServiceImpl.getInstance();

    public HuoshanTranslation(PropValueSupplier propValueSupplier) {
        super(propValueSupplier);
        SecretAccessKey = propValueSupplier.get("huoshan.secretAccessKey");
        AccessKeyID = propValueSupplier.get("huoshan.accessKeyID");
        translateService.setAccessKey(AccessKeyID);
        translateService.setSecretKey(SecretAccessKey);
    }

    @Override
    protected void initSupportComparisonTable(Map<String, String> countryComparisonTable) {
        countryComparisonTable.put("ja", "ja");
    }

    @Override
    public TranslationResponseBean requestTranslation(TranslationPostBean postBean) {
        TranslateTextRequest translateTextRequest = new TranslateTextRequest();
        translateTextRequest.setSourceLanguage(postBean.getSourceCountry()); // 不设置表示自动检测
        translateTextRequest.setTargetLanguage(postBean.getTargetCountry());
        translateTextRequest.setTextList(Collections.singletonList(postBean.getSourceString()));

        try {
            TranslateTextResponse translateText = translateService.translateText(translateTextRequest);
            if (translateText.getResponseMetadata().getError() == null &&
                    !translateText.getTranslationList().isEmpty()) {
                return new TranslationResponseBean()
                        .setTargetResult(translateText.getTranslationList().get(0).getTranslation())
                        .setSourceString(postBean.getSourceString())
                        .setSourceCountry(postBean.getSourceCountry())
                        .setTargetCountry(postBean.getTargetCountry());
            } else {
                requestTranslation(postBean);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new TranslationResponseBean()
                .setTargetResult("未完成翻译")
                .setSourceString(postBean.sourceString)
                .setSourceCountry(postBean.sourceCountry)
                .setTargetCountry(postBean.targetCountry);
    }
}
配置火山翻译AK、SK

注意:各个翻译厂商的翻译请求的安全验证都不一样。并不是都用一个token或者都用ak、sk参数。很多时候,请求的参数千奇百怪,加密方式也是一遍又一遍。火山翻译使用的ak、sk参数与lingvanex翻译的token建议全部配置在项目根目录的config.properties 文件内。至于配置的ak、sk名字你随意填写都可,而后在你的HuoShanTranslation.java的构造函数当中,可以使用 PropValueSupplier 对象的 get 方法取出对应的ak、sk。

替换当前翻译链

写完了记得去TranslationManager.java把翻译调用链首个改成现在这个火山翻译:

package org.example.translation;

import org.example.baidu_translation.BaiduTranslation;
import org.example.huoshan_translation.HuoshanTranslation;
import org.example.lingvanex_translation.Translation;
import org.example.youdao_translation.YoudaoTranslation;
import org.jetbrains.annotations.Nullable;

import java.util.function.Function;

public class TranslationManager implements TranslationAble {

    private final LangSupportHandler translationHeader;
    private final TranslationKeyReader keyReader = new TranslationKeyReader();

    public TranslationManager() {
        keyReader.read();
        //先使用lingvanex翻译 找不到就用 有道翻译
        //translationHeader = new Translation(keyReader).setNext(new YoudaoTranslation(keyReader));
        //只使用火山翻译
        translationHeader = new HuoshanTranslation(keyReader);
    }

    @Override
    public @Nullable TranslationResponseBean translation(TranslationPostBean postBean) {
        LangSupportHandler langSupportHandler = translationHeader.process(new LangSupportRequest(postBean.targetCountry));
        if (langSupportHandler == null) {
            new Exception("未找到有效的服务器进行翻译 ---》 " + postBean.targetCountry).printStackTrace();
            return null;
        } else {
            return langSupportHandler.translation(postBean);
        }
    }

    public String getValueFileCountry(String fileName, Function<String, String> translation) {
        return translation.apply(fileName);
    }

}

这里额外说一下这个 翻译链 怎么搞。我们刚刚新建的火山翻译类HuoshanTranslation继承的LangSupportHandler是一个 责任链模式,具体表现为(简写此类):

public abstract class LangSupportHandler implements TranslationAble{
    //下一个翻译者
    protected LangSupportHandler next;
	//设置下一个翻译者
    public LangSupportHandler setNext(LangSupportHandler next) {
        this.next = next;
        return this;
    }
    //查询翻译者是否支持对应语言的翻译,若查询不到,就查询下一个翻译者是否支持,一直到某一个翻译者支持或者全部不支持抛出异常
    public LangSupportHandler process(@NotNull LangSupportRequest supportRequest){
        String targetCountry = getSupportLang(supportRequest.getTargetCountry());
        if (targetCountry != null){
            return this;
        }else {
            if (next == null){
                return null;
            }else {
                return next.process(supportRequest);
            }
        }
    }
}

如果当你有点强迫症,把所有翻译者(七龙珠)全部配置好(集齐)。你可以在TranslationManager的构造当中这么写:

		translationHeader = new HuoshanTranslation(keyReader)//火山翻译
                .setNext(new LingvanexTranslation(keyReader)//lingvanex翻译
                        .setNext(new BaiduTranslation(keyReader)//百度翻译
                                .setNext(new YoudaoTranslation(keyReader)//有道翻译
                                        .setNext(new TencentTranslation(keyReader)))));//腾讯翻译

运行程序的时候它就会一个一个查询下来。
现在可以跑一下dom4j.java了:
在这里插入图片描述
基本的就到此为止了。

【新版本新增】我需要在填充式翻译当中强行翻译部分指定的条目

在使用填充式翻译时,可能出现部分条目需要强行翻译,在FilledTranslationConsumer.java的构造当中填入强制翻译的name属性即可:

new FilledTranslationConsumer(Arrays.asList("all_sport","tips"))

注意。强制翻译只有FilledTranslationConsumer.java有。AllReplaceTranslationConsumer.java没有


转XLS表格

打开java/org/example/dom4j/StringXmlOutput.java

    public static void main(String[] args) {
        try {
            outputAll("C:\\Users\\hp\\Desktop\\values");
        } catch (DocumentException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (WriteException e) {
            throw new RuntimeException(e);
        }
    }

在main方法当中输入你翻译的文档地址就行了,运行程序之后,会在项目根目录下生成output.xls,所有的语言都会集合到一张表当中:
在这里插入图片描述
在这里插入图片描述
程序当中额外标注了对应列的语言类型,是由key值为“lang”对应的。但是dom4j的排序似乎有点问题,lang的位置总是乱跑。可以搜索一下,然后手动置顶即可:
在这里插入图片描述


翻译后的XLS表格重新填充到XML当中

注意:此填充是基于上一个部分的xls表格格式进行的。

注意:翻译后的xml表格必须包含lang字段,并且此行必须基于所有翻译行的顶部

用上一个xls表格举例子,我们现在得到的表格内包含enja两种语言。我们将此文件送给专业人士进行日语翻译,并且翻译人士翻译之后,在文档标注出带颜色的部分是修改部分,例如这样:
在这里插入图片描述
打开Jxl2Dom4j.java:

   public static void main(String[] args) {

        try {
            xls2Xml("C:\\Users\\hp\\IdeaProjects\\demo1\\untitled1\\output.xls",
                   "C:\\Users\\hp\\Desktop\\values\\values-ja\\strings.xml",
                    0,"ja","C:\\Users\\hp\\IdeaProjects\\demo1\\untitled1\\output.xml");
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (BiffException e) {
            throw new RuntimeException(e);
        } catch (DocumentException e) {
            throw new RuntimeException(e);
        }
    }

参数标注:

    /**
     * xls转xml
     * @param translationXlsPath 已经翻译的xls表格
     * @param xml 翻译语言的对应的xml文件
     * @param titleRow lang 出现在的第几行
     * @param witchLang 转换的是哪个语言
     * @param outputFilePath 结果输出位置
     * @throws IOException
     * @throws BiffException
     * @throws DocumentException
     */
    private static void xls2Xml(String translationXlsPath,String xml,int titleRow,String witchLang,String outputFilePath) throws IOException, BiffException, DocumentException {
    	//...
    }

运行即可:
在这里插入图片描述
把生成的这个文件的内容复制粘贴到对应语言文件即可。


鸣谢:

  • Okhttp 优秀的网络请求库
  • Gson 优秀的Json解析库
  • Dom4j 优秀的dom解析库
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值