@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,10 +3,26 @@ import { getCollection, type CollectionEntry, render } from 'astro:content';
3
3
  import PageLayout from '@jet-w/astro-blog/layouts/PageLayout.astro';
4
4
  import SlidesLayout from '@jet-w/astro-blog/layouts/SlidesLayout.astro';
5
5
  import FloatingToc from '@jet-w/astro-blog/components/blog/FloatingToc.vue';
6
+ import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
7
+ import { defaultI18nConfig } from '../../config/i18n';
8
+ import { getLocaleFromPath, getLocaleConfig, getLocalePrefix } from '../../utils/i18n';
6
9
 
7
10
  export async function getStaticPaths() {
8
11
  const posts = await getCollection('posts');
9
12
 
13
+ // Build folder title map from README files (inline function)
14
+ const folderTitles: Record<string, string> = {};
15
+ posts.forEach(post => {
16
+ const pathParts = post.id.split('/');
17
+ const fileName = pathParts[pathParts.length - 1].toLowerCase();
18
+ if (fileName === 'readme' || fileName === 'readme.md' || fileName === 'readme.mdx') {
19
+ const folderPath = pathParts.slice(0, -1).join('/').toLowerCase();
20
+ if (folderPath && post.data.title) {
21
+ folderTitles[folderPath] = post.data.title;
22
+ }
23
+ }
24
+ });
25
+
10
26
  // 按文件名称排序,readme.md 排在每个文件夹的最前面
11
27
  const sortedPosts = posts.sort((a, b) => {
12
28
  // 获取文件夹路径和文件名
@@ -50,6 +66,7 @@ export async function getStaticPaths() {
50
66
  prevPost: CollectionEntry<'posts'> | null;
51
67
  nextPost: CollectionEntry<'posts'> | null;
52
68
  isDirectory: boolean;
69
+ folderTitles: Record<string, string>;
53
70
  };
54
71
  };
55
72
 
@@ -61,6 +78,7 @@ export async function getStaticPaths() {
61
78
  prevPost: index > 0 ? sortedPosts[index - 1] : null,
62
79
  nextPost: index < sortedPosts.length - 1 ? sortedPosts[index + 1] : null,
63
80
  isDirectory: false,
81
+ folderTitles,
64
82
  },
65
83
  }));
66
84
 
@@ -83,6 +101,7 @@ export async function getStaticPaths() {
83
101
  prevPost: null,
84
102
  nextPost: null,
85
103
  isDirectory: true,
104
+ folderTitles,
86
105
  },
87
106
  });
88
107
  }
@@ -96,11 +115,59 @@ type Props = {
96
115
  prevPost: CollectionEntry<'posts'> | null;
97
116
  nextPost: CollectionEntry<'posts'> | null;
98
117
  isDirectory?: boolean;
118
+ folderTitles: Record<string, string>;
99
119
  };
100
120
 
101
- const { post, prevPost, nextPost, isDirectory } = Astro.props;
121
+ // Build breadcrumb items for a post
122
+ function buildBreadcrumbItems(
123
+ postId: string,
124
+ postTitle: string,
125
+ folderTitles: Record<string, string>
126
+ ): Array<{ title: string; path: string; isLast: boolean }> {
127
+ const parts = postId.toLowerCase().split('/');
128
+ const items: Array<{ title: string; path: string; isLast: boolean }> = [];
129
+ let currentPath = '';
130
+
131
+ // Build breadcrumb for each folder level
132
+ for (let i = 0; i < parts.length - 1; i++) {
133
+ currentPath = currentPath ? `${currentPath}/${parts[i]}` : parts[i];
134
+ const title = folderTitles[currentPath] || parts[i];
135
+ items.push({ title, path: currentPath, isLast: false });
136
+ }
137
+
138
+ // Add the current post/file
139
+ const fileName = parts[parts.length - 1];
140
+ const isReadme = fileName === 'readme' || fileName === 'readme.md' || fileName === 'readme.mdx';
141
+
142
+ if (!isReadme) {
143
+ items.push({ title: postTitle, path: postId.toLowerCase(), isLast: true });
144
+ } else if (items.length > 0) {
145
+ items[items.length - 1].isLast = true;
146
+ }
147
+
148
+ return items;
149
+ }
150
+
151
+ const { post, prevPost, nextPost, isDirectory, folderTitles } = Astro.props;
102
152
  const Content = post ? (await render(post)).Content : null;
