@jet-w/astro-blog 0.1.6 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/dist/chunk-6D3XRDNY.js +145 -0
  2. package/dist/chunk-A2E2VSAQ.js +246 -0
  3. package/dist/{chunk-GYLSY3OJ.js → chunk-TJTPX2WP.js} +1 -1
  4. package/dist/config/index.d.ts +3 -47
  5. package/dist/config/index.js +18 -2
  6. package/dist/i18n-PgMCFBw0.d.ts +222 -0
  7. package/dist/index.d.ts +204 -7
  8. package/dist/index.js +255 -3
  9. package/dist/integration.d.ts +9 -1
  10. package/dist/integration.js +2 -1
  11. package/dist/{sidebar-DNdiCKBw.d.ts → sidebar-Da-W_4Lr.d.ts} +1 -1
  12. package/dist/utils/sidebar.d.ts +1 -1
  13. package/package.json +1 -1
  14. package/src/components/blog/FloatingToc.vue +11 -3
  15. package/src/components/blog/Hero.astro +17 -2
  16. package/src/components/blog/NavigationTabs.vue +46 -15
  17. package/src/components/blog/PostCard.astro +28 -10
  18. package/src/components/blog/RelatedPosts.astro +23 -7
  19. package/src/components/blog/TableOfContents.astro +10 -4
  20. package/src/components/blog/TagCloud.astro +4 -3
  21. package/src/components/home/FeaturedPostsSection.astro +22 -6
  22. package/src/components/home/QuickNavSection.astro +33 -4
  23. package/src/components/home/RecentPostsSection.astro +22 -6
  24. package/src/components/home/StatsSection.astro +24 -6
  25. package/src/components/layout/Footer.astro +36 -20
  26. package/src/components/layout/Header.astro +75 -17
  27. package/src/components/layout/Sidebar.astro +40 -25
  28. package/src/components/ui/LanguageSwitcher.vue +183 -0
  29. package/src/components/ui/SearchBox.vue +13 -5
  30. package/src/components/ui/SearchInterface.vue +49 -25
  31. package/src/layouts/BaseLayout.astro +77 -52
  32. package/src/layouts/PageLayout.astro +22 -27
  33. package/src/layouts/SlidesLayout.astro +14 -2
  34. package/src/pages/archives/[year]/[month].astro +36 -17
  35. package/src/pages/archives/index.astro +36 -20
  36. package/src/pages/categories/[category].astro +33 -16
  37. package/src/pages/categories/index.astro +37 -14
  38. package/src/pages/posts/[...slug].astro +125 -18
  39. package/src/pages/posts/index.astro +59 -37
  40. package/src/pages/posts/page/[page].astro +65 -27
  41. package/src/pages/rss.xml.ts +18 -6
  42. package/src/pages/search.astro +50 -14
  43. package/src/pages/slides/index.astro +25 -6
  44. package/src/pages/tags/[tag].astro +32 -15
  45. package/src/pages/tags/index.astro +39 -16
  46. package/src/plugins/remark-containers.mjs +351 -322
  47. package/src/plugins/remark-protect-code.mjs +69 -0
  48. package/src/styles/global.css +35 -1
  49. package/templates/default/.claude/ralph-loop.local.md +48 -0
  50. package/templates/default/astro.config.mjs +33 -4
  51. package/templates/default/content/posts/blog_docs_en/01.get-started/01-intro.md +81 -0
  52. package/templates/default/content/posts/blog_docs_en/01.get-started/02-install.md +137 -0
  53. package/templates/default/content/posts/blog_docs_en/01.get-started/03-create-post.md +176 -0
  54. package/templates/default/content/posts/blog_docs_en/01.get-started/04-structure.md +173 -0
  55. package/templates/default/content/posts/blog_docs_en/01.get-started/05-deploy.md +208 -0
  56. package/templates/default/content/posts/blog_docs_en/01.get-started/README.md +52 -0
  57. package/templates/default/content/posts/blog_docs_en/02.guide/02-containers.md +245 -0
  58. package/templates/default/content/posts/blog_docs_en/02.guide/03-code-blocks.md +207 -0
  59. package/templates/default/content/posts/blog_docs_en/02.guide/03-mermaid.md +194 -0
  60. package/templates/default/content/posts/blog_docs_en/02.guide/04-icons.md +229 -0
  61. package/templates/default/content/posts/blog_docs_en/02.guide/06-latex.md +233 -0
  62. package/templates/default/content/posts/blog_docs_en/02.guide/07-video.md +184 -0
  63. package/templates/default/content/posts/blog_docs_en/02.guide/08-slides.md +359 -0
  64. package/templates/default/content/posts/blog_docs_en/02.guide/README.md +213 -0
  65. package/templates/default/content/posts/blog_docs_en/03.config/01-site.md +208 -0
  66. package/templates/default/content/posts/blog_docs_en/03.config/02-sidebar.md +240 -0
  67. package/templates/default/content/posts/blog_docs_en/03.config/03-i18n.md +349 -0
  68. package/templates/default/content/posts/blog_docs_en/03.config/README.md +85 -0
  69. package/templates/default/content/posts/blog_docs_en/README.md +79 -0
  70. package/templates/default/content/posts/blog_docs_zh/01.get-started/01-intro.md +81 -0
  71. package/templates/default/content/posts/blog_docs_zh/01.get-started/02-install.md +137 -0
  72. package/templates/default/content/posts/blog_docs_zh/01.get-started/03-create-post.md +176 -0
  73. package/templates/default/content/posts/blog_docs_zh/01.get-started/04-structure.md +173 -0
  74. package/templates/default/content/posts/blog_docs_zh/01.get-started/05-deploy.md +208 -0
  75. package/templates/default/content/posts/blog_docs_zh/01.get-started/README.md +52 -0
  76. package/templates/default/content/posts/blog_docs_zh/02.guide/02-containers.md +245 -0
  77. package/templates/default/content/posts/blog_docs_zh/02.guide/03-code-blocks.md +206 -0
  78. package/templates/default/content/posts/blog_docs_zh/02.guide/03-mermaid.md +194 -0
  79. package/templates/default/content/posts/blog_docs_zh/02.guide/04-icons.md +229 -0
  80. package/templates/default/content/posts/blog_docs_zh/02.guide/06-latex.md +233 -0
  81. package/templates/default/content/posts/blog_docs_zh/02.guide/07-video.md +184 -0
  82. package/templates/default/content/posts/blog_docs_zh/02.guide/08-slides.md +359 -0
  83. package/templates/default/content/posts/blog_docs_zh/02.guide/README.md +213 -0
  84. package/templates/default/content/posts/blog_docs_zh/03.config/01-site.md +208 -0
  85. package/templates/default/content/posts/blog_docs_zh/03.config/02-sidebar.md +240 -0
  86. package/templates/default/content/posts/blog_docs_zh/03.config/03-i18n.md +348 -0
  87. package/templates/default/content/posts/blog_docs_zh/03.config/README.md +85 -0
  88. package/templates/default/content/posts/blog_docs_zh/README.md +78 -0
  89. package/templates/default/package-lock.json +9667 -0
  90. package/templates/default/package.json +1 -1
  91. package/templates/default/src/config/footer.ts +14 -11
  92. package/templates/default/src/config/locales/en/footer.ts +17 -0
  93. package/templates/default/src/config/locales/en/index.ts +20 -0
  94. package/templates/default/src/config/locales/en/menu.ts +14 -0
  95. package/templates/default/src/config/locales/en/sidebar.ts +34 -0
  96. package/templates/default/src/config/locales/en/site.ts +7 -0
  97. package/templates/default/src/config/locales/en/ui.ts +29 -0
  98. package/templates/default/src/config/locales/index.ts +7 -0
  99. package/templates/default/src/config/locales/zh-CN/footer.ts +17 -0
  100. package/templates/default/src/config/locales/zh-CN/index.ts +20 -0
  101. package/templates/default/src/config/locales/zh-CN/menu.ts +14 -0
  102. package/templates/default/src/config/locales/zh-CN/sidebar.ts +34 -0
  103. package/templates/default/src/config/locales/zh-CN/site.ts +7 -0
  104. package/templates/default/src/config/locales/zh-CN/ui.ts +29 -0
  105. package/templates/default/src/config/sidebar.ts +10 -12
  106. package/templates/default/src/config/site.ts +2 -2
  107. package/templates/default/src/content.config.ts +15 -3
  108. package/templates/default/src/env.d.ts +7 -0
  109. package/dist/chunk-MQXPSOYB.js +0 -124
  110. package/templates/default/content/posts/blog_docs/01-quick-start.md +0 -162
  111. package/templates/default/content/posts/blog_docs/02-frontmatter.md +0 -277
  112. package/templates/default/content/posts/blog_docs/03-markdown-basic.md +0 -350
  113. package/templates/default/content/posts/blog_docs/04-containers.md +0 -331
  114. package/templates/default/content/posts/blog_docs/05-code-blocks.md +0 -388
  115. package/templates/default/content/posts/blog_docs/06-mermaid.md +0 -431
  116. package/templates/default/content/posts/blog_docs/07-video.md +0 -243
  117. package/templates/default/content/posts/blog_docs/08-latex.md +0 -382
  118. package/templates/default/content/posts/blog_docs/09-icons.md +0 -326
  119. package/templates/default/content/posts/blog_docs/10-sidebar.md +0 -445
  120. package/templates/default/content/posts/blog_docs/11-config.md +0 -334
  121. package/templates/default/content/posts/blog_docs/12-slides.mdx +0 -552
  122. package/templates/default/content/posts/blog_docs/README.md +0 -151
