@jet-w/astro-blog 0.2.4 → 0.2.6

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 (41) hide show
  1. package/dist/{chunk-Z3O3JK56.js → chunk-CEZBWSMU.js} +34 -5
  2. package/dist/index.js +1 -1
  3. package/dist/utils/i18n.d.ts +34 -2
  4. package/dist/utils/i18n.js +6 -2
  5. package/package.json +1 -1
  6. package/src/components/blog/Hero.astro +13 -6
  7. package/src/components/blog/PostCard.astro +1 -1
  8. package/src/components/home/FeaturedPostsSection.astro +10 -4
  9. package/src/components/home/QuickNavSection.astro +10 -4
  10. package/src/components/home/RecentPostsSection.astro +10 -4
  11. package/src/components/home/StatsSection.astro +9 -3
  12. package/src/components/layout/Footer.astro +17 -7
  13. package/src/components/layout/Header.astro +20 -13
  14. package/src/components/layout/Sidebar.astro +12 -6
  15. package/src/components/media/Slides.astro +22 -6
  16. package/src/components/ui/LanguageSwitcher.vue +24 -12
  17. package/src/components/ui/MobileMenu.vue +20 -3
  18. package/src/layouts/BaseLayout.astro +19 -8
  19. package/src/layouts/PageLayout.astro +9 -3
  20. package/src/layouts/SlidesLayout.astro +34 -16
  21. package/src/pages/archives/[year]/[month]/page/[page].astro +42 -18
  22. package/src/pages/archives/[year]/[month].astro +8 -2
  23. package/src/pages/archives/index.astro +8 -3
  24. package/src/pages/categories/[category]/page/[page].astro +40 -18
  25. package/src/pages/categories/[category].astro +8 -2
  26. package/src/pages/categories/index.astro +8 -3
  27. package/src/pages/posts/[...slug].astro +8 -3
  28. package/src/pages/posts/index.astro +8 -3
  29. package/src/pages/posts/page/[page].astro +8 -2
  30. package/src/pages/search.astro +9 -3
  31. package/src/pages/slides/index.astro +8 -3
  32. package/src/pages/tags/[tag]/page/[page].astro +39 -17
  33. package/src/pages/tags/[tag].astro +8 -2
  34. package/src/pages/tags/index.astro +8 -3
  35. package/src/plugins/rehype-relative-links.mjs +90 -14
  36. package/src/utils/i18n.ts +83 -4
  37. package/templates/default/.claude/ralph-loop.local.md +4 -43
  38. package/templates/default/Makefile +37 -0
  39. package/templates/default/astro.config.mjs +7 -3
  40. package/templates/default/{package.dev.json → package.prod.json} +1 -1
  41. package/templates/default/package-lock.json +0 -9667
