@coffic/cosy-ui 0.4.7 → 0.5.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/app.css +1 -1
- package/dist/components/base/Image.astro +0 -3
- package/dist/components/errors/404.astro +10 -0
- package/dist/components/icons/ChevronDownIcon.astro +34 -0
- package/dist/components/layouts/Footer.astro +2 -2
- package/dist/components/layouts/Header.astro +35 -110
- package/dist/components/layouts/Sidebar.astro +2 -2
- package/dist/components/layouts/SidebarNav.astro +5 -5
- package/dist/components/navigation/LanguageSwitcher.astro +22 -68
- package/dist/components/navigation/TableOfContents.astro +2 -2
- package/dist/database/BaseDB.ts +2 -2
- package/dist/entities/SidebarItem.ts +20 -23
- package/dist/index.ts +3 -0
- package/dist/models/BaseDoc.ts +11 -11
- package/dist/types/header.ts +4 -6
- package/dist/types/sidebar.ts +4 -6
- package/dist/utils/language.ts +137 -133
- package/dist/utils/link.ts +29 -3
- package/package.json +1 -1
- package/dist/database/MetaDB.ts +0 -75
- package/dist/i18n/Language.ts +0 -25
- package/dist/i18n/ui.ts +0 -18
- package/dist/i18n/utils.ts +0 -54
package/dist/utils/language.ts
CHANGED
@@ -4,168 +4,172 @@
|
|
4
4
|
* 提供语言相关的工具函数,用于多语言支持
|
5
5
|
*/
|
6
6
|
|
7
|
+
import { getRelativeLocaleUrl } from 'astro:i18n';
|
8
|
+
import { logger } from './logger';
|
9
|
+
import { LinkUtil } from './link';
|
10
|
+
import type { AstroGlobal } from 'astro';
|
11
|
+
|
7
12
|
// 支持的语言列表
|
8
13
|
export const SUPPORTED_LANGUAGES = ['en', 'zh-cn', 'zh'] as const;
|
9
14
|
export type SupportedLanguage = typeof SUPPORTED_LANGUAGES[number];
|
10
15
|
|
11
|
-
// 语言来源枚举
|
12
|
-
export enum LanguageSource {
|
13
|
-
USER = 'user', // 用户指定
|
14
|
-
URL = 'url', // 从URL获取
|
15
|
-
BROWSER = 'browser', // 从浏览器设置获取
|
16
|
-
DEFAULT = 'default' // 默认语言
|
17
|
-
}
|
18
|
-
|
19
|
-
// 语言信息接口
|
20
|
-
export interface LanguageInfo {
|
21
|
-
code: SupportedLanguage; // 语言代码
|
22
|
-
source: LanguageSource; // 语言来源
|
23
|
-
}
|
24
|
-
|
25
16
|
// 默认语言
|
26
17
|
export const DEFAULT_LANGUAGE: SupportedLanguage = 'en';
|
27
18
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
if (!lang) return false;
|
35
|
-
return SUPPORTED_LANGUAGES.includes(lang as SupportedLanguage);
|
36
|
-
}
|
37
|
-
|
38
|
-
/**
|
39
|
-
* 获取有效的语言代码
|
40
|
-
* @param lang 用户提供的语言代码
|
41
|
-
* @returns 有效的语言代码,如果提供的语言不支持则返回默认语言
|
42
|
-
*/
|
43
|
-
export function getValidLanguage(lang?: string): SupportedLanguage {
|
44
|
-
if (lang && isLanguageSupported(lang)) {
|
45
|
-
return lang;
|
46
|
-
}
|
47
|
-
return DEFAULT_LANGUAGE;
|
48
|
-
}
|
19
|
+
export class LanguageUtil {
|
20
|
+
static getRelativeLink(locale: string, astro: AstroGlobal): string {
|
21
|
+
const debug = false;
|
22
|
+
const currentLocale = astro.currentLocale;
|
23
|
+
const originalPath = astro.originPathname;
|
24
|
+
const result = getRelativeLocaleUrl(locale, originalPath.replaceAll("/" + currentLocale, ""));
|
49
25
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
* @returns 从URL中提取的语言代码,如果无法提取则返回undefined
|
54
|
-
*/
|
55
|
-
function getLanguageFromURL(url?: string): string | undefined {
|
56
|
-
let currentUrl = url;
|
26
|
+
if (debug) {
|
27
|
+
logger.debug(`getRelativeLink: locale=${locale}, currentPath=${originalPath}, currentLocale=${currentLocale}, result=${result}`);
|
28
|
+
}
|
57
29
|
|
58
|
-
|
59
|
-
if (typeof window === 'undefined') return undefined;
|
60
|
-
currentUrl = window.location.href;
|
30
|
+
return result;
|
61
31
|
}
|
62
32
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
33
|
+
static getLanguageName(code: string | undefined): string {
|
34
|
+
switch (code) {
|
35
|
+
case 'en':
|
36
|
+
return 'English';
|
37
|
+
case 'zh-cn':
|
38
|
+
return '简体中文';
|
39
|
+
case 'zh':
|
40
|
+
return '中文';
|
41
|
+
case undefined:
|
42
|
+
default:
|
43
|
+
return 'not known';
|
44
|
+
}
|
68
45
|
}
|
69
46
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
47
|
+
/**
|
48
|
+
* 检查语言是否被支持
|
49
|
+
* @param lang 要检查的语言代码
|
50
|
+
* @returns 如果语言被支持返回true,否则返回false
|
51
|
+
*/
|
52
|
+
static isLanguageSupported(lang?: string): lang is SupportedLanguage {
|
53
|
+
if (!lang) return false;
|
54
|
+
return SUPPORTED_LANGUAGES.includes(lang as SupportedLanguage);
|
75
55
|
}
|
76
56
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
57
|
+
/**
|
58
|
+
* 获取有效的语言代码
|
59
|
+
* @param lang 用户提供的语言代码
|
60
|
+
* @returns 有效的语言代码,如果提供的语言不支持则返回默认语言
|
61
|
+
*/
|
62
|
+
static getValidLanguage(lang?: string): SupportedLanguage {
|
63
|
+
if (lang && this.isLanguageSupported(lang)) {
|
64
|
+
return lang;
|
65
|
+
}
|
66
|
+
return DEFAULT_LANGUAGE;
|
83
67
|
}
|
84
68
|
|
85
|
-
|
86
|
-
|
69
|
+
/**
|
70
|
+
* 从URL中提取语言代码
|
71
|
+
* @param url 当前URL(可选)
|
72
|
+
* @returns 从URL中提取的语言代码,如果无法提取则返回undefined
|
73
|
+
*/
|
74
|
+
static getLanguageFromURL(url?: string): string | undefined {
|
75
|
+
let currentUrl = url;
|
76
|
+
|
77
|
+
if (currentUrl === undefined) {
|
78
|
+
if (typeof window === 'undefined') return undefined;
|
79
|
+
currentUrl = window.location.href;
|
80
|
+
}
|
87
81
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
82
|
+
// 尝试从路径中提取语言代码
|
83
|
+
// 例如: /zh-cn/components/button
|
84
|
+
const pathMatch = currentUrl.match(/^\/([\w-]+)\//);
|
85
|
+
if (pathMatch && this.isLanguageSupported(pathMatch[1])) {
|
86
|
+
return pathMatch[1];
|
87
|
+
}
|
88
|
+
|
89
|
+
// 如果网站运行在二级目录,则从路径中提取语言代码
|
90
|
+
// 例如: /docs/zh-cn/components/button
|
91
|
+
const pathMatch2 = currentUrl.match(/^\/([^\/]+)\/([\w-]+)\//);
|
92
|
+
if (pathMatch2 && this.isLanguageSupported(pathMatch2[2])) {
|
93
|
+
return pathMatch2[2];
|
94
|
+
}
|
94
95
|
|
95
|
-
|
96
|
-
|
96
|
+
// 尝试从查询参数中提取语言代码
|
97
|
+
// 例如: ?lang=zh-cn
|
98
|
+
const urlParams = new URLSearchParams(currentUrl.split('?')[1]);
|
99
|
+
const langParam = urlParams.get('lang');
|
100
|
+
if (langParam && this.isLanguageSupported(langParam)) {
|
101
|
+
return langParam;
|
102
|
+
}
|
97
103
|
|
98
|
-
|
99
|
-
if (isLanguageSupported(browserLang)) {
|
100
|
-
return browserLang;
|
104
|
+
return undefined;
|
101
105
|
}
|
102
106
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
107
|
+
/**
|
108
|
+
* 从浏览器设置中获取首选语言
|
109
|
+
* @returns 从浏览器设置中获取的语言代码,如果无法获取则返回undefined
|
110
|
+
*/
|
111
|
+
static getLanguageFromBrowser(): string | undefined {
|
112
|
+
if (typeof navigator === 'undefined') return undefined;
|
113
|
+
|
114
|
+
// 获取浏览器语言
|
115
|
+
const browserLang = navigator.language.toLowerCase();
|
116
|
+
|
117
|
+
// 检查完整匹配
|
118
|
+
if (this.isLanguageSupported(browserLang)) {
|
119
|
+
return browserLang;
|
109
120
|
}
|
110
|
-
}
|
111
121
|
|
112
|
-
|
113
|
-
|
122
|
+
// 检查语言前缀匹配
|
123
|
+
// 例如: zh-TW -> zh-cn
|
124
|
+
const langPrefix = browserLang.split('-')[0];
|
125
|
+
for (const lang of SUPPORTED_LANGUAGES) {
|
126
|
+
if (lang.startsWith(langPrefix)) {
|
127
|
+
return lang;
|
128
|
+
}
|
129
|
+
}
|
114
130
|
|
115
|
-
|
116
|
-
* 自动检测当前语言
|
117
|
-
* @param url 当前URL(可选)
|
118
|
-
* @returns 检测到的语言信息,包括语言代码和来源
|
119
|
-
*/
|
120
|
-
function detectLanguage(url?: string): LanguageInfo {
|
121
|
-
// 尝试从URL中获取语言
|
122
|
-
const urlLang = getLanguageFromURL(url);
|
123
|
-
if (urlLang) {
|
124
|
-
return {
|
125
|
-
code: urlLang as SupportedLanguage,
|
126
|
-
source: LanguageSource.URL
|
127
|
-
};
|
131
|
+
return undefined;
|
128
132
|
}
|
133
|
+
/**
|
134
|
+
* 获取当前语言
|
135
|
+
* @param userLang 用户指定的语言(可选)
|
136
|
+
* @param url 当前URL(可选)
|
137
|
+
* @returns 当前应使用的语言信息,包括语言代码和来源
|
138
|
+
*/
|
139
|
+
static getCurrentLanguage(userLang?: string, url?: string): string {
|
140
|
+
// 如果用户指定了语言,优先使用用户指定的语言
|
141
|
+
if (userLang) {
|
142
|
+
if (this.isLanguageSupported(userLang)) {
|
143
|
+
return userLang as SupportedLanguage;
|
144
|
+
} else {
|
145
|
+
// 用户指定的语言不支持,使用默认语言
|
146
|
+
return DEFAULT_LANGUAGE;
|
147
|
+
}
|
148
|
+
}
|
129
149
|
|
130
|
-
|
131
|
-
|
132
|
-
if (browserLang) {
|
133
|
-
return {
|
134
|
-
code: browserLang as SupportedLanguage,
|
135
|
-
source: LanguageSource.BROWSER
|
136
|
-
};
|
150
|
+
// 否则自动检测语言
|
151
|
+
return this.detectLanguage();
|
137
152
|
}
|
138
153
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
154
|
+
/**
|
155
|
+
* 自动检测当前语言
|
156
|
+
* @param url 当前URL(可选)
|
157
|
+
* @returns 检测到的语言信息,包括语言代码和来源
|
158
|
+
*/
|
159
|
+
static detectLanguage(url?: string): string {
|
160
|
+
// 尝试从URL中获取语言
|
161
|
+
const urlLang = this.getLanguageFromURL(url);
|
162
|
+
if (urlLang) {
|
163
|
+
return urlLang as SupportedLanguage;
|
164
|
+
}
|
145
165
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
* @returns 当前应使用的语言信息,包括语言代码和来源
|
151
|
-
*/
|
152
|
-
export function getCurrentLanguage(userLang?: string, url?: string): LanguageInfo {
|
153
|
-
// 如果用户指定了语言,优先使用用户指定的语言
|
154
|
-
if (userLang) {
|
155
|
-
if (isLanguageSupported(userLang)) {
|
156
|
-
return {
|
157
|
-
code: userLang as SupportedLanguage,
|
158
|
-
source: LanguageSource.USER
|
159
|
-
};
|
160
|
-
} else {
|
161
|
-
// 用户指定的语言不支持,使用默认语言
|
162
|
-
return {
|
163
|
-
code: DEFAULT_LANGUAGE,
|
164
|
-
source: LanguageSource.DEFAULT
|
165
|
-
};
|
166
|
+
// 尝试从浏览器设置中获取语言
|
167
|
+
const browserLang = this.getLanguageFromBrowser();
|
168
|
+
if (browserLang) {
|
169
|
+
return browserLang as SupportedLanguage;
|
166
170
|
}
|
167
|
-
}
|
168
171
|
|
169
|
-
|
170
|
-
|
171
|
-
}
|
172
|
+
// 如果无法检测,返回默认语言
|
173
|
+
return DEFAULT_LANGUAGE;
|
174
|
+
}
|
175
|
+
}
|
package/dist/utils/link.ts
CHANGED
@@ -1,6 +1,22 @@
|
|
1
1
|
import { logger } from "./logger";
|
2
2
|
|
3
|
-
export
|
3
|
+
export class LinkUtil {
|
4
|
+
// 从 astro.config.ts 中获取基础路径
|
5
|
+
static getBaseUrl = (): string => {
|
6
|
+
return import.meta.env.BASE_URL
|
7
|
+
};
|
8
|
+
|
9
|
+
// 生成带基础路径的完整 URL
|
10
|
+
static createUrl = (path: string): string => {
|
11
|
+
const baseUrl = LinkUtil.getBaseUrl();
|
12
|
+
// 如果路径以 '/' 开头,去除开头的 '/'
|
13
|
+
if (path.startsWith('/')) {
|
14
|
+
path = path.substring(1);
|
15
|
+
}
|
16
|
+
|
17
|
+
return `${baseUrl}${path}`;
|
18
|
+
};
|
19
|
+
|
4
20
|
/**
|
5
21
|
* 规范化语言代码
|
6
22
|
* @param lang - 语言代码
|
@@ -16,7 +32,7 @@ export default class LinkUtil {
|
|
16
32
|
}
|
17
33
|
|
18
34
|
static getHomeLink(lang: string): string {
|
19
|
-
return
|
35
|
+
return `${this.getBaseUrl}/${lang}`;
|
20
36
|
}
|
21
37
|
|
22
38
|
static getLessonsLink(lang: string): string {
|
@@ -213,4 +229,14 @@ export default class LinkUtil {
|
|
213
229
|
static getLoginLink(currentOrigin: string): string {
|
214
230
|
return `${currentOrigin}/api/login`;
|
215
231
|
}
|
216
|
-
|
232
|
+
|
233
|
+
static getActiveLink(currentLink: string, links: string[]): string {
|
234
|
+
let activeLink = '';
|
235
|
+
for (const link of links) {
|
236
|
+
if (currentLink.startsWith(link) && link.length > activeLink.length) {
|
237
|
+
activeLink = link;
|
238
|
+
}
|
239
|
+
}
|
240
|
+
return activeLink;
|
241
|
+
}
|
242
|
+
}
|
package/package.json
CHANGED
package/dist/database/MetaDB.ts
DELETED
@@ -1,75 +0,0 @@
|
|
1
|
-
import MetaDoc from "@/models/MetaDoc";
|
2
|
-
import { logger } from "@/utils/logger";
|
3
|
-
import { type CollectionEntry } from "astro:content";
|
4
|
-
import BaseDB from "./BaseDB";
|
5
|
-
|
6
|
-
export const COLLECTION_NAME = 'meta' as const;
|
7
|
-
export type MetaEntry = CollectionEntry<typeof COLLECTION_NAME>;
|
8
|
-
|
9
|
-
/**
|
10
|
-
* 元数据数据库类,用于管理网站的元数据内容集合(如"关于我们"等页面)
|
11
|
-
*
|
12
|
-
* 目录结构:
|
13
|
-
* ```
|
14
|
-
* meta/
|
15
|
-
* ├── zh-cn/ # 中文内容
|
16
|
-
* │ ├── about.md # 关于我们
|
17
|
-
* │ ├── advice.md # 建议
|
18
|
-
* └── en/ # 英文内容
|
19
|
-
* ├── about.md
|
20
|
-
* ├── privacy.md
|
21
|
-
* └── terms.md
|
22
|
-
* ```
|
23
|
-
*/
|
24
|
-
class MetaDB extends BaseDB<typeof COLLECTION_NAME, MetaEntry, MetaDoc> {
|
25
|
-
protected collectionName = COLLECTION_NAME;
|
26
|
-
|
27
|
-
protected createDoc(entry: MetaEntry): MetaDoc {
|
28
|
-
return new MetaDoc(entry);
|
29
|
-
}
|
30
|
-
|
31
|
-
/**
|
32
|
-
* 获取指定文档的兄弟文档
|
33
|
-
* 例如:对于 'zh-cn/about',会返回 'zh-cn' 下的文档
|
34
|
-
*
|
35
|
-
* @param targetId - 目标文档ID
|
36
|
-
* @returns 返回兄弟文档数组(包括目标文档本身)
|
37
|
-
*/
|
38
|
-
async getSiblings(targetId: string): Promise<MetaDoc[]> {
|
39
|
-
const target = await this.find(targetId);
|
40
|
-
if (!target) {
|
41
|
-
return [];
|
42
|
-
}
|
43
|
-
const docs = await this.getDocsByDepth(2);
|
44
|
-
return docs.filter(doc => doc.getLang() === target.getLang());
|
45
|
-
}
|
46
|
-
|
47
|
-
/**
|
48
|
-
* 获取用于 Astro 静态路由生成的路径参数,专门配合 [lang]/meta/[slug].astro 使用
|
49
|
-
*
|
50
|
-
* @returns 返回路径参数数组
|
51
|
-
*/
|
52
|
-
async getStaticPaths() {
|
53
|
-
const debug = false;
|
54
|
-
const docs = await this.getDescendantDocs('');
|
55
|
-
|
56
|
-
const paths = docs.map((doc) => {
|
57
|
-
return {
|
58
|
-
params: {
|
59
|
-
lang: doc.getLang(),
|
60
|
-
slug: doc.getSlug(),
|
61
|
-
},
|
62
|
-
};
|
63
|
-
});
|
64
|
-
|
65
|
-
if (debug) {
|
66
|
-
logger.array('所有元数据文档的路径', paths);
|
67
|
-
}
|
68
|
-
|
69
|
-
return paths;
|
70
|
-
}
|
71
|
-
}
|
72
|
-
|
73
|
-
// 创建并导出单例实例
|
74
|
-
const metaDB = new MetaDB();
|
75
|
-
export default metaDB;
|
package/dist/i18n/Language.ts
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
export type LangCode = 'zh-cn' | 'en';
|
2
|
-
|
3
|
-
export default class Language {
|
4
|
-
code: LangCode;
|
5
|
-
|
6
|
-
constructor(code: LangCode) {
|
7
|
-
this.code = code;
|
8
|
-
}
|
9
|
-
|
10
|
-
static fromString(lang: string): Language {
|
11
|
-
return new Language(lang as LangCode);
|
12
|
-
}
|
13
|
-
|
14
|
-
isChinese(): boolean {
|
15
|
-
return this.code === 'zh-cn';
|
16
|
-
}
|
17
|
-
|
18
|
-
isEnglish(): boolean {
|
19
|
-
return this.code === 'en';
|
20
|
-
}
|
21
|
-
|
22
|
-
static isCodeValid(code: string): boolean {
|
23
|
-
return code === 'zh-cn' || code === 'en';
|
24
|
-
}
|
25
|
-
}
|
package/dist/i18n/ui.ts
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
export const languages = {
|
2
|
-
en: 'English',
|
3
|
-
'zh-cn': '简体中文',
|
4
|
-
};
|
5
|
-
|
6
|
-
export const defaultLang = 'zh-cn';
|
7
|
-
|
8
|
-
export const ui = {
|
9
|
-
en: {
|
10
|
-
'nav.home': 'Home',
|
11
|
-
'nav.about': 'About',
|
12
|
-
'nav.twitter': 'Twitter',
|
13
|
-
},
|
14
|
-
'zh-cn': {
|
15
|
-
'nav.home': '首页',
|
16
|
-
'nav.about': '关于',
|
17
|
-
},
|
18
|
-
} as const;
|
package/dist/i18n/utils.ts
DELETED
@@ -1,54 +0,0 @@
|
|
1
|
-
import { ui, defaultLang } from './ui';
|
2
|
-
|
3
|
-
export function getLangFromUrl(url: URL) {
|
4
|
-
const [, lang] = url.pathname.split('/');
|
5
|
-
if (lang in ui) return lang as keyof typeof ui;
|
6
|
-
return defaultLang;
|
7
|
-
}
|
8
|
-
|
9
|
-
export function t(lang: string, key: keyof typeof ui[typeof defaultLang]) {
|
10
|
-
return ui[lang in ui ? lang as keyof typeof ui : defaultLang][key];
|
11
|
-
}
|
12
|
-
|
13
|
-
export const normalizeLang = (lang: string) => {
|
14
|
-
if (lang === 'zh-CN') {
|
15
|
-
return 'zh-cn';
|
16
|
-
}
|
17
|
-
return lang;
|
18
|
-
}
|
19
|
-
|
20
|
-
export const getLangFromSlug = (slug: string) => {
|
21
|
-
let lang = slug.split('/')[0];
|
22
|
-
|
23
|
-
return normalizeLang(lang);
|
24
|
-
}
|
25
|
-
|
26
|
-
export const getLangFromPathname = (pathname: string) => {
|
27
|
-
// 去除开头的 /
|
28
|
-
pathname = pathname.slice(1);
|
29
|
-
|
30
|
-
let lang = pathname.split('/')[0];
|
31
|
-
|
32
|
-
return normalizeLang(lang);
|
33
|
-
}
|
34
|
-
|
35
|
-
export const isValidLang = (lang: string) => {
|
36
|
-
return ['zh-cn', 'en'].includes(lang);
|
37
|
-
}
|
38
|
-
|
39
|
-
export const getLang = (...args: string[]) => {
|
40
|
-
const debug = false
|
41
|
-
|
42
|
-
if (debug) {
|
43
|
-
// eslint-disable-next-line no-console
|
44
|
-
console.log('getLang', args);
|
45
|
-
}
|
46
|
-
|
47
|
-
for (const arg of args) {
|
48
|
-
let normalizedLang = normalizeLang(arg);
|
49
|
-
if (isValidLang(normalizedLang)) {
|
50
|
-
return normalizedLang;
|
51
|
-
}
|
52
|
-
}
|
53
|
-
return 'zh-cn';
|
54
|
-
}
|