@@ -3,6 +3,9 @@ import PageLayout from '@jet-w/astro-blog/layouts/PageLayout.astro';
3
3
  import PostCard from '@jet-w/astro-blog/components/blog/PostCard.astro';
4
4
  import Pagination from '@jet-w/astro-blog/components/ui/Pagination.astro';
5
5
  import { getCollection } from 'astro:content';
6
+ import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
7
+ import { defaultI18nConfig } from '../../../config/i18n';
8
+ import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, filterPostsByLocale } from '../../../utils/i18n';
6
9
 
7
10
  export async function getStaticPaths() {
8
11
  const allPostsCollection = await getCollection('posts');
@@ -20,9 +23,20 @@ export async function getStaticPaths() {
20
23
  const { page } = Astro.params;
21
24
  const currentPage = parseInt(page as string, 10);
22
25
 
26
+ // Get i18n config
27
+ const i18nConfig = virtualI18nConfig || defaultI18nConfig;
28
+ const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
29
+ const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
30
+ const ui = localeConfig.ui;
31
+ const localePrefix = getLocalePrefix(currentLocale, i18nConfig);
32
+
23
33
  // 从新的内容目录获取文章数据
24
34
  const allPostsCollection = await getCollection('posts');
25
- const publishedPosts = allPostsCollection
35
+
36
+ // Filter posts by current locale
37
+ const localeFilteredPosts = filterPostsByLocale(allPostsCollection, currentLocale, i18nConfig);
38
+
39
+ const publishedPosts = localeFilteredPosts
26
40
  .filter(post => !post.data.draft)
27
41
  .sort((a, b) => (b.data.pubDate?.getTime() ?? 0) - (a.data.pubDate?.getTime() ?? 0));
28
42
 
@@ -52,27 +66,41 @@ const allCategories = [...new Set(allPosts.flatMap(post => post.categories || []
52
66
  ---
53
67
 
54
68
  <PageLayout
55
- title={`文章列表 - ${currentPage} 页`}
56
- description="浏览所有技术文章和分享内容"
69
+ title={`${ui.postList} - ${ui.page} ${currentPage}`}
70
+ description={localeConfig.site.description}
57
71
  showSidebar={true}
72
+ i18nConfig={i18nConfig}
58
73
  >
74
+ <!-- 面包屑导航 -->
75
+ <nav class="flex items-center space-x-2 text-sm text-slate-600 dark:text-slate-400 mb-8">
76
+ <a href={`${localePrefix}/`} class="hover:text-primary-500 transition-colors">{ui.home}</a>
77
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
78
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
79
+ </svg>
80
+ <a href={`${localePrefix}/posts`} class="hover:text-primary-500 transition-colors">{ui.postList}</a>
81
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
82
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
83
+ </svg>
84
+ <span class="text-slate-900 dark:text-slate-100">{ui.page} {currentPage}</span>
85
+ </nav>
86
+
59
87
  <!-- 页面头部 -->
60
88
  <div class="mb-12">
61
89
  <div class="text-center">
62
90
  <h1 class="text-4xl font-bold text-slate-900 dark:text-slate-100 mb-4">
63
- 文章列表
91
+ {ui.postList}
64
92
  </h1>
65
93
  <p class="text-xl text-slate-600 dark:text-slate-400 mb-8">
66
- 分享技术思考和学习心得
94
+ {localeConfig.site.description}
67
95
  </p>
68
96
 
69
97
  <!-- 统计信息 -->
70
98
  <div class="inline-flex items-center space-x-6 text-sm text-slate-500 dark:text-slate-400 bg-slate-50 dark:bg-slate-800 px-6 py-3 rounded-lg">
71
- <span>共 {totalPosts} 篇文章</span>
99
+ <span>{totalPosts} {ui.posts}</span>
72
100
  <span>•</span>
73
- <span>{allTags.length} 个标签</span>
101
+ <span>{allTags.length} {ui.tags}</span>
74
102
  <span>•</span>
75
- <span>{allCategories.length} 个分类</span>
103
+ <span>{allCategories.length} {ui.categories}</span>
76
104
  </div>
77
105
  </div>
78
106
  </div>
@@ -83,34 +111,44 @@ const allCategories = [...new Set(allPosts.flatMap(post => post.categories || []
83
111
  <!-- 按标签筛选 -->
84
112
  <div>
85
113
  <h3 class="text-sm font-semibold text-slate-900 dark:text-slate-100 mb-3">
86
- 按标签筛选
114
+ {ui.filterByTag}
87
115
  </h3>
88
116
  <div class="flex flex-wrap gap-2">
89
- <button class="px-3 py-1 text-xs rounded-full bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 hover:bg-primary-200 dark:hover:bg-primary-900/50 transition-colors">
90
- 全部
91
- </button>
92
- {allTags.map((tag) => (
93
- <button class="px-3 py-1 text-xs rounded-full bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-600 transition-colors">
117
+ <a href={`${localePrefix}/tags`} class="px-3 py-1 text-xs rounded-full bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 hover:bg-primary-200 dark:hover:bg-primary-900/50 transition-colors">
118
+ {ui.allTags}
119
+ </a>
120
+ {allTags.slice(0, 10).map((tag) => (
121
+ <a href={`${localePrefix}/tags/${tag.toLowerCase().replace(/\s+/g, '-')}`} class="px-3 py-1 text-xs rounded-full bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-600 transition-colors">
94
122
  {tag}
95
- </button>
123
+ </a>
96
124
  ))}
125
+ {allTags.length > 10 && (
126
+ <a href={`${localePrefix}/tags`} class="px-3 py-1 text-xs rounded-full bg-slate-100 dark:bg-slate-700 text-slate-500 dark:text-slate-400 hover:bg-slate-200 dark:hover:bg-slate-600 transition-colors">
127
+ +{allTags.length - 10}
128
+ </a>
129
+ )}
97
130
  </div>
98
131
  </div>
99
132
 
100
133
  <!-- 按分类筛选 -->
101
134
  <div>
102
135
  <h3 class="text-sm font-semibold text-slate-900 dark:text-slate-100 mb-3">
103
- 按分类筛选
136
+ {ui.filterByCategory}
104
137
  </h3>
105
138
  <div class="flex flex-wrap gap-2">
106
- <button class="px-3 py-1 text-xs rounded-full bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 hover:bg-primary-200 dark:hover:bg-primary-900/50 transition-colors">
107
- 全部
108
- </button>
109
- {allCategories.map((category) => (
110
- <button class="px-3 py-1 text-xs rounded-full bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-600 transition-colors">
139
+ <a href={`${localePrefix}/categories`} class="px-3 py-1 text-xs rounded-full bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 hover:bg-primary-200 dark:hover:bg-primary-900/50 transition-colors">
140
+ {ui.allCategories}
141
+ </a>
142
+ {allCategories.slice(0, 8).map((category) => (
143
+ <a href={`${localePrefix}/categories/${category.toLowerCase().replace(/\s+/g, '-')}`} class="px-3 py-1 text-xs rounded-full bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-600 transition-colors">
111
144
  {category}
112
- </button>
145
+ </a>
113
146
  ))}
147
+ {allCategories.length > 8 && (
148
+ <a href={`${localePrefix}/categories`} class="px-3 py-1 text-xs rounded-full bg-slate-100 dark:bg-slate-700 text-slate-500 dark:text-slate-400 hover:bg-slate-200 dark:hover:bg-slate-600 transition-colors">
149
+ +{allCategories.length - 8}
150
+ </a>
151
+ )}
114
152
  </div>
115
153
  </div>
116
154
  </div>
@@ -118,19 +156,19 @@ const allCategories = [...new Set(allPosts.flatMap(post => post.categories || []
118
156
 
119
157
  <!-- 文章列表 -->
120
158
  {posts.length > 0 ? (
121
- <div class="space-y-8 mb-12">
159
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-12">
122
160
  {posts.map((post) => (
123
- <PostCard post={post} layout="horizontal" />
161
+ <PostCard post={post} layout="horizontal" localePrefix={localePrefix} locale={localeConfig.locale.dateLocale} ui={ui} />
124
162
  ))}
125
163
  </div>
126
164
  ) : (
127
165
  <div class="text-center py-16">
128
166
  <div class="text-6xl mb-4">📝</div>
129
167
  <h3 class="text-xl font-semibold text-slate-900 dark:text-slate-100 mb-2">
130
- 暂无文章
168
+ {ui.noPostsFound}
131
169
  </h3>
132
170
  <p class="text-slate-600 dark:text-slate-400">
133
- 目前还没有发布任何文章,请稍后再来查看。
171
+ {ui.noPostsFound}
134
172
  </p>
135
173
  </div>
136
174
  )}
@@ -140,7 +178,7 @@ const allCategories = [...new Set(allPosts.flatMap(post => post.categories || []
140
178
  <Pagination
141
179
  currentPage={currentPage}
142
180
  totalPages={totalPages}
143
- baseUrl="/posts"
181
+ baseUrl={`${localePrefix}/posts`}
144
182
  />
145
183
  )}
146
184
  </PageLayout>
@@ -1,11 +1,23 @@
1
1
  import rss from '@astrojs/rss';
2
2
  import { getCollection } from 'astro:content';
3
3
  import { siteConfig } from '@jet-w/astro-blog/config';
4
+ import { defaultI18nConfig } from '../config/i18n';
5
+ import { getLocaleFromPath, getLocaleConfig, getLocalePrefix } from '../utils/i18n';
4
6
 
5
- export async function GET(context: { site: URL }) {
7
+ export async function GET(context: { site: URL; request: Request }) {
6
8
  const posts = await getCollection('posts', ({ data }) => !data.draft);
7
9
 
8
- // 按日期排序,最新的在前
10
+ // Get locale from URL path
11
+ const url = new URL(context.request.url);
12
+ const i18nConfig = defaultI18nConfig; // In real usage, this would be passed from integration
13
+ const currentLocale = getLocaleFromPath(url.pathname, i18nConfig);
14
+ const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
15
+ const localePrefix = getLocalePrefix(currentLocale, i18nConfig);
16
+
17
+ // Use locale-specific site config
18
+ const localeSiteConfig = localeConfig.site;
19
+
20
+ // Sort by date, newest first
9
21
  const sortedPosts = posts.sort((a, b) => {
10
22
  const dateA = new Date(a.data.date || 0);
11
23
  const dateB = new Date(b.data.date || 0);
@@ -13,16 +25,16 @@ export async function GET(context: { site: URL }) {
13
25
  });
14
26
 
15
27
  return rss({
16
- title: siteConfig.title,
17
- description: siteConfig.description,
28
+ title: localeSiteConfig.title || siteConfig.title,
29
+ description: localeSiteConfig.description || siteConfig.description,
18
30
  site: context.site,
19
31
  items: sortedPosts.map((post) => ({
20
32
  title: post.data.title,
21
33
  pubDate: post.data.date ? new Date(post.data.date) : new Date(),
22
34
  description: post.data.description || '',
23
- link: `/posts/${post.id.toLowerCase()}/`,
35
+ link: `${localePrefix}/posts/${post.id.toLowerCase()}/`,
24
36
  categories: [...(post.data.categories || []), ...(post.data.tags || [])]
25
37
  })),
26
- customData: `<language>zh-CN</language>`
38
+ customData: `<language>${localeConfig.locale.htmlLang}</language>`
27
39
  });
28
40
  }
@@ -1,47 +1,83 @@
1
1
  ---
2
2
  import PageLayout from '@jet-w/astro-blog/layouts/PageLayout.astro';
3
3
  import SearchInterface from '@jet-w/astro-blog/components/ui/SearchInterface.vue';
4
+ import type { I18nConfig } from '../config/i18n';
5
+ import { defaultI18nConfig } from '../config/i18n';
6
+ import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
7
+ import { getLocaleFromPath, getLocaleConfig } from '../utils/i18n';
8
+
9
+ export interface Props {
10
+ i18nConfig?: I18nConfig;
11
+ }
12
+
13
+ const { i18nConfig = virtualI18nConfig || defaultI18nConfig } = Astro.props;
14
+
15
+ // Get current locale from URL
16
+ const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
17
+ const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
18
+ const ui = localeConfig.ui;
19
+
20
+ // Get date locale for formatting
21
+ const locale = i18nConfig.locales.find(l => l.code === currentLocale);
22
+ const dateLocale = locale?.dateLocale || 'zh-CN';
4
23
  ---
5
24
 
6
25
  <PageLayout
7
- title="搜索"
8
- description="搜索博客文章和内容"
26
+ title={ui.search}
27
+ description={ui.searchInAllArticles}
9
28
  showSidebar={true}
10
29
  >
11
30
  <div class="max-w-4xl mx-auto">
12
31
  <!-- 页面头部 -->
13
32
  <div class="text-center mb-12">
14
33
  <h1 class="text-4xl font-bold text-slate-900 dark:text-slate-100 mb-4">
15
- 搜索文章
34
+ {ui.searchArticles}
16
35
  </h1>
17
36
  <p class="text-xl text-slate-600 dark:text-slate-400">
18
- 在所有文章中查找您感兴趣的内容
37
+ {ui.searchInAllArticles}
19
38
  </p>
20
39
  </div>
21
40
 
22
41
  <!-- 搜索界面 -->
23
- <SearchInterface client:load />
42
+ <SearchInterface
43
+ client:load
44
+ placeholder={ui.searchPlaceholder}
45
+ noResults={ui.noResults}
46
+ tags={ui.tags}
47
+ categories={ui.categories}
48
+ sortLabel={ui.sortBy}
49
+ relevance={ui.sortByDate}
50
+ publishTime={ui.publishedOn}
51
+ titleLabel={ui.sortByTitle}
52
+ all={ui.allPosts}
53
+ searching={ui.searching}
54
+ noResultsTitle={ui.noResults}
55
+ clearSearch={ui.clearFilter}
56
+ previousPage={ui.previousPage}
57
+ nextPage={ui.nextPage}
58
+ dateLocale={dateLocale}
59
+ />
24
60
 
25
61
  <!-- 搜索提示 -->
26
62
  <div class="mt-12 p-6 bg-slate-50 dark:bg-slate-800 rounded-xl">
27
63
  <h2 class="text-lg font-semibold text-slate-900 dark:text-slate-100 mb-4">
28
- 搜索技巧
64
+ {ui.searchTips}
29
65
  </h2>
30
66
  <div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm text-slate-600 dark:text-slate-400">
31
67
  <div>
32
- <h3 class="font-medium text-slate-900 dark:text-slate-100 mb-2">基础搜索</h3>
68
+ <h3 class="font-medium text-slate-900 dark:text-slate-100 mb-2">{ui.basicSearch}</h3>
33
69
  <ul class="space-y-1">
34
- <li>• 输入关键词搜索标题和内容</li>
35
- <li>• 支持中英文混合搜索</li>
36
- <li>• 自动忽略大小写</li>
70
+ <li>• {ui.searchTipKeyword}</li>
71
+ <li>• {ui.searchTipMixedLang}</li>
72
+ <li>• {ui.searchTipCaseInsensitive}</li>
37
73
  </ul>
38
74
  </div>
39
75
  <div>
40
- <h3 class="font-medium text-slate-900 dark:text-slate-100 mb-2">高级功能</h3>
76
+ <h3 class="font-medium text-slate-900 dark:text-slate-100 mb-2">{ui.advancedFeatures}</h3>
41
77
  <ul class="space-y-1">
42
- <li>• 实时搜索建议</li>
43
- <li>• 按标签和分类筛选</li>
44
- <li>• 支持模糊匹配</li>
78
+ <li>• {ui.searchTipRealtime}</li>
79
+ <li>• {ui.searchTipFilter}</li>
80
+ <li>• {ui.searchTipFuzzy}</li>
45
81
  </ul>
46
82
  </div>
47
83
  </div>
@@ -1,6 +1,16 @@
1
1
  ---
2
2
  import { getCollection } from 'astro:content';
3
3
  import PageLayout from '@jet-w/astro-blog/layouts/PageLayout.astro';
4
+ import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
5
+ import { defaultI18nConfig } from '../../config/i18n';
6
+ import { getLocaleFromPath, getLocaleConfig, getLocalePrefix } from '../../utils/i18n';
7
+
8
+ // Get i18n config
9
+ const i18nConfig = virtualI18nConfig || defaultI18nConfig;
10
+ const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
11
+ const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
12
+ const ui = localeConfig.ui;
13
+ const localePrefix = getLocalePrefix(currentLocale, i18nConfig);
4
14
 
5
15
  // 获取所有非草稿的 slides
6
16
  const slides = (await getCollection('slides', ({ data }) => !data.draft))
@@ -20,28 +30,37 @@ function formatDate(date: Date | undefined): string {
20
30
  }
21
31
  ---
22
32
 
23
- <PageLayout title="幻灯片" description="演示文稿列表" showSidebar={false}>
33
+ <PageLayout title={ui.slides} description={ui.slidesList} showSidebar={false} i18nConfig={i18nConfig}>
34
+ <!-- 面包屑导航 -->
35
+ <nav class="flex items-center space-x-2 text-sm text-slate-600 dark:text-slate-400 mb-8">
36
+ <a href={`${localePrefix}/`} class="hover:text-primary-500 transition-colors">{ui.home}</a>
37
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
38
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
39
+ </svg>
40
+ <span class="text-slate-900 dark:text-slate-100">{ui.slides}</span>
41
+ </nav>
42
+
24
43
  <div class="slides-list-page">
25
44
  <header class="mb-8">
26
45
  <h1 class="text-3xl font-bold text-slate-900 dark:text-slate-100 mb-2">
27
- <span class="mr-2">📽️</span>幻灯片
46
+ {ui.slides}
28
47
  </h1>
29
48
  <p class="text-slate-600 dark:text-slate-400">
30
- 基于 Reveal.js 的演示文稿,支持 Markdown 语法
49
+ {ui.slidesList}
31
50
  </p>
32
51
  </header>
33
52
 
34
53
  {slides.length === 0 ? (
35
54
  <div class="text-center py-16 text-slate-500 dark:text-slate-400">
36
55
  <div class="text-6xl mb-4">📭</div>
37
- <p>暂无幻灯片</p>
38
- <p class="text-sm mt-2">在 <code class="px-2 py-1 bg-slate-100 dark:bg-slate-800 rounded">content/slides/</code> 目录下创建 Markdown 文件即可开始</p>
56
+ <p>{ui.noPostsFound}</p>
57
+ <p class="text-sm mt-2">content/slides/</p>
39
58
  </div>
40
59
  ) : (
41
60
  <div class="grid gap-6 md:grid-cols-2">
42
61
  {slides.map((slide) => (
43
62
  <a
44
- href={`/slides/${slide.id}`}
63
+ href={`${localePrefix}/slides/${slide.id}`}
45
64
  class="slide-card group block p-6 bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 hover:border-primary-500 dark:hover:border-primary-500 transition-all hover:shadow-lg hover:-translate-y-1"
46
65
  >
47
66
  <div class="flex items-start justify-between mb-3">
@@ -3,6 +3,9 @@ import { getCollection } from 'astro:content';
3
3
  import PageLayout from '@jet-w/astro-blog/layouts/PageLayout.astro';
4
4
  import PostCard from '@jet-w/astro-blog/components/blog/PostCard.astro';
5
5
  import Pagination from '@jet-w/astro-blog/components/ui/Pagination.astro';
6
+ import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
7
+ import { defaultI18nConfig } from '../../config/i18n';
8
+ import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, filterPostsByLocale } from '../../utils/i18n';
6
9
 
7
10
  export async function getStaticPaths() {
8
11
  const allPosts = await getCollection('posts', ({ data }) => !data.draft);
@@ -30,8 +33,18 @@ export async function getStaticPaths() {
30
33
 
31
34
  const { tagSlug, tagName, tagCount } = Astro.props;
32
35
 
36
+ // Get i18n config
37
+ const i18nConfig = virtualI18nConfig || defaultI18nConfig;
38
+ const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
39
+ const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
40
+ const ui = localeConfig.ui;
41
+ const localePrefix = getLocalePrefix(currentLocale, i18nConfig);
42
+
33
43
  // 获取所有文章并筛选包含该标签的文章
34
- const allPosts = await getCollection('posts', ({ data }) => !data.draft);
44
+ const allPostsCollection = await getCollection('posts', ({ data }) => !data.draft);
45
+
46
+ // Filter posts by current locale
47
+ const allPosts = filterPostsByLocale(allPostsCollection, currentLocale, i18nConfig);
35
48
 
36
49
  // 筛选包含该标签的文章
37
50
  const filteredPosts = allPosts
@@ -75,17 +88,18 @@ const relatedTags = Array.from(relatedTagMap.entries())
75
88
  ---
76
89
 
77
90
  <PageLayout
78
- title={`标签: ${tagName}`}
79
- description={`浏览所有关于 ${tagName} 的文章`}
91
+ title={`${ui.taggedWith}: ${tagName}`}
92
+ description={`${ui.taggedWith} ${tagName}`}
80
93
  showSidebar={true}
94
+ i18nConfig={i18nConfig}
81
95
  >
82
96
  <!-- 面包屑导航 -->
83
97
  <nav class="flex items-center space-x-2 text-sm text-slate-600 dark:text-slate-400 mb-8">
84
- <a href="/" class="hover:text-primary-500 transition-colors">首页</a>
98
+ <a href={`${localePrefix}/`} class="hover:text-primary-500 transition-colors">{ui.home}</a>
85
99
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
86
100
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
87
101
  </svg>
88
- <a href="/tags" class="hover:text-primary-500 transition-colors">标签</a>
102
+ <a href={`${localePrefix}/tags`} class="hover:text-primary-500 transition-colors">{ui.allTags}</a>
89
103
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
90
104
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
91
105
  </svg>
@@ -105,18 +119,18 @@ const relatedTags = Array.from(relatedTagMap.entries())
105
119
  </h1>
106
120
 
107
121
  <p class="text-xl text-slate-600 dark:text-slate-400 mb-8">
108
- 找到 {totalPosts} 篇关于 {tagName} 的文章
122
+ {totalPosts} {ui.posts}
109
123
  </p>
110
124
 
111
125
  <!-- 标签统计 -->
112
126
  <div class="inline-flex items-center space-x-6 text-sm text-slate-500 dark:text-slate-400 bg-slate-50 dark:bg-slate-800 px-6 py-3 rounded-lg">
113
127
  <span>#{tagName}</span>
114
128
  <span>•</span>
115
- <span>{totalPosts} 篇文章</span>
129
+ <span>{totalPosts} {ui.posts}</span>
116
130
  {totalPages > 1 && (
117
131
  <>
118
132
  <span>•</span>
119
- <span>共 {totalPages} 页</span>
133
+ <span>{totalPages} {ui.page}</span>
120
134
  </>
121
135
  )}
122
136
  </div>
@@ -138,6 +152,9 @@ const relatedTags = Array.from(relatedTagMap.entries())
138
152
  image: post.data.image
139
153
  }}
140
154
  layout="horizontal"
155
+ localePrefix={localePrefix}
156
+ locale={localeConfig.locale.dateLocale}
157
+ ui={ui}
141
158
  />
142
159
  ))}
143
160
  </div>
@@ -145,13 +162,13 @@ const relatedTags = Array.from(relatedTagMap.entries())
145
162
  <div class="text-center py-16">
146
163
  <div class="text-6xl mb-4">📝</div>
147
164
  <h3 class="text-xl font-semibold text-slate-900 dark:text-slate-100 mb-2">
148
- 暂无相关文章
165
+ {ui.noPostsFound}
149
166
  </h3>
150
167
  <p class="text-slate-600 dark:text-slate-400 mb-6">
151
- 目前还没有关于 {tagName} 的文章,请查看其他标签。
168
+ {ui.noPostsFound}
152
169
  </p>
153
- <a href="/tags" class="btn-secondary">
154
- 浏览所有标签
170
+ <a href={`${localePrefix}/tags`} class="btn-secondary">
171
+ {ui.allTags}
155
172
  </a>
156
173
  </div>
157
174
  )}
@@ -161,7 +178,7 @@ const relatedTags = Array.from(relatedTagMap.entries())
161
178
  <Pagination
162
179
  currentPage={currentPage}
163
180
  totalPages={totalPages}
164
- baseUrl={`/tags/${tagSlug}`}
181
+ baseUrl={`${localePrefix}/tags/${tagSlug}`}
165
182
  />
166
183
  )}
167
184
 
@@ -169,12 +186,12 @@ const relatedTags = Array.from(relatedTagMap.entries())
169
186
  {relatedTags.length > 0 && (
170
187
  <section class="mt-16 pt-8 border-t border-slate-200 dark:border-slate-700">
171
188
  <h2 class="text-2xl font-bold text-slate-900 dark:text-slate-100 mb-6">
172
- 相关标签
189
+ {ui.popularTags}
173
190
  </h2>
174
191
  <div class="flex flex-wrap gap-3">
175
192
  {relatedTags.map((relatedTag) => (
176
193
  <a
177
- href={`/tags/${relatedTag.slug}`}
194
+ href={`${localePrefix}/tags/${relatedTag.slug}`}
178
195
  class="inline-flex items-center px-4 py-2 bg-white dark:bg-slate-800 border border-slate-300 dark:border-slate-600 rounded-lg hover:border-primary-300 dark:hover:border-primary-600 hover:shadow-md transition-all duration-200 group"
179
196
  >
180
197
  <span class="font-medium text-slate-900 dark:text-slate-100 group-hover:text-primary-500 transition-colors">
@@ -2,9 +2,22 @@
2
2
  import { getCollection } from 'astro:content';
3
3
  import PageLayout from '@jet-w/astro-blog/layouts/PageLayout.astro';
4
4
  import TagCloud from '@jet-w/astro-blog/components/blog/TagCloud.astro';
5
+ import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
6
+ import { defaultI18nConfig } from '../../config/i18n';
7
+ import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, filterPostsByLocale } from '../../utils/i18n';
8
+
9
+ // Get i18n config
10
+ const i18nConfig = virtualI18nConfig || defaultI18nConfig;
11
+ const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
12
+ const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
13
+ const ui = localeConfig.ui;
14
+ const localePrefix = getLocalePrefix(currentLocale, i18nConfig);
5
15
 
6
16
  // 从文章集合动态获取所有标签
7
- const allPosts = await getCollection('posts', ({ data }) => !data.draft);
17
+ const allPostsCollection = await getCollection('posts', ({ data }) => !data.draft);
18
+
19
+ // Filter posts by current locale
20
+ const allPosts = filterPostsByLocale(allPostsCollection, currentLocale, i18nConfig);
8
21
 
9
22
  // 统计标签
10
23
  const tagMap = new Map<string, { name: string; count: number }>();
@@ -54,44 +67,54 @@ const sortedGroups = Object.entries(groupedByLetter).sort(([a], [b]) => {
54
67
  ---
55
68
 
56
69
  <PageLayout
57
- title="标签"
58
- description="按标签浏览文章"
70
+ title={ui.allTags}
71
+ description={ui.allTags}
59
72
  showSidebar={true}
73
+ i18nConfig={i18nConfig}
60
74
  >
75
+ <!-- 面包屑导航 -->
76
+ <nav class="flex items-center space-x-2 text-sm text-slate-600 dark:text-slate-400 mb-8">
77
+ <a href={`${localePrefix}/`} class="hover:text-primary-500 transition-colors">{ui.home}</a>
78
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
79
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
80
+ </svg>
81
+ <span class="text-slate-900 dark:text-slate-100">{ui.allTags}</span>
82
+ </nav>
83
+
61
84
  <!-- 页面头部 -->
62
85
  <div class="text-center mb-12">
63
86
  <h1 class="text-4xl font-bold text-slate-900 dark:text-slate-100 mb-4">
64
- 文章标签
87
+ {ui.allTags}
65
88
  </h1>
66
89
  <p class="text-xl text-slate-600 dark:text-slate-400 mb-8">
67
- 按标签分类浏览所有文章
90
+ {ui.filterByTag}
68
91
  </p>
69
92
 
70
93
  <!-- 统计信息 -->
71
94
  <div class="inline-flex items-center space-x-6 text-sm text-slate-500 dark:text-slate-400 bg-slate-50 dark:bg-slate-800 px-6 py-3 rounded-lg">
72
- <span>共 {allTags.length} 个标签</span>
95
+ <span>{allTags.length} {ui.tags}</span>
73
96
  <span>•</span>
74
- <span>{totalPosts} 篇文章</span>
97
+ <span>{totalPosts} {ui.posts}</span>
75
98
  </div>
76
99
  </div>
77
100
 
78
101
  <!-- 标签云 -->
79
102
  <section class="mb-16">
80
103
  <h2 class="text-2xl font-bold text-slate-900 dark:text-slate-100 mb-8">
81
- 标签云
104
+ {ui.tags}
82
105
  </h2>
83
- <TagCloud tags={allTags} />
106
+ <TagCloud tags={allTags} localePrefix={localePrefix} />
84
107
  </section>
85
108
 
86
109
  <!-- 热门标签 -->
87
110
  <section class="mb-16">
88
111
  <h2 class="text-2xl font-bold text-slate-900 dark:text-slate-100 mb-8">
89
- 热门标签
112
+ {ui.popularTags}
90
113
  </h2>
91
114
  <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
92
115
  {sortedTags.slice(0, 9).map((tag) => (
93
116
  <a
94
- href={`/tags/${tag.slug}`}
117
+ href={`${localePrefix}/tags/${tag.slug}`}
95
118
  class="group card hover:shadow-lg transform hover:-translate-y-1 transition-all duration-300"
96
119
  >
97
120
  <div class="flex items-center justify-between">
@@ -100,7 +123,7 @@ const sortedGroups = Object.entries(groupedByLetter).sort(([a], [b]) => {
100
123
  {tag.name}
101
124
  </h3>
102
125
  <p class="text-sm text-slate-600 dark:text-slate-400">
103
- {tag.count} 篇文章
126
+ {tag.count} {ui.posts}
104
127
  </p>
105
128
  </div>
106
129
  <div class="text-3xl font-bold text-primary-500 opacity-80">
@@ -121,12 +144,12 @@ const sortedGroups = Object.entries(groupedByLetter).sort(([a], [b]) => {
121
144
  <!-- 所有标签 -->
122
145
  <section class="mb-16">
123
146
  <h2 class="text-2xl font-bold text-slate-900 dark:text-slate-100 mb-8">
124
- 所有标签
147
+ {ui.allTags}
125
148
  </h2>
126
149
  <div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
127
150
  {sortedTags.map((tag) => (
128
151
  <a
129
- href={`/tags/${tag.slug}`}
152
+ href={`${localePrefix}/tags/${tag.slug}`}
130
153
  class="flex items-center justify-between p-4 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg hover:shadow-md hover:border-primary-300 dark:hover:border-primary-600 transition-all duration-200 group"
131
154
  >
132
155
  <span class="font-medium text-slate-900 dark:text-slate-100 group-hover:text-primary-500 transition-colors">
@@ -143,7 +166,7 @@ const sortedGroups = Object.entries(groupedByLetter).sort(([a], [b]) => {
143
166
  <!-- 按字母分组 -->
144
167
  <section>
145
168
  <h2 class="text-2xl font-bold text-slate-900 dark:text-slate-100 mb-8">
146
- 按首字母分组
169
+ {ui.tags}
147
170
  </h2>
148
171
  {sortedGroups.map(([letter, tags]) => (
149
172
  <div class="mb-6">
@@ -153,7 +176,7 @@ const sortedGroups = Object.entries(groupedByLetter).sort(([a], [b]) => {
153
176
  <div class="flex flex-wrap gap-2">
154
177
  {tags.sort((a, b) => b.count - a.count).map((tag) => (
155
178
  <a
156
- href={`/tags/${tag.slug}`}
179
+ href={`${localePrefix}/tags/${tag.slug}`}
157
180
  class="inline-flex items-center px-3 py-1 bg-slate-100 dark:bg-slate-800 hover:bg-primary-100 dark:hover:bg-primary-900/30 text-slate-700 dark:text-slate-300 hover:text-primary-700 dark:hover:text-primary-300 rounded-full transition-colors text-sm"
158
181
  >
159
182
  {tag.name}