@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.
- package/dist/{chunk-HVQKQN6B.js → chunk-6D3XRDNY.js} +1 -1
- package/dist/{chunk-ATRISB7B.js → chunk-A2E2VSAQ.js} +43 -3
- package/dist/chunk-DAH2XP4W.js +154 -0
- package/dist/{chunk-AZHCNNAC.js → chunk-PG43JO4O.js} +1 -153
- package/dist/chunk-PZICDGJG.js +69 -0
- package/dist/chunk-Z3O3JK56.js +186 -0
- package/dist/config/index.d.ts +2 -2
- package/dist/config/index.js +9 -7
- package/dist/{i18n-5H4W145i.d.ts → i18n-DYYPTq4o.d.ts} +21 -1
- package/dist/index.d.ts +10 -184
- package/dist/index.js +37 -210
- package/dist/integration.d.ts +2 -2
- package/dist/integration.js +2 -2
- package/dist/{sidebar-Da-W_4Lr.d.ts → sidebar-DNdiCKBw.d.ts} +1 -1
- package/dist/utils/i18n.d.ts +133 -0
- package/dist/utils/i18n.js +49 -0
- package/dist/utils/sidebar.d.ts +1 -1
- package/dist/utils/useI18n.d.ts +74 -0
- package/dist/utils/useI18n.js +15 -0
- package/package.json +9 -1
- package/src/components/blog/FloatingToc.vue +11 -3
- package/src/components/blog/Hero.astro +17 -2
- package/src/components/blog/NavigationTabs.vue +46 -15
- package/src/components/blog/PostCard.astro +28 -10
- package/src/components/blog/RelatedPosts.astro +23 -7
- package/src/components/blog/TableOfContents.astro +10 -4
- package/src/components/blog/TagCloud.astro +4 -3
- package/src/components/home/FeaturedPostsSection.astro +22 -6
- package/src/components/home/QuickNavSection.astro +33 -4
- package/src/components/home/RecentPostsSection.astro +22 -6
- package/src/components/home/StatsSection.astro +24 -6
- package/src/components/layout/Header.astro +9 -5
- package/src/components/layout/Sidebar.astro +14 -11
- package/src/components/ui/SearchBox.vue +13 -5
- package/src/components/ui/SearchInterface.vue +49 -25
- package/src/pages/archives/[year]/[month].astro +36 -17
- package/src/pages/archives/index.astro +36 -20
- package/src/pages/categories/[category].astro +33 -16
- package/src/pages/categories/index.astro +37 -14
- package/src/pages/posts/[...slug].astro +125 -18
- package/src/pages/posts/index.astro +59 -37
- package/src/pages/posts/page/[page].astro +65 -27
- package/src/pages/search.astro +50 -14
- package/src/pages/slides/index.astro +25 -6
- package/src/pages/tags/[tag].astro +32 -15
- package/src/pages/tags/index.astro +39 -16
- package/src/plugins/remark-containers.mjs +351 -322
- package/src/plugins/remark-protect-code.mjs +69 -0
- package/src/styles/global.css +35 -1
- package/templates/default/.claude/ralph-loop.local.md +48 -0
- package/templates/default/astro.config.mjs +13 -4
- package/templates/default/content/posts/blog_docs_en/{get-started → 01.get-started}/01-intro.md +1 -1
- package/templates/default/content/posts/blog_docs_en/{get-started → 01.get-started}/02-install.md +1 -1
- package/templates/default/content/posts/blog_docs_en/{get-started → 01.get-started}/03-create-post.md +1 -1
- package/templates/default/content/posts/blog_docs_en/{get-started → 01.get-started}/04-structure.md +1 -1
- package/templates/default/content/posts/blog_docs_en/01.get-started/05-deploy.md +208 -0
- package/templates/default/content/posts/blog_docs_en/{get-started → 01.get-started}/README.md +1 -1
- package/templates/default/content/posts/blog_docs_en/02.guide/02-containers.md +245 -0
- package/templates/default/content/posts/blog_docs_en/{guide/markdown → 02.guide}/03-code-blocks.md +2 -1
- package/templates/default/content/posts/blog_docs_en/{guide/features/01-mermaid.md → 02.guide/03-mermaid.md} +1 -1
- package/templates/default/content/posts/blog_docs_en/{guide/features → 02.guide}/04-icons.md +4 -2
- package/templates/default/content/posts/blog_docs_en/{guide/features/02-latex.md → 02.guide/06-latex.md} +1 -1
- package/templates/default/content/posts/blog_docs_en/{guide/features/03-video.md → 02.guide/07-video.md} +1 -1
- package/templates/default/content/posts/blog_docs_en/02.guide/08-slides.md +359 -0
- package/templates/default/content/posts/blog_docs_en/{guide/markdown → 02.guide}/README.md +22 -3
- package/templates/default/content/posts/blog_docs_en/{config → 03.config}/01-site.md +1 -1
- package/templates/default/content/posts/blog_docs_en/{config → 03.config}/02-sidebar.md +1 -1
- package/templates/default/content/posts/blog_docs_en/{config → 03.config}/03-i18n.md +88 -24
- package/templates/default/content/posts/blog_docs_en/{config → 03.config}/README.md +1 -1
- package/templates/default/content/posts/blog_docs_en/README.md +2 -1
- package/templates/default/content/posts/blog_docs_zh/01.get-started/01-intro.md +81 -0
- package/templates/default/content/posts/blog_docs_zh/01.get-started/02-install.md +137 -0
- package/templates/default/content/posts/blog_docs_zh/01.get-started/03-create-post.md +176 -0
- package/templates/default/content/posts/blog_docs_zh/01.get-started/04-structure.md +173 -0
- package/templates/default/content/posts/blog_docs_zh/01.get-started/05-deploy.md +208 -0
- package/templates/default/content/posts/blog_docs_zh/01.get-started/README.md +52 -0
- package/templates/default/content/posts/blog_docs_zh/02.guide/02-containers.md +245 -0
- package/templates/default/content/posts/blog_docs_zh/02.guide/03-code-blocks.md +206 -0
- package/templates/default/content/posts/blog_docs_zh/02.guide/03-mermaid.md +194 -0
- package/templates/default/content/posts/blog_docs_zh/02.guide/04-icons.md +229 -0
- package/templates/default/content/posts/blog_docs_zh/02.guide/06-latex.md +233 -0
- package/templates/default/content/posts/blog_docs_zh/02.guide/07-video.md +184 -0
- package/templates/default/content/posts/blog_docs_zh/02.guide/08-slides.md +359 -0
- package/templates/default/content/posts/blog_docs_zh/02.guide/README.md +213 -0
- package/templates/default/content/posts/blog_docs_zh/03.config/01-site.md +208 -0
- package/templates/default/content/posts/blog_docs_zh/03.config/02-sidebar.md +240 -0
- package/templates/default/content/posts/blog_docs_zh/03.config/03-i18n.md +348 -0
- package/templates/default/content/posts/blog_docs_zh/03.config/README.md +85 -0
- package/templates/default/content/posts/blog_docs_zh/README.md +78 -0
- package/templates/default/package.dev.json +31 -0
- package/templates/default/package.json +1 -1
- package/templates/default/src/config/locales/en/index.ts +5 -1
- package/templates/default/src/config/locales/en/menu.ts +3 -1
- package/templates/default/src/config/locales/en/sidebar.ts +18 -2
- package/templates/default/src/config/locales/en/site.ts +1 -1
- package/templates/default/src/config/locales/en/ui.ts +29 -0
- package/templates/default/src/config/locales/zh-CN/index.ts +5 -1
- package/templates/default/src/config/locales/zh-CN/menu.ts +7 -5
- package/templates/default/src/config/locales/zh-CN/sidebar.ts +22 -6
- package/templates/default/src/config/locales/zh-CN/site.ts +2 -2
- package/templates/default/src/config/locales/zh-CN/ui.ts +29 -0
- package/templates/default/src/config/site.ts +2 -2
- package/templates/default/src/content.config.ts +15 -3
- package/templates/default/content/posts/blog_docs/01-quick-start.md +0 -162
- package/templates/default/content/posts/blog_docs/02-frontmatter.md +0 -277
- package/templates/default/content/posts/blog_docs/03-markdown-basic.md +0 -350
- package/templates/default/content/posts/blog_docs/04-containers.md +0 -331
- package/templates/default/content/posts/blog_docs/05-code-blocks.md +0 -388
- package/templates/default/content/posts/blog_docs/06-mermaid.md +0 -431
- package/templates/default/content/posts/blog_docs/07-video.md +0 -243
- package/templates/default/content/posts/blog_docs/08-latex.md +0 -382
- package/templates/default/content/posts/blog_docs/09-icons.md +0 -326
- package/templates/default/content/posts/blog_docs/10-sidebar.md +0 -445
- package/templates/default/content/posts/blog_docs/11-config.md +0 -334
- package/templates/default/content/posts/blog_docs/12-i18n.md +0 -355
- package/templates/default/content/posts/blog_docs/12-slides.mdx +0 -552
- package/templates/default/content/posts/blog_docs/README.md +0 -152
- package/templates/default/content/posts/blog_docs_en/get-started/05-deploy.md +0 -197
- package/templates/default/content/posts/blog_docs_en/guide/README.md +0 -59
- package/templates/default/content/posts/blog_docs_en/guide/features/README.md +0 -51
- 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
|
+
};
|
package/dist/utils/sidebar.d.ts
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
|
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="
|
|
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="
|
|
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
|
|
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="
|
|
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="
|
|
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
|
|
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: '
|
|
205
|
-
{ id: 'archives', name: '
|
|
206
|
-
{ id: 'categories', name: '
|
|
207
|
-
{ id: 'timeline', name: '
|
|
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(
|
|
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 {
|
|
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(
|
|
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={
|
|
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={
|
|
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={
|
|
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={
|
|
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={
|
|
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}
|
|
150
|
+
<span>{post.readingTime} {ui.minuteRead}</span>
|
|
133
151
|
</span>
|
|
134
152
|
)}
|
|
135
153
|
</div>
|
|
136
154
|
|
|
137
|
-
<a href={
|
|
138
|
-
<span class="text-sm font-medium"
|
|
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>
|