@jet-w/astro-blog 0.2.0 → 0.2.2

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 (121) hide show
  1. package/dist/{chunk-HVQKQN6B.js → chunk-6D3XRDNY.js} +1 -1
  2. package/dist/{chunk-ATRISB7B.js → chunk-A2E2VSAQ.js} +43 -3
  3. package/dist/chunk-DAH2XP4W.js +154 -0
  4. package/dist/{chunk-AZHCNNAC.js → chunk-PG43JO4O.js} +1 -153
  5. package/dist/chunk-PZICDGJG.js +69 -0
  6. package/dist/chunk-Z3O3JK56.js +186 -0
  7. package/dist/config/index.d.ts +2 -2
  8. package/dist/config/index.js +9 -7
  9. package/dist/{i18n-5H4W145i.d.ts → i18n-DYYPTq4o.d.ts} +21 -1
  10. package/dist/index.d.ts +10 -184
  11. package/dist/index.js +37 -210
  12. package/dist/integration.d.ts +2 -2
  13. package/dist/integration.js +2 -2
  14. package/dist/{sidebar-Da-W_4Lr.d.ts → sidebar-DNdiCKBw.d.ts} +1 -1
  15. package/dist/utils/i18n.d.ts +133 -0
  16. package/dist/utils/i18n.js +49 -0
  17. package/dist/utils/sidebar.d.ts +1 -1
  18. package/dist/utils/useI18n.d.ts +74 -0
  19. package/dist/utils/useI18n.js +15 -0
  20. package/package.json +9 -1
  21. package/src/components/blog/FloatingToc.vue +11 -3
  22. package/src/components/blog/Hero.astro +17 -2
  23. package/src/components/blog/NavigationTabs.vue +46 -15
  24. package/src/components/blog/PostCard.astro +28 -10
  25. package/src/components/blog/RelatedPosts.astro +23 -7
  26. package/src/components/blog/TableOfContents.astro +10 -4
  27. package/src/components/blog/TagCloud.astro +4 -3
  28. package/src/components/home/FeaturedPostsSection.astro +22 -6
  29. package/src/components/home/QuickNavSection.astro +33 -4
  30. package/src/components/home/RecentPostsSection.astro +22 -6
  31. package/src/components/home/StatsSection.astro +24 -6
  32. package/src/components/layout/Header.astro +9 -5
  33. package/src/components/layout/Sidebar.astro +14 -11
  34. package/src/components/ui/SearchBox.vue +13 -5
  35. package/src/components/ui/SearchInterface.vue +49 -25
  36. package/src/pages/archives/[year]/[month].astro +36 -17
  37. package/src/pages/archives/index.astro +36 -20
  38. package/src/pages/categories/[category].astro +33 -16
  39. package/src/pages/categories/index.astro +37 -14
  40. package/src/pages/posts/[...slug].astro +125 -18
  41. package/src/pages/posts/index.astro +59 -37
  42. package/src/pages/posts/page/[page].astro +65 -27
  43. package/src/pages/search.astro +50 -14
  44. package/src/pages/slides/index.astro +25 -6
  45. package/src/pages/tags/[tag].astro +32 -15
  46. package/src/pages/tags/index.astro +39 -16
  47. package/src/plugins/remark-containers.mjs +351 -322
  48. package/src/plugins/remark-protect-code.mjs +69 -0
  49. package/src/styles/global.css +35 -1
  50. package/templates/default/.claude/ralph-loop.local.md +48 -0
  51. package/templates/default/astro.config.mjs +13 -4
  52. package/templates/default/content/posts/blog_docs_en/{get-started → 01.get-started}/01-intro.md +1 -1
  53. package/templates/default/content/posts/blog_docs_en/{get-started → 01.get-started}/02-install.md +1 -1
  54. package/templates/default/content/posts/blog_docs_en/{get-started → 01.get-started}/03-create-post.md +1 -1
  55. package/templates/default/content/posts/blog_docs_en/{get-started → 01.get-started}/04-structure.md +1 -1
  56. package/templates/default/content/posts/blog_docs_en/01.get-started/05-deploy.md +208 -0
  57. package/templates/default/content/posts/blog_docs_en/{get-started → 01.get-started}/README.md +1 -1
  58. package/templates/default/content/posts/blog_docs_en/02.guide/02-containers.md +245 -0
  59. package/templates/default/content/posts/blog_docs_en/{guide/markdown → 02.guide}/03-code-blocks.md +2 -1
  60. package/templates/default/content/posts/blog_docs_en/{guide/features/01-mermaid.md → 02.guide/03-mermaid.md} +1 -1
  61. package/templates/default/content/posts/blog_docs_en/{guide/features → 02.guide}/04-icons.md +4 -2
  62. package/templates/default/content/posts/blog_docs_en/{guide/features/02-latex.md → 02.guide/06-latex.md} +1 -1
  63. package/templates/default/content/posts/blog_docs_en/{guide/features/03-video.md → 02.guide/07-video.md} +1 -1
  64. package/templates/default/content/posts/blog_docs_en/02.guide/08-slides.md +359 -0
  65. package/templates/default/content/posts/blog_docs_en/{guide/markdown → 02.guide}/README.md +22 -3
  66. package/templates/default/content/posts/blog_docs_en/{config → 03.config}/01-site.md +1 -1
  67. package/templates/default/content/posts/blog_docs_en/{config → 03.config}/02-sidebar.md +1 -1
  68. package/templates/default/content/posts/blog_docs_en/{config → 03.config}/03-i18n.md +88 -24
  69. package/templates/default/content/posts/blog_docs_en/{config → 03.config}/README.md +1 -1
  70. package/templates/default/content/posts/blog_docs_en/README.md +2 -1
  71. package/templates/default/content/posts/blog_docs_zh/01.get-started/01-intro.md +81 -0
  72. package/templates/default/content/posts/blog_docs_zh/01.get-started/02-install.md +137 -0
  73. package/templates/default/content/posts/blog_docs_zh/01.get-started/03-create-post.md +176 -0
  74. package/templates/default/content/posts/blog_docs_zh/01.get-started/04-structure.md +173 -0
  75. package/templates/default/content/posts/blog_docs_zh/01.get-started/05-deploy.md +208 -0
  76. package/templates/default/content/posts/blog_docs_zh/01.get-started/README.md +52 -0
  77. package/templates/default/content/posts/blog_docs_zh/02.guide/02-containers.md +245 -0
  78. package/templates/default/content/posts/blog_docs_zh/02.guide/03-code-blocks.md +206 -0
  79. package/templates/default/content/posts/blog_docs_zh/02.guide/03-mermaid.md +194 -0
  80. package/templates/default/content/posts/blog_docs_zh/02.guide/04-icons.md +229 -0
  81. package/templates/default/content/posts/blog_docs_zh/02.guide/06-latex.md +233 -0
  82. package/templates/default/content/posts/blog_docs_zh/02.guide/07-video.md +184 -0
  83. package/templates/default/content/posts/blog_docs_zh/02.guide/08-slides.md +359 -0
  84. package/templates/default/content/posts/blog_docs_zh/02.guide/README.md +213 -0
  85. package/templates/default/content/posts/blog_docs_zh/03.config/01-site.md +208 -0
  86. package/templates/default/content/posts/blog_docs_zh/03.config/02-sidebar.md +240 -0
  87. package/templates/default/content/posts/blog_docs_zh/03.config/03-i18n.md +348 -0
  88. package/templates/default/content/posts/blog_docs_zh/03.config/README.md +85 -0
  89. package/templates/default/content/posts/blog_docs_zh/README.md +78 -0
  90. package/templates/default/package.dev.json +31 -0
  91. package/templates/default/package.json +1 -1
  92. package/templates/default/src/config/locales/en/index.ts +5 -1
  93. package/templates/default/src/config/locales/en/menu.ts +3 -1
  94. package/templates/default/src/config/locales/en/sidebar.ts +18 -2
  95. package/templates/default/src/config/locales/en/site.ts +1 -1
  96. package/templates/default/src/config/locales/en/ui.ts +29 -0
  97. package/templates/default/src/config/locales/zh-CN/index.ts +5 -1
  98. package/templates/default/src/config/locales/zh-CN/menu.ts +7 -5
  99. package/templates/default/src/config/locales/zh-CN/sidebar.ts +22 -6
  100. package/templates/default/src/config/locales/zh-CN/site.ts +2 -2
  101. package/templates/default/src/config/locales/zh-CN/ui.ts +29 -0
  102. package/templates/default/src/config/site.ts +2 -2
  103. package/templates/default/src/content.config.ts +15 -3
  104. package/templates/default/content/posts/blog_docs/01-quick-start.md +0 -162
  105. package/templates/default/content/posts/blog_docs/02-frontmatter.md +0 -277
  106. package/templates/default/content/posts/blog_docs/03-markdown-basic.md +0 -350
  107. package/templates/default/content/posts/blog_docs/04-containers.md +0 -331
  108. package/templates/default/content/posts/blog_docs/05-code-blocks.md +0 -388
  109. package/templates/default/content/posts/blog_docs/06-mermaid.md +0 -431
  110. package/templates/default/content/posts/blog_docs/07-video.md +0 -243
  111. package/templates/default/content/posts/blog_docs/08-latex.md +0 -382
  112. package/templates/default/content/posts/blog_docs/09-icons.md +0 -326
  113. package/templates/default/content/posts/blog_docs/10-sidebar.md +0 -445
  114. package/templates/default/content/posts/blog_docs/11-config.md +0 -334
  115. package/templates/default/content/posts/blog_docs/12-i18n.md +0 -355
  116. package/templates/default/content/posts/blog_docs/12-slides.mdx +0 -552
  117. package/templates/default/content/posts/blog_docs/README.md +0 -152
  118. package/templates/default/content/posts/blog_docs_en/get-started/05-deploy.md +0 -197
  119. package/templates/default/content/posts/blog_docs_en/guide/README.md +0 -59
  120. package/templates/default/content/posts/blog_docs_en/guide/features/README.md +0 -51
  121. package/templates/default/content/posts/blog_docs_en/guide/markdown/02-containers.md +0 -226
