@coffic/cosy-ui 0.9.1 → 0.9.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.
@@ -12,19 +12,35 @@
12
12
  * 2. 响应式适配 - 在移动端和桌面端都有合适的展现形式
13
13
  * 3. 可定制性 - 支持多种配置选项,适应不同网站的风格和需求
14
14
  * 4. 多语言支持 - 内置语言切换功能,便于构建国际化网站
15
+ *
16
+ * @usage
17
+ * 基本用法:
18
+ * ```astro
19
+ * <Header />
20
+ * ```
21
+ *
22
+ * 启用语言切换功能:
23
+ * ```astro
24
+ * ---
25
+ * import * as astroI18n from 'astro:i18n';
26
+ * ---
27
+ * <Header astroI18n={astroI18n} />
28
+ * ```
29
+ *
30
+ * @props
31
+ * - astroI18n - 完整的 astro:i18n 模块(启用语言切换时需要)
15
32
  */
16
33
  import {
34
+ Image,
17
35
  LanguageSwitcher,
36
+ Link,
18
37
  LinkUtil,
19
38
  type IHeaderProps,
20
39
  type INavItem,
21
40
  NavItems,
22
- Link,
23
- Image,
24
41
  ThemeSwitcher,
25
42
  } from '../../index-astro';
26
43
  import Logo from '../../src/assets/logo-rounded.png';
27
- import { i18n } from 'astro:config/server';
28
44
 
