mirror of
https://example.com
synced 2024-11-22 11:16:38 +09:00
feat: ability to translate changelog
This commit is contained in:
parent
7f056e729f
commit
16b4f78dfb
4 changed files with 129 additions and 2 deletions
|
@ -40,7 +40,6 @@
|
|||
|
||||
- 翻訳機能にて、投稿言語が指定されていない場合にのみ言語の自動検出を用いるように変更
|
||||
- アップデート時に更新内容を確認できる機能を追加
|
||||
- 英語のメッセージしか出ないけど、日本語に翻訳する機能も欲しいですか?
|
||||
- 依存ライブラリを最新版にアップデート
|
||||
- ちゃんと動くか本家に push する前に実験したいという意図もあります
|
||||
- 中国語の猫モードでは 0.1 の確率で投稿の末尾に「喵」を追加するように
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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);
|
||||
});
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue