mirror of
https://github.com/XTLS/Xray-docs-next.git
synced 2026-09-22 14:34:42 +03:00
翻译过时提醒
This commit is contained in:
+1
-1
@@ -4,4 +4,4 @@ node_modules/
|
||||
# VitePress
|
||||
.vitepress/dist/
|
||||
.vitepress/cache/
|
||||
.vitepress/.generated/contributors.json
|
||||
.vitepress/.generated/
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<script setup lang="ts">
|
||||
import statusMap from "../../.generated/i18n-status.json";
|
||||
import { useRoute, useData } from "vitepress";
|
||||
import { computed } from "vue";
|
||||
|
||||
const route = useRoute();
|
||||
const { lang } = useData();
|
||||
|
||||
const info = computed(() => {
|
||||
const map = statusMap as Record<string, any>;
|
||||
const raw = route.path;
|
||||
const keys = [
|
||||
raw,
|
||||
raw.replace(/\.html$/, ""),
|
||||
raw.endsWith("/") ? raw.slice(0, -1) : raw + "/",
|
||||
raw.replace(/\.html$/, "").endsWith("/")
|
||||
? raw.replace(/\.html$/, "").slice(0, -1)
|
||||
: raw.replace(/\.html$/, "") + "/",
|
||||
];
|
||||
for (const k of keys) if (map[k]) return map[k];
|
||||
return null;
|
||||
});
|
||||
|
||||
const isZh = computed(() => (lang.value || "").toLowerCase().startsWith("zh"));
|
||||
|
||||
const text = computed(() => {
|
||||
const l = (lang.value || "en").toLowerCase();
|
||||
const dict: any = {
|
||||
en: {
|
||||
title: "Translation notice",
|
||||
body: "This translation may be outdated. Please refer to the Chinese original.",
|
||||
go: "View Chinese original",
|
||||
},
|
||||
ru: {
|
||||
title: "Уведомление о переводе",
|
||||
body: "Этот перевод может быть устаревшим. Пожалуйста, обратитесь к оригинальной китайской версии.",
|
||||
go: "Посмотреть оригинальную китайскую версию",
|
||||
},
|
||||
};
|
||||
return dict[l] || dict[l.split("-")[0]] || dict.en;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="!isZh && info && info.stale"
|
||||
class="custom-block warning vp-translation-warning"
|
||||
>
|
||||
<p class="custom-block-title">
|
||||
{{ text.title }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span v-if="info.translated">
|
||||
{{ text.body }}
|
||||
</span>
|
||||
<span v-else>
|
||||
This page is not translated yet. Please refer to the Chinese original.
|
||||
</span>
|
||||
<br />
|
||||
<a :href="info.zhRoute" class="link">
|
||||
{{ text.go }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.vp-translation-warning {
|
||||
margin: 16px 0;
|
||||
}
|
||||
</style>
|
||||
@@ -9,6 +9,7 @@ import { onMounted, watch, nextTick } from "vue";
|
||||
import { useRoute } from "vitepress";
|
||||
|
||||
import { h } from "vue";
|
||||
import TranslationNotice from "./components/TranslationNotice.vue";
|
||||
import PageContributors from "./components/PageContributors.vue";
|
||||
|
||||
export default {
|
||||
@@ -31,6 +32,7 @@ export default {
|
||||
|
||||
Layout() {
|
||||
return h(DefaultTheme.Layout, null, {
|
||||
"doc-before": () => h(TranslationNotice),
|
||||
"doc-after": () => h(PageContributors),
|
||||
});
|
||||
},
|
||||
|
||||
+3
-3
@@ -6,8 +6,8 @@
|
||||
"vitepress-plugin-mermaid": "^2.0.17"
|
||||
},
|
||||
"scripts": {
|
||||
"docs:dev": "node scripts/gen-contributors.mjs && vitepress dev",
|
||||
"docs:build": "node scripts/gen-contributors.mjs && vitepress build",
|
||||
"docs:preview": "node scripts/gen-contributors.mjs && vitepress preview"
|
||||
"docs:dev": "node scripts/gen-i18n-stale.mjs && node scripts/gen-contributors.mjs && vitepress dev",
|
||||
"docs:build": "node scripts/gen-i18n-stale.mjs && node scripts/gen-contributors.mjs && vitepress build",
|
||||
"docs:preview": "node scripts/gen-i18n-stale.mjs && node scripts/gen-contributors.mjs && vitepress preview"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { execSync } from "node:child_process";
|
||||
|
||||
const ROOT = process.cwd();
|
||||
const DOCS_DIR = path.resolve(ROOT, "docs");
|
||||
const OUT_DIR = path.resolve(ROOT, ".vitepress/.generated");
|
||||
const OUT_FILE = path.join(OUT_DIR, "i18n-status.json");
|
||||
|
||||
const LOCALES = ["en", "ru"];
|
||||
|
||||
function walk(dir) {
|
||||
const out = [];
|
||||
for (const name of fs.readdirSync(dir)) {
|
||||
const p = path.join(dir, name);
|
||||
const st = fs.statSync(p);
|
||||
if (st.isDirectory()) out.push(...walk(p));
|
||||
else if (p.endsWith(".md")) out.push(p);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function gitLastCommitISO(fileAbsPath) {
|
||||
const rel = path.relative(ROOT, fileAbsPath).replaceAll("\\", "/");
|
||||
try {
|
||||
const iso = execSync(`git log -1 --format=%cI -- "${rel}"`, {
|
||||
encoding: "utf8",
|
||||
}).trim();
|
||||
return iso || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function toRouteFromDocsRel(relToDocs) {
|
||||
let route = "/" + relToDocs.replace(/\.md$/, "");
|
||||
route = route.replace(/\/index$/, "/");
|
||||
return route;
|
||||
}
|
||||
|
||||
const zhFiles = walk(DOCS_DIR).filter((p) => {
|
||||
const rel = path.relative(DOCS_DIR, p).replaceAll("\\", "/");
|
||||
return !LOCALES.some((l) => rel.startsWith(l + "/"));
|
||||
});
|
||||
|
||||
const zhByRel = new Map(); // relToDocs -> abs
|
||||
for (const abs of zhFiles) {
|
||||
const relToDocs = path.relative(DOCS_DIR, abs).replaceAll("\\", "/");
|
||||
zhByRel.set(relToDocs, abs);
|
||||
}
|
||||
|
||||
const data = {};
|
||||
for (const locale of LOCALES) {
|
||||
const localeDir = path.join(DOCS_DIR, locale);
|
||||
if (!fs.existsSync(localeDir)) continue;
|
||||
|
||||
const tFiles = walk(localeDir);
|
||||
for (const tAbs of tFiles) {
|
||||
const relToDocs = path.relative(DOCS_DIR, tAbs).replaceAll("\\", "/"); // en/config/log.md
|
||||
const relNoLocale = relToDocs.replace(new RegExp(`^${locale}/`), ""); // config/log.md
|
||||
const zhAbs = zhByRel.get(relNoLocale);
|
||||
|
||||
const tRoute = toRouteFromDocsRel(relToDocs); // /en/config/log
|
||||
const zhRoute = toRouteFromDocsRel(relNoLocale); // /config/log
|
||||
|
||||
const tISO = gitLastCommitISO(tAbs);
|
||||
const zhISO = zhAbs ? gitLastCommitISO(zhAbs) : null;
|
||||
|
||||
const stale =
|
||||
Boolean(zhISO && tISO) &&
|
||||
new Date(tISO).getTime() < new Date(zhISO).getTime();
|
||||
|
||||
data[tRoute] = {
|
||||
locale,
|
||||
zhRoute,
|
||||
translated: true,
|
||||
stale,
|
||||
tLastUpdated: tISO,
|
||||
zhLastUpdated: zhISO,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
for (const [zhRel, zhAbs] of zhByRel.entries()) {
|
||||
const zhRoute = toRouteFromDocsRel(zhRel);
|
||||
const zhISO = gitLastCommitISO(zhAbs);
|
||||
|
||||
for (const locale of LOCALES) {
|
||||
const tRoute = "/" + locale + (zhRoute === "/" ? "/" : zhRoute);
|
||||
if (!data[tRoute]) {
|
||||
data[tRoute] = {
|
||||
locale,
|
||||
zhRoute,
|
||||
translated: false,
|
||||
stale: true,
|
||||
tLastUpdated: null,
|
||||
zhLastUpdated: zhISO,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fs.mkdirSync(OUT_DIR, { recursive: true });
|
||||
fs.writeFileSync(OUT_FILE, JSON.stringify(data, null, 2), "utf8");
|
||||
console.log(`Generated i18n status -> ${OUT_FILE}`);
|
||||
Reference in New Issue
Block a user