@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
@@ -0,0 +1,133 @@
1
+ import { I as I18nConfig, L as Locale, F as FooterConfig, U as UITranslations } from '../i18n-DYYPTq4o.js';
2
+ export { n as LocaleConfig, j as builtInTranslations, g as defaultI18nConfig, h as defineI18nConfig, k as enTranslations, i as getUITranslations, z as zhCNTranslations } from '../i18n-DYYPTq4o.js';
3
+ import { SiteConfig, NavigationItem } from '../types/index.js';
4
+ import { S as SidebarConfig } from '../sidebar-DNdiCKBw.js';
5
+
6
+ /**
7
+ * i18n Utility Functions
8
+ *
9
+ * Provides helper functions for multi-language support.
10
+ */
11
+
12
+ /**
13
+ * Merged locale configuration with all defaults applied
14
+ */
15
+ interface MergedLocaleConfig {
16
+ locale: Locale;
17
+ site: SiteConfig;
18
+ menu: NavigationItem[];
19
+ footer: FooterConfig;
20
+ sidebar: SidebarConfig;
21
+ ui: UITranslations;
22
+ }
23
+ /**
24
+ * Alternate link for SEO (hreflang)
25
+ */
26
+ interface AlternateLink {
27
+ locale: string;
28
+ url: string;
29
+ hreflang: string;
30
+ }
31
+ /**
32
+ * Get current locale from URL pathname
33
+ *
34
+ * @example
35
+ * getLocaleFromPath('/en/posts', config) // 'en'
36
+ * getLocaleFromPath('/posts', config) // 'zh-CN' (default)
37
+ * getLocaleFromPath('/zh-CN/about', config) // 'zh-CN'
38
+ */
39
+ declare function getLocaleFromPath(pathname: string, config?: I18nConfig): string;
40
+ /**
41
+ * Get locale data by code
42
+ */
43
+ declare function getLocaleByCode(code: string, config?: I18nConfig): Locale | undefined;
44
+ /**
45
+ * Remove locale prefix from pathname
46
+ *
47
+ * @example
48
+ * removeLocalePrefix('/en/posts', config) // '/posts'
49
+ * removeLocalePrefix('/posts', config) // '/posts'
50
+ */
51
+ declare function removeLocalePrefix(pathname: string, config?: I18nConfig): string;
52
+ /**
53
+ * Get localized path for a given locale
54
+ *
55
+ * @example
56
+ * getLocalizedPath('/posts', 'en', config) // '/en/posts'
57
+ * getLocalizedPath('/en/posts', 'zh-CN', config) // '/posts' (if zh-CN is default)
58
+ */
59
+ declare function getLocalizedPath(pathname: string, targetLocale: string, config?: I18nConfig): string;
60
+ /**
61
+ * Get all alternate links for SEO (hreflang tags)
62
+ *
63
+ * @example
64
+ * getAlternateLinks('/posts', 'https://example.com', config)
65
+ * // Returns links for all locales
66
+ */
67
+ declare function getAlternateLinks(pathname: string, baseUrl: string, config?: I18nConfig): AlternateLink[];
68
+ /**
69
+ * Get merged configuration for a specific locale
70
+ * Combines default config with locale-specific overrides
71
+ */
72
+ declare function getLocaleConfig(locale: string, config?: I18nConfig): MergedLocaleConfig;
73
+ /**
74
+ * Translation function - get a UI translation string
75
+ *
76
+ * @example
77
+ * t('readMore', 'en') // 'Read more'
78
+ * t('readMore', 'zh-CN') // '阅读更多'
79
+ */
80
+ declare function t(key: keyof UITranslations, locale: string, config?: I18nConfig): string;
81
+ /**
82
+ * Format date according to locale
83
+ *
84
+ * @example
85
+ * formatDate(new Date(), 'en') // 'January 1, 2024'
86
+ * formatDate(new Date(), 'zh-CN') // '2024年1月1日'
87
+ */
88
+ declare function formatDate(date: Date | string, locale: string, options?: Intl.DateTimeFormatOptions): string;
89
+ /**
90
+ * Format date in short format
91
+ *
92
+ * @example
93
+ * formatDateShort(new Date(), 'en') // '1/1/2024'
94
+ * formatDateShort(new Date(), 'zh-CN') // '2024/1/1'
95
+ */
96
+ declare function formatDateShort(date: Date | string, locale: string): string;
97
+ /**
98
+ * Check if a locale is RTL (right-to-left)
99
+ */
100
+ declare function isRTL(locale: string, config?: I18nConfig): boolean;
101
+ /**
102
+ * Get the HTML dir attribute value
103
+ */
104
+ declare function getTextDirection(locale: string, config?: I18nConfig): 'ltr' | 'rtl';
105
+ /**
106
+ * Check if multi-language is enabled (more than one locale)
107
+ */
108
+ declare function isMultiLanguageEnabled(config?: I18nConfig): boolean;
109
+ /**
110
+ * Get prefix for a locale in routes
111
+ * Returns empty string for default locale if prefixDefaultLocale is false
112
+ */
113
+ declare function getLocalePrefix(locale: string, config?: I18nConfig): string;
114
+ /**
115
+ * Get content path prefix for a specific locale
116
+ * Returns the contentPathPrefix from locale config, or undefined if not set
117
+ */
118
+ declare function getContentPathPrefix(locale: string, config?: I18nConfig): string | undefined;
119
+ /**
120
+ * Filter posts by locale based on contentPathPrefix
121
+ * If contentPathPrefix is set, only return posts that start with that prefix
122
+ * If not set, return all posts (backward compatible)
123
+ *
124
+ * @example
125
+ * // If en locale has contentPathPrefix: 'blog_docs_en'
126
+ * filterPostsByLocale(posts, 'en', config)
127
+ * // Returns only posts with id starting with 'blog_docs_en/'
128
+ */
129
+ declare function filterPostsByLocale<T extends {
130
+ id: string;
131
+ }>(posts: T[], locale: string, config?: I18nConfig): T[];
132
+
133
+ export { type AlternateLink, I18nConfig, Locale, type MergedLocaleConfig, UITranslations, filterPostsByLocale, formatDate, formatDateShort, getAlternateLinks, getContentPathPrefix, getLocaleByCode, getLocaleConfig, getLocaleFromPath, getLocalePrefix, getLocalizedPath, getTextDirection, isMultiLanguageEnabled, isRTL, removeLocalePrefix, t };
@@ -0,0 +1,49 @@
1
+ import {
2
+ filterPostsByLocale,
3
+ formatDate,
4
+ formatDateShort,
5
+ getAlternateLinks,
6
+ getContentPathPrefix,
7
+ getLocaleByCode,
8
+ getLocaleConfig,
9
+ getLocaleFromPath,
10
+ getLocalePrefix,
11
+ getLocalizedPath,
12
+ getTextDirection,
13
+ isMultiLanguageEnabled,
14
+ isRTL,
15
+ removeLocalePrefix,
16
+ t
17
+ } from "../chunk-Z3O3JK56.js";
18
+ import "../chunk-DAH2XP4W.js";
19
+ import {
20
+ builtInTranslations,
21
+ defaultI18nConfig,
22
+ defineI18nConfig,
23
+ enTranslations,
24
+ getUITranslations,
25
+ zhCNTranslations
26
+ } from "../chunk-A2E2VSAQ.js";
27
+ export {
28
+ builtInTranslations,
29
+ defaultI18nConfig,
30
+ defineI18nConfig,
31
+ enTranslations,
32
+ filterPostsByLocale,
33
+ formatDate,
34
+ formatDateShort,
35
+ getAlternateLinks,
36
+ getContentPathPrefix,
37
+ getLocaleByCode,
38
+ getLocaleConfig,
39
+ getLocaleFromPath,
40
+ getLocalePrefix,
41
+ getLocalizedPath,
42
+ getTextDirection,
43
+ getUITranslations,
44
+ isMultiLanguageEnabled,
45
+ isRTL,
46
+ removeLocalePrefix,
47
+ t,
48
+ zhCNTranslations
49
+ };
@@ -1,4 +1,4 @@
1
- import { S as SidebarItem, a as SidebarGroup, b as SidebarConfig } from '../sidebar-Da-W_4Lr.js';
1
+ import { c as SidebarItem, b as SidebarGroup, S as SidebarConfig } from '../sidebar-DNdiCKBw.js';
2
2
 
