i18n_config.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 国际化配置管理器
  5. 处理中英文文档的配置、目录结构和内容生成
  6. """
  7. import os
  8. import re
  9. import yaml
  10. from pathlib import Path
  11. from typing import Dict, List, Optional, Tuple, Any
  12. from dataclasses import dataclass
  13. @dataclass
  14. class CategoryConfig:
  15. """分类配置"""
  16. name: str
  17. name_en: str
  18. description: str
  19. patterns: List[str]
  20. @dataclass
  21. class ProjectConfig:
  22. """项目配置"""
  23. name: str
  24. title: str
  25. description: str
  26. description_en: str
  27. version: str
  28. author: str
  29. copyright: str
  30. website: str
  31. @dataclass
  32. class I18nConfig:
  33. """国际化配置"""
  34. project: ProjectConfig
  35. categories: Dict[str, CategoryConfig]
  36. repository: Dict[str, Any]
  37. generation: Dict[str, Any]
  38. sphinx: Dict[str, Any]
  39. class I18nConfigManager:
  40. """国际化配置管理器"""
  41. def __init__(self, config_path: Path):
  42. self.config_path = config_path
  43. self.config = self._load_config()
  44. def _load_config(self) -> I18nConfig:
  45. """加载配置文件"""
  46. with open(self.config_path, 'r', encoding='utf-8') as f:
  47. data = yaml.safe_load(f) or {}
  48. # 解析项目配置
  49. project_data = data.get('project', {})
  50. project = ProjectConfig(
  51. name=project_data.get('name', ''),
  52. title=project_data.get('title', ''),
  53. description=project_data.get('description', ''),
  54. description_en=project_data.get('description_en', ''),
  55. version=project_data.get('version', ''),
  56. author=project_data.get('author', ''),
  57. copyright=project_data.get('copyright', ''),
  58. website=project_data.get('website', '')
  59. )
  60. # 解析分类配置
  61. categories = {}
  62. for key, cat_data in data.get('categories', {}).items():
  63. categories[key] = CategoryConfig(
  64. name=cat_data.get('name', ''),
  65. name_en=cat_data.get('name_en', ''),
  66. description=cat_data.get('description', ''),
  67. patterns=cat_data.get('patterns', [])
  68. )
  69. return I18nConfig(
  70. project=project,
  71. categories=categories,
  72. repository=data.get('repository', {}),
  73. generation=data.get('generation', {}),
  74. sphinx=data.get('sphinx', {})
  75. )
  76. def get_category_name(self, category_key: str, language: str = 'zh') -> str:
  77. """获取分类名称"""
  78. if category_key not in self.config.categories:
  79. return category_key
  80. category = self.config.categories[category_key]
  81. if language == 'en':
  82. return category.name_en or category.name
  83. return category.name
  84. def get_project_config(self, language: str = 'zh') -> Dict[str, str]:
  85. """获取项目配置"""
  86. project = self.config.project
  87. if language == 'en':
  88. return {
  89. 'name': project.name,
  90. 'title': f"{project.name} Documentation",
  91. 'description': project.description_en or project.description,
  92. 'version': project.version,
  93. 'author': project.author,
  94. 'copyright': project.copyright,
  95. 'website': project.website,
  96. 'language': 'en'
  97. }
  98. else:
  99. return {
  100. 'name': project.name,
  101. 'title': project.title,
  102. 'description': project.description,
  103. 'version': project.version,
  104. 'author': project.author,
  105. 'copyright': project.copyright,
  106. 'website': project.website,
  107. 'language': 'zh_CN'
  108. }
  109. def get_sphinx_config(self, language: str = 'zh') -> Dict[str, Any]:
  110. """获取Sphinx配置"""
  111. base_config = self.config.sphinx.copy()
  112. if language == 'en':
  113. base_config.update({
  114. 'language': 'en',
  115. 'html_lang': 'en',
  116. 'html_title': f"{self.config.project.name} Documentation",
  117. 'html_short_title': self.config.project.name,
  118. 'html_search_language': 'en',
  119. 'html_search_options': {
  120. 'dict': 'en'
  121. }
  122. })
  123. else:
  124. base_config.update({
  125. 'language': 'zh_CN',
  126. 'html_lang': 'zh-CN',
  127. 'html_title': self.config.project.title,
  128. 'html_short_title': self.config.project.title,
  129. 'html_search_language': 'zh',
  130. 'html_search_options': {
  131. 'dict': 'zh'
  132. }
  133. })
  134. return base_config
  135. def get_toc_structure(self, language: str = 'zh') -> List[Dict[str, Any]]:
  136. """获取目录结构"""
  137. structure = []
  138. for category_key in self.config.generation.get('output_structure', []):
  139. if category_key in self.config.categories:
  140. category = self.config.categories[category_key]
  141. structure.append({
  142. 'key': category_key,
  143. 'name': self.get_category_name(category_key, language),
  144. 'description': category.description,
  145. 'patterns': category.patterns
  146. })
  147. return structure
  148. def get_file_suffix(self, language: str = 'zh') -> str:
  149. """获取文件后缀"""
  150. return '_zh' if language == 'zh' else ''
  151. def get_index_filename(self, language: str = 'zh') -> str:
  152. """获取索引文件名"""
  153. return f"index{self.get_file_suffix(language)}.rst"
  154. def get_html_filename(self, base_name: str, language: str = 'zh') -> str:
  155. """获取HTML文件名"""
  156. if language == 'zh':
  157. return f"{base_name}_zh.html"
  158. return f"{base_name}.html"
  159. def get_markdown_filename(self, base_name: str, language: str = 'zh') -> str:
  160. """获取Markdown文件名"""
  161. if language == 'zh':
  162. return f"{base_name}_zh.md"
  163. return f"{base_name}.md"
  164. def extract_headings_from_markdown(self, file_path: Path) -> List[Dict[str, Any]]:
  165. """从Markdown文件中提取标题结构"""
  166. headings = []
  167. if not file_path.exists():
  168. return headings
  169. with open(file_path, 'r', encoding='utf-8') as f:
  170. content = f.read()
  171. # 匹配标题的正则表达式
  172. heading_pattern = r'^(#{1,6})\s+(.+)$'
  173. for line_num, line in enumerate(content.split('\n'), 1):
  174. match = re.match(heading_pattern, line.strip())
  175. if match:
  176. level = len(match.group(1))
  177. title = match.group(2).strip()
  178. # 生成锚点ID
  179. anchor_id = self._generate_anchor_id(title)
  180. headings.append({
  181. 'level': level,
  182. 'title': title,
  183. 'anchor_id': anchor_id,
  184. 'line_num': line_num
  185. })
  186. return headings
  187. def _generate_anchor_id(self, title: str) -> str:
  188. """生成锚点ID"""
  189. # 移除特殊字符,转换为小写,用连字符连接
  190. anchor_id = re.sub(r'[^\w\s-]', '', title.lower())
  191. anchor_id = re.sub(r'[-\s]+', '-', anchor_id)
  192. return anchor_id.strip('-')
  193. def generate_toc_content(self, category_key: str, language: str = 'zh') -> str:
  194. """生成目录内容"""
  195. category = self.config.categories.get(category_key)
  196. if not category:
  197. return ""
  198. category_name = self.get_category_name(category_key, language)
  199. # 查找该分类下的所有项目
  200. projects = []
  201. docs_dir = self.config_path.parent
  202. for pattern in category.patterns:
  203. # 在docs目录下查找匹配的项目
  204. for item in docs_dir.glob(f"**/{pattern}"):
  205. if item.is_dir():
  206. # 查找README文件
  207. readme_file = None
  208. if language == 'zh':
  209. readme_file = item / "README_zh.md"
  210. if not readme_file.exists():
  211. readme_file = item / "README.md"
  212. else:
  213. readme_file = item / "README.md"
  214. if not readme_file.exists():
  215. readme_file = item / "README_zh.md"
  216. if readme_file and readme_file.exists():
  217. # 提取项目名称
  218. project_name = self._extract_project_name(readme_file, language)
  219. projects.append({
  220. 'name': project_name,
  221. 'path': readme_file.relative_to(docs_dir),
  222. 'headings': self.extract_headings_from_markdown(readme_file)
  223. })
  224. # 生成目录内容
  225. toc_lines = [f"{category_name}", "=" * len(category_name), ""]
  226. for project in projects:
  227. toc_lines.append(f".. toctree::")
  228. toc_lines.append(f" :maxdepth: 3")
  229. toc_lines.append(f" :caption: {project['name']}")
  230. toc_lines.append(f"")
  231. toc_lines.append(f" {project['path'].with_suffix('')}")
  232. toc_lines.append(f"")
  233. return "\n".join(toc_lines)
  234. def _extract_project_name(self, readme_file: Path, language: str = 'zh') -> str:
  235. """从README文件中提取项目名称"""
  236. try:
  237. with open(readme_file, 'r', encoding='utf-8') as f:
  238. first_line = f.readline().strip()
  239. # 移除Markdown标题标记
  240. if first_line.startswith('#'):
  241. return first_line.lstrip('#').strip()
  242. return first_line
  243. except Exception:
  244. return readme_file.parent.name
  245. def get_language_config(self, language: str = 'zh') -> Dict[str, Any]:
  246. """获取语言特定配置"""
  247. return {
  248. 'language': language,
  249. 'file_suffix': self.get_file_suffix(language),
  250. 'index_filename': self.get_index_filename(language),
  251. 'project_config': self.get_project_config(language),
  252. 'sphinx_config': self.get_sphinx_config(language),
  253. 'toc_structure': self.get_toc_structure(language)
  254. }
  255. def main():
  256. """测试函数"""
  257. config_path = Path(__file__).parent.parent / 'config.yaml'
  258. manager = I18nConfigManager(config_path)
  259. print("中文配置:")
  260. zh_config = manager.get_language_config('zh')
  261. print(f" 语言: {zh_config['language']}")
  262. print(f" 文件后缀: {zh_config['file_suffix']}")
  263. print(f" 索引文件: {zh_config['index_filename']}")
  264. print(f" 项目名称: {zh_config['project_config']['title']}")
  265. print("\n英文配置:")
  266. en_config = manager.get_language_config('en')
  267. print(f" 语言: {en_config['language']}")
  268. print(f" 文件后缀: {en_config['file_suffix']}")
  269. print(f" 索引文件: {en_config['index_filename']}")
  270. print(f" 项目名称: {en_config['project_config']['title']}")
  271. print("\n分类结构:")
  272. for category in en_config['toc_structure']:
  273. print(f" {category['key']}: {category['name']}")
  274. if __name__ == "__main__":
  275. main()