mirror of
https://github.com/KivotosRailway/website.git
synced 2026-11-03 11:06:32 +09:00
为新闻页面增加多语言支持
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from deep_translator import GoogleTranslator
|
||||
from opencc import OpenCC
|
||||
|
||||
BASE = Path(r"d:\UserData\Documents\GitHub\KR-website\src\data\news\posts")
|
||||
SRC = BASE / "zh-CN"
|
||||
TARGETS = {"zh-TW": BASE / "zh-TW", "en": BASE / "en", "ja": BASE / "ja"}
|
||||
|
||||
s2t = OpenCC("s2t")
|
||||
|
||||
RE_FRONTMATTER = re.compile(r"^---\r?\n([\s\S]*?)\r?\n---\r?\n?", re.M)
|
||||
|
||||
TERM_REPLACEMENTS = {
|
||||
"zh-TW": {
|
||||
"彗星快线": "彗星快線",
|
||||
"快线": "快線",
|
||||
"A线": "A線",
|
||||
"B线": "B線",
|
||||
"C线": "C線",
|
||||
"D线": "D線",
|
||||
"E线": "E線",
|
||||
"F线": "F線",
|
||||
"快线A线": "CLE-A Line",
|
||||
"快线B线": "CLE-B Line",
|
||||
"快线C线": "CLE-C Line",
|
||||
"快线D线": "CLE-D Line",
|
||||
"快线E线": "CLE-E Line",
|
||||
"快线F线": "CLE-F Line",
|
||||
"彗星快线A线": "CLE-A Line",
|
||||
"彗星快线B线": "CLE-B Line",
|
||||
"彗星快线C线": "CLE-C Line",
|
||||
"彗星快线D线": "CLE-D Line",
|
||||
"彗星快线E线": "CLE-E Line",
|
||||
"彗星快线F线": "CLE-F Line",
|
||||
},
|
||||
"en": {
|
||||
"彗星快线": "Comet Express",
|
||||
"快线": "Express Line",
|
||||
"A线": "A Line",
|
||||
"B线": "B Line",
|
||||
"C线": "C Line",
|
||||
"D线": "D Line",
|
||||
"E线": "E Line",
|
||||
"F线": "F Line",
|
||||
"快线A线": "CLE-A Line",
|
||||
"快线B线": "CLE-B Line",
|
||||
"快线C线": "CLE-C Line",
|
||||
"快线D线": "CLE-D Line",
|
||||
"快线E线": "CLE-E Line",
|
||||
"快线F线": "CLE-F Line",
|
||||
"彗星快线A线": "CLE-A Line",
|
||||
"彗星快线B线": "CLE-B Line",
|
||||
"彗星快线C线": "CLE-C Line",
|
||||
"彗星快线D线": "CLE-D Line",
|
||||
"彗星快线E线": "CLE-E Line",
|
||||
"彗星快线F线": "CLE-F Line",
|
||||
"彗星快线A线": "CLE-A Line",
|
||||
"彗星快线B线": "CLE-B Line",
|
||||
"彗星快线C线": "CLE-C Line",
|
||||
"彗星快线D线": "CLE-D Line",
|
||||
"彗星快线E线": "CLE-E Line",
|
||||
"彗星快线F线": "CLE-F Line",
|
||||
},
|
||||
"ja": {
|
||||
"彗星快线": "彗星快線",
|
||||
"快线": "快線",
|
||||
"A线": "A線",
|
||||
"B线": "B線",
|
||||
"C线": "C線",
|
||||
"D线": "D線",
|
||||
"E线": "E線",
|
||||
"F线": "F線",
|
||||
"快线A线": "CLE-A Line",
|
||||
"快线B线": "CLE-B Line",
|
||||
"快线C线": "CLE-C Line",
|
||||
"快线D线": "CLE-D Line",
|
||||
"快线E线": "CLE-E Line",
|
||||
"快线F线": "CLE-F Line",
|
||||
"彗星快线A线": "CLE-A Line",
|
||||
"彗星快线B线": "CLE-B Line",
|
||||
"彗星快线C线": "CLE-C Line",
|
||||
"彗星快线D线": "CLE-D Line",
|
||||
"彗星快线E线": "CLE-E Line",
|
||||
"彗星快线F线": "CLE-F Line",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def split_frontmatter(text: str):
|
||||
match = RE_FRONTMATTER.match(text)
|
||||
if not match:
|
||||
return "", text
|
||||
return match.group(1), text[match.end() :]
|
||||
|
||||
|
||||
def parse_frontmatter(frontmatter: str):
|
||||
result: dict[str, str | list[str]] = {}
|
||||
list_key: str | None = None
|
||||
for line in frontmatter.splitlines():
|
||||
if not line.strip():
|
||||
continue
|
||||
if re.match(r"^\s*-\s+", line):
|
||||
if list_key and isinstance(result.get(list_key), list):
|
||||
result[list_key].append(line.strip()[2:].strip())
|
||||
continue
|
||||
if ":" not in line:
|
||||
continue
|
||||
key, raw_value = line.split(":", 1)
|
||||
key = key.strip()
|
||||
value = raw_value.strip().strip("'\"")
|
||||
if not key:
|
||||
continue
|
||||
if value == "":
|
||||
result[key] = []
|
||||
list_key = key
|
||||
else:
|
||||
result[key] = value
|
||||
list_key = None
|
||||
return result
|
||||
|
||||
|
||||
def apply_term_replacements(text: str, locale: str) -> str:
|
||||
result = text
|
||||
for old, new in TERM_REPLACEMENTS[locale].items():
|
||||
result = result.replace(old, new)
|
||||
return result
|
||||
|
||||
|
||||
def translate_chunk(chunk: str, locale: str) -> str:
|
||||
if locale == "zh-TW":
|
||||
return apply_term_replacements(s2t.convert(chunk), locale)
|
||||
if not chunk or not chunk.strip():
|
||||
return chunk
|
||||
if chunk.startswith("http") or chunk.startswith("!") or re.fullmatch(r"https?://\S+", chunk):
|
||||
return chunk
|
||||
translated = None
|
||||
for attempt in range(3):
|
||||
try:
|
||||
translator = GoogleTranslator(source="zh-CN", target=locale)
|
||||
translated = translator.translate(chunk)
|
||||
if translated:
|
||||
return apply_term_replacements(translated, locale)
|
||||
except Exception:
|
||||
time.sleep(0.8 * (attempt + 1))
|
||||
continue
|
||||
return apply_term_replacements(chunk, locale)
|
||||
|
||||
|
||||
def translate_text(text: str, locale: str) -> str:
|
||||
if locale == "zh-TW":
|
||||
return apply_term_replacements(s2t.convert(text), locale)
|
||||
|
||||
blocks = re.split(r"(\n\s*\n+)", text)
|
||||
translated_parts: list[str] = []
|
||||
buffer = ""
|
||||
for block in blocks:
|
||||
if not block:
|
||||
continue
|
||||
candidate = buffer + block
|
||||
if len(candidate.encode("utf-8")) > 2000 and buffer:
|
||||
translated_parts.append(translate_chunk(buffer, locale))
|
||||
buffer = block
|
||||
else:
|
||||
buffer = candidate
|
||||
if buffer:
|
||||
translated_parts.append(translate_chunk(buffer, locale))
|
||||
return "".join(translated_parts)
|
||||
|
||||
|
||||
def build_frontmatter(title: str, date: str, tags: list[str], category: list[str], cover: str) -> str:
|
||||
tag_lines = "".join(f" - {tag}\n" for tag in tags)
|
||||
category_lines = "".join(f" - {item}\n" for item in category)
|
||||
return (
|
||||
"---\n"
|
||||
f"title: {title}\n"
|
||||
f"date: {date}\n"
|
||||
"tags:\n"
|
||||
f"{tag_lines}"
|
||||
"category:\n"
|
||||
f"{category_lines}"
|
||||
f"cover: {cover}\n"
|
||||
"---\n"
|
||||
)
|
||||
|
||||
|
||||
def ensure_locale_dir(locale_dir: Path):
|
||||
locale_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
for locale, locale_dir in TARGETS.items():
|
||||
ensure_locale_dir(locale_dir)
|
||||
src_files = sorted(p.name for p in SRC.iterdir() if p.is_file() and p.suffix.lower() == ".md")
|
||||
existing = {p.name for p in locale_dir.iterdir() if p.is_file() and p.suffix.lower() == ".md"}
|
||||
missing = [name for name in src_files if name not in existing]
|
||||
print(f"[{locale}] missing {len(missing)} files")
|
||||
|
||||
for file_name in missing:
|
||||
src_path = SRC / file_name
|
||||
text = src_path.read_text(encoding="utf-8")
|
||||
frontmatter_text, body = split_frontmatter(text)
|
||||
frontmatter = parse_frontmatter(frontmatter_text)
|
||||
|
||||
title = str(frontmatter.get("title") or src_path.stem)
|
||||
date = str(frontmatter.get("date") or "")
|
||||
tags = frontmatter.get("tags")
|
||||
if isinstance(tags, str):
|
||||
tags = [tags]
|
||||
elif not isinstance(tags, list):
|
||||
tags = []
|
||||
categories = frontmatter.get("category")
|
||||
if isinstance(categories, str):
|
||||
categories = [categories]
|
||||
elif not isinstance(categories, list):
|
||||
categories = []
|
||||
cover = str(frontmatter.get("cover") or "")
|
||||
|
||||
translated_title = translate_chunk(title, locale)
|
||||
translated_tags = [translate_chunk(tag, locale) for tag in tags]
|
||||
translated_categories = [translate_chunk(item, locale) for item in categories]
|
||||
translated_body = translate_text(body.strip(), locale)
|
||||
|
||||
output = build_frontmatter(translated_title, date, translated_tags, translated_categories, cover)
|
||||
output += translated_body.strip() + "\n"
|
||||
dest = locale_dir / file_name
|
||||
dest.write_text(output, encoding="utf-8")
|
||||
print(f" wrote {locale}/{file_name}")
|
||||
time.sleep(0.5)
|
||||
Reference in New Issue
Block a user