1
0
Fork 1
mirror of https://example.com synced 2024-11-22 11:16:38 +09:00

feat: ability to translate changelog

This commit is contained in:
naskya 2024-01-02 14:07:14 +09:00
parent 7f056e729f
commit 16b4f78dfb
Signed by: naskya
GPG key ID: 712D413B3A9FED5C
4 changed files with 129 additions and 2 deletions

View file

@ -40,7 +40,6 @@
- 翻訳機能にて、投稿言語が指定されていない場合にのみ言語の自動検出を用いるように変更
- アップデート時に更新内容を確認できる機能を追加
- 英語のメッセージしか出ないけど、日本語に翻訳する機能も欲しいですか?
- 依存ライブラリを最新版にアップデート
- ちゃんと動くか本家に push する前に実験したいという意図もあります
- 中国語の猫モードでは 0.1 の確率で投稿の末尾に「喵」を追加するように

View file

@ -296,6 +296,7 @@ import * as ep___customSplashIcons from "./endpoints/custom-splash-icons.js";
import * as ep___latestVersion from "./endpoints/latest-version.js";
import * as ep___patrons from "./endpoints/patrons.js";
import * as ep___release from "./endpoints/release.js";
import * as ep___release_translate from "./endpoints/release/translate.js";
import * as ep___promo_read from "./endpoints/promo/read.js";
import * as ep___requestResetPassword from "./endpoints/request-reset-password.js";
import * as ep___resetDb from "./endpoints/reset-db.js";
@ -655,6 +656,7 @@ const eps = [
["latest-version", ep___latestVersion],
["patrons", ep___patrons],
["release", ep___release],
["release/translate", ep___release_translate],
["promo/read", ep___promo_read],
["request-reset-password", ep___requestResetPassword],
["reset-db", ep___resetDb],

View file

@ -0,0 +1,92 @@
import * as fs from "node:fs/promises";
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
import define from "@/server/api/define.js";
import translate from "@/misc/translate.js";
import type { Language } from "@/misc/langmap.js";
import RE2 from "re2";
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
export const meta = {
tags: ["meta"],
description: "Translate changelog",
requireCredential: true,
requireCredentialPrivateMode: true,
res: {
type: "array",
optional: false,
nullable: false,
items: {
type: "string",
optional: false,
nullable: false,
},
},
} as const;
export const paramDef = {
type: "object",
properties: {
targetLang: { type: "string" },
},
required: ["targetLang"],
} as const;
async function translateCommitMsg(msg: string, targetLang: Language) {
const regex = new RE2(/^(.*) (\(by .*\))$/);
const matches = regex.match(msg);
if (matches == null) return msg;
if (targetLang.startsWith("ja")) {
const prefixes = {
chore: "雑務",
dev: "開発",
docker: "Docker",
docs: "ドキュメント",
feat: "新機能",
fix: "修正",
hotfix: "緊急修正",
locale: "翻訳",
perf: "パフォーマンス",
refactor: "再設計",
style: "体裁",
};
for (const [prefix, translatedPrefix] of Object.entries(prefixes)) {
if (msg.startsWith(`${prefix}:`))
return `${translatedPrefix}: ${
(await translate(matches[1].split(":")[1].trim(), "en", "ja")).text
} ${matches[2]}`;
if (msg.startsWith(`${prefix} (minor):`))
return `${translatedPrefix}(小規模): ${
(await translate(matches[1].split(":")[1].trim(), "en", "ja")).text
} ${matches[2]}`;
}
}
return `${(await translate(matches[1], "en", targetLang)).text} ${
matches[2]
}`;
}
export default define(meta, paramDef, async (ps) => {
const releaseNotes = (
await fs.readFile(
`${_dirname}/../../../../../../../neko/volume/CHANGELOG`,
"utf-8",
)
)
.trim()
.split("\n");
const promises = [];
for (const msg of releaseNotes)
promises.push(translateCommitMsg(msg, ps.targetLang as Language));
return await Promise.all(promises);
});

View file

@ -3,22 +3,34 @@
:initial-width="800"
:can-resize="true"
:front="true"
:buttons-right="buttonsRight"
@closed="emit('closed')"
>
<template #header>
{{ i18n.ts.releaseNotes }}
</template>
<div class="asnohbod">
<div v-if="!translating && translations.length === 0" class="asnohbod">
<ul>
<li v-for="(item, i) in notes" :key="i">{{ item }}</li>
</ul>
</div>
<MkLoading v-else-if="translating" mini />
<div v-else class="asnohbod">
<ul>
<li v-for="(item, i) in translations" :key="i">
{{ item }}
</li>
</ul>
</div>
</XWindow>
</template>
<script lang="ts" setup>
import { computed, ref } from "vue";
import XWindow from "@/components/MkWindow.vue";
import { i18n } from "@/i18n";
import { api } from "@/os";
import icon from "@/scripts/icon";
defineProps<{
notes: string[];
@ -27,6 +39,28 @@ defineProps<{
const emit = defineEmits<{
(ev: "closed"): void;
}>();
const translating = ref(false);
const translations = ref([] as string[]);
const lang = localStorage.getItem("lang");
const translateLang = localStorage.getItem("translateLang");
async function translate() {
translating.value = true;
translations.value = await api("release/translate", {
targetLang: translateLang ?? lang ?? navigator.language,
});
translating.value = false;
}
const buttonsRight = computed(() => [
{
icon: `${icon("ph-translate")}`,
title: i18n.ts.translate,
onClick: translate,
},
]);
</script>
<style lang="scss" scoped>