@@ -145,11 +145,16 @@ function getTextDirection(locale, config = defaultI18nConfig) {
145
145
  function isMultiLanguageEnabled(config = defaultI18nConfig) {
146
146
  return config.locales.length > 1;
147
147
  }
148
- function getLocalePrefix(locale, config = defaultI18nConfig) {
149
- if (locale === config.defaultLocale && !config.routing.prefixDefaultLocale) {
150
- return "";
148
+ function getLocalePrefix(locale, config = defaultI18nConfig, base) {
149
+ const normalizedBase = base ? base.replace(/\/$/, "") : "";
150
+ let localePrefix = "";
151
+ if (locale !== config.defaultLocale || config.routing.prefixDefaultLocale) {
152
+ localePrefix = `/${locale}`;
153
+ }
154
+ if (!normalizedBase || normalizedBase === "") {
155
+ return localePrefix;
151
156
  }
152
- return `/${locale}`;
157
+ return `${normalizedBase}${localePrefix}`;
153
158
  }
154
159
  function getContentPathPrefix(locale, config = defaultI18nConfig) {
155
160
  const localeConfig = config.localeConfigs[locale];
@@ -166,6 +171,28 @@ function filterPostsByLocale(posts, locale, config = defaultI18nConfig) {
166
171
  return postPath.startsWith(prefix + "/") || postPath === prefix;
167
172
  });
168
173
  }
174
+ function withBase(path, base) {
175
+ const baseUrl = (base || "/").replace(/\/$/, "");
176
+ if (!baseUrl || baseUrl === "") {
177
+ return path;
178
+ }
179
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
180
+ if (normalizedPath === "/") {
181
+ return `${baseUrl}/`;
182
+ }
183
+ return `${baseUrl}${normalizedPath}`;
184
+ }
185
+ function removeBase(path, base) {
186
+ const baseUrl = (base || "/").replace(/\/$/, "");
187
+ if (!baseUrl || baseUrl === "") {
188
+ return path;
189
+ }
190
+ if (path.startsWith(baseUrl)) {
191
+ const rest = path.slice(baseUrl.length);
192
+ return rest || "/";
193
+ }
194
+ return path;
195
+ }
169
196
 
170
197
  export {
171
198
  getLocaleFromPath,
@@ -182,5 +209,7 @@ export {
182
209
  isMultiLanguageEnabled,
183
210
  getLocalePrefix,
184
211
  getContentPathPrefix,
185
- filterPostsByLocale
212
+ filterPostsByLocale,
213
+ withBase,
214
+ removeBase
186
215
  };
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  isRTL,
22
22
  removeLocalePrefix,
23
23
  t
24
- } from "./chunk-Z3O3JK56.js";
24
+ } from "./chunk-CEZBWSMU.js";
25
25
  import {
26
26
  defaultFooterConfig,
27
27
  defaultMenu,
@@ -109,8 +109,18 @@ declare function isMultiLanguageEnabled(config?: I18nConfig): boolean;
109
109
  /**
110
110
  * Get prefix for a locale in routes
111
111
  * Returns empty string for default locale if prefixDefaultLocale is false
112
+ * If base is provided, it will be prepended to the locale prefix
113
+ *
114
+ * @example
115
+ * // Without base
116
+ * getLocalePrefix('en', config) // '' (for default locale)
117
+ * getLocalePrefix('zh-CN', config) // '/zh-CN'
118
+ *
119
+ * // With base '/my-blog'
120
+ * getLocalePrefix('en', config, '/my-blog') // '/my-blog'
121
+ * getLocalePrefix('zh-CN', config, '/my-blog') // '/my-blog/zh-CN'
112
122
  */
113
- declare function getLocalePrefix(locale: string, config?: I18nConfig): string;
123
+ declare function getLocalePrefix(locale: string, config?: I18nConfig, base?: string): string;
114
124
  /**
115
125
  * Get content path prefix for a specific locale
116
126
  * Returns the contentPathPrefix from locale config, or undefined if not set
@@ -129,5 +139,27 @@ declare function getContentPathPrefix(locale: string, config?: I18nConfig): stri
129
139
  declare function filterPostsByLocale<T extends {
130
140
  id: string;
131
141
  }>(posts: T[], locale: string, config?: I18nConfig): T[];
142
+ /**
143
+ * Add base URL prefix to a path
144
+ * This is used when the site is deployed to a subdirectory (e.g., /jet-w.astro-blog/)
145
+ *
146
+ * @example
147
+ * // If BASE_URL is '/jet-w.astro-blog'
148
+ * withBase('/posts') // '/jet-w.astro-blog/posts'
149
+ * withBase('/') // '/jet-w.astro-blog/'
150
+ *
151
+ * // If BASE_URL is '/'
152
+ * withBase('/posts') // '/posts'
153
+ */
154
+ declare function withBase(path: string, base?: string): string;
155
+ /**
156
+ * Remove base URL prefix from a path
157
+ * Useful for getting the actual path without base prefix
158
+ *
159
+ * @example
160
+ * // If BASE_URL is '/jet-w.astro-blog'
161
+ * removeBase('/jet-w.astro-blog/posts', '/jet-w.astro-blog') // '/posts'
162
+ */
163
+ declare function removeBase(path: string, base?: string): string;
132
164
 
133
- export { type AlternateLink, I18nConfig, Locale, type MergedLocaleConfig, UITranslations, filterPostsByLocale, formatDate, formatDateShort, getAlternateLinks, getContentPathPrefix, getLocaleByCode, getLocaleConfig, getLocaleFromPath, getLocalePrefix, getLocalizedPath, getTextDirection, isMultiLanguageEnabled, isRTL, removeLocalePrefix, t };
165
+ export { type AlternateLink, I18nConfig, Locale, type MergedLocaleConfig, UITranslations, filterPostsByLocale, formatDate, formatDateShort, getAlternateLinks, getContentPathPrefix, getLocaleByCode, getLocaleConfig, getLocaleFromPath, getLocalePrefix, getLocalizedPath, getTextDirection, isMultiLanguageEnabled, isRTL, removeBase, removeLocalePrefix, t, withBase };
@@ -12,9 +12,11 @@ import {
12
12
  getTextDirection,
13
13
  isMultiLanguageEnabled,
14
14
  isRTL,
15
+ removeBase,
15
16
  removeLocalePrefix,
16
- t
17
- } from "../chunk-Z3O3JK56.js";
17
+ t,
18
+ withBase
19
+ } from "../chunk-CEZBWSMU.js";
18
20
  import "../chunk-DAH2XP4W.js";
19
21
  import {
20
22
  builtInTranslations,
@@ -43,7 +45,9 @@ export {
43
45
  getUITranslations,
44
46
  isMultiLanguageEnabled,
45
47
  isRTL,
48
+ removeBase,
46
49
  removeLocalePrefix,
47
50
  t,
51
+ withBase,
48
52
  zhCNTranslations
49
53
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jet-w/astro-blog",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "A modern Astro blog theme with Vue and Tailwind CSS support",
5
5
  "type": "module",
6
6
  "exports": {
@@ -3,7 +3,7 @@ import { siteConfig } from '@jet-w/astro-blog/config';
3
3
  import type { I18nConfig } from '../../config/i18n';
4
4
  import { defaultI18nConfig } from '../../config/i18n';
5
5
  import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
6
- import { getLocaleFromPath, getLocaleConfig } from '../../utils/i18n';
6
+ import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, withBase, removeBase } from '../../utils/i18n';
7
7
 
8
8
  export interface Props {
9
9
  i18nConfig?: I18nConfig;
@@ -11,10 +11,17 @@ export interface Props {
11
11
 
12
12
  const { i18nConfig = virtualI18nConfig || defaultI18nConfig } = Astro.props;
13
13
 
14
- // Get current locale from URL
15
- const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
14
+ // Get base URL for prefixing links
15
+ const base = import.meta.env.BASE_URL;
16
+
17
+ // Remove base URL from current path for locale detection
18
+ const pathWithoutBase = removeBase(Astro.url.pathname, base);
19
+
20
+ // Get current locale from URL (use path without base)
21
+ const currentLocale = getLocaleFromPath(pathWithoutBase, i18nConfig);
16
22
  const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
17
23
  const ui = localeConfig.ui;
24
+ const localePrefix = getLocalePrefix(currentLocale, i18nConfig, base);
18
25
  ---
19
26
 
20
27
  <section class="py-16 mb-16">
@@ -23,7 +30,7 @@ const ui = localeConfig.ui;
23
30
  {siteConfig.avatar && (
24
31
  <div class="mb-8">
25
32
  <img
26
- src={siteConfig.avatar}
33
+ src={withBase(siteConfig.avatar, base)}
27
34
  alt={siteConfig.author}
28
35
  class="w-32 h-32 rounded-full mx-auto shadow-lg"
29
36
  />
@@ -101,7 +108,7 @@ const ui = localeConfig.ui;
101
108
  <!-- 行动按钮 -->
102
109
  <div class="flex flex-col sm:flex-row gap-4 justify-center">
103
110
  <a
104
- href="/posts"
111
+ href={`${localePrefix}/posts`}
105
112
  class="btn-primary inline-flex items-center space-x-2"
106
113
  >
107
114
  <span>{ui.browsePosts}</span>
@@ -111,7 +118,7 @@ const ui = localeConfig.ui;
111
118
  </a>
112
119
 
113
120
  <a
114
- href="/about"
121
+ href={`${localePrefix}/about`}
115
122
  class="btn-secondary inline-flex items-center space-x-2"
116
123
  >
117
124
  <span>{ui.aboutMe}</span>
@@ -45,7 +45,7 @@ const tagToSlug = (tag: string) => tag.toLowerCase().replace(/\s+/g, '-');
45
45
  // 将分类名转换为 slug 格式
46
46
  const categoryToSlug = (category: string) => category.toLowerCase().replace(/\s+/g, '-');
47
47
 
48
- // Build URLs with locale prefix
48
+ // Build URLs with locale prefix (localePrefix already contains base URL)
49
49
  const postUrl = `${localePrefix}/posts/${post.slug}`;
50
50
  const tagUrl = (tag: string) => `${localePrefix}/tags/${tagToSlug(tag)}`;
51
51
  const categoryUrl = (category: string) => `${localePrefix}/categories/${categoryToSlug(category)}`;
@@ -7,7 +7,7 @@ import { getCollection } from 'astro:content';
7
7
  import type { I18nConfig } from '../../config/i18n';
8
8
  import { defaultI18nConfig } from '../../config/i18n';
9
9
  import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
10
- import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, filterPostsByLocale } from '../../utils/i18n';
10
+ import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, filterPostsByLocale, removeBase } from '../../utils/i18n';
11
11
 
12
12
  export interface Props {
13
13
  count?: number;
@@ -16,11 +16,17 @@ export interface Props {
16
16
 
17
17
  const { count = 3, i18nConfig = virtualI18nConfig || defaultI18nConfig } = Astro.props;
18
18
 
19
- // Get current locale from URL
20
- const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
19
+ // Get base URL for prefixing links
20
+ const base = import.meta.env.BASE_URL;
21
+
22
+ // Remove base URL from current path for locale detection
23
+ const pathWithoutBase = removeBase(Astro.url.pathname, base);
24
+
25
+ // Get current locale from URL (use path without base)
26
+ const currentLocale = getLocaleFromPath(pathWithoutBase, i18nConfig);
21
27
  const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
22
28
  const ui = localeConfig.ui;
23
- const localePrefix = getLocalePrefix(currentLocale, i18nConfig);
29
+ const localePrefix = getLocalePrefix(currentLocale, i18nConfig, base);
24
30
 
25
31
  const allPosts = await getCollection('posts');
26
32
  const publishedPosts = allPosts
@@ -7,7 +7,7 @@ import { getCollection } from 'astro:content';
7
7
  import type { I18nConfig } from '../../config/i18n';
8
8
  import { defaultI18nConfig } from '../../config/i18n';
9
9
  import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
10
- import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, filterPostsByLocale } from '../../utils/i18n';
10
+ import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, filterPostsByLocale, removeBase } from '../../utils/i18n';
11
11
 
12
12
  interface Props {
13
13
  i18nConfig?: I18nConfig;
@@ -15,11 +15,17 @@ interface Props {
15
15
 
16
16
  const { i18nConfig = virtualI18nConfig || defaultI18nConfig } = Astro.props;
17
17
 
18
- // Get current locale and translations
19
- const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
18
+ // Get base URL for prefixing links
19
+ const base = import.meta.env.BASE_URL;
20
+
21
+ // Remove base URL from current path for locale detection
22
+ const pathWithoutBase = removeBase(Astro.url.pathname, base);
23
+
24
+ // Get current locale and translations (use path without base)
25
+ const currentLocale = getLocaleFromPath(pathWithoutBase, i18nConfig);
20
26
  const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
21
27
  const ui = localeConfig.ui;
22
- const localePrefix = getLocalePrefix(currentLocale, i18nConfig);
28
+ const localePrefix = getLocalePrefix(currentLocale, i18nConfig, base);
23
29
 
24
30
  const allPosts = await getCollection('posts', ({ data }) => !data.draft);
25
31
 
@@ -7,7 +7,7 @@ import { getCollection } from 'astro:content';
7
7
  import type { I18nConfig } from '../../config/i18n';
8
8
  import { defaultI18nConfig } from '../../config/i18n';
9
9
  import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
10
- import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, filterPostsByLocale } from '../../utils/i18n';
10
+ import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, filterPostsByLocale, removeBase } from '../../utils/i18n';
11
11
 
12
12
  export interface Props {
13
13
  count?: number;
@@ -16,11 +16,17 @@ export interface Props {
16
16
 
17
17
  const { count = 6, i18nConfig = virtualI18nConfig || defaultI18nConfig } = Astro.props;
18
18
 
19
- // Get current locale from URL
20
- const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
19
+ // Get base URL for prefixing links
20
+ const base = import.meta.env.BASE_URL;
21
+
22
+ // Remove base URL from current path for locale detection
23
+ const pathWithoutBase = removeBase(Astro.url.pathname, base);
24
+
25
+ // Get current locale from URL (use path without base)
26
+ const currentLocale = getLocaleFromPath(pathWithoutBase, i18nConfig);
21
27
  const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
22
28
  const ui = localeConfig.ui;
23
- const localePrefix = getLocalePrefix(currentLocale, i18nConfig);
29
+ const localePrefix = getLocalePrefix(currentLocale, i18nConfig, base);
24
30
 
25
31
  const allPosts = await getCollection('posts');
26
32
  const publishedPosts = allPosts
@@ -6,7 +6,7 @@ import { getCollection } from 'astro:content';
6
6
  import type { I18nConfig } from '../../config/i18n';
7
7
  import { defaultI18nConfig } from '../../config/i18n';
8
8
  import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
9
- import { getLocaleFromPath, getLocaleConfig, filterPostsByLocale } from '../../utils/i18n';
9
+ import { getLocaleFromPath, getLocaleConfig, filterPostsByLocale, removeBase } from '../../utils/i18n';
10
10
 
11
11
  export interface Props {
12
12
  i18nConfig?: I18nConfig;
@@ -14,8 +14,14 @@ export interface Props {
14
14
 
15
15
  const { i18nConfig = virtualI18nConfig || defaultI18nConfig } = Astro.props;
16
16
 
17
- // Get current locale from URL
18
- const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
17
+ // Get base URL for prefixing links
18
+ const base = import.meta.env.BASE_URL;
19
+
20
+ // Remove base URL from current path for locale detection
21
+ const pathWithoutBase = removeBase(Astro.url.pathname, base);
22
+
23
+ // Get current locale from URL (use path without base)
24
+ const currentLocale = getLocaleFromPath(pathWithoutBase, i18nConfig);
19
25
  const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
20
26
  const ui = localeConfig.ui;
21
27
 
@@ -2,7 +2,7 @@
2
2
  import { siteConfig, footerConfig, defaultIcons } from '@jet-w/astro-blog/config';
3
3
  import type { I18nConfig } from '../../config/i18n';
4
4
  import { defaultI18nConfig } from '../../config/i18n';
5
- import { getLocaleFromPath, getLocaleConfig, getLocalePrefix } from '../../utils/i18n';
5
+ import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, removeBase, withBase } from '../../utils/i18n';
6
6
 
7
7
  export interface Props {
8
8
  i18nConfig?: I18nConfig;
@@ -10,7 +10,13 @@ export interface Props {
10
10
 
11
11
  const { i18nConfig = defaultI18nConfig } = Astro.props;
12
12
 
13
- const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
13
+ // Get base URL for prefixing links
14
+ const base = import.meta.env.BASE_URL;
15
+
16
+ // Remove base URL from current path for locale detection
17
+ const pathWithoutBase = removeBase(Astro.url.pathname, base);
18
+
19
+ const currentLocale = getLocaleFromPath(pathWithoutBase, i18nConfig);
14
20
  const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
15
21
  const ui = localeConfig.ui;
16
22
 
@@ -24,9 +30,13 @@ function getIcon(link: { type: string; icon?: string }): string {
24
30
  return link.icon || defaultIcons[link.type] || '';
25
31
  }
26
32
 
27
- // Get locale prefix for RSS link
28
- const localePrefix = getLocalePrefix(currentLocale, i18nConfig);
29
- const rssUrl = localeFooterConfig.rssUrl || footerConfig.rssUrl || `${localePrefix}/rss.xml`;
33
+ // Get locale prefix for RSS link (already includes base)
34
+ const localePrefix = getLocalePrefix(currentLocale, i18nConfig, base);
35
+ // Apply withBase to rssUrl if it's a relative path
36
+ const configRssUrl = localeFooterConfig.rssUrl || footerConfig.rssUrl;
37
+ const rssUrl = configRssUrl
38
+ ? (configRssUrl.startsWith('/') && !configRssUrl.startsWith(base) ? withBase(configRssUrl, base) : configRssUrl)
39
+ : `${localePrefix}/rss.xml`;
30
40
 
31
41
  const copyright = (localeFooterConfig.copyright || footerConfig.copyright)
32
42
  .replace('{year}', String(currentYear))
@@ -40,7 +50,7 @@ const copyright = (localeFooterConfig.copyright || footerConfig.copyright)
40
50
  <div class="flex items-center space-x-3 mb-3">
41
51
  {(localeSiteConfig.avatar || siteConfig.avatar) && (
42
52
  <img
43
- src={localeSiteConfig.avatar || siteConfig.avatar}
53
+ src={withBase(localeSiteConfig.avatar || siteConfig.avatar || '', base)}
44
54
  alt={localeSiteConfig.title || siteConfig.title}
45
55
  class="w-8 h-8 rounded-full"
46
56
  />
@@ -63,7 +73,7 @@ const copyright = (localeFooterConfig.copyright || footerConfig.copyright)
63
73
  <div class="flex flex-wrap gap-x-4 gap-y-1">
64
74
  {(localeFooterConfig.quickLinks || footerConfig.quickLinks).map((item) => (
65
75
  <a
66
- href={item.href}
76
+ href={withBase(item.href, base)}
67
77
  class="text-sm text-slate-600 dark:text-slate-400 hover:text-primary-500 transition-colors"
68
78
  >
69
79
  {item.name}
@@ -11,7 +11,9 @@ import {
11
11
  getLocaleFromPath,
12
12
  getLocaleConfig,
13
13
  removeLocalePrefix,
14
+ removeBase,
14
15
  isMultiLanguageEnabled,
16
+ withBase,
15
17
  } from '../../utils/i18n';
16
18
 
17
19
  export interface Props {
@@ -21,7 +23,15 @@ export interface Props {
21
23
  const { i18nConfig = virtualI18nConfig || defaultI18nConfig } = Astro.props;
22
24
 
23
25
  const currentPath = Astro.url.pathname;
24
- const currentLocale = getLocaleFromPath(currentPath, i18nConfig);
26
+
27
+ // Get base URL for prefixing links
28
+ const base = import.meta.env.BASE_URL;
29
+
30
+ // Get base path without base URL - needed for locale detection
31
+ const pathWithoutBase = removeBase(currentPath, base);
32
+
33
+ // Detect current locale from path (without base URL)
34
+ const currentLocale = getLocaleFromPath(pathWithoutBase, i18nConfig);
25
35
  const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
26
36
  const ui = localeConfig.ui;
27
37
 
@@ -29,8 +39,8 @@ const ui = localeConfig.ui;
29
39
  const localeSiteConfig = localeConfig.site;
30
40
  const menu = localeConfig.menu;
31
41
 
32
- // Get base path without locale prefix for language switcher
33
- const basePath = removeLocalePrefix(currentPath, i18nConfig);
42
+ // Get path without locale prefix for language switcher
43
+ const pathWithoutLocale = removeLocalePrefix(pathWithoutBase, i18nConfig);
34
44
 
35
45
  // Check if multi-language is enabled
36
46
  const showLanguageSwitcher = isMultiLanguageEnabled(i18nConfig);
@@ -46,10 +56,10 @@ const localesForSwitcher = i18nConfig.locales.map(l => ({
46
56
  <div class="container mx-auto px-4">
47
57
  <nav class="flex items-center justify-between h-16">
48
58
  <div class="flex items-center space-x-4">
49
- <a href="/" class="flex items-center space-x-3 hover:opacity-80 transition-opacity">
59
+ <a href={withBase('/', base)} class="flex items-center space-x-3 hover:opacity-80 transition-opacity">
50
60
  {(localeSiteConfig.avatar || siteConfig.avatar) && (
51
61
  <img
52
- src={localeSiteConfig.avatar || siteConfig.avatar}
62
+ src={withBase(localeSiteConfig.avatar || siteConfig.avatar || '', base)}
53
63
  alt={localeSiteConfig.title || siteConfig.title}
54
64
  class="w-8 h-8 rounded-full"
55
65
  />
@@ -70,9 +80,9 @@ const localesForSwitcher = i18nConfig.locales.map(l => ({
70
80
  <div class="hidden md:flex items-center space-x-8">
71
81
  {menu.map((item) => (
72
82
  <a
73
- href={item.href}
83
+ href={withBase(item.href, base)}
74
84
  class={`text-sm font-medium transition-colors hover:text-primary-500 ${
75
- currentPath === item.href || (item.href !== '/' && currentPath.startsWith(item.href))
85
+ currentPath === withBase(item.href, base) || (item.href !== '/' && currentPath.startsWith(withBase(item.href, base)))
76
86
  ? 'text-primary-500'
77
87
  : 'text-slate-700 dark:text-slate-300'
78
88
  }`}
@@ -98,9 +108,10 @@ const localesForSwitcher = i18nConfig.locales.map(l => ({
98
108
  client:load
99
109
  locales={localesForSwitcher}
100
110
  currentLocale={currentLocale}
101
- currentPath={basePath}
111
+ currentPath={pathWithoutLocale}
102
112
  defaultLocale={i18nConfig.defaultLocale}
103
113
  prefixDefaultLocale={i18nConfig.routing.prefixDefaultLocale}
114
+ base={base}
104
115
  />
105
116
  </div>
106
117
  )}
@@ -113,11 +124,7 @@ const localesForSwitcher = i18nConfig.locales.map(l => ({
113
124
  <MobileMenu
114
125
  client:load
115
126
  navigation={menu}
116
- locales={showLanguageSwitcher ? localesForSwitcher : []}
117
- currentLocale={currentLocale}
118
- currentPath={basePath}
119
- defaultLocale={i18nConfig.defaultLocale}
120
- prefixDefaultLocale={i18nConfig.routing.prefixDefaultLocale}
127
+ base={base}
121
128
  />
122
129
  </div>
123
130
  </div>
@@ -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, filterPostsByLocale } from '../../utils/i18n';
13
+ import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, formatDate, filterPostsByLocale, removeBase } from '../../utils/i18n';
14
14
 
15
15
  interface Props {
16
16
  currentPath?: string;
@@ -19,11 +19,17 @@ interface Props {
19
19
 
20
20
  const { currentPath = Astro.url.pathname, i18nConfig = defaultI18nConfig } = Astro.props;
21
21
 
22
- // Get current locale and translations
23
- const currentLocale = getLocaleFromPath(currentPath, i18nConfig);
22
+ // Get base URL for prefixing links
23
+ const base = import.meta.env.BASE_URL;
24
+
25
+ // Remove base URL from current path for locale detection and path filtering
26
+ const pathWithoutBase = removeBase(currentPath, base);
27
+
28
+ // Get current locale and translations (use path without base)
29
+ const currentLocale = getLocaleFromPath(pathWithoutBase, i18nConfig);
24
30
  const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
25
31
  const ui = localeConfig.ui;
26
- const localePrefix = getLocalePrefix(currentLocale, i18nConfig);
32
+ const localePrefix = getLocalePrefix(currentLocale, i18nConfig, base);
27
33
 
28
34
  // Get locale-specific sidebar config (already merged with defaults in getLocaleConfig)
29
35
  const sidebarConfig = localeConfig.sidebar;
@@ -34,8 +40,8 @@ const allPosts = await getCollection('posts', ({ data }) => !data.draft);
34
40
  // Filter posts by current locale for sidebar widgets
35
41
  const localePosts = filterPostsByLocale(allPosts, currentLocale, i18nConfig);
36
42
 
37
- // 根据当前路径过滤侧边栏组
38
- const filteredGroups = filterGroupsByPath(sidebarConfig.groups, currentPath);
43
+ // 根据当前路径过滤侧边栏组 (use path without base for pattern matching)
44
+ const filteredGroups = filterGroupsByPath(sidebarConfig.groups, pathWithoutBase);
39
45
 
40
46
  // 创建过滤后的配置
41
47
  const filteredConfig = { ...sidebarConfig, groups: filteredGroups };
@@ -78,6 +78,9 @@ const revealConfig = JSON.stringify({
78
78
  touch: true,
79
79
  });
80
80
 
81
+ // Get base URL for static assets
82
+ const base = import.meta.env.BASE_URL;
83
+
81
84
  // 获取 slot 内容
82
85
  const slotContent = await Astro.slots.render('default');
83
86
  const hasInlineContent = slotContent && slotContent.trim().length > 0;
@@ -164,13 +167,26 @@ const slidesData = hasInlineContent ? parseSlides(slotContent) : [];
164
167
  </div>
165
168
 
166
169
  <!-- Reveal.js 资源 -->
167
- <link rel="stylesheet" href="/slides/reveal.css" />
168
- <link rel="stylesheet" href={`/slides/theme/${theme}.css`} />
169
- <link rel="stylesheet" href="/slides/plugin/highlight/monokai.css" />
170
+ <link rel="stylesheet" href={`${base}slides/reveal.css`} />
171
+ <link rel="stylesheet" href={`${base}slides/theme/${theme}.css`} />
172
+ <link rel="stylesheet" href={`${base}slides/plugin/highlight/monokai.css`} />
170
173
 
171
- <script is:inline src="/slides/reveal.js"></script>
172
- <script is:inline src="/slides/plugin/markdown/markdown.js"></script>
173
- <script is:inline src="/slides/plugin/highlight/highlight.js"></script>
174
+ <!-- Reveal.js - dynamically loaded with base URL -->
175
+ <script is:inline define:vars={{ baseUrl: base }}>
176
+ (function() {
177
+ var scripts = [
178
+ baseUrl + 'slides/reveal.js',
179
+ baseUrl + 'slides/plugin/markdown/markdown.js',
180
+ baseUrl + 'slides/plugin/highlight/highlight.js'
181
+ ];
182
+ scripts.forEach(function(src) {
183
+ var script = document.createElement('script');
184
+ script.src = src;
185
+ script.async = false;
186
+ document.head.appendChild(script);
187
+ });
188
+ })();
189
+ </script>
174
190
 
175
191
  <script is:inline define:vars={{ slidesId, revealConfig }}>
176
192
  (function() {
@@ -101,16 +101,19 @@ interface Props {
101
101
  locales: LocaleInfo[];
102
102
  /** Current locale code */
103
103
  currentLocale: string;
104
- /** Current page path (without locale prefix) */
104
+ /** Current page path (without locale prefix and without base) */
105
105
  currentPath: string;
106
106
  /** Default locale code */
107
107
  defaultLocale: string;
108
108
  /** Whether to prefix the default locale in URLs */
109
109
  prefixDefaultLocale?: boolean;
110
+ /** Base URL for the site (e.g., '/jet-w.astro-blog') */
111
+ base?: string;
110
112
  }
111
113
 
112
114
  const props = withDefaults(defineProps<Props>(), {
113
115
  prefixDefaultLocale: false,
116
+ base: '/',
114
117
  });
115
118
 
116
119
  const isOpen = ref(false);
@@ -138,23 +141,32 @@ function closeDropdown() {
138
141
  }
139
142
 
140
143
  function getLocalizedUrl(targetLocale: string): string {
141
- // Normalize the path
142
- let basePath = props.currentPath;
143
- if (!basePath.startsWith('/')) {
144
- basePath = '/' + basePath;
144
+ // Normalize the current path (without base and without locale)
145
+ let pagePath = props.currentPath;
146
+ if (!pagePath.startsWith('/')) {
147
+ pagePath = '/' + pagePath;
145
148
  }
146
149
 
147
- // If target is default locale and prefix is not required
148
- if (targetLocale === props.defaultLocale && !props.prefixDefaultLocale) {
149
- return basePath === '/' ? '/' : basePath;
150
+ // Normalize base URL - remove trailing slash
151
+ const baseUrl = props.base?.replace(/\/$/, '') || '';
152
+
153
+ // Build locale prefix
154
+ let localePrefix = '';
155
+ if (targetLocale !== props.defaultLocale || props.prefixDefaultLocale) {
156
+ localePrefix = `/${targetLocale}`;
150
157
  }
151
158
 
152
- // Add locale prefix
153
- if (basePath === '/') {
154
- return `/${targetLocale}`;
159
+ // Combine: base + locale + path
160
+ if (pagePath === '/') {
161
+ // Home page
162
+ if (localePrefix) {
163
+ return baseUrl ? `${baseUrl}${localePrefix}/` : `${localePrefix}/`;
164
+ }
165
+ return baseUrl ? `${baseUrl}/` : '/';
155
166
  }
156
167
 
157
- return `/${targetLocale}${basePath}`;
168
+ // Other pages
169
+ return `${baseUrl}${localePrefix}${pagePath}`;
158
170
  }
159
171
 
160
172
  // Close dropdown when clicking outside