@@ -6,9 +6,25 @@ export interface Props {
6
6
  tags?: string[];
7
7
  categories?: string[];
8
8
  };
9
+ ui?: {
10
+ relatedPosts?: string;
11
+ readMore?: string;
12
+ minuteRead?: string;
13
+ postList?: string;
14
+ };
15
+ localePrefix?: string;
9
16
  }
10
17
 
11
- const { currentPost } = Astro.props;
18
+ const {
19
+ currentPost,
20
+ ui = {
21
+ relatedPosts: '相关文章',
22
+ readMore: '阅读更多',
23
+ minuteRead: '分钟',
24
+ postList: '查看更多文章',
25
+ },
26
+ localePrefix = '',
27
+ } = Astro.props;
12
28
 
13
29
  // 模拟获取相关文章
14
30
  const allPosts = [
@@ -83,14 +99,14 @@ const formattedDate = (date: Date) => new Intl.DateTimeFormat('zh-CN', {
83
99
  {relatedPosts.length > 0 && (
84
100
  <section class="mt-16">
85
101
  <h2 class="text-2xl font-bold text-slate-900 dark:text-slate-100 mb-8">
86
- 相关文章
102
+ {ui.relatedPosts}
87
103
  </h2>
88
104
 
89
105
  <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
90
106
  {relatedPosts.map((post) => (
91
107
  <article class="group">
92
108
  <a
93
- href={`/posts/${post.slug}`}
109
+ href={`${localePrefix}/posts/${post.slug}`}
94
110
  class="card hover:shadow-lg transform hover:-translate-y-1 transition-all duration-300 block h-full"
95
111
  >
96
112
  <div class="flex flex-col h-full">
@@ -100,7 +116,7 @@ const formattedDate = (date: Date) => new Intl.DateTimeFormat('zh-CN', {
100
116
  {formattedDate(post.pubDate)}
101
117
  </time>
102
118
  {post.readingTime && (
103
- <span>{post.readingTime}分钟</span>
119
+ <span>{post.readingTime} {ui.minuteRead}</span>
104
120
  )}
105
121
  </div>
106
122
 
@@ -132,7 +148,7 @@ const formattedDate = (date: Date) => new Intl.DateTimeFormat('zh-CN', {
132
148
 
133
149
  <!-- 阅读链接 -->
134
150
  <div class="flex items-center text-primary-500 group-hover:text-primary-600 transition-colors text-sm font-medium">
135
- <span>阅读更多</span>
151
+ <span>{ui.readMore}</span>
136
152
  <svg class="w-4 h-4 ml-1 group-hover:translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
137
153
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
138
154
  </svg>
@@ -146,10 +162,10 @@ const formattedDate = (date: Date) => new Intl.DateTimeFormat('zh-CN', {
146
162
  <!-- 查看更多按钮 -->
147
163
  <div class="text-center mt-8">
148
164
  <a
149
- href="/posts"
165
+ href={`${localePrefix}/posts`}
150
166
  class="btn-secondary inline-flex items-center space-x-2"
151
167
  >
152
- <span>查看更多文章</span>
168
+ <span>{ui.postList}</span>
153
169
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
154
170
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
155
171
  </svg>
@@ -5,15 +5,21 @@ export interface Props {
5
5
  text: string;
6
6
  id: string;
7
7
  }>;
8
+ tocTitle?: string;
9
+ progressTitle?: string;
8
10
  }
9
11
 
10
- const { headings } = Astro.props;
12
+ const {
13
+ headings,
14
+ tocTitle = 'Table of Contents',
15
+ progressTitle = 'Reading Progress',
16
+ } = Astro.props;
11
17
  ---
12
18
 
13
19
  {headings.length > 0 && (
14
- <nav class="sticky top-20 p-6" aria-label="目录">
20
+ <nav class="sticky top-20 p-6" aria-label={tocTitle}>
15
21
  <h3 class="text-lg font-semibold text-slate-900 dark:text-slate-100 mb-4 border-b border-slate-200 dark:border-slate-700 pb-2">
16
- 目录
22
+ {tocTitle}
17
23
  </h3>
18
24
 
19
25
  <ol class="space-y-2 text-sm">
@@ -38,7 +44,7 @@ const { headings } = Astro.props;
38
44
 
39
45
  <!-- 进度指示器 -->
40
46
  <div class="mt-8 pt-4 border-t border-slate-200 dark:border-slate-700">
41
- <div class="text-xs text-slate-500 dark:text-slate-400 mb-2">阅读进度</div>
47
+ <div class="text-xs text-slate-500 dark:text-slate-400 mb-2">{progressTitle}</div>
42
48
  <div class="w-full bg-slate-200 dark:bg-slate-700 rounded-full h-2">
43
49
  <div class="bg-primary-500 h-2 rounded-full transition-all duration-300" id="reading-progress" style="width: 0%"></div>
44
50
  </div>
@@ -7,9 +7,10 @@ export interface Props {
7
7
  }>;
8
8
  maxSize?: number;
9
9
  minSize?: number;
10
+ localePrefix?: string;
10
11
  }
11
12
 
12
- const { tags, maxSize = 2.5, minSize = 0.8 } = Astro.props;
13
+ const { tags, maxSize = 2.5, minSize = 0.8, localePrefix = '' } = Astro.props;
13
14
 
14
15
  // 计算字体大小
15
16
  const maxCount = Math.max(...tags.map(tag => tag.count));
@@ -48,10 +49,10 @@ const shuffledTags = [...tags].sort(() => Math.random() - 0.5);
48
49
  <div class="flex flex-wrap items-center justify-center gap-3 leading-relaxed">
49
50
  {shuffledTags.map((tag, index) => (
50
51
  <a
51
- href={`/tags/${tag.slug}`}
52
+ href={`${localePrefix}/tags/${tag.slug}`}
52
53
  class={`inline-block font-medium transition-all duration-300 hover:scale-110 ${getColorClass(index)}`}
53
54
  style={`font-size: ${getFontSize(tag.count)}rem`}
54
- title={`${tag.name} (${tag.count} 篇文章)`}
55
+ title={`${tag.name} (${tag.count})`}
55
56
  >
56
57
  {tag.name}
57
58
  </a>
@@ -4,19 +4,33 @@
4
4
  */
5
5
  import PostCard from '../../components/blog/PostCard.astro';
6
6
  import { getCollection } from 'astro:content';
7
+ import type { I18nConfig } from '../../config/i18n';
8
+ import { defaultI18nConfig } from '../../config/i18n';
9
+ import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
10
+ import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, filterPostsByLocale } from '../../utils/i18n';
7
11
 
8
12
  export interface Props {
9
13
  count?: number;
14
+ i18nConfig?: I18nConfig;
10
15
  }
11
16
 
12
- const { count = 3 } = Astro.props;
17
+ const { count = 3, i18nConfig = virtualI18nConfig || defaultI18nConfig } = Astro.props;
18
+
19
+ // Get current locale from URL
20
+ const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
21
+ const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
22
+ const ui = localeConfig.ui;
23
+ const localePrefix = getLocalePrefix(currentLocale, i18nConfig);
13
24
 
14
25
  const allPosts = await getCollection('posts');
15
26
  const publishedPosts = allPosts
16
27
  .filter(post => !post.data.draft)
17
28
  .sort((a, b) => (b.data.pubDate?.getTime() ?? 0) - (a.data.pubDate?.getTime() ?? 0));
18
29
 
19
- const featuredPosts = publishedPosts.slice(0, count).map(post => ({
30
+ // Filter posts by current locale
31
+ const localePosts = filterPostsByLocale(publishedPosts, currentLocale, i18nConfig);
32
+
33
+ const featuredPosts = localePosts.slice(0, count).map(post => ({
20
34
  slug: post.id.toLowerCase(),
21
35
  title: post.data.title,
22
36
  description: post.data.description,
@@ -26,19 +40,21 @@ const featuredPosts = publishedPosts.slice(0, count).map(post => ({
26
40
  author: post.data.author,
27
41
  readingTime: 5
28
42
  }));
43
+
44
+ const postsUrl = `${localePrefix}/posts`;
29
45
  ---
30
46
 
31
47
  {featuredPosts.length > 0 && (
32
48
  <section class="mb-16">
33
49
  <div class="flex items-center justify-between mb-8">
34
50
  <h2 class="text-2xl font-bold text-slate-900 dark:text-slate-100">
35
- 特色文章
51
+ {ui.posts}
36
52
  </h2>
37
53
  <a
38
- href="/posts"
54
+ href={postsUrl}
39
55
  class="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400 font-medium text-sm flex items-center space-x-1 transition-colors"
40
56
  >
41
- <span>查看全部</span>
57
+ <span>{ui.allPosts}</span>
42
58
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
43
59
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
44
60
  </svg>
@@ -47,7 +63,7 @@ const featuredPosts = publishedPosts.slice(0, count).map(post => ({
47
63
 
48
64
  <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
49
65
  {featuredPosts.map((post) => (
50
- <PostCard post={post} featured={true} />
66
+ <PostCard post={post} featured={true} localePrefix={localePrefix} locale={localeConfig.locale.dateLocale} ui={ui} />
51
67
  ))}
52
68
  </div>
53
69
  </section>
@@ -4,10 +4,29 @@
4
4
  */
5
5
  import NavigationTabs from '../../components/blog/NavigationTabs.vue';
6
6
  import { getCollection } from 'astro:content';
7
+ import type { I18nConfig } from '../../config/i18n';
8
+ import { defaultI18nConfig } from '../../config/i18n';
9
+ import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
10
+ import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, filterPostsByLocale } from '../../utils/i18n';
7
11
 
8
- const allPosts = await getCollection('posts');
9
- const publishedPosts = allPosts
10
- .filter(post => !post.data.draft)
12
+ interface Props {
13
+ i18nConfig?: I18nConfig;
14
+ }
15
+
16
+ const { i18nConfig = virtualI18nConfig || defaultI18nConfig } = Astro.props;
17
+
18
+ // Get current locale and translations
19
+ const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
20
+ const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
21
+ const ui = localeConfig.ui;
22
+ const localePrefix = getLocalePrefix(currentLocale, i18nConfig);
23
+
24
+ const allPosts = await getCollection('posts', ({ data }) => !data.draft);
25
+
26
+ // Filter posts by locale
27
+ const localePosts = filterPostsByLocale(allPosts, currentLocale, i18nConfig);
28
+
29
+ const publishedPosts = localePosts
11
30
  .sort((a, b) => (b.data.pubDate?.getTime() ?? 0) - (a.data.pubDate?.getTime() ?? 0));
12
31
 
13
32
  const posts = publishedPosts.map(post => ({
@@ -68,7 +87,7 @@ const timelineData = posts.slice(0, 10).map(post => ({
68
87
  <section class="mb-16">
69
88
  <div class="flex items-center justify-between mb-8">
70
89
  <h2 class="text-2xl font-bold text-slate-900 dark:text-slate-100">
71
- 快速导航
90
+ {ui.quickNavigation}
72
91
  </h2>
73
92
  </div>
74
93
  <NavigationTabs
@@ -77,5 +96,15 @@ const timelineData = posts.slice(0, 10).map(post => ({
77
96
  archives={archivesData}
78
97
  categories={categoriesData}
79
98
  timeline={timelineData}
99
+ localePrefix={localePrefix}
100
+ dateLocale={localeConfig.locale?.dateLocale || currentLocale}
101
+ ui={{
102
+ tags: ui.tags,
103
+ archives: ui.archives,
104
+ categories: ui.categories,
105
+ timeline: ui.timeline,
106
+ viewAllTimeline: ui.viewAllTimeline,
107
+ postsCount: ui.postsCount,
108
+ }}
80
109
  />
81
110
  </section>
@@ -4,19 +4,33 @@
4
4
  */
5
5
  import PostCard from '../../components/blog/PostCard.astro';
6
6
  import { getCollection } from 'astro:content';
7
+ import type { I18nConfig } from '../../config/i18n';
8
+ import { defaultI18nConfig } from '../../config/i18n';
9
+ import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
10
+ import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, filterPostsByLocale } from '../../utils/i18n';
7
11
 
8
12
  export interface Props {
9
13
  count?: number;
14
+ i18nConfig?: I18nConfig;
10
15
  }
11
16
 
12
- const { count = 6 } = Astro.props;
17
+ const { count = 6, i18nConfig = virtualI18nConfig || defaultI18nConfig } = Astro.props;
18
+
19
+ // Get current locale from URL
20
+ const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
21
+ const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
22
+ const ui = localeConfig.ui;
23
+ const localePrefix = getLocalePrefix(currentLocale, i18nConfig);
13
24
 
14
25
  const allPosts = await getCollection('posts');
15
26
  const publishedPosts = allPosts
16
27
  .filter(post => !post.data.draft)
17
28
  .sort((a, b) => (b.data.pubDate?.getTime() ?? 0) - (a.data.pubDate?.getTime() ?? 0));
18
29
 
19
- const recentPosts = publishedPosts.slice(0, count).map(post => ({
30
+ // Filter posts by current locale
31
+ const localePosts = filterPostsByLocale(publishedPosts, currentLocale, i18nConfig);
32
+
33
+ const recentPosts = localePosts.slice(0, count).map(post => ({
20
34
  slug: post.id.toLowerCase(),
21
35
  title: post.data.title,
22
36
  description: post.data.description,
@@ -26,18 +40,20 @@ const recentPosts = publishedPosts.slice(0, count).map(post => ({
26
40
  author: post.data.author,
27
41
  readingTime: 5
28
42
  }));
43
+
44
+ const postsUrl = `${localePrefix}/posts`;
29
45
  ---
30
46
 
31
47
  <section class="mb-16">
32
48
  <div class="flex items-center justify-between mb-8">
33
49
  <h2 class="text-2xl font-bold text-slate-900 dark:text-slate-100">
34
- 最新文章
50
+ {ui.recentPosts}
35
51
  </h2>
36
52
  <a
37
- href="/posts"
53
+ href={postsUrl}
38
54
  class="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400 font-medium text-sm flex items-center space-x-1 transition-colors"
39
55
  >
40
- <span>查看全部</span>
56
+ <span>{ui.allPosts}</span>
41
57
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
42
58
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
43
59
  </svg>
@@ -46,7 +62,7 @@ const recentPosts = publishedPosts.slice(0, count).map(post => ({
46
62
 
47
63
  <div class="space-y-6">
48
64
  {recentPosts.map((post) => (
49
- <PostCard post={post} layout="horizontal" />
65
+ <PostCard post={post} layout="horizontal" localePrefix={localePrefix} />
50
66
  ))}
51
67
  </div>
52
68
  </section>
@@ -3,13 +3,31 @@
3
3
  * 首页统计信息组件
4
4
  */
5
5
  import { getCollection } from 'astro:content';
6
+ import type { I18nConfig } from '../../config/i18n';
7
+ import { defaultI18nConfig } from '../../config/i18n';
8
+ import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
9
+ import { getLocaleFromPath, getLocaleConfig, filterPostsByLocale } from '../../utils/i18n';
10
+
11
+ export interface Props {
12
+ i18nConfig?: I18nConfig;
13
+ }
14
+
15
+ const { i18nConfig = virtualI18nConfig || defaultI18nConfig } = Astro.props;
16
+
17
+ // Get current locale from URL
18
+ const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
19
+ const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
20
+ const ui = localeConfig.ui;
6
21
 
7
22
  const allPosts = await getCollection('posts');
8
23
  const publishedPosts = allPosts.filter(post => !post.data.draft);
9
24
 
10
- const totalPosts = publishedPosts.length;
11
- const allTags = new Set(publishedPosts.flatMap(post => post.data.tags || []));
12
- const allCategories = new Set(publishedPosts.flatMap(post => post.data.categories || []));
25
+ // Filter posts by current locale
26
+ const localePosts = filterPostsByLocale(publishedPosts, currentLocale, i18nConfig);
27
+
28
+ const totalPosts = localePosts.length;
29
+ const allTags = new Set(localePosts.flatMap(post => post.data.tags || []));
30
+ const allCategories = new Set(localePosts.flatMap(post => post.data.categories || []));
13
31
  const totalTags = allTags.size;
14
32
  const totalCategories = allCategories.size;
15
33
  ---
@@ -21,7 +39,7 @@ const totalCategories = allCategories.size;
21
39
  {totalPosts}
22
40
  </div>
23
41
  <div class="text-sm text-slate-600 dark:text-slate-400">
24
- 文章总数
42
+ {ui.posts}
25
43
  </div>
26
44
  </div>
27
45
  <div class="card text-center">
@@ -29,7 +47,7 @@ const totalCategories = allCategories.size;
29
47
  {totalTags}
30
48
  </div>
31
49
  <div class="text-sm text-slate-600 dark:text-slate-400">
32
- 标签数量
50
+ {ui.tags}
33
51
  </div>
34
52
  </div>
35
53
  <div class="card text-center">
@@ -37,7 +55,7 @@ const totalCategories = allCategories.size;
37
55
  {totalCategories}
38
56
  </div>
39
57
  <div class="text-sm text-slate-600 dark:text-slate-400">
40
- 分类数量
58
+ {ui.categories}
41
59
  </div>
42
60
  </div>
43
61
  </div>
@@ -6,6 +6,7 @@ import MobileMenu from '../ui/MobileMenu.vue';
6
6
  import LanguageSwitcher from '../ui/LanguageSwitcher.vue';
7
7
  import type { I18nConfig } from '../../config/i18n';
8
8
  import { defaultI18nConfig } from '../../config/i18n';
9
+ import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
9
10
  import {
10
11
  getLocaleFromPath,
11
12
  getLocaleConfig,
@@ -17,7 +18,7 @@ export interface Props {
17
18
  i18nConfig?: I18nConfig;
18
19
  }
19
20
 
20
- const { i18nConfig = defaultI18nConfig } = Astro.props;
21
+ const { i18nConfig = virtualI18nConfig || defaultI18nConfig } = Astro.props;
21
22
 
22
23
  const currentPath = Astro.url.pathname;
23
24
  const currentLocale = getLocaleFromPath(currentPath, i18nConfig);
@@ -55,11 +56,13 @@ const localesForSwitcher = i18nConfig.locales.map(l => ({
55
56
  )}
56
57
  <div class="hidden sm:block">
57
58
  <h1 class="text-xl font-bold text-slate-900 dark:text-slate-100">
58
- {localeSiteConfig.title || siteConfig.title}
59
+ {localeSiteConfig.title ?? siteConfig.title}
59
60
  </h1>
60
- <p class="text-xs text-slate-600 dark:text-slate-400 -mt-1">
61
- {localeSiteConfig.description || siteConfig.description}
62
- </p>
61
+ {(localeSiteConfig.description ?? siteConfig.description) && (
62
+ <p class="text-xs text-slate-600 dark:text-slate-400 -mt-1">
63
+ {localeSiteConfig.description ?? siteConfig.description}
64
+ </p>
65
+ )}
63
66
  </div>
64
67
  </a>
65
68
  </div>
@@ -85,6 +88,7 @@ const localesForSwitcher = i18nConfig.locales.map(l => ({
85
88
  client:load
86
89
  placeholder={ui.searchPlaceholder}
87
90
  searchLabel={ui.search}
91
+ noResultsText={ui.noResults}
88
92
  />
89
93
  </div>
90
94
 
@@ -10,7 +10,7 @@ import {
10
10
  } from '@jet-w/astro-blog/utils/sidebar';
11
11
  import type { I18nConfig } from '../../config/i18n';
12
12
  import { defaultI18nConfig } from '../../config/i18n';
13
- import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, formatDate } from '../../utils/i18n';
13
+ import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, formatDate, filterPostsByLocale } from '../../utils/i18n';
14
14
 
15
15
  interface Props {
16
16
  currentPath?: string;
@@ -31,26 +31,29 @@ const sidebarConfig = localeConfig.sidebar;
31
31
  // 获取所有文章
32
32
  const allPosts = await getCollection('posts', ({ data }) => !data.draft);
33
33
 
34
+ // Filter posts by current locale for sidebar widgets
35
+ const localePosts = filterPostsByLocale(allPosts, currentLocale, i18nConfig);
36
+
34
37
  // 根据当前路径过滤侧边栏组
35
38
  const filteredGroups = filterGroupsByPath(sidebarConfig.groups, currentPath);
36
39
 
37
40
  // 创建过滤后的配置
38
41
  const filteredConfig = { ...sidebarConfig, groups: filteredGroups };
39
42
 
40
- // 处理侧边栏配置
41
- const processedGroups = await processSidebarConfig(filteredConfig, allPosts);
43
+ // 处理侧边栏配置 - 使用按语言过滤后的文章
44
+ const processedGroups = await processSidebarConfig(filteredConfig, localePosts);
42
45
 
43
- // 获取其他数据
46
+ // 获取其他数据 - 使用按语言过滤后的文章
44
47
  const recentPosts = sidebarConfig.showRecentPosts
45
- ? getRecentPosts(allPosts, sidebarConfig.recentPostsCount)
48
+ ? getRecentPosts(localePosts, sidebarConfig.recentPostsCount)
46
49
  : [];
47
50
 
48
51
  const popularTags = sidebarConfig.showPopularTags
49
- ? getPopularTags(allPosts, sidebarConfig.popularTagsCount)
52
+ ? getPopularTags(localePosts, sidebarConfig.popularTagsCount)
50
53
  : [];
51
54
 
52
55
  const archives = sidebarConfig.showArchives
53
- ? getArchives(allPosts, sidebarConfig.archivesCount)
56
+ ? getArchives(localePosts, sidebarConfig.archivesCount)
54
57
  : [];
55
58
  ---
56
59
 
@@ -197,7 +200,7 @@ const archives = sidebarConfig.showArchives
197
200
  </a>
198
201
  ) : (
199
202
  <a
200
- href={`/posts/${item.slug?.toLowerCase()}`}
203
+ href={`${localePrefix}/posts/${item.slug?.toLowerCase()}`}
201
204
  class="tree-file flex items-center gap-2 px-2 py-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors"
202
205
  title={item.title}
203
206
  >
@@ -238,7 +241,7 @@ const archives = sidebarConfig.showArchives
238
241
  </a>
239
242
  ) : (
240
243
  <a
241
- href={`/posts/${grandChild.slug?.toLowerCase()}`}
244
+ href={`${localePrefix}/posts/${grandChild.slug?.toLowerCase()}`}
242
245
  class="tree-file flex items-center gap-2 px-2 py-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors"
243
246
  title={grandChild.title}
244
247
  >
@@ -289,7 +292,7 @@ const archives = sidebarConfig.showArchives
289
292
  </a>
290
293
  ) : (
291
294
  <a
292
- href={`/posts/${child.slug?.toLowerCase()}`}
295
+ href={`${localePrefix}/posts/${child.slug?.toLowerCase()}`}
293
296
  class="tree-file flex items-center gap-2 px-2 py-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors"
294
297
  title={child.title}
295
298
  >
@@ -340,7 +343,7 @@ const archives = sidebarConfig.showArchives
340
343
  </a>
341
344
  ) : (
342
345
  <a
343
- href={`/posts/${node.slug?.toLowerCase()}`}
346
+ href={`${localePrefix}/posts/${node.slug?.toLowerCase()}`}
344
347
  class="tree-file flex items-center gap-2 px-2 py-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors"
345
348
  title={node.title}
346
349
  >
@@ -2,12 +2,13 @@
2
2
  <div class="relative">
3
3
  <div class="relative">
4
4
  <input
5
+ ref="searchInputRef"
5
6
  v-model="searchQuery"
6
7
  @input="handleInput"
7
8
  @focus="handleFocus"
8
9
  @blur="handleBlur"
9
10
  type="text"
10
- placeholder="搜索文章..."
11
+ :placeholder="props.placeholder || '搜索文章...'"
11
12
  class="w-64 pl-10 pr-4 py-2 text-sm bg-slate-100 dark:bg-slate-800 border border-slate-300 dark:border-slate-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-colors"
12
13
  />
13
14
  <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
@@ -24,7 +25,7 @@
24
25
  class="absolute top-full left-0 right-0 mt-2 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg shadow-lg z-50 max-h-96 overflow-y-auto"
25
26
  >
26
27
  <div v-if="searchResults.length === 0 && searchQuery.length > 0" class="p-4 text-center text-slate-500 dark:text-slate-400">
27
- 没有找到相关文章
28
+ {{ props.noResultsText || '没有找到相关文章' }}
28
29
  </div>
29
30
  <div v-else class="py-2">
30
31
  <a
@@ -55,6 +56,12 @@
55
56
  <script setup lang="ts">
56
57
  import { ref, onMounted } from 'vue'
57
58
 
59
+ const props = defineProps<{
60
+ placeholder?: string;
61
+ searchLabel?: string;
62
+ noResultsText?: string;
63
+ }>();
64
+
58
65
  interface SearchResult {
59
66
  title: string
60
67
  description: string
@@ -145,14 +152,15 @@ const highlightText = (text: string) => {
145
152
  }
146
153
 
147
154
  // 键盘快捷键支持
155
+ const searchInputRef = ref<HTMLInputElement | null>(null)
156
+
148
157
  onMounted(() => {
149
158
  // Ctrl/Cmd + K 打开搜索
150
159
  document.addEventListener('keydown', (e) => {
151
160
  if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
152
161
  e.preventDefault()
153
- const input = document.querySelector('input[placeholder="搜索文章..."]') as HTMLInputElement
154
- if (input) {
155
- input.focus()
162
+ if (searchInputRef.value) {
163
+ searchInputRef.value.focus()
156
164
  }
157
165
  }
158
166
  })