import java.io.IOException;import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;import java.nio.charset.StandardCharsets;import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.StandardOpenOption;import java.util.ArrayList;import java.util.HashMap;import jason.JsonReader;import jason.JsonWriter;/* arg.json:{project: "******"component: "******"language: "zh_Hans"auth: "Token wlu_************************************"page: 1overwrite: falsedicFiles: ["******.txt""******.txt"]}*/public final class WeblateTrans {static final class Arg {String project;String component;String language; // "zh_Hans"String auth; // "Token wlu_......"int page; // base-1boolean overwrite; // 覆盖已存在的译文final ArrayList<String> dicFiles = new ArrayList<>();}static final class Units {static String urlFmt = "https://hosted.weblate.org/api/translations/%s/%s/%s/units/"; // project, component, languagestatic String urlPageFmt = "https://hosted.weblate.org/api/translations/%s/%s/%s/units/?page=%d"; // project, component, language, pageint count;String next;// String previous;final ArrayList<Unit> results = new ArrayList<>();}static final class Unit {// static String urlFmt = "https://hosted.weblate.org/api/units/%d/"; // idlong id;String context; // keyfinal ArrayList<String> source = new ArrayList<>(); // 通常只有1个final ArrayList<String> target = new ArrayList<>(); // 通常只有1个,空字符串表示未翻译String url; // "https://hosted.weblate.org/api/units/%d/"int state; // 单元状态,0——未翻译、10——需要编辑、20——已翻译、30——已核准、100——只读// final ArrayList<String> labels = new ArrayList<>(); // 译文单元标签,在原文单元上可用// String translation; // "https://hosted.weblate.org/api/translations/%s/%s/%s/", project, component, language// String language_code; // "zh_Hans"// String explanation; // 字符串的解释,可在源单元获得// String flags; // 翻译单元的标记// String extra_flags; // 另外的字符串标记,可在源单元获得// int position; // 单元在翻译文件中的位次. base-1// boolean translated; // 单元是否被翻译// boolean approved; // 译文是否已核准}static final class UnitMod {int state; // 单元状态,0——未翻译、10——需要编辑、20——已翻译、30——已核准(需启用审校流程)final ArrayList<String> target = new ArrayList<>(); // 目标字符串. 通常只有1个,空字符串表示未翻译// String explanation; // 字符串的解释,可在源单元获得// String extra_flags; // 另外的字符串标记,可在源单元获得// final ArrayList<String> labels = new ArrayList<>(); // 标签,在源单元上可用}record KV(String k, String v) {}static final long INTERVAL_MS = 3_000;static long lastMs;static void delay() throws InterruptedException {var curMs = System.nanoTime() / 1_000_000;final var delayMs = INTERVAL_MS - (curMs - lastMs);if (delayMs > 0) {System.out.format("(wait %s ms) ... ", delayMs);Thread.sleep(delayMs);curMs = System.nanoTime() / 1_000_000;}lastMs = curMs;}static void parseDicFile(final HashMap<String, KV> dicMap, final String dicFile) throws IOException {String key = null, k = null, v = null;final var sb = new StringBuilder();for (var line : Files.readAllLines(Path.of(dicFile), StandardCharsets.UTF_8)) {if (key == null) {if (!line.isEmpty() && line.charAt(0) == '>') {key = line.substring(1).trim();final int p = key.indexOf(' ');if (p >= 0)key = key.substring(p + 1) + '.' + key.substring(0, p);}} else {int p = line.indexOf("\"\"\"");if (sb.isEmpty()) {if (line.isEmpty())key = k = null;else if (line.charAt(0) != ';') {if (p != 0)v = line;else {p = line.indexOf("\"\"\"", 3);if (p >= 0)v = line.substring(3, p);elsesb.append(line, 3, line.length()).append('\n');}}} else if (p < 0)sb.append(line).append('\n');else {sb.append(line, 0, p);v = sb.toString();sb.setLength(0);}}if (v != null) {if (k == null)k = v;else {if (!k.equals(v))dicMap.put(key, new KV(k, v));key = k = null;}v = null;}}}static void main(final String[] args) throws Exception {if (args.length != 1) {System.out.println("USAGE: java -cp jason.jar;. WeblateTrans <arg.json>");return;}final var arg = JsonReader.local().buf(Files.readAllBytes(Path.of(args[0]))).parse(Arg.class);assert arg != null;if (arg.page < 1)arg.page = 1;final var dicMap = new HashMap<String, KV>();for (final var dicFile : arg.dicFiles)parseDicFile(dicMap, dicFile);System.out.format(" INFO: loaded %d translations\n", dicMap.size());try (final var hc = HttpClient.newHttpClient()) {var url = arg.page > 1? String.format(Units.urlPageFmt, arg.project, arg.component, arg.language, arg.page): String.format(Units.urlFmt, arg.project, arg.component, arg.language);final var unitMod = new UnitMod();unitMod.state = 20;unitMod.target.add("");for (; ; arg.page++) {System.out.format(" INFO: units: %s ... ", url);delay();var req = HttpRequest.newBuilder().uri(URI.create(url)).setHeader("Authorization", arg.auth).GET().build();var res = hc.send(req, HttpResponse.BodyHandlers.ofByteArray());if (res.statusCode() != 200) {System.out.format("ERROR: HTTP Request Failed(%d): %s\n", res.statusCode(), req.uri());break;}final var resBody = res.body();Files.write(Path.of(String.format("units.%s.%s.%s.%d.json", arg.project, arg.component, arg.language, arg.page)),resBody, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);final var units = JsonReader.local().buf(resBody).parse(Units.class);assert units != null;System.out.format("OK (%d units, %d ms)\n", units.count, System.nanoTime() / 1_000_000 - lastMs);for (final var unit : units.results) {if (unit.source.size() != 1) {System.out.format("ERROR: unit: id=%d key=%s state=%d res=sourceSize=%d!=1\n",unit.id, unit.context, unit.state, unit.source.size());} else if (unit.target.size() > 1) {System.out.format("ERROR: unit: id=%d key=%s state=%d res=targetSize=%d>1\n",unit.id, unit.context, unit.state, unit.target.size());} else {final var kv = dicMap.get(unit.context);if (kv == null) {System.out.format(" WARN: unit: id=%d key=%s state=%d res=undefined source='%s'\n",unit.id, unit.context, unit.state, unit.source.getFirst());} else if (!kv.k.trim().equals(unit.source.getFirst().trim())) {System.out.format(" WARN: unit: id=%d key=%s state=%d res=sourceUnmatched source='%s'!='%s'\n",unit.id, unit.context, unit.state, unit.source.getFirst(), kv.k);} else if (kv.v.equals(unit.target.getFirst())) {System.out.format(" INFO: unit: id=%d key=%s state=%d res=matched\n",unit.id, unit.context, unit.state);} else {final String r;if (!unit.target.getFirst().isEmpty()) {if (!arg.overwrite) {System.out.format(" WARN: unit: id=%d key=%s state=%d res=targetUnmatched source='%s' target='%s'!='%s'\n",unit.id, unit.context, unit.state, unit.source.getFirst(), unit.target.getFirst(), kv.v);continue;}r = "overwriteTranslation";} elser = "needTranslation";System.out.format(" INFO: unit: id=%d key=%s state=%d res=%s source='%s' target='%s' ... ",unit.id, unit.context, unit.state, r, kv.k, kv.v);unitMod.target.set(0, kv.v);final var modJson = JsonWriter.local().clear().write(unitMod).toBytes();delay();req = HttpRequest.newBuilder().uri(URI.create(unit.url)).setHeader("Authorization", arg.auth).setHeader("Content-Type", "application/json; charset=utf-8").method("PATCH", HttpRequest.BodyPublishers.ofByteArray(modJson)).build();res = hc.send(req, HttpResponse.BodyHandlers.ofByteArray());if (res.statusCode() != 200) {System.out.format("ERROR: HTTP Request Failed(%d): %s\n%s\n",res.statusCode(), req.uri(), new String(res.body(), StandardCharsets.UTF_8));} elseSystem.out.format("OK (%d ms)\n", System.nanoTime() / 1_000_000 - lastMs);}}}url = units.next;if (url == null || url.isBlank() || url.equalsIgnoreCase("null")) {System.out.println(" INFO: No Next");break;}}System.out.println(" INFO: Done!");}}}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。