@coffic/cosy-ui 0.9.93 → 0.9.94

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.
@@ -26,6 +26,11 @@ export interface IHeaderProps {
26
26
  * 完整的 astro:i18n 模块(启用语言切换时需要)
27
27
  */
28
28
  astroI18n?: any;
29
+ /**
30
+ * 支持的语言列表,如:['zh-cn', 'en']
31
+ * 用于语言切换功能
32
+ */
33
+ locales?: string[];
29
34
  /**
30
35
  * 导航栏高度
31
36
  * @default 'md'
@@ -52,6 +52,7 @@ const {
52
52
  showThemeSwitcher = true,
53
53
  gap = 2,
54
54
  astroI18n,
55
+ locales,
55
56
  } = Astro.props;
56
57
 
57
58
  // 根据高度设置样式(使用 common 中的 heightClasses)
@@ -142,8 +143,7 @@ const containerClassList = [
142
143
  activeLink={activeLink}
143
144
  linkHeightClass={linkHeightClass}
144
145
  showThemeSwitcher={showThemeSwitcher}
145
- isI18nEnabled={isI18nEnabled}
146
- astroI18n={astroI18n}>
146
+ locales={locales}>
147
147
  <slot name="navbar-end" />
148
148
  </HeaderEnd>
149
149
  </div>
@@ -17,8 +17,7 @@
17
17
  * activeLink={activeLink}
18
18
  * linkHeightClass={linkHeightClass}
19
19
  * showThemeSwitcher={showThemeSwitcher}
20
- * isI18nEnabled={isI18nEnabled}
21
- * astroI18n={astroI18n}
20
+ * locales={locales}
22
21
  * />
23
22
  * ```
24
23
  *
@@ -28,8 +27,7 @@
28
27
  * - activeLink - 当前激活的链接
29
28
  * - linkHeightClass - 链接高度类名
30
29
  * - showThemeSwitcher - 是否显示主题切换器
31
- * - isI18nEnabled - 是否启用国际化
32
- * - astroI18n - astro:i18n 模块
30
+ * - locales - 支持的语言列表(用于语言切换器)
33
31
  */
34
32
  import { type INavItem, LanguageSwitcher, MenuIcon } from "../../index-astro";
35
33
  import NavItems from "./NavItems.astro";
@@ -41,8 +39,7 @@ export interface Props {
41
39
  activeLink: string;
42
40
  linkHeightClass: string;
43
41
  showThemeSwitcher: boolean;
44
- isI18nEnabled: boolean;
45
- astroI18n?: any;
42
+ locales?: string[];
46
43
  }
47
44
 
48
45
  const {
@@ -51,8 +48,7 @@ const {
51
48
  activeLink,
52
49
  linkHeightClass,
53
50
  showThemeSwitcher,
54
- isI18nEnabled,
55
- astroI18n,
51
+ locales,
56
52
  } = Astro.props;
57
53
  ---
58
54
 
@@ -71,7 +67,7 @@ const {
71
67
  }
72
68
 
73
69
  {showThemeSwitcher && <ThemeSwitcher />}
74
- {isI18nEnabled && <LanguageSwitcher astroI18n={astroI18n} />}
70
+ {locales && locales.length > 0 && <LanguageSwitcher locales={locales} />}
75
71
 
76
72
  <!-- 移动端汉堡菜单按钮 -->
77
73
  {
@@ -1,7 +1,4 @@
1
1
  ---
2
-
3
-
4
-
5
2
  /**
6
3
  * @component ThemeSwitcher
7
4
  *
@@ -24,6 +24,7 @@ export interface IHeaderPropsBuilder {
24
24
  showThemeSwitcher(value?: boolean): IHeaderPropsBuilder;
25
25
  gap(value: NonNullable<IHeaderProps["gap"]>): IHeaderPropsBuilder;
26
26
  astroI18n(value: any): IHeaderPropsBuilder;
27
+ locales(value: NonNullable<IHeaderProps["locales"]>): IHeaderPropsBuilder;
27
28
  build(): IHeaderProps;
28
29
  }
29
30
 
@@ -107,6 +108,10 @@ export function createHeaderProps(
107
108
  props = { ...props, astroI18n: value };
108
109
  return api;
109
110
  },
111
+ locales(value) {
112
+ props = { ...props, locales: value };
113
+ return api;
114
+ },
110
115
  build() {
111
116
  return props;
112
117
  },
@@ -1,16 +1,33 @@
1
1
  ---
2
-
3
-
4
2
  /**
5
3
  * @component LanguageSwitcher
6
4
  *
7
5
  * @description
8
- * LanguageSwitcher 组件提供一个语言切换下拉菜单,支持多语言网站的语言切换功能。
9
- * 组件会自动处理 URL 路径,确保在切换语言时保留当前页面路径。
6
+ * LanguageSwitcher 组件提供一个语言切换下拉菜单,适用于使用 [lang] 动态路由的多语言网站。
7
+ *
8
+ * **URL 生成规则**:
9
+ * - 为每个语言生成格式为 `/{locale}{path}` 的 URL
10
+ * - 例如:`/zh-cn/manuals`、`/en/manuals`
11
+ * - 根路径:`/zh-cn`、`/en`
12
+ *
13
+ * **适用场景**:
14
+ * ✅ 使用 Astro 动态路由 `src/pages/[lang]/xxx.astro`
15
+ * ✅ 所有语言都需要 URL 前缀(包括默认语言)
16
+ *
17
+ * **不适用场景**:
18
+ * ❌ 使用标准 Astro i18n 路由(`prefixDefaultLocale: true` 且不使用 [lang] 路由)
19
+ * ❌ 默认语言不需要前缀的情况(如 `/` 对应英文,`/zh-cn` 对应中文)
20
+ *
21
+ * 如果您的项目不符合上述适用场景,请不要使用此组件,或自行实现语言切换逻辑。
10
22
  *
11
23
  * @props
12
- * - astroI18n - 完整的 astro:i18n 模块
24
+ * - locales - 必填,支持的语言列表,如:['zh-cn', 'en']
13
25
  * - class - 自定义CSS类名
26
+ *
27
+ * @example
28
+ * ```astro
29
+ * <LanguageSwitcher locales={['zh-cn', 'en']} />
30
+ * ```
14
31
  */
15
32
 
16
33
  import { cn } from '../../src/class';
@@ -18,17 +35,16 @@ import { ChevronDownIcon, GlobeIcon } from '../icons';
18
35
  import { Link } from '../link';
19
36
  import { ListItem } from '../list';
20
37
  import {
21
- checkSwitcherRenderState,
22
38
  generateSwitcherLinks,
23
- type IAstroI18n,
24
39
  type SwitcherLink,
25
40
  } from './util';
26
41
 
27
42
  interface Props {
28
43
  /**
29
- * 完整的 astro:i18n 模块
44
+ * 必填:支持的语言列表
45
+ * 例如:['zh-cn', 'en']
30
46
  */
31
- astroI18n?: IAstroI18n;
47
+ locales: string[];
32
48
 
33
49
  /**
34
50
  * 自定义类名
@@ -36,32 +52,29 @@ interface Props {
36
52
  class?: string;
37
53
  }
38
54
 
39
- const { astroI18n, class: className = '' } = Astro.props;
55
+ const { locales, class: className = '' } = Astro.props;
40
56
 
41
- // 检查渲染状态
42
- const renderState = checkSwitcherRenderState(Astro.currentLocale, astroI18n);
57
+ // 验证必填参数
58
+ if (!locales || locales.length === 0) {
59
+ throw new Error('LanguageSwitcher: locales prop is required and must not be empty');
60
+ }
43
61
 
44
- // 输出警告信息
45
- if (renderState.warnings) {
46
- renderState.warnings.forEach((warning) => {
47
- console.warn(warning);
48
- });
62
+ if (!Astro.currentLocale) {
63
+ throw new Error('LanguageSwitcher: Astro.currentLocale is not defined. Make sure i18n is configured.');
49
64
  }
50
65
 
51
66
  let links: SwitcherLink[] = [];
52
67
 
53
- // 如果应该渲染,生成切换链接
54
- if (renderState.shouldRender && Astro.currentLocale && astroI18n) {
55
- try {
56
- links = generateSwitcherLinks(
57
- astroI18n,
58
- Astro.currentLocale,
59
- Astro.url.pathname
60
- );
61
- } catch (error) {
62
- // 如果生成链接失败,设置为不渲染
63
- renderState.shouldRender = false;
64
- }
68
+ // 生成切换链接
69
+ try {
70
+ links = generateSwitcherLinks(
71
+ Astro.currentLocale,
72
+ Astro.url.pathname,
73
+ locales
74
+ );
75
+ } catch (error) {
76
+ console.error('LanguageSwitcher: Failed to generate links:', error);
77
+ throw error;
65
78
  }
66
79
 
67
80
  // 使用 classBuilder 构建类名
@@ -101,12 +114,12 @@ const ulClass = cn()
101
114
  ---
102
115
 
103
116
  {
104
- renderState.shouldRender && (
117
+ links.length > 0 && (
105
118
  <div class={dropdownClass}>
106
119
  <div tabindex="0" role="button" class={buttonClass}>
107
120
  <GlobeIcon size="16px" class={iconClass} />
108
121
  <span class={spanClass}>
109
- {renderState.currentLanguageName}
122
+ {links.find(l => l.locale === Astro.currentLocale)?.name}
110
123
  </span>
111
124
  <ChevronDownIcon size="16px" class={chevronClass} />
112
125
  </div>
@@ -7,84 +7,104 @@ export interface SwitcherLink {
7
7
  }
8
8
 
9
9
  /**
10
- * Astro i18n 模块类型定义
10
+ * 获取路径部分(去除语言前缀)
11
+ * @param pathname - 完整路径
12
+ * @param localePrefix - 语言前缀(如 /zh-cn/ 或 /zh-cn)
13
+ * @returns 不含语言前缀的路径
11
14
  */
12
- export interface IAstroI18n {
13
- getRelativeLocaleUrl: (locale: string, path: string) => string;
14
- getRelativeLocaleUrlList: (slug: string) => string[];
15
- }
15
+ const getPathWithoutLocale = (
16
+ pathname: string,
17
+ _currentLocale: string,
18
+ locales: string[],
19
+ ): string => {
20
+ // 规范化路径:移除末尾的斜杠(除非是根路径)
21
+ const normalizedPath =
22
+ pathname.endsWith("/") && pathname !== "/"
23
+ ? pathname.slice(0, -1)
24
+ : pathname;
16
25
 
17
- /**
18
- * 获取基础 URL
19
- */
20
- export const getBaseUrl = (): string => {
21
- return import.meta.env.BASE_URL || "/";
22
- };
26
+ // 如果是根路径,返回空字符串
27
+ if (normalizedPath === "/" || normalizedPath === "") {
28
+ return "";
29
+ }
23
30
 
24
- /**
25
- * URL 中提取语言代码
26
- */
27
- export const getLocaleFromUrl = (url: string): string => {
28
- return url.replace(getBaseUrl(), "").split("/")[0];
31
+ // 尝试移除语言前缀
32
+ // 检查是否以 /locale /locale/ 开头
33
+ for (const locale of locales) {
34
+ const prefix = `/${locale}`;
35
+
36
+ // 情况1: /zh-cn -> 空(首页)
37
+ if (normalizedPath === prefix) {
38
+ return "";
39
+ }
40
+
41
+ // 情况2: /zh-cn/xxx -> /xxx
42
+ if (normalizedPath.startsWith(`${prefix}/`)) {
43
+ return normalizedPath.slice(prefix.length);
44
+ }
45
+ }
46
+
47
+ // 没有匹配到任何语言前缀,说明是默认语言(prefixDefaultLocale: false)
48
+ // 直接返回路径
49
+ return normalizedPath;
29
50
  };
30
51
 
31
52
  /**
32
53
  * 生成语言切换链接
33
- * @param astroI18n - astro:i18n 模块
34
- * @param currentLocale - 当前语言代码
35
- * @param pathname - 当前页面路径
54
+ *
55
+ * **URL 生成规则**:
56
+ * - 手动构建格式为 `/{locale}{path}` 的 URL
57
+ * - 适用于使用 [lang] 动态路由的项目
58
+ * - 所有语言都会有 URL 前缀,包括默认语言
59
+ *
60
+ * **示例**:
61
+ * - 当前页面:`/zh-cn/manuals` → 英文链接:`/en/manuals`
62
+ * - 当前页面:`/zh-cn` → 英文链接:`/en`
63
+ * - 当前页面:`/en/demos` → 中文链接:`/zh-cn/demos`
64
+ *
65
+ * @param currentLocale - 当前语言代码(如 'zh-cn')
66
+ * @param pathname - 当前页面路径(如 '/zh-cn/manuals')
67
+ * @param locales - 所有支持的语言列表(如 ['zh-cn', 'en'])
36
68
  * @returns 语言切换链接数组
37
69
  */
38
70
  export const generateSwitcherLinks = (
39
- astroI18n: IAstroI18n,
40
71
  currentLocale: string,
41
72
  pathname: string,
73
+ locales: string[],
42
74
  ): SwitcherLink[] => {
43
75
  try {
44
- const { getRelativeLocaleUrl, getRelativeLocaleUrlList } = astroI18n;
76
+ if (locales.length === 0) {
77
+ console.warn("LanguageSwitcher: locales array is empty");
78
+ return [];
79
+ }
80
+
81
+ // 获取不含语言前缀的路径
82
+ const pathWithoutLocale = getPathWithoutLocale(
83
+ pathname,
84
+ currentLocale,
85
+ locales,
86
+ );
45
87
 
46
- const currentLocalURLPrefix = getRelativeLocaleUrl(currentLocale, "");
47
- const pathWithSlash = `${pathname}/`;
48
- const slug = pathWithSlash.replace(currentLocalURLPrefix, "");
49
- const urls = getRelativeLocaleUrlList(slug);
88
+ // 为每个语言生成切换链接
89
+ return locales.map((locale) => {
90
+ // 手动构建 URL:/{locale}{pathWithoutLocale}
91
+ // 去除尾部斜杠(除了根路径)
92
+ const basePath = `/${locale}${pathWithoutLocale}`;
93
+ const url =
94
+ basePath.endsWith("/") && basePath !== `/${locale}/`
95
+ ? basePath.slice(0, -1)
96
+ : basePath === `/${locale}/`
97
+ ? `/${locale}`
98
+ : basePath;
50
99
 
51
- return urls.map((url: string) => ({
52
- locale: getLocaleFromUrl(url),
53
- name: LanguageUtil.getLanguageName(getLocaleFromUrl(url)),
54
- url: url,
55
- }));
100
+ return {
101
+ locale: locale,
102
+ name: LanguageUtil.getLanguageName(locale),
103
+ url: url,
104
+ };
105
+ });
56
106
  } catch (error) {
57
- console.warn("LanguageSwitcher: Error generating switcher links:", error);
107
+ console.error("LanguageSwitcher: Error generating switcher links:", error);
58
108
  throw error;
59
109
  }
60
110
  };
61
-
62
- /**
63
- * 检查语言切换器是否应该渲染
64
- * @param currentLocale - 当前语言代码
65
- * @param astroI18n - astro:i18n 模块
66
- * @returns 是否应该渲染的状态信息
67
- */
68
- export const checkSwitcherRenderState = (
69
- currentLocale: string | undefined,
70
- astroI18n: IAstroI18n | undefined,
71
- ): {
72
- shouldRender: boolean;
73
- currentLanguageName?: string;
74
- warnings?: string[];
75
- } => {
76
- const warnings: string[] = [];
77
-
78
- if (!currentLocale) {
79
- return { shouldRender: false, warnings };
80
- }
81
-
82
- if (!astroI18n) {
83
- return { shouldRender: false, warnings };
84
- }
85
-
86
- return {
87
- shouldRender: true,
88
- currentLanguageName: LanguageUtil.getLanguageName(currentLocale),
89
- };
90
- };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffic/cosy-ui",
3
- "version": "0.9.93",
3
+ "version": "0.9.94",
4
4
  "description": "An astro component library",
5
5
  "author": {
6
6
  "name": "nookery",