3
3
  /**
4
4
  * 侧边栏工具函数
@@ -0,0 +1,74 @@
1
+ import { ComputedRef } from 'vue';
2
+ import { U as UITranslations, I as I18nConfig } from '../i18n-DYYPTq4o.js';
3
+ export { L as Locale } from '../i18n-DYYPTq4o.js';
4
+ import '../types/index.js';
5
+ import '../sidebar-DNdiCKBw.js';
6
+
7
+ /**
8
+ * Vue Composable for i18n
9
+ *
10
+ * Provides i18n support for Vue components in the blog.
11
+ */
12
+
13
+ /**
14
+ * i18n injection keys
15
+ */
16
+ declare const I18N_LOCALE_KEY: unique symbol;
17
+ declare const I18N_CONFIG_KEY: unique symbol;
18
+ declare const I18N_TRANSLATIONS_KEY: unique symbol;
19
+ /**
20
+ * i18n context provided to Vue components
21
+ */
22
+ interface I18nContext {
23
+ locale: string;
24
+ translations: UITranslations;
25
+ config?: I18nConfig;
26
+ }
27
+ /**
28
+ * Return type of useI18n composable
29
+ */
30
+ interface UseI18nReturn {
31
+ /** Current locale code */
32
+ locale: ComputedRef<string>;
33
+ /** Translation function */
34
+ t: (key: keyof UITranslations) => string;
35
+ /** Format date according to locale */
36
+ formatDate: (date: Date | string, options?: Intl.DateTimeFormatOptions) => string;
37
+ /** Format date in short format */
38
+ formatDateShort: (date: Date | string) => string;
39
+ /** All translations for current locale */
40
+ translations: ComputedRef<UITranslations>;
41
+ }
42
+ /**
43
+ * Vue composable for i18n support
44
+ *
45
+ * @example
46
+ * ```vue
47
+ * <script setup>
48
+ * import { useI18n } from '@jet-w/astro-blog/utils/useI18n';
49
+ *
50
+ * const { t, formatDate, locale } = useI18n();
51
+ * </script>
52
+ *
53
+ * <template>
54
+ * <h1>{{ t('postList') }}</h1>
55
+ * <span>{{ formatDate(post.pubDate) }}</span>
56
+ * </template>
57
+ * ```
58
+ */
59
+ declare function useI18n(): UseI18nReturn;
60
+ /**
61
+ * Create i18n context for providing to Vue components
62
+ *
63
+ * @example
64
+ * ```astro
65
+ * ---
66
+ * import { createI18nContext } from '@jet-w/astro-blog/utils/useI18n';
67
+ * const i18nContext = createI18nContext('en', i18nConfig);
68
+ * ---
69
+ * <Component client:load {...i18nContext} />
70
+ * ```
71
+ */
72
+ declare function createI18nContext(locale: string, config?: I18nConfig): I18nContext;
73
+
74
+ export { I18N_CONFIG_KEY, I18N_LOCALE_KEY, I18N_TRANSLATIONS_KEY, I18nConfig, type I18nContext, UITranslations, type UseI18nReturn, createI18nContext, useI18n };
@@ -0,0 +1,15 @@
1
+ import {
2
+ I18N_CONFIG_KEY,
3
+ I18N_LOCALE_KEY,
4
+ I18N_TRANSLATIONS_KEY,
5
+ createI18nContext,
6
+ useI18n
7
+ } from "../chunk-PZICDGJG.js";
8
+ import "../chunk-A2E2VSAQ.js";
9
+ export {
10
+ I18N_CONFIG_KEY,
11
+ I18N_LOCALE_KEY,
12
+ I18N_TRANSLATIONS_KEY,
13
+ createI18nContext,
14
+ useI18n
15
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jet-w/astro-blog",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "A modern Astro blog theme with Vue and Tailwind CSS support",
5
5
  "type": "module",
6
6
  "exports": {
@@ -24,6 +24,14 @@
24
24
  "./utils/sidebar": {
25
25
  "types": "./dist/utils/sidebar.d.ts",
26
26
  "import": "./dist/utils/sidebar.js"
27
+ },
28
+ "./utils/i18n": {
29
+ "types": "./dist/utils/i18n.d.ts",
30
+ "import": "./dist/utils/i18n.js"
31
+ },
32
+ "./utils/useI18n": {
33
+ "types": "./dist/utils/useI18n.d.ts",
34
+ "import": "./dist/utils/useI18n.js"
27
35
  }
28
36
  },
29
37
  "files": [
@@ -9,7 +9,7 @@
9
9
  <button
10
10
  class="p-3 bg-white dark:bg-slate-800 rounded-full shadow-lg border border-slate-200 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors"
11
11
  :class="{ 'bg-primary-50 dark:bg-primary-900/30': showToc }"
12
- aria-label="显示目录"
12
+ :aria-label="tocTitle"
13
13
  >
14
14
  <svg class="w-5 h-5 text-slate-600 dark:text-slate-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
15
15
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" />
@@ -35,7 +35,7 @@
35
35
  <svg class="w-4 h-4 text-primary-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
36
36
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
37
37
  </svg>
38
- 页面目录
38
+ {{ tocTitle }}
39
39
  </h3>
40
40
  </div>
41
41
 
@@ -66,7 +66,7 @@
66
66
  <!-- 进度条 -->
67
67
  <div class="px-4 py-2 border-t border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-900/50">
68
68
  <div class="flex items-center justify-between text-xs text-slate-500 dark:text-slate-400 mb-1">
69
- <span>阅读进度</span>
69
+ <span>{{ progressTitle }}</span>
70
70
  <span>{{ Math.round(progress * 100) }}%</span>
71
71
  </div>
72
72
  <div class="w-full bg-slate-200 dark:bg-slate-700 rounded-full h-1.5">
@@ -91,6 +91,14 @@ interface Heading {
91
91
  level: number
92
92
  }
93
93
 
94
+ const props = withDefaults(defineProps<{
95
+ tocTitle?: string
96
+ progressTitle?: string
97
+ }>(), {
98
+ tocTitle: 'Table of Contents',
99
+ progressTitle: 'Reading Progress',
100
+ })
101
+
94
102
  const showToc = ref(false)
95
103
  const headings = ref<Heading[]>([])
96
104
  const activeId = ref('')
@@ -1,5 +1,20 @@
1
1
  ---
2
2
  import { siteConfig } from '@jet-w/astro-blog/config';
3
+ import type { I18nConfig } from '../../config/i18n';
4
+ import { defaultI18nConfig } from '../../config/i18n';
5
+ import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
6
+ import { getLocaleFromPath, getLocaleConfig } from '../../utils/i18n';
7
+
8
+ export interface Props {
9
+ i18nConfig?: I18nConfig;
10
+ }
11
+
12
+ const { i18nConfig = virtualI18nConfig || defaultI18nConfig } = Astro.props;
13
+
14
+ // Get current locale from URL
15
+ const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
16
+ const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
17
+ const ui = localeConfig.ui;
3
18
  ---
4
19
 
5
20
  <section class="py-16 mb-16">
@@ -89,7 +104,7 @@ import { siteConfig } from '@jet-w/astro-blog/config';
89
104
  href="/posts"
90
105
  class="btn-primary inline-flex items-center space-x-2"
91
106
  >
92
- <span>浏览文章</span>
107
+ <span>{ui.browsePosts}</span>
93
108
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
94
109
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
95
110
  </svg>
@@ -99,7 +114,7 @@ import { siteConfig } from '@jet-w/astro-blog/config';
99
114
  href="/about"
100
115
  class="btn-secondary inline-flex items-center space-x-2"
101
116
  >
102
- <span>关于我</span>
117
+ <span>{ui.aboutMe}</span>
103
118
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
104
119
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
105
120
  </svg>
@@ -26,7 +26,7 @@
26
26
  <a
27
27
  v-for="tag in tags"
28
28
  :key="tag.name"
29
- :href="`/tags/${encodeTag(tag.name)}`"
29
+ :href="`${props.localePrefix}/tags/${encodeTag(tag.name)}`"
30
30
  class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-sm transition-colors"
31
31
  :class="getTagColorClass(tag.count)"
32
32
  >
@@ -40,7 +40,7 @@
40
40
  <a
41
41
  v-for="archive in archives"
42
42
  :key="archive.key"
43
- :href="`/archives/${archive.year}/${archive.month}`"
43
+ :href="`${props.localePrefix}/archives/${archive.year}/${archive.month}`"
44
44
  class="flex items-center justify-between px-3 py-2 rounded-lg hover:bg-slate-50 dark:hover:bg-slate-700/50 transition-colors group"
45
45
  >
46
46
  <div class="flex items-center gap-3">
@@ -48,11 +48,11 @@
48
48
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
49
49
  </svg>
50
50
  <span class="text-slate-700 dark:text-slate-300 group-hover:text-primary-600 dark:group-hover:text-primary-400">
51
- {{ archive.year }}年{{ archive.month }}
51
+ {{ formatArchiveDate(archive.year, archive.month) }}
52
52
  </span>
53
53
  </div>
54
54
  <span class="text-xs text-slate-400 bg-slate-100 dark:bg-slate-700 px-2 py-0.5 rounded-full">
55
- {{ archive.count }}
55
+ {{ archive.count }} {{ props.ui?.postsCount || 'posts' }}
56
56
  </span>
57
57
  </a>
58
58
  </div>
@@ -62,7 +62,7 @@
62
62
  <a
63
63
  v-for="category in categories"
64
64
  :key="category.name"
65
- :href="`/categories/${encodeCategory(category.name)}`"
65
+ :href="`${props.localePrefix}/categories/${encodeCategory(category.name)}`"
66
66
  class="flex items-center justify-between px-3 py-2 rounded-lg hover:bg-slate-50 dark:hover:bg-slate-700/50 transition-colors group"
67
67
  >
68
68
  <div class="flex items-center gap-3">
@@ -74,7 +74,7 @@
74
74
  </span>
75
75
  </div>
76
76
  <span class="text-xs text-slate-400 bg-slate-100 dark:bg-slate-700 px-2 py-0.5 rounded-full">
77
- {{ category.count }}
77
+ {{ category.count }} {{ props.ui?.postsCount || 'posts' }}
78
78
  </span>
79
79
  </a>
80
80
  </div>
@@ -86,7 +86,7 @@
86
86
  <div v-for="item in timeline" :key="item.slug" class="relative pl-10">
87
87
  <div class="absolute left-2.5 w-3 h-3 rounded-full bg-primary-500 border-2 border-white dark:border-slate-800"></div>
88
88
  <a
89
- :href="`/posts/${item.slug}`"
89
+ :href="`${props.localePrefix}/posts/${item.slug}`"
90
90
  class="block p-3 rounded-lg hover:bg-slate-50 dark:hover:bg-slate-700/50 transition-colors group"
91
91
  >
92
92
  <div class="text-xs text-slate-400 mb-1">{{ formatDate(item.pubDate) }}</div>
@@ -97,10 +97,10 @@
97
97
  </div>
98
98
  </div>
99
99
  <a
100
- href="/archives"
100
+ :href="`${props.localePrefix}/archives`"
101
101
  class="mt-4 flex items-center justify-center gap-2 py-2 text-sm text-primary-500 hover:text-primary-600 dark:hover:text-primary-400 transition-colors"
102
102
  >
103
- <span>查看全部时间轴</span>
103
+ <span>{{ props.ui?.viewAllTimeline || 'View all timeline' }}</span>
104
104
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
105
105
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
106
106
  </svg>
@@ -136,14 +136,37 @@ interface TimelineItem {
136
136
  pubDate: string
137
137
  }
138
138
 
139
+ interface UIProps {
140
+ tags?: string
141
+ archives?: string
142
+ categories?: string
143
+ timeline?: string
144
+ viewAllTimeline?: string
145
+ postsCount?: string
146
+ }
147
+
139
148
  interface Props {
140
149
  tags: TagItem[]
141
150
  archives: ArchiveItem[]
142
151
  categories: CategoryItem[]
143
152
  timeline: TimelineItem[]
153
+ localePrefix?: string
154
+ dateLocale?: string
155
+ ui?: UIProps
144
156
  }
145
157
 
146
- const props = defineProps<Props>()
158
+ const props = withDefaults(defineProps<Props>(), {
159
+ localePrefix: '',
160
+ dateLocale: 'en',
161
+ ui: () => ({
162
+ tags: 'Tags',
163
+ archives: 'Archives',
164
+ categories: 'Categories',
165
+ timeline: 'Timeline',
166
+ viewAllTimeline: 'View all timeline',
167
+ postsCount: 'posts',
168
+ })
169
+ })
147
170
 
148
171
  const activeTab = ref('tags')
149
172
 
@@ -201,10 +224,10 @@ const TimelineIcon = {
201
224
  }
202
225
 
203
226
  const tabs = computed(() => [
204
- { id: 'tags', name: '标签', count: props.tags.length, icon: TagIcon },
205
- { id: 'archives', name: '归档', count: props.archives.length, icon: ArchiveIcon },
206
- { id: 'categories', name: '分类', count: props.categories.length, icon: CategoryIcon },
207
- { id: 'timeline', name: '时间轴', count: props.timeline.length, icon: TimelineIcon },
227
+ { id: 'tags', name: props.ui?.tags || 'Tags', count: props.tags.length, icon: TagIcon },
228
+ { id: 'archives', name: props.ui?.archives || 'Archives', count: props.archives.length, icon: ArchiveIcon },
229
+ { id: 'categories', name: props.ui?.categories || 'Categories', count: props.categories.length, icon: CategoryIcon },
230
+ { id: 'timeline', name: props.ui?.timeline || 'Timeline', count: props.timeline.length, icon: TimelineIcon },
208
231
  ])
209
232
 
210
233
  const encodeTag = (tag: string) => {
@@ -227,12 +250,20 @@ const getTagColorClass = (count: number) => {
227
250
 
228
251
  const formatDate = (dateStr: string) => {
229
252
  const date = new Date(dateStr)
230
- return new Intl.DateTimeFormat('zh-CN', {
253
+ return new Intl.DateTimeFormat(props.dateLocale, {
231
254
  year: 'numeric',
232
255
  month: '2-digit',
233
256
  day: '2-digit'
234
257
  }).format(date)
235
258
  }
259
+
260
+ const formatArchiveDate = (year: string, month: string) => {
261
+ const date = new Date(parseInt(year), parseInt(month) - 1, 1)
262
+ return new Intl.DateTimeFormat(props.dateLocale, {
263
+ year: 'numeric',
264
+ month: 'long'
265
+ }).format(date)
266
+ }
236
267
  </script>
237
268
 
238
269
  <style scoped>
@@ -13,12 +13,25 @@ export interface Props {
13
13
  };
14
14
  featured?: boolean;
15
15
  layout?: 'vertical' | 'horizontal';
16
+ localePrefix?: string;
17
+ locale?: string;
18
+ ui?: {
19
+ readMore?: string;
20
+ minuteRead?: string;
21
+ };
16
22
  }
17
23
 
18
- const { post, featured = false, layout = 'vertical' } = Astro.props;
24
+ const {
25
+ post,
26
+ featured = false,
27
+ layout = 'vertical',
28
+ localePrefix = '',
29
+ locale = 'zh-CN',
30
+ ui = { readMore: '阅读更多', minuteRead: '分钟' }
31
+ } = Astro.props;
19
32
 
20
33
  const formattedDate = post.pubDate
21
- ? new Intl.DateTimeFormat('zh-CN', {
34
+ ? new Intl.DateTimeFormat(locale, {
22
35
  year: 'numeric',
23
36
  month: 'long',
24
37
  day: 'numeric'
@@ -31,12 +44,17 @@ const isHorizontal = layout === 'horizontal';
31
44
  const tagToSlug = (tag: string) => tag.toLowerCase().replace(/\s+/g, '-');
32
45
  // 将分类名转换为 slug 格式
33
46
  const categoryToSlug = (category: string) => category.toLowerCase().replace(/\s+/g, '-');
47
+
48
+ // Build URLs with locale prefix
49
+ const postUrl = `${localePrefix}/posts/${post.slug}`;
50
+ const tagUrl = (tag: string) => `${localePrefix}/tags/${tagToSlug(tag)}`;
51
+ const categoryUrl = (category: string) => `${localePrefix}/categories/${categoryToSlug(category)}`;
34
52
  ---
35
53
 
36
54
  <article class={`group card hover:shadow-lg transform hover:-translate-y-1 transition-all duration-300 ${isHorizontal ? 'flex gap-6 items-center' : 'block'}`}>
37
55
  <!-- 文章图片 -->
38
56
  {post.image && (
39
- <a href={`/posts/${post.slug}`} class={`block overflow-hidden rounded-lg ${
57
+ <a href={postUrl} class={`block overflow-hidden rounded-lg ${
40
58
  isHorizontal ? 'w-48 h-32 flex-shrink-0' : 'w-full h-48 mb-4'
41
59
  }`}>
42
60
  <img
@@ -55,7 +73,7 @@ const categoryToSlug = (category: string) => category.toLowerCase().replace(/\s+
55
73
  <div class="flex flex-wrap gap-2 mb-2">
56
74
  {post.categories.slice(0, 2).map((category) => (
57
75
  <a
58
- href={`/categories/${categoryToSlug(category)}`}
76
+ href={categoryUrl(category)}
59
77
  class="text-xs px-2 py-1 bg-amber-100 dark:bg-amber-900/30 text-amber-700 dark:text-amber-300 rounded-full hover:bg-amber-200 dark:hover:bg-amber-900/50 transition-colors"
60
78
  onclick="event.stopPropagation();"
61
79
  >
@@ -70,7 +88,7 @@ const categoryToSlug = (category: string) => category.toLowerCase().replace(/\s+
70
88
  <div class="flex flex-wrap gap-2 mb-3">
71
89
  {post.tags.slice(0, 3).map((tag) => (
72
90
  <a
73
- href={`/tags/${tagToSlug(tag)}`}
91
+ href={tagUrl(tag)}
74
92
  class="text-xs px-2 py-1 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"
75
93
  onclick="event.stopPropagation();"
76
94
  >
@@ -86,7 +104,7 @@ const categoryToSlug = (category: string) => category.toLowerCase().replace(/\s+
86
104
  )}
87
105
 
88
106
  <!-- 标题 -->
89
- <a href={`/posts/${post.slug}`}>
107
+ <a href={postUrl}>
90
108
  <h3 class={`font-bold text-slate-900 dark:text-slate-100 hover:text-primary-500 transition-colors line-clamp-2 mb-3 ${
91
109
  featured ? 'text-xl' : isHorizontal ? 'text-lg' : 'text-lg'
92
110
  }`}>
@@ -95,7 +113,7 @@ const categoryToSlug = (category: string) => category.toLowerCase().replace(/\s+
95
113
  </a>
96
114
 
97
115
  <!-- 描述 -->
98
- <a href={`/posts/${post.slug}`}>
116
+ <a href={postUrl}>
99
117
  <p class={`text-slate-600 dark:text-slate-400 line-clamp-3 mb-4 hover:text-slate-700 dark:hover:text-slate-300 transition-colors ${
100
118
  isHorizontal ? 'text-sm' : 'text-base'
101
119
  }`}>
@@ -129,13 +147,13 @@ const categoryToSlug = (category: string) => category.toLowerCase().replace(/\s+
129
147
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
130
148
  <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" />
131
149
  </svg>
132
- <span>{post.readingTime}分钟</span>
150
+ <span>{post.readingTime} {ui.minuteRead}</span>
133
151
  </span>
134
152
  )}
135
153
  </div>
136
154
 
137
- <a href={`/posts/${post.slug}`} class="flex items-center text-primary-500 hover:text-primary-600 transition-colors">
138
- <span class="text-sm font-medium">阅读更多</span>
155
+ <a href={postUrl} class="flex items-center text-primary-500 hover:text-primary-600 transition-colors">
156
+ <span class="text-sm font-medium">{ui.readMore}</span>
139
157
  <svg class="w-4 h-4 ml-1 group-hover:translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
140
158
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
141
159
  </svg>