Skip to content

Commit b29d0b3

Browse files
i18n support
1 parent 884d549 commit b29d0b3

281 files changed

Lines changed: 57645 additions & 1 deletion

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# PenguinLab Translation API Configuration
2+
# Copy this file to .env and fill in your key.
3+
4+
# Anthropic (default engine)
5+
ANTHROPIC_AUTH_TOKEN=
6+
ANTHROPIC_BASE_URL=https://api.anthropic.com
7+
ANTHROPIC_DEFAULT_SONNET_MODEL=claude-sonnet-4-20250514
8+
9+
# OpenAI (alternative engine)
10+
# OPENAI_API_KEY=
11+
# OPENAI_BASE_URL=https://api.openai.com/v1
12+
13+
# DeepL (alternative engine)
14+
# DEEPL_API_KEY=
15+
16+
# Generic fallback (used by any engine if its specific var is not set)
17+
# TRANSLATE_API_KEY=

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ helpers/
77
out/
88
compile_commands.json
99
.cache
10+
.env
1011

1112
# TODO is private currently
1213
todo/

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
# PenguinLab
44

5+
<!-- COVERAGE_START -->
6+
![English Coverage](https://img.shields.io/badge/en_coverage-100%25-green.svg) 262/262 docs translated
7+
<!-- COVERAGE_END -->
8+
59
**Linux 内核学习站 — 从 QEMU 实践到内核原理、驱动开发与嵌入式全栈**
610

711
[![Kernel](https://img.shields.io/badge/Linux%20Kernel-6.19.y-blue)](https://kernel.org)

scripts/coverage.py

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Translation coverage report for PenguinLab.
4+
5+
Scans document/ and site/i18n/en/docusaurus-plugin-content-docs/current/
6+
to calculate translation coverage per category and overall.
7+
8+
Usage:
9+
python3 scripts/coverage.py # Print report to stdout
10+
python3 scripts/coverage.py --json # Output JSON for shields.io badge
11+
python3 scripts/coverage.py --update # Update README.md badge section
12+
"""
13+
14+
import json
15+
import os
16+
import re
17+
import sys
18+
from pathlib import Path
19+
from collections import defaultdict
20+
21+
PROJECT_ROOT = Path(__file__).parent.parent
22+
DOCS_DIR = PROJECT_ROOT / 'document'
23+
I18N_DIR = (PROJECT_ROOT / 'site' / 'i18n' / 'en' /
24+
'docusaurus-plugin-content-docs' / 'current')
25+
26+
# Categories to track (subdirectory under document/)
27+
CATEGORIES = [
28+
'tutorials/foundations',
29+
'tutorials/kernel',
30+
'tutorials/drivers',
31+
'tutorials/embedded',
32+
'tutorials/debugging',
33+
'tutorials/virtualization',
34+
'notes',
35+
]
36+
37+
# Top-level docs (not in a category)
38+
TOP_LEVEL_FILES = ['intro.md', 'booklist.md', 'qemu-reference.md']
39+
40+
41+
def find_md_files(directory: Path) -> list[Path]:
42+
"""Find all .md files excluding images dirs and _category_.json."""
43+
files = []
44+
for f in directory.rglob('*.md'):
45+
if any(part == 'images' for part in f.parts):
46+
continue
47+
if f.name == '_category_.json':
48+
continue
49+
files.append(f)
50+
return sorted(files)
51+
52+
53+
def get_rel_path(file_path: Path, base: Path) -> str:
54+
"""Get relative path from base directory."""
55+
try:
56+
return str(file_path.relative_to(base))
57+
except ValueError:
58+
return str(file_path)
59+
60+
61+
def compute_coverage():
62+
"""Compute translation coverage stats."""
63+
results = {}
64+
total_source = 0
65+
total_translated = 0
66+
67+
# Per-category stats
68+
for cat in CATEGORIES:
69+
source_dir = DOCS_DIR / cat
70+
if not source_dir.exists():
71+
results[cat] = {'source': 0, 'translated': 0, 'percentage': 0}
72+
continue
73+
74+
source_files = find_md_files(source_dir)
75+
translated_count = 0
76+
77+
for sf in source_files:
78+
rel = get_rel_path(sf, DOCS_DIR)
79+
translated_path = I18N_DIR / rel
80+
if translated_path.exists():
81+
translated_count += 1
82+
83+
count = len(source_files)
84+
pct = round(translated_count / count * 100) if count > 0 else 0
85+
results[cat] = {
86+
'source': count,
87+
'translated': translated_count,
88+
'percentage': pct,
89+
}
90+
total_source += count
91+
total_translated += translated_count
92+
93+
# Top-level files
94+
tl_translated = 0
95+
tl_count = 0
96+
for fname in TOP_LEVEL_FILES:
97+
source = DOCS_DIR / fname
98+
if source.exists():
99+
tl_count += 1
100+
if (I18N_DIR / fname).exists():
101+
tl_translated += 1
102+
results['_root'] = {
103+
'source': tl_count,
104+
'translated': tl_translated,
105+
'percentage': round(tl_translated / tl_count * 100) if tl_count > 0 else 0,
106+
}
107+
total_source += tl_count
108+
total_translated += tl_translated
109+
110+
overall_pct = round(total_translated / total_source * 100) if total_source > 0 else 0
111+
112+
return {
113+
'overall': {
114+
'source': total_source,
115+
'translated': total_translated,
116+
'percentage': overall_pct,
117+
},
118+
'categories': results,
119+
}
120+
121+
122+
def print_report(data):
123+
"""Print a human-readable coverage report."""
124+
print(f"Translation Coverage Report")
125+
print(f"{'=' * 50}")
126+
print(f"Overall: {data['overall']['translated']}/{data['overall']['source']} "
127+
f"({data['overall']['percentage']}%)")
128+
print(f"{'-' * 50}")
129+
130+
labels = {
131+
'tutorials/foundations': 'Foundations',
132+
'tutorials/kernel': 'Kernel Subsystems',
133+
'tutorials/drivers': 'Driver Development',
134+
'tutorials/embedded': 'Embedded Full Stack',
135+
'tutorials/debugging': 'Debugging & Perf',
136+
'tutorials/virtualization': 'Virtualization',
137+
'notes': 'Notes',
138+
'_root': 'Root Pages',
139+
}
140+
141+
for cat, stats in data['categories'].items():
142+
label = labels.get(cat, cat)
143+
pct = stats['percentage']
144+
bar = '█' * (pct // 5) + '░' * (20 - pct // 5)
145+
print(f" {label:<25} {bar} {stats['translated']:>3}/{stats['source']:<3} ({pct}%)")
146+
147+
print(f"{'=' * 50}")
148+
149+
150+
def output_json(data):
151+
"""Output JSON for shields.io endpoint badge."""
152+
badge = {
153+
'schemaVersion': 1,
154+
'label': 'en coverage',
155+
'message': f"{data['overall']['percentage']}%",
156+
'color': 'green' if data['overall']['percentage'] >= 80 else
157+
'yellow' if data['overall']['percentage'] >= 50 else 'red',
158+
}
159+
print(json.dumps(badge, indent=2))
160+
161+
162+
def update_readme(data):
163+
"""Update README.md with coverage stats."""
164+
readme_path = PROJECT_ROOT / 'README.md'
165+
if not readme_path.exists():
166+
print("README.md not found", file=sys.stderr)
167+
return
168+
169+
content = readme_path.read_text(encoding='utf-8')
170+
pct = data['overall']['percentage']
171+
translated = data['overall']['translated']
172+
total = data['overall']['source']
173+
174+
badge_url = f"https://img.shields.io/badge/en_coverage-{pct}%25-{'green' if pct >= 80 else 'yellow' if pct >= 50 else 'red'}.svg"
175+
176+
# Look for existing coverage section and replace, or insert after title
177+
coverage_line = f"![English Coverage]({badge_url}) {translated}/{total} docs translated"
178+
179+
marker_start = '<!-- COVERAGE_START -->'
180+
marker_end = '<!-- COVERAGE_END -->'
181+
182+
if marker_start in content:
183+
# Replace existing section
184+
pattern = f"{marker_start}.*?{marker_end}"
185+
replacement = f"{marker_start}\n{coverage_line}\n{marker_end}"
186+
content = re.sub(pattern, replacement, content, flags=re.DOTALL)
187+
else:
188+
# Insert after first heading
189+
lines = content.split('\n')
190+
inserted = False
191+
new_lines = []
192+
for line in lines:
193+
new_lines.append(line)
194+
if not inserted and line.startswith('# '):
195+
new_lines.append('')
196+
new_lines.append(f'{marker_start}')
197+
new_lines.append(coverage_line)
198+
new_lines.append(f'{marker_end}')
199+
inserted = True
200+
content = '\n'.join(new_lines)
201+
202+
readme_path.write_text(content, encoding='utf-8')
203+
print(f"Updated README.md: {translated}/{total} ({pct}%)")
204+
205+
206+
def main():
207+
if len(sys.argv) > 1:
208+
arg = sys.argv[1]
209+
if arg == '--json':
210+
data = compute_coverage()
211+
output_json(data)
212+
elif arg == '--update':
213+
data = compute_coverage()
214+
update_readme(data)
215+
else:
216+
print(f"Unknown argument: {arg}", file=sys.stderr)
217+
sys.exit(1)
218+
else:
219+
data = compute_coverage()
220+
print_report(data)
221+
222+
223+
if __name__ == '__main__':
224+
main()

scripts/site-dev.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env bash
2+
# PenguinLab Docusaurus dev server (hot-reload)
3+
set -euo pipefail
4+
5+
cd "$(git rev-parse --show-toplevel)/site"
6+
exec pnpm start "$@"

scripts/site-serve.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env bash
2+
# PenguinLab production build + serve
3+
set -euo pipefail
4+
5+
cd "$(git rev-parse --show-toplevel)/site"
6+
pnpm build
7+
exec pnpm serve "$@"

scripts/terminology.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# PenguinLab Terminology Glossary
2+
3+
> Linux kernel / embedded systems Chinese-to-English terminology reference.
4+
> Used by `scripts/translate.py` for consistent technical translations.
5+
6+
| English | Chinese | Notes |
7+
|---------|---------|-------|
8+
| Kernel Module | 内核模块 | |
9+
| Character Device | 字符设备 | |
10+
| Block Device | 块设备 | |
11+
| Network Device | 网络设备 | |
12+
| Device Tree | 设备树 | |
13+
| Device Tree Source (DTS) | 设备树源文件 | |
14+
| Platform Driver | 平台驱动 | |
15+
| Interrupt | 中断 | |
16+
| Interrupt Handler | 中断处理函数 | |
17+
| Interrupt Context | 中断上下文 | |
18+
| Top Half | 上半部 | Interrupt handling |
19+
| Bottom Half | 下半部 | Interrupt handling |
20+
| Softirq | 软中断 | |
21+
| Tasklet | tasklet | |
22+
| Workqueue | 工作队列 | |
23+
| Threaded Interrupt | 线程化中断 | |
24+
| Scheduler | 调度器 | |
25+
| Completely Fair Scheduler (CFS) | 完全公平调度器 | |
26+
| Scheduling Entity | 调度实体 | |
27+
| Virtual Runtime (vruntime) | 虚拟运行时间 | |
28+
| Run Queue | 运行队列 | |
29+
| Context Switch | 上下文切换 | |
30+
| Process | 进程 | |
31+
| Thread | 线程 | |
32+
| Task Structure (task_struct) | 任务结构体 | |
33+
| Process Descriptor | 进程描述符 | |
34+
| Memory Management | 内存管理 | |
35+
| Page Frame | 页帧 | |
36+
| Page Table | 页表 | |
37+
| Buddy System | 伙伴系统 | |
38+
| Slab Allocator | Slab 分配器 | |
39+
| Slub Allocator | Slub 分配器 | |
40+
| Slob Allocator | Slob 分配器 | |
41+
| Page Cache | 页缓存 | |
42+
| Page Reclaim | 页面回收 | |
43+
| Swap | 交换空间 | |
44+
| Memory Mapping | 内存映射 | |
45+
| Virtual Memory Area (VMA) | 虚拟内存区域 | |
46+
| kmalloc | kmalloc | Keep as-is |
47+
| vmalloc | vmalloc | Keep as-is |
48+
| Filesystem | 文件系统 | |
49+
| Virtual File System (VFS) | 虚拟文件系统 | |
50+
| Superblock | 超级块 | |
51+
| Inode | 索引节点 | |
52+
| Dentry | 目录项 | |
53+
| File Operations (file_operations) | 文件操作 | |
54+
| Mount | 挂载 | |
55+
| Root Filesystem | 根文件系统 | |
56+
| Network Stack | 网络协议栈 | |
57+
| Socket | 套接字 | |
58+
| Network Device Driver | 网络设备驱动 | |
59+
| Netfilter | Netfilter | Keep as-is |
60+
| sk_buff | sk_buff | Keep as-is |
61+
| Spinlock | 自旋锁 | |
62+
| Mutex | 互斥锁 | |
63+
| Semaphore | 信号量 | |
64+
| Read-Copy Update (RCU) | 读-拷贝更新 | |
65+
| Atomic Operation | 原子操作 | |
66+
| Memory Barrier | 内存屏障 | |
67+
| Completion | 完成量 | |
68+
| ftrace | ftrace | Keep as-is |
69+
| perf | perf | Keep as-is |
70+
| eBPF | eBPF | Keep as-is |
71+
| kprobes | kprobes | Keep as-is |
72+
| printk | printk | Keep as-is |
73+
| dmesg | dmesg | Keep as-is |
74+
| Cross-compilation | 交叉编译 | |
75+
| Toolchain | 工具链 | |
76+
| Bootloader | 引导加载程序 | |
77+
| U-Boot | U-Boot | Keep as-is |
78+
| Buildroot | Buildroot | Keep as-is |
79+
| Yocto | Yocto | Keep as-is |
80+
| Board Support Package (BSP) | 板级支持包 | |
81+
| Rootfs | 根文件系统 | |
82+
| BusyBox | BusyBox | Keep as-is |
83+
| QEMU | QEMU | Keep as-is |
84+
| Kernel Configuration | 内核配置 | |
85+
| defconfig | defconfig | Keep as-is |
86+
| Kconfig | Kconfig | Keep as-is |
87+
| Makefile | Makefile | Keep as-is |
88+
| Kbuild | Kbuild | Keep as-is |
89+
| initcall | initcall | Keep as-is |
90+
| Sysfs | sysfs | Keep as-is |
91+
| Procfs | procfs | Keep as-is |
92+
| Debugfs | debugfs | Keep as-is |
93+
| IOCTL | ioctl | Keep as-is |
94+
| Memory-Mapped I/O (MMIO) | 内存映射 I/O | |
95+
| Port-Mapped I/O (PMIO) | 端口映射 I/O | |
96+
| Direct Memory Access (DMA) | 直接内存访问 | |
97+
| General-Purpose I/O (GPIO) | 通用输入输出 | |
98+
| I2C | I2C | Keep as-is |
99+
| SPI | SPI | Keep as-is |
100+
| UART | UART | Keep as-is |
101+
| USB | USB | Keep as-is |
102+
| PCI/PCIe | PCI/PCIe | Keep as-is |
103+
| Generic Interrupt Controller (GIC) | 通用中断控制器 | |
104+
| ARM | ARM | Keep as-is |
105+
| RISC-V | RISC-V | Keep as-is |
106+
| x86_64 | x86_64 | Keep as-is |
107+
| Virtualization | 虚拟化 | |
108+
| Hypervisor | Hypervisor | Keep as-is |
109+
| KVM | KVM | Keep as-is |
110+
| Container | 容器 | |
111+
| Namespace | 命名空间 | |
112+
| cgroup | cgroup | Keep as-is |
113+
| Merge Window | 合并窗口 | |
114+
| Patch | 补丁 | |
115+
| Commit | 提交 | |
116+
| Mainline Kernel | 主线内核 | |
117+
| Stable Kernel | 稳定版内核 | |
118+
| Long-Term Support (LTS) | 长期支持 | |
119+
| Linux Kernel Mailing List (LKML) | Linux 内核邮件列表 | |

0 commit comments

Comments
 (0)