29
45
  export interface Props extends IHeaderProps {
30
46
  debug?: boolean;
@@ -42,6 +58,7 @@ const {
42
58
  paddingVertical = 'none',
43
59
  navPosition = 'center',
44
60
  showThemeSwitcher = true,
61
+ astroI18n,
45
62
  } = Astro.props;
46
63
 
47
64
  // 根据高度设置样式
@@ -79,6 +96,10 @@ const logoSizeClasses = {
79
96
 
80
97
  const logoSizeClass = logoSizeClasses[height];
81
98
  const linkHeightClass = linkHeightClasses[height];
99
+
100
+ // 检查 i18n 是否启用,通过 Astro.currentLocale 来判断
101
+ let isI18nEnabled = Astro.currentLocale !== undefined;
102
+
82
103
  const currentPath = Astro.url.pathname;
83
104
  const activeLink = LinkUtil.getActiveLink(
84
105
  currentPath,
@@ -200,7 +221,7 @@ const activeLink = LinkUtil.getActiveLink(
200
221
  }
201
222
 
202
223
  {showThemeSwitcher && <ThemeSwitcher />}
203
- {i18n !== undefined && <LanguageSwitcher />}
224
+ {isI18nEnabled && <LanguageSwitcher astroI18n={astroI18n} />}
204
225
 
205
226
  <slot name="navbar-end" />
206
227
  </div>
@@ -14,51 +14,71 @@
14
14
  * 4. 一致的视觉风格 - 使用与整体设计系统一致的下拉菜单样式
15
15
  *
16
16
  * @usage
17
- * 基本用法:
17
+ * 基本用法(需要用户传入 astro:i18n 模块):
18
18
  * ```astro
19
- * <LanguageSwitcher />
19
+ * ---
20
+ * import * as astroI18n from 'astro:i18n';
21
+ * ---
22
+ * <LanguageSwitcher astroI18n={astroI18n} />
20
23
  * ```
21
24
  *
25
+ * @props
26
+ * - astroI18n - 完整的 astro:i18n 模块
27
+ * - class - 自定义CSS类名
22
28
  */
23
29
 
30
+ import { ChevronDownIcon, Link, ListItem } from '../../index-astro';
24
31
  import {
25
- ChevronDownIcon,
26
- LanguageUtil,
27
- Link,
28
- ListItem,
29
- } from '../../index-astro';
32
+ checkSwitcherRenderState,
33
+ generateSwitcherLinks,
34
+ type SwitcherLink,
35
+ } from './switcher_util';
30
36
  import '../../style.ts';
31
- import { i18n } from 'astro:config/server';
32
37
 
33
38
  interface Props {
39
+ /**
40
+ * 完整的 astro:i18n 模块
41
+ */
42
+ astroI18n?: any;
43
+
34
44
  /**
35
45
  * 自定义类名
36
46
  */
37
47
  class?: string;
38
48
  }
39
49
 
40
- const { class: className = '' } = Astro.props;
50
+ const { astroI18n, class: className = '' } = Astro.props;
41
51
 
42
- let links: any[] = [];
43
- let currentLocale: string | undefined = undefined;
44
- let currentLanguageName: string | undefined = undefined;
52
+ // 检查渲染状态
53
+ const renderState = checkSwitcherRenderState(Astro.currentLocale, astroI18n);
54
+
55
+ // 输出警告信息
56
+ if (renderState.warnings) {
57
+ renderState.warnings.forEach((warning) => console.warn(warning));
58
+ }
45
59
 
46
- if (i18n !== undefined) {
47
- const { getSwitcherLinks } = await import('./switcher_util.ts');
48
- links = getSwitcherLinks(Astro);
60
+ let links: SwitcherLink[] = [];
49
61
 
50
- currentLocale = Astro.currentLocale!;
51
- currentLanguageName = LanguageUtil.getLanguageName(currentLocale);
52
- } else {
53
- throw new Error('can not use LanguageSwitcher when i18n is disabled');
62
+ // 如果应该渲染,生成切换链接
63
+ if (renderState.shouldRender && Astro.currentLocale) {
64
+ try {
65
+ links = generateSwitcherLinks(
66
+ astroI18n,
67
+ Astro.currentLocale,
68
+ Astro.url.pathname
69
+ );
70
+ } catch (error) {
71
+ // 如果生成链接失败,设置为不渲染
72
+ renderState.shouldRender = false;
73
+ }
54
74
  }
55
75
  ---
56
76
 
57
77
  {
58
- i18n !== undefined && (
78
+ renderState.shouldRender && (
59
79
  <div class={`cosy:dropdown cosy:dropdown-end ${className}`}>
60
80
  <div tabindex="0" role="button" class:list={['cosy:btn cosy:btn-ghost']}>
61
- <span class="cosy:mr-1">{currentLanguageName}</span>
81
+ <span class="cosy:mr-1">{renderState.currentLanguageName}</span>
62
82
  <ChevronDownIcon size="16px" class="cosy:w-4 cosy:h-4" />
63
83
  </div>
64
84
  <ul
@@ -66,7 +86,7 @@ if (i18n !== undefined) {
66
86
  class="cosy:z-[1] cosy:bg-base-100 cosy:shadow cosy:p-2 cosy:rounded-box cosy:w-32 cosy:dropdown-content cosy:menu">
67
87
  {links.map((link) => (
68
88
  <ListItem>
69
- <Link href={link.url} active={currentLocale === link.locale}>
89
+ <Link href={link.url} active={Astro.currentLocale === link.locale}>
70
90
  {link.name}
71
91
  </Link>
72
92
  </ListItem>
@@ -1,6 +1,4 @@
1
- import { getRelativeLocaleUrl, getRelativeLocaleUrlList } from 'astro:i18n';
2
- import type { AstroGlobal } from 'astro';
3
- import { LanguageUtil } from '../../index-astro';
1
+ import { LanguageUtil } from "../../src/utils/language"
4
2
 
5
3
  export interface SwitcherLink {
6
4
  locale: string;
@@ -8,33 +6,79 @@ export interface SwitcherLink {
8
6
  url: string;
9
7
  }
10
8
 
11
- // 获取基础路径
12
- const getBaseUrl = (): string => {
13
- return import.meta.env.BASE_URL;
9
+ /**
10
+ * 获取基础 URL
11
+ */
12
+ export const getBaseUrl = (): string => {
13
+ return import.meta.env.BASE_URL || '/';
14
14
  };
15
15
 
16
- const getLocaleFromUrl = (url: string): string => {
16
+ /**
17
+ * 从 URL 中提取语言代码
18
+ */
19
+ export const getLocaleFromUrl = (url: string): string => {
17
20
  return url.replace(getBaseUrl(), '').split('/')[0];
18
21
  };
19
22
 
20
- export function getSwitcherLinks(astro: AstroGlobal): SwitcherLink[] {
21
- const currentLocale = astro.currentLocale;
23
+ /**
24
+ * 生成语言切换链接
25
+ * @param astroI18n - astro:i18n 模块
26
+ * @param currentLocale - 当前语言代码
27
+ * @param pathname - 当前页面路径
28
+ * @returns 语言切换链接数组
29
+ */
30
+ export const generateSwitcherLinks = (
31
+ astroI18n: any,
32
+ currentLocale: string,
33
+ pathname: string
34
+ ): SwitcherLink[] => {
35
+ try {
36
+ const { getRelativeLocaleUrl, getRelativeLocaleUrlList } = astroI18n;
22
37
 
23
- if (currentLocale === undefined) {
24
- throw new Error('can not get switcher links when i18n is disabled');
38
+ const currentLocalURLPrefix = getRelativeLocaleUrl(currentLocale, '');
39
+ const pathWithSlash = pathname + '/';
40
+ const slug = pathWithSlash.replace(currentLocalURLPrefix, '');
41
+ const urls = getRelativeLocaleUrlList(slug);
42
+
43
+ return urls.map((url: string) => ({
44
+ locale: getLocaleFromUrl(url),
45
+ name: LanguageUtil.getLanguageName(getLocaleFromUrl(url)),
46
+ url: url,
47
+ }));
48
+ } catch (error) {
49
+ console.warn('LanguageSwitcher: Error generating switcher links:', error);
50
+ throw error;
25
51
  }
52
+ };
26
53
 
27
- const currentLocalURLPrefix = getRelativeLocaleUrl(currentLocale, '');
28
- const pathname = astro.url.pathname + '/';
29
- const slug = pathname.replace(
30
- currentLocalURLPrefix,
31
- ''
32
- );
33
- const urls = getRelativeLocaleUrlList(slug);
34
-
35
- return urls.map((url) => ({
36
- locale: getLocaleFromUrl(url),
37
- name: LanguageUtil.getLanguageName(getLocaleFromUrl(url)),
38
- url: url
39
- }));
40
- }
54
+ /**
55
+ * 检查语言切换器是否应该渲染
56
+ * @param currentLocale - 当前语言代码
57
+ * @param astroI18n - astro:i18n 模块
58
+ * @returns 是否应该渲染的状态信息
59
+ */
60
+ export const checkSwitcherRenderState = (
61
+ currentLocale: string | undefined,
62
+ astroI18n: any
63
+ ): {
64
+ shouldRender: boolean;
65
+ currentLanguageName?: string;
66
+ warnings?: string[];
67
+ } => {
68
+ const warnings: string[] = [];
69
+
70
+ if (!currentLocale) {
71
+ warnings.push('LanguageSwitcher: i18n is not enabled in the current project');
72
+ return { shouldRender: false, warnings };
73
+ }
74
+
75
+ if (!astroI18n) {
76
+ warnings.push('LanguageSwitcher: astroI18n module is required. Please pass the astro:i18n module as a prop.');
77
+ return { shouldRender: false, warnings };
78
+ }
79
+
80
+ return {
81
+ shouldRender: true,
82
+ currentLanguageName: LanguageUtil.getLanguageName(currentLocale),
83
+ };
84
+ };
@@ -115,7 +115,7 @@ const hrefToIconMap: Record<string, string> = {
115
115
  */
116
116
  export function getIconFromHref(href: string, fallbackIcon: string = 'folder'): string {
117
117
  // 将 href 转换为小写并移除路径分隔符
118
- const normalizedHref = href.toLowerCase().replace(/[\/\-_]/g, '');
118
+ const normalizedHref = href.toLowerCase().replace(/[/\-_]/g, '');
119
119
 
120
120
  // 遍历映射表,找到匹配的关键词
121
121
  for (const [keyword, iconName] of Object.entries(hrefToIconMap)) {
@@ -1,4 +1,5 @@
1
1
  import type { INavItem } from './nav';
2
+ import type { ImageMetadata } from 'astro';
2
3
 
3
4
  export interface IHeaderProps {
4
5
  /**
@@ -7,6 +8,11 @@ export interface IHeaderProps {
7
8
  */
8
9
  defaultSidebarOpen?: boolean;
9
10
 
11
+ /**
12
+ * 完整的 astro:i18n 模块(启用语言切换时需要)
13
+ */
14
+ astroI18n?: any;
15
+
10
16
  /**
11
17
  * 导航栏高度
12
18
  * @default "md"
@@ -5,67 +5,67 @@ import type { IMetaProps } from './meta';
5
5
  import type { ISidebarProps } from './sidebar';
6
6
 
7
7
  export interface IAppLayoutProps {
8
- /**
9
- * 是否显示侧边栏
10
- * @default true
11
- */
12
- showSidebar?: boolean;
8
+ /**
9
+ * 是否显示侧边栏
10
+ * @default true
11
+ */
12
+ showSidebar?: boolean;
13
13
 
14
- /**
15
- * 是否显示页眉
16
- * @default true
17
- */
18
- showHeader?: boolean;
14
+ /**
15
+ * 是否显示页眉
16
+ * @default true
17
+ */
18
+ showHeader?: boolean;
19
19
 
20
- /**
21
- * 是否显示页脚
22
- * @default true
23
- */
24
- showFooter?: boolean;
20
+ /**
21
+ * 是否显示页脚
22
+ * @default true
23
+ */
24
+ showFooter?: boolean;
25
25
 
26
- /**
27
- * 自定义头部内容
28
- */
29
- head?: astroHTML.JSX.Element;
26
+ /**
27
+ * 自定义头部内容
28
+ */
29
+ head?: any;
30
30
 
31
- /**
32
- * 自定义头部内容
33
- */
34
- headerConfig: IHeaderProps;
31
+ /**
32
+ * 自定义头部内容
33
+ */
34
+ headerConfig: IHeaderProps;
35
35
 
36
- /**
37
- * 侧边栏配置
38
- */
39
- sidebarConfig: ISidebarProps;
36
+ /**
37
+ * 侧边栏配置
38
+ */
39
+ sidebarConfig: ISidebarProps;
40
40
 
41
- /**
42
- * 主内容配置
43
- */
44
- mainContentConfig: IMainContentProps;
41
+ /**
42
+ * 主内容配置
43
+ */
44
+ mainContentConfig: IMainContentProps;
45
45
 
46
- /**
47
- * 页面类名
48
- */
49
- class?: string;
46
+ /**
47
+ * 页面类名
48
+ */
49
+ class?: string;
50
50
 
51
- /**
52
- * 类名列表
53
- */
54
- 'class:list'?: any;
51
+ /**
52
+ * 类名列表
53
+ */
54
+ 'class:list'?: any;
55
55
 
56
- /**
57
- * 调试模式,显示各个部分的边框
58
- * @default false
59
- */
60
- debug?: boolean;
56
+ /**
57
+ * 调试模式,显示各个部分的边框
58
+ * @default false
59
+ */
60
+ debug?: boolean;
61
61
 
62
- /**
63
- * 元数据配置
64
- */
65
- metaConfig: IMetaProps;
62
+ /**
63
+ * 元数据配置
64
+ */
65
+ metaConfig: IMetaProps;
66
66
 
67
- /**
68
- * 页脚相关配置
69
- */
70
- footerConfig: IFooterProps;
67
+ /**
68
+ * 页脚相关配置
69
+ */
70
+ footerConfig: IFooterProps;
71
71
  }
@@ -1,3 +1,5 @@
1
+ import type { ImageMetadata } from 'astro';
2
+
1
3
  export interface IMetaProps {
2
4
  title: string;
3
5
  description: string;
@@ -41,7 +43,7 @@ export interface IMetaProps {
41
43
  /**
42
44
  * 自定义头部内容
43
45
  */
44
- head?: astroHTML.JSX.Element;
46
+ head?: any;
45
47
 
46
48
  /**
47
49
  * 页面类名
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffic/cosy-ui",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "An astro component library",
5
5
  "author": {
6
6
  "name": "nookery",
@@ -1,121 +0,0 @@
1
- import { getRelativeLocaleUrl } from 'astro:i18n';
2
- import type { AstroGlobal } from 'astro';
3
- import { cosyLogger } from '../cosy';
4
-
5
- // 默认语言
6
- export const DEFAULT_LANGUAGE = 'en';
7
-
8
- /**
9
- * 语言工具模块
10
- *
11
- * 提供语言相关的工具函数,用于多语言支持
12
- */
13
- export class LanguageUtil {
14
- static getRelativeLink(locale: string, astro: AstroGlobal): string {
15
- const debug = false;
16
- const currentLocale = astro.currentLocale;
17
- const originalPath = astro.originPathname;
18
- const result = getRelativeLocaleUrl(
19
- locale,
20
- originalPath.replaceAll('/' + currentLocale, '')
21
- );
22
-
23
- if (debug) {
24
- cosyLogger.debug(
25
- `getRelativeLink: locale=${locale}, currentPath=${originalPath}, currentLocale=${currentLocale}, result=${result}`
26
- );
27
- }
28
-
29
- return result;
30
- }
31
-
32
- static getLanguageName(code: string | undefined): string {
33
- switch (code) {
34
- case 'en':
35
- return 'English';
36
- case 'zh-cn':
37
- return '简体中文';
38
- case 'zh':
39
- return '中文';
40
- case undefined:
41
- default:
42
- return 'not known';
43
- }
44
- }
45
-
46
- /**
47
- * 获取当前语言
48
- * @param astro Astro全局对象
49
- * @returns 当前应使用的语言代码
50
- */
51
- static getCurrentLanguage(astro: AstroGlobal): string {
52
- // 尝试从Astro全局对象中获取语言
53
- const astroLang = astro.currentLocale;
54
- if (astroLang) {
55
- return astroLang;
56
- }
57
- // 尝试从URL中获取语言
58
- const urlLang = this.getLanguageFromURL(astro.url.pathname);
59
- if (urlLang) {
60
- return urlLang;
61
- }
62
-
63
- // 尝试从浏览器设置中获取语言
64
- const browserLang = this.getLanguageFromBrowser();
65
- if (browserLang) {
66
- return browserLang;
67
- }
68
-
69
- // 尝试从Astro全局对象中获取语言
70
- const preferredLocale = astro.preferredLocale;
71
- if (preferredLocale) {
72
- return preferredLocale;
73
- }
74
-
75
- // 如果无法检测,返回默认语言
76
- return DEFAULT_LANGUAGE;
77
- }
78
-
79
- /**
80
- * 从URL中提取语言代码
81
- * @param url 当前URL
82
- * @returns 从URL中提取的语言代码,如果无法提取则返回undefined
83
- */
84
- private static getLanguageFromURL(url: string): string | undefined {
85
- let currentUrl = url;
86
-
87
- if (currentUrl === undefined) {
88
- if (typeof window === 'undefined') return undefined;
89
- currentUrl = window.location.href;
90
- }
91
-
92
- // 尝试从路径中提取语言代码
93
- // 例如: /zh-cn/components/button
94
- const pathMatch = currentUrl.match(/^\/([\w-]+)\//);
95
- if (pathMatch) {
96
- return pathMatch[1];
97
- }
98
-
99
- // 尝试从查询参数中提取语言代码
100
- // 例如: ?lang=zh-cn
101
- const urlParams = new URLSearchParams(currentUrl.split('?')[1]);
102
- const langParam = urlParams.get('lang');
103
- if (langParam) {
104
- return langParam;
105
- }
106
-
107
- return undefined;
108
- }
109
-
110
- /**
111
- * 从浏览器设置中获取首选语言
112
- * @returns 从浏览器设置中获取的语言代码,如果无法获取则返回undefined
113
- */
114
- private static getLanguageFromBrowser(): string | undefined {
115
- if (typeof navigator === 'undefined') return undefined;
116
-
117
- // 获取浏览器语言
118
- const browserLang = navigator.language.toLowerCase();
119
- return browserLang;
120
- }
121
- }