103
153
 
154
+ // Get i18n config
155
+ const i18nConfig = virtualI18nConfig || defaultI18nConfig;
156
+ const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
157
+ const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
158
+ const ui = localeConfig.ui;
159
+ const localePrefix = getLocalePrefix(currentLocale, i18nConfig);
160
+
161
+ // Get the current slug from URL
162
+ const urlPath = Astro.url.pathname;
163
+ const postsMatch = urlPath.match(/\/posts\/(.+?)\/?$/);
164
+ const currentSlug = postsMatch ? postsMatch[1] : '';
165
+
166
+ // Build breadcrumb items
167
+ const breadcrumbItems = post
168
+ ? buildBreadcrumbItems(post.id, post.data.title, folderTitles)
169
+ : buildBreadcrumbItems(currentSlug, ui.documentTree, folderTitles);
170
+
104
171
  // 判断是否使用 slides 布局
105
172
  const isSlides = post?.data.layout === 'slides';
106
173
 
@@ -157,9 +224,31 @@ const formatDate = (date: Date) => {
157
224
  title={post.data.title}
158
225
  description={post.data.description}
159
226
  showSidebar={true}
227
+ i18nConfig={i18nConfig}
160
228
  >
229
+ <!-- 面包屑导航 -->
230
+ <nav class="flex items-center flex-wrap gap-y-1 text-sm text-slate-600 dark:text-slate-400 mb-8">
231
+ <a href={`${localePrefix}/`} class="hover:text-primary-500 transition-colors">{ui.home}</a>
232
+ {breadcrumbItems.map((item) => (
233
+ <>
234
+ <svg class="w-4 h-4 mx-2 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
235
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
236
+ </svg>
237
+ {item.isLast ? (
238
+ <span class="text-slate-900 dark:text-slate-100 truncate max-w-xs">{item.title}</span>
239
+ ) : (
240
+ <a href={`${localePrefix}/posts/${item.path}`} class="hover:text-primary-500 transition-colors">{item.title}</a>
241
+ )}
242
+ </>
243
+ ))}
244
+ </nav>
245
+
161
246
  <!-- 浮动目录 -->
162
- <FloatingToc client:load />
247
+ <FloatingToc
248
+ client:load
249
+ tocTitle={ui.tableOfContents}
250
+ progressTitle={ui.readingProgress}
251
+ />
163
252
 
164
253
  <!-- 文章头部 -->
165
254
  <article class="prose prose-slate dark:prose-invert max-w-none">
@@ -198,7 +287,7 @@ const formatDate = (date: Date) => {
198
287
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
199
288
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
200
289
  </svg>
201
- <span>约 {readingTime} 分钟阅读</span>
290
+ <span>{readingTime} {ui.minuteRead}</span>
202
291
  </div>
203
292
  </div>
204
293
 
@@ -207,7 +296,7 @@ const formatDate = (date: Date) => {
207
296
  <div class="flex flex-wrap justify-center gap-2 mt-6">
208
297
  {post.data.tags.map((tag) => (
209
298
  <a
210
- href={`/tags/${tag}`}
299
+ href={`${localePrefix}/tags/${tag.toLowerCase().replace(/\s+/g, '-')}`}
211
300
  class="px-3 py-1 text-xs font-medium bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 rounded-full hover:bg-primary-200 dark:hover:bg-primary-900/50 transition-colors"
212
301
  >
213
302
  #{tag}
@@ -241,12 +330,12 @@ const formatDate = (date: Date) => {
241
330
  {post.data.categories && post.data.categories.length > 0 && (
242
331
  <div class="mb-8">
243
332
  <h3 class="text-lg font-semibold text-slate-900 dark:text-slate-100 mb-4">
244
- 分类
333
+ {ui.categories}
245
334
  </h3>
246
335
  <div class="flex flex-wrap gap-2">
247
336
  {post.data.categories.map((category) => (
248
337
  <a
249
- href={`/categories/${category}`}
338
+ href={`${localePrefix}/categories/${category.toLowerCase().replace(/\s+/g, '-')}`}
250
339
  class="px-4 py-2 text-sm font-medium bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 rounded-lg hover:bg-slate-200 dark:hover:bg-slate-700 transition-colors"
251
340
  >
252
341
  {category}
@@ -270,10 +359,10 @@ const formatDate = (date: Date) => {
270
359
  <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
271
360
  <!-- 上一篇 -->
272
361
  <div class="text-center md:text-left">
273
- <div class="text-sm text-slate-500 dark:text-slate-400 mb-2">上一篇</div>
362
+ <div class="text-sm text-slate-500 dark:text-slate-400 mb-2">{ui.previousPost}</div>
274
363
  {prevPost ? (
275
364
  <a
276
- href={`/posts/${prevPost.id.toLowerCase()}`}
365
+ href={`${localePrefix}/posts/${prevPost.id.toLowerCase()}`}
277
366
  class="group inline-flex items-center text-primary-500 hover:text-primary-600 dark:hover:text-primary-400 font-medium transition-colors"
278
367
  >
279
368
  <svg class="w-4 h-4 mr-2 group-hover:-translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -282,16 +371,16 @@ const formatDate = (date: Date) => {
282
371
  <span class="line-clamp-1">{prevPost.data.title}</span>
283
372
  </a>
284
373
  ) : (
285
- <span class="text-slate-400 dark:text-slate-600">已经是第一篇了</span>
374
+ <span class="text-slate-400 dark:text-slate-600">-</span>
286
375
  )}
287
376
  </div>
288
377
 
289
378
  <!-- 下一篇 -->
290
379
  <div class="text-center md:text-right">
291
- <div class="text-sm text-slate-500 dark:text-slate-400 mb-2">下一篇</div>
380
+ <div class="text-sm text-slate-500 dark:text-slate-400 mb-2">{ui.nextPost}</div>
292
381
  {nextPost ? (
293
382
  <a
294
- href={`/posts/${nextPost.id.toLowerCase()}`}
383
+ href={`${localePrefix}/posts/${nextPost.id.toLowerCase()}`}
295
384
  class="group inline-flex items-center justify-end text-primary-500 hover:text-primary-600 dark:hover:text-primary-400 font-medium transition-colors"
296
385
  >
297
386
  <span class="line-clamp-1">{nextPost.data.title}</span>
@@ -300,7 +389,7 @@ const formatDate = (date: Date) => {
300
389
  </svg>
301
390
  </a>
302
391
  ) : (
303
- <span class="text-slate-400 dark:text-slate-600">已经是最后一篇了</span>
392
+ <span class="text-slate-400 dark:text-slate-600">-</span>
304
393
  )}
305
394
  </div>
306
395
  </div>
@@ -308,10 +397,28 @@ const formatDate = (date: Date) => {
308
397
  </PageLayout>
309
398
  ) : (
310
399
  <PageLayout
311
- title="目录"
312
- description="此目录暂无内容"
400
+ title={ui.documentTree}
401
+ description={ui.noPostsFound}
313
402
  showSidebar={true}
403
+ i18nConfig={i18nConfig}
314
404
  >
405
+ <!-- 面包屑导航 -->
406
+ <nav class="flex items-center flex-wrap gap-y-1 text-sm text-slate-600 dark:text-slate-400 mb-8">
407
+ <a href={`${localePrefix}/`} class="hover:text-primary-500 transition-colors">{ui.home}</a>
408
+ {breadcrumbItems.map((item) => (
409
+ <>
410
+ <svg class="w-4 h-4 mx-2 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
411
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
412
+ </svg>
413
+ {item.isLast ? (
414
+ <span class="text-slate-900 dark:text-slate-100 truncate max-w-xs">{item.title}</span>
415
+ ) : (
416
+ <a href={`${localePrefix}/posts/${item.path}`} class="hover:text-primary-500 transition-colors">{item.title}</a>
417
+ )}
418
+ </>
419
+ ))}
420
+ </nav>
421
+
315
422
  <div class="text-center py-16">
316
423
  <div class="inline-flex items-center justify-center w-20 h-20 bg-slate-100 dark:bg-slate-800 rounded-full mb-6">
317
424
  <svg class="w-10 h-10 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -319,16 +426,16 @@ const formatDate = (date: Date) => {
319
426
  </svg>
320
427
  </div>
321
428
  <h1 class="text-2xl font-bold text-slate-900 dark:text-slate-100 mb-4">
322
- 此目录暂无内容
429
+ {ui.noPostsFound}
323
430
  </h1>
324
431
  <p class="text-slate-600 dark:text-slate-400 mb-8">
325
- 该目录下暂无 README 文件
432
+ {ui.noPostsFound}
326
433
  </p>
327
- <a href="/posts" class="inline-flex items-center px-4 py-2 bg-primary-500 text-white rounded-lg hover:bg-primary-600 transition-colors">
434
+ <a href={`${localePrefix}/posts`} class="inline-flex items-center px-4 py-2 bg-primary-500 text-white rounded-lg hover:bg-primary-600 transition-colors">
328
435
  <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
329
436
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
330
437
  </svg>
331
- 返回文章列表
438
+ {ui.postList}
332
439
  </a>
333
440
  </div>
334
441
  </PageLayout>
@@ -4,10 +4,24 @@ 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 NavigationTabs from '@jet-w/astro-blog/components/blog/NavigationTabs.vue';
6
6
  import { getCollection } from 'astro:content';
7
+ import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
8
+ import { defaultI18nConfig } from '../../config/i18n';
9
+ import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, filterPostsByLocale } from '../../utils/i18n';
10
+
11
+ // Get i18n config
12
+ const i18nConfig = virtualI18nConfig || defaultI18nConfig;
13
+ const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
14
+ const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
15
+ const ui = localeConfig.ui;
16
+ const localePrefix = getLocalePrefix(currentLocale, i18nConfig);
7
17
 
8
18
  // 从新的内容目录获取文章数据
9
19
  const allPostsCollection = await getCollection('posts');
10
- const publishedPosts = allPostsCollection
20
+
21
+ // Filter posts by current locale
22
+ const localeFilteredPosts = filterPostsByLocale(allPostsCollection, currentLocale, i18nConfig);
23
+
24
+ const publishedPosts = localeFilteredPosts
11
25
  .filter(post => !post.data.draft)
12
26
  .sort((a, b) => (b.data.pubDate?.getTime() ?? 0) - (a.data.pubDate?.getTime() ?? 0));
13
27
 
@@ -170,9 +184,9 @@ const allTags = [...new Set(allPosts.flatMap(post => post.tags || []))];
170
184
  const allCategories = [...new Set(allPosts.flatMap(post => post.categories || []))];
171
185
 
172
186
  // 格式化日期
173
- const formatDate = (date?: Date) => {
187
+ const formatDateLocal = (date?: Date) => {
174
188
  if (!date) return '';
175
- return new Intl.DateTimeFormat('zh-CN', {
189
+ return new Intl.DateTimeFormat(localeConfig.locale.dateLocale, {
176
190
  year: 'numeric',
177
191
  month: '2-digit',
178
192
  day: '2-digit'
@@ -181,27 +195,37 @@ const formatDate = (date?: Date) => {
181
195
  ---
182
196
 
183
197
  <PageLayout
184
- title="文章列表"
185
- description="浏览所有技术文章和分享内容"
198
+ title={ui.postList}
199
+ description={localeConfig.site.description}
186
200
  showSidebar={true}
201
+ i18nConfig={i18nConfig}
187
202
  >
203
+ <!-- 面包屑导航 -->
204
+ <nav class="flex items-center space-x-2 text-sm text-slate-600 dark:text-slate-400 mb-8">
205
+ <a href={`${localePrefix}/`} class="hover:text-primary-500 transition-colors">{ui.home}</a>
206
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
207
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
208
+ </svg>
209
+ <span class="text-slate-900 dark:text-slate-100">{ui.postList}</span>
210
+ </nav>
211
+
188
212
  <!-- 页面头部 -->
189
213
  <div class="mb-12">
190
214
  <div class="text-center">
191
215
  <h1 class="text-4xl font-bold text-slate-900 dark:text-slate-100 mb-4">
192
- 文章列表
216
+ {ui.postList}
193
217
  </h1>
194
218
  <p class="text-xl text-slate-600 dark:text-slate-400 mb-8">
195
- 分享技术思考和学习心得
219
+ {localeConfig.site.description}
196
220
  </p>
197
221
 
198
222
  <!-- 统计信息 -->
199
223
  <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">
200
- <span>共 {totalPosts} 篇文章</span>
224
+ <span>{totalPosts} {ui.posts}</span>
201
225
  <span>•</span>
202
- <span>{allTags.length} 个标签</span>
226
+ <span>{allTags.length} {ui.tags}</span>
203
227
  <span>•</span>
204
- <span>{allCategories.length} 个分类</span>
228
+ <span>{allCategories.length} {ui.categories}</span>
205
229
  </div>
206
230
  </div>
207
231
  </div>
@@ -223,20 +247,20 @@ const formatDate = (date?: Date) => {
223
247
  <!-- 按标签筛选 -->
224
248
  <div>
225
249
  <h3 class="text-sm font-semibold text-slate-900 dark:text-slate-100 mb-3">
226
- 按标签筛选
250
+ {ui.filterByTag}
227
251
  </h3>
228
252
  <div class="flex flex-wrap gap-2">
229
- <a href="/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">
230
- 全部标签
253
+ <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">
254
+ {ui.allTags}
231
255
  </a>
232
256
  {allTags.slice(0, 10).map((tag) => (
233
- <a href={`/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">
257
+ <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">
234
258
  {tag}
235
259
  </a>
236
260
  ))}
237
261
  {allTags.length > 10 && (
238
- <a href="/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">
239
- +{allTags.length - 10} 更多
262
+ <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">
263
+ +{allTags.length - 10}
240
264
  </a>
241
265
  )}
242
266
  </div>
@@ -245,20 +269,20 @@ const formatDate = (date?: Date) => {
245
269
  <!-- 按分类筛选 -->
246
270
  <div>
247
271
  <h3 class="text-sm font-semibold text-slate-900 dark:text-slate-100 mb-3">
248
- 按分类筛选
272
+ {ui.filterByCategory}
249
273
  </h3>
250
274
  <div class="flex flex-wrap gap-2">
251
- <a href="/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">
252
- 全部分类
275
+ <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">
276
+ {ui.allCategories}
253
277
  </a>
254
278
  {allCategories.slice(0, 8).map((category) => (
255
- <a href={`/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">
279
+ <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">
256
280
  {category}
257
281
  </a>
258
282
  ))}
259
283
  {allCategories.length > 8 && (
260
- <a href="/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">
261
- +{allCategories.length - 8} 更多
284
+ <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">
285
+ +{allCategories.length - 8}
262
286
  </a>
263
287
  )}
264
288
  </div>
@@ -295,21 +319,19 @@ const formatDate = (date?: Date) => {
295
319
  <!-- 卡片模式 -->
296
320
  <div id="card-view" class="view-container">
297
321
  {posts.length > 0 ? (
298
- <div class="flex flex-wrap gap-6 mb-12">
322
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-12">
299
323
  {posts.map((post) => (
300
- <div class="flex-shrink-0">
301
- <PostCard post={post} layout="horizontal" />
302
- </div>
324
+ <PostCard post={post} layout="horizontal" localePrefix={localePrefix} locale={localeConfig.locale.dateLocale} ui={ui} />
303
325
  ))}
304
326
  </div>
305
327
  ) : (
306
328
  <div class="text-center py-16">
307
329
  <div class="text-6xl mb-4">📝</div>
308
330
  <h3 class="text-xl font-semibold text-slate-900 dark:text-slate-100 mb-2">
309
- 暂无文章
331
+ {ui.noPostsFound}
310
332
  </h3>
311
333
  <p class="text-slate-600 dark:text-slate-400">
312
- 目前还没有发布任何文章,请稍后再来查看。
334
+ {ui.noPostsFound}
313
335
  </p>
314
336
  </div>
315
337
  )}
@@ -352,7 +374,7 @@ const formatDate = (date?: Date) => {
352
374
  <div class="folder-children hidden">
353
375
  {child.children.map((grandChild) => (
354
376
  <a
355
- href={`/posts/${grandChild.slug}`}
377
+ href={`${localePrefix}/posts/${grandChild.slug}`}
356
378
  class="ml-12 flex items-start gap-4 px-4 py-3 hover:bg-slate-50 dark:hover:bg-slate-700/50 transition-colors border-b border-slate-100 dark:border-slate-700 group"
357
379
  >
358
380
  <svg class="w-4 h-4 text-slate-400 mt-1 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -365,7 +387,7 @@ const formatDate = (date?: Date) => {
365
387
  )}
366
388
  </div>
367
389
  {grandChild.pubDate && (
368
- <time class="text-xs text-slate-400 flex-shrink-0">{formatDate(grandChild.pubDate)}</time>
390
+ <time class="text-xs text-slate-400 flex-shrink-0">{formatDateLocal(grandChild.pubDate)}</time>
369
391
  )}
370
392
  </a>
371
393
  ))}
@@ -373,7 +395,7 @@ const formatDate = (date?: Date) => {
373
395
  </div>
374
396
  ) : (
375
397
  <a
376
- href={`/posts/${child.slug}`}
398
+ href={`${localePrefix}/posts/${child.slug}`}
377
399
  class="ml-6 flex items-start gap-4 px-4 py-3 hover:bg-slate-50 dark:hover:bg-slate-700/50 transition-colors border-b border-slate-100 dark:border-slate-700 group"
378
400
  >
379
401
  <svg class="w-4 h-4 text-slate-400 mt-1 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -386,7 +408,7 @@ const formatDate = (date?: Date) => {
386
408
  )}
387
409
  </div>
388
410
  {child.pubDate && (
389
- <time class="text-xs text-slate-400 flex-shrink-0">{formatDate(child.pubDate)}</time>
411
+ <time class="text-xs text-slate-400 flex-shrink-0">{formatDateLocal(child.pubDate)}</time>
390
412
  )}
391
413
  </a>
392
414
  )}
@@ -396,7 +418,7 @@ const formatDate = (date?: Date) => {
396
418
  </div>
397
419
  ) : (
398
420
  <a
399
- href={`/posts/${node.slug}`}
421
+ href={`${localePrefix}/posts/${node.slug}`}
400
422
  class="flex items-start gap-4 px-4 py-3 hover:bg-slate-50 dark:hover:bg-slate-700/50 transition-colors border-b border-slate-100 dark:border-slate-700 group"
401
423
  >
402
424
  <svg class="w-4 h-4 text-slate-400 mt-1 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -409,7 +431,7 @@ const formatDate = (date?: Date) => {
409
431
  )}
410
432
  </div>
411
433
  {node.pubDate && (
412
- <time class="text-xs text-slate-400 flex-shrink-0">{formatDate(node.pubDate)}</time>
434
+ <time class="text-xs text-slate-400 flex-shrink-0">{formatDateLocal(node.pubDate)}</time>
413
435
  )}
414
436
  </a>
415
437
  )}
@@ -421,10 +443,10 @@ const formatDate = (date?: Date) => {
421
443
  <div class="text-center py-16">
422
444
  <div class="text-6xl mb-4">📝</div>
423
445
  <h3 class="text-xl font-semibold text-slate-900 dark:text-slate-100 mb-2">
424
- 暂无文章
446
+ {ui.noPostsFound}
425
447
  </h3>
426
448
  <p class="text-slate-600 dark:text-slate-400">
427
- 目前还没有发布任何文章,请稍后再来查看。
449
+ {ui.noPostsFound}
428
450
  </p>
429
451
  </div>
430
452
  )}
@@ -437,7 +459,7 @@ const formatDate = (date?: Date) => {
437
459
  <Pagination
438
460
  currentPage={currentPage}
439
461
  totalPages={totalPages}
440
- baseUrl="/posts"
462
+ baseUrl={`${localePrefix}/posts`}
441
463
  />
442
464
  )}
443
465
  </div>