@hagicode/hagilight 0.1.2-dev.6.1.f3f7b3d → 0.1.2-dev.8.1.a91b4b9

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.
@@ -112,11 +112,15 @@ const { fallback, locale = Astro.currentLocale ?? 'en' } = Astro.props;
112
112
 
113
113
  .hagilight-promoto__title,
114
114
  .hagilight-promoto__description {
115
+ display: -webkit-box;
116
+ overflow: hidden;
115
117
  margin: 0;
116
118
  color: var(--sl-color-text-high, #111827);
117
119
  font-size: var(--sl-text-sm);
118
120
  font-weight: 600;
119
121
  line-height: 1.35;
122
+ -webkit-box-orient: vertical;
123
+ -webkit-line-clamp: 2;
120
124
  }
121
125
 
122
126
  .hagilight-promoto__description {
@@ -230,13 +234,14 @@ const { fallback, locale = Astro.currentLocale ?? 'en' } = Astro.props;
230
234
  }
231
235
 
232
236
  .hagilight-promoto__slide {
233
- grid-template-columns: minmax(0, 1fr) 4rem;
234
- gap: 0.7rem;
235
- padding: 0.85rem 3.5rem 0.85rem 0.85rem;
237
+ grid-template-columns: minmax(0, 1fr) auto;
238
+ gap: 0.5rem;
239
+ padding: 0.65rem 3.25rem 0.65rem 0.75rem;
236
240
  }
237
241
 
238
242
  .hagilight-promoto__copy {
239
- grid-column: 1 / -1;
243
+ grid-column: 1;
244
+ grid-row: 1 / 3;
240
245
  }
241
246
 
242
247
  .hagilight-promoto__title {
@@ -244,31 +249,50 @@ const { fallback, locale = Astro.currentLocale ?? 'en' } = Astro.props;
244
249
  }
245
250
 
246
251
  .hagilight-promoto__description {
247
- display: -webkit-box;
248
- overflow: hidden;
249
252
  font-size: var(--sl-text-xs);
250
253
  font-weight: 500;
251
- -webkit-box-orient: vertical;
252
- -webkit-line-clamp: 2;
253
254
  }
254
255
 
255
256
  .hagilight-promoto__image {
256
257
  grid-column: 2;
257
258
  grid-row: 1;
258
- width: 4rem;
259
+ width: 3rem;
259
260
  }
260
261
 
261
262
  .hagilight-promoto__cta {
262
- grid-column: 1 / -1;
263
- width: 100%;
264
- min-height: 2.3rem;
263
+ grid-column: 2;
264
+ grid-row: 2;
265
+ min-width: 4rem;
266
+ min-height: 1.9rem;
267
+ padding-inline: 0.5rem;
268
+ font-size: var(--sl-text-xs);
269
+ overflow-wrap: anywhere;
270
+ }
271
+
272
+ .hagilight-promoto__slide[data-has-image='false'] {
273
+ grid-template-rows: auto;
274
+ }
275
+
276
+ .hagilight-promoto__slide[data-has-image='false'] .hagilight-promoto__copy,
277
+ .hagilight-promoto__slide[data-has-image='false'] .hagilight-promoto__cta {
278
+ grid-row: 1;
279
+ }
280
+
281
+ .hagilight-promoto__slide[data-has-image='false'] .hagilight-promoto__cta {
282
+ min-width: 4.5rem;
265
283
  }
266
284
 
267
285
  .hagilight-promoto__controls {
268
286
  justify-content: flex-start;
269
287
  flex-wrap: wrap;
270
288
  gap: 0.35rem;
271
- padding: 0 0.85rem 0.85rem;
289
+ padding: 0 0.75rem 0.6rem;
290
+ }
291
+
292
+ .hagilight-promoto__control {
293
+ min-width: 2rem;
294
+ min-height: 2rem;
295
+ padding-inline: 0.5rem;
272
296
  }
273
297
  }
274
298
 
package/logo.png ADDED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hagicode/hagilight",
3
- "version": "0.1.2-dev.6.1.f3f7b3d",
3
+ "version": "0.1.2-dev.8.1.a91b4b9",
4
4
  "type": "module",
5
5
  "description": "Shared Astro components for HagiCode sites",
6
6
  "repository": {
@@ -15,13 +15,16 @@
15
15
  "exports": {
16
16
  "./Copyright": "./Copyright.astro",
17
17
  "./PromotoBanner": "./PromotoBanner.astro",
18
+ "./logo.png": "./logo.png",
18
19
  "./site-links": "./site-links.ts",
19
20
  "./GoogleAnalytics": "./GoogleAnalytics.astro",
20
21
  "./Analytics51LA": "./Analytics51LA.astro"
21
22
  },
22
23
  "files": [
23
24
  "*.astro",
24
- "*.ts"
25
+ "*.png",
26
+ "*.ts",
27
+ "*.json"
25
28
  ],
26
29
  "peerDependencies": {
27
30
  "astro": "^7.3.5"
package/promoto-banner.ts CHANGED
@@ -216,6 +216,7 @@ export function definePromotoBannerElement(): void {
216
216
  this.cards.forEach((card, index) => {
217
217
  const slide = document.createElement('article');
218
218
  slide.className = 'hagilight-promoto__slide';
219
+ slide.dataset.hasImage = String(Boolean(card.image));
219
220
  slide.setAttribute('aria-hidden', index === this.currentIndex ? 'false' : 'true');
220
221
  if (index !== this.currentIndex) slide.setAttribute('inert', '');
221
222
 
@@ -0,0 +1,226 @@
1
+ [
2
+ {
3
+ "id": "hagicode-main",
4
+ "name": {
5
+ "zh-CN": "HagiCode 主站",
6
+ "zh-Hant": "HagiCode 主站",
7
+ "en-US": "HagiCode Main Site",
8
+ "ja-JP": "HagiCode 公式サイト",
9
+ "ko-KR": "HagiCode 메인 사이트",
10
+ "de-DE": "HagiCode Hauptseite",
11
+ "fr-FR": "Site principal HagiCode",
12
+ "es-ES": "Sitio principal de HagiCode",
13
+ "pt-BR": "Site principal da HagiCode",
14
+ "ru-RU": "Главный сайт HagiCode"
15
+ },
16
+ "description": {
17
+ "zh-CN": "产品入口",
18
+ "zh-Hant": "產品入口",
19
+ "en-US": "Primary product entry.",
20
+ "ja-JP": "製品全体の入口です。",
21
+ "ko-KR": "제품으로 들어가는 기본 진입점입니다.",
22
+ "de-DE": "Zentraler Einstieg zum Produkt.",
23
+ "fr-FR": "Point d'entrée principal du produit.",
24
+ "es-ES": "Entrada principal al producto.",
25
+ "pt-BR": "Entrada principal do produto.",
26
+ "ru-RU": "Основной вход в продукт."
27
+ },
28
+ "url": "https://www.hagicode.com/"
29
+ },
30
+ {
31
+ "id": "newbe-blog",
32
+ "name": {
33
+ "zh-CN": "newbe",
34
+ "zh-Hant": "newbe",
35
+ "en-US": "newbe",
36
+ "ja-JP": "newbe",
37
+ "ko-KR": "newbe",
38
+ "de-DE": "newbe",
39
+ "fr-FR": "newbe",
40
+ "es-ES": "newbe",
41
+ "pt-BR": "newbe",
42
+ "ru-RU": "newbe"
43
+ },
44
+ "description": {
45
+ "zh-CN": "作者博客",
46
+ "zh-Hant": "作者部落格",
47
+ "en-US": "Author blog.",
48
+ "ja-JP": "作者ブログです。",
49
+ "ko-KR": "작성자 블로그입니다.",
50
+ "de-DE": "Blog des Autors.",
51
+ "fr-FR": "Blog de l'auteur.",
52
+ "es-ES": "Blog del autor.",
53
+ "pt-BR": "Blog do autor.",
54
+ "ru-RU": "Блог автора."
55
+ },
56
+ "url": "https://newbe.hagicode.com/"
57
+ },
58
+ {
59
+ "id": "index-data",
60
+ "name": {
61
+ "zh-CN": "Index Data Mirror",
62
+ "zh-Hant": "Index Data Mirror",
63
+ "en-US": "Index Data Mirror",
64
+ "ja-JP": "Index Data Mirror",
65
+ "ko-KR": "Index Data Mirror",
66
+ "de-DE": "Index Data Mirror",
67
+ "fr-FR": "Index Data Mirror",
68
+ "es-ES": "Index Data Mirror",
69
+ "pt-BR": "Index Data Mirror",
70
+ "ru-RU": "Index Data Mirror"
71
+ },
72
+ "description": {
73
+ "zh-CN": "公开数据",
74
+ "zh-Hant": "公開資料",
75
+ "en-US": "Public data mirror.",
76
+ "ja-JP": "公開データミラーです。",
77
+ "ko-KR": "공개 데이터 미러입니다.",
78
+ "de-DE": "Öffentlicher Daten-Spiegel.",
79
+ "fr-FR": "Miroir de données public.",
80
+ "es-ES": "Espejo de datos público.",
81
+ "pt-BR": "Espelho público de dados.",
82
+ "ru-RU": "Публичное зеркало данных."
83
+ },
84
+ "url": "https://index.hagicode.com/data/"
85
+ },
86
+ {
87
+ "id": "compose-builder",
88
+ "name": {
89
+ "zh-CN": "Docker Compose Builder",
90
+ "zh-Hant": "Docker Compose Builder",
91
+ "en-US": "Docker Compose Builder",
92
+ "ja-JP": "Docker Compose Builder",
93
+ "ko-KR": "Docker Compose Builder",
94
+ "de-DE": "Docker Compose Builder",
95
+ "fr-FR": "Docker Compose Builder",
96
+ "es-ES": "Docker Compose Builder",
97
+ "pt-BR": "Docker Compose Builder",
98
+ "ru-RU": "Docker Compose Builder"
99
+ },
100
+ "description": {
101
+ "zh-CN": "Docker 部署 Hagicode",
102
+ "zh-Hant": "使用 Docker 部署 Hagicode",
103
+ "en-US": "Deploy HagiCode with Docker.",
104
+ "ja-JP": "Docker で HagiCode をデプロイします。",
105
+ "ko-KR": "Docker로 HagiCode를 배포합니다.",
106
+ "de-DE": "HagiCode mit Docker bereitstellen.",
107
+ "fr-FR": "Déployer HagiCode avec Docker.",
108
+ "es-ES": "Despliega HagiCode con Docker.",
109
+ "pt-BR": "Implante a HagiCode com Docker.",
110
+ "ru-RU": "Развёртывание HagiCode через Docker."
111
+ },
112
+ "url": "https://builder.hagicode.com/"
113
+ },
114
+ {
115
+ "id": "status-page",
116
+ "name": {
117
+ "zh-CN": "HagiCode Status",
118
+ "zh-Hant": "HagiCode Status",
119
+ "en-US": "HagiCode Status",
120
+ "ja-JP": "HagiCode Status",
121
+ "ko-KR": "HagiCode Status",
122
+ "de-DE": "HagiCode Status",
123
+ "fr-FR": "HagiCode Status",
124
+ "es-ES": "HagiCode Status",
125
+ "pt-BR": "HagiCode Status",
126
+ "ru-RU": "HagiCode Status"
127
+ },
128
+ "description": {
129
+ "zh-CN": "服务状态",
130
+ "zh-Hant": "服務狀態",
131
+ "en-US": "Service status.",
132
+ "ja-JP": "サービス状態の確認ページです。",
133
+ "ko-KR": "서비스 상태를 확인하는 페이지입니다.",
134
+ "de-DE": "Dienststatus.",
135
+ "fr-FR": "État des services.",
136
+ "es-ES": "Estado del servicio.",
137
+ "pt-BR": "Status do serviço.",
138
+ "ru-RU": "Статус сервисов."
139
+ },
140
+ "url": "https://status.hagicode.com/"
141
+ },
142
+ {
143
+ "id": "awesome-design-gallery",
144
+ "name": {
145
+ "zh-CN": "Awesome Design MD",
146
+ "zh-Hant": "Awesome Design MD",
147
+ "en-US": "Awesome Design MD",
148
+ "ja-JP": "Awesome Design MD",
149
+ "ko-KR": "Awesome Design MD",
150
+ "de-DE": "Awesome Design MD",
151
+ "fr-FR": "Awesome Design MD",
152
+ "es-ES": "Awesome Design MD",
153
+ "pt-BR": "Awesome Design MD",
154
+ "ru-RU": "Awesome Design MD"
155
+ },
156
+ "description": {
157
+ "zh-CN": "超酷的AI Web 设计",
158
+ "zh-Hant": "超酷的 AI Web 設計",
159
+ "en-US": "A gallery of standout AI web design work.",
160
+ "ja-JP": "印象的な AI Web デザインを集めたギャラリーです。",
161
+ "ko-KR": "인상적인 AI 웹 디자인을 모아 둔 갤러리입니다.",
162
+ "de-DE": "Galerie mit markanten AI-Webdesigns.",
163
+ "fr-FR": "Galerie de designs web IA remarquables.",
164
+ "es-ES": "Galería de diseños web de IA destacados.",
165
+ "pt-BR": "Galeria com designs web de IA de destaque.",
166
+ "ru-RU": "Галерея выразительных AI web-дизайнов."
167
+ },
168
+ "url": "https://design.hagicode.com/"
169
+ },
170
+ {
171
+ "id": "soul-builder",
172
+ "name": {
173
+ "zh-CN": "Soul Builder",
174
+ "zh-Hant": "Soul Builder",
175
+ "en-US": "Soul Builder",
176
+ "ja-JP": "Soul Builder",
177
+ "ko-KR": "Soul Builder",
178
+ "de-DE": "Soul Builder",
179
+ "fr-FR": "Soul Builder",
180
+ "es-ES": "Soul Builder",
181
+ "pt-BR": "Soul Builder",
182
+ "ru-RU": "Soul Builder"
183
+ },
184
+ "description": {
185
+ "zh-CN": "Agent之魂",
186
+ "zh-Hant": "Agent 之魂",
187
+ "en-US": "Define the soul of an agent.",
188
+ "ja-JP": "Agent の魂を形にする体験です。",
189
+ "ko-KR": "에이전트의 영혼을 설계하는 경험입니다.",
190
+ "de-DE": "Forme die Seele eines Agenten.",
191
+ "fr-FR": "Façonnez l'âme d'un agent.",
192
+ "es-ES": "Define el alma de un agente.",
193
+ "pt-BR": "Defina a alma de um agente.",
194
+ "ru-RU": "Определите душу агента."
195
+ },
196
+ "url": "https://soul.hagicode.com/"
197
+ },
198
+ {
199
+ "id": "trait-builder",
200
+ "name": {
201
+ "zh-CN": "Trait Builder",
202
+ "zh-Hant": "Trait Builder",
203
+ "en-US": "Trait Builder",
204
+ "ja-JP": "Trait Builder",
205
+ "ko-KR": "Trait Builder",
206
+ "de-DE": "Trait Builder",
207
+ "fr-FR": "Trait Builder",
208
+ "es-ES": "Trait Builder",
209
+ "pt-BR": "Trait Builder",
210
+ "ru-RU": "Trait Builder"
211
+ },
212
+ "description": {
213
+ "zh-CN": "Agent之能",
214
+ "zh-Hant": "Agent 之能",
215
+ "en-US": "Shape what an agent can do.",
216
+ "ja-JP": "Agent の能力を組み立てる体験です。",
217
+ "ko-KR": "에이전트의 능력을 설계하는 경험입니다.",
218
+ "de-DE": "Definiere, was ein Agent kann.",
219
+ "fr-FR": "Définissez ce qu'un agent peut faire.",
220
+ "es-ES": "Define lo que un agente puede hacer.",
221
+ "pt-BR": "Defina o que um agente pode fazer.",
222
+ "ru-RU": "Соберите способности агента."
223
+ },
224
+ "url": "https://trait.hagicode.com/"
225
+ }
226
+ ]
package/site-links.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import docsRelatedSites from './related-sites.json' with { type: 'json' };
2
+
1
3
  export type SiteLinkKey =
2
4
  | 'home'
3
5
  | 'blog'
@@ -6,20 +8,26 @@ export type SiteLinkKey =
6
8
  | 'about'
7
9
  | 'dockerCompose'
8
10
  | 'productDocs'
11
+ | 'blogPosts'
9
12
  | 'rss'
10
13
  | 'costCalculator'
11
14
  | 'github'
12
15
  | 'discord'
16
+ | 'issueFeedback'
17
+ | 'contactEmail'
13
18
  | 'qqGroup'
14
- | 'steam';
19
+ | 'steam'
20
+ | 'icpFiling'
21
+ | 'publicSecurityFiling';
15
22
 
16
- export type LinkGroup = 'header' | 'quick' | 'community';
23
+ export type LinkGroup = 'header' | 'quick' | 'community' | 'filings';
17
24
  type LocalizedText = string | Readonly<Record<string, string>>;
18
25
 
19
26
  export interface SiteLink {
20
27
  id: SiteLinkKey | string;
21
28
  label: string;
22
29
  href: string;
30
+ ariaLabel?: string;
23
31
  target?: '_blank';
24
32
  rel?: 'noopener noreferrer';
25
33
  }
@@ -54,44 +62,156 @@ interface LinkDefinition {
54
62
  label: LocalizedText;
55
63
  href: string | ((locale: string) => string);
56
64
  external?: boolean;
65
+ ariaLabel?: LocalizedText;
57
66
  }
58
67
 
59
68
  const translations = {
60
- home: { 'zh-CN': '首页', 'zh-Hant': '首頁', 'en-US': 'Home' },
61
- blog: { 'zh-CN': '博客', 'zh-Hant': '部落格', 'en-US': 'Blog' },
62
- support: { 'zh-CN': '获取技术支持', 'zh-Hant': '獲取技術支援', 'en-US': 'Get Support' },
63
- downloadClient: { 'zh-CN': '下载客户端', 'zh-Hant': '下載客戶端', 'en-US': 'Download Client' },
64
- about: { 'zh-CN': '关于 HagiCode', 'zh-Hant': '關於 HagiCode', 'en-US': 'About HagiCode' },
65
- dockerCompose: { 'zh-CN': 'Docker Compose 安装', 'zh-Hant': 'Docker Compose 安裝', 'en-US': 'Docker Compose Installation' },
66
- productDocs: { 'zh-CN': '产品文档', 'zh-Hant': '產品文件', 'en-US': 'Product Docs' },
67
- rss: { 'zh-CN': 'RSS 订阅', 'zh-Hant': 'RSS 訂閱', 'en-US': 'RSS Feed' },
68
- costCalculator: { 'zh-CN': '算一算,AI 会不会淘汰我', 'zh-Hant': '算一算,AI 會不會淘汰我', 'en-US': 'Will AI Replace Me?' },
69
- github: { 'zh-CN': 'GitHub', 'zh-Hant': 'GitHub', 'en-US': 'GitHub' },
70
- discord: { 'zh-CN': 'Discord 社区', 'zh-Hant': 'Discord 社群', 'en-US': 'Discord Community' },
71
- qqGroup: { 'zh-CN': 'QQ 群 610394020', 'zh-Hant': 'QQ 群 610394020', 'en-US': 'QQ Group 610394020' },
72
- steam: { 'zh-CN': 'Steam', 'zh-Hant': 'Steam', 'en-US': 'Steam' },
69
+ home: {
70
+ 'zh-CN': '首页', 'zh-Hant': '首頁', 'en-US': 'Home', 'ja-JP': 'ホーム',
71
+ 'ko-KR': '홈', 'de-DE': 'Startseite', 'fr-FR': 'Accueil', 'es-ES': 'Inicio',
72
+ 'pt-BR': 'Início', 'ru-RU': 'Главная',
73
+ },
74
+ blog: {
75
+ 'zh-CN': '博客', 'zh-Hant': '部落格', 'en-US': 'Blog', 'ja-JP': 'ブログ',
76
+ 'ko-KR': '블로그', 'de-DE': 'Blog', 'fr-FR': 'Blog', 'es-ES': 'Blog',
77
+ 'pt-BR': 'Blog', 'ru-RU': 'Блог',
78
+ },
79
+ support: {
80
+ 'zh-CN': '获取技术支持', 'zh-Hant': '獲取技術支援', 'en-US': 'Get Support',
81
+ 'ja-JP': 'サポート', 'ko-KR': '기술 지원', 'de-DE': 'Support',
82
+ 'fr-FR': 'Assistance', 'es-ES': 'Soporte', 'pt-BR': 'Suporte', 'ru-RU': 'Поддержка',
83
+ },
84
+ downloadClient: {
85
+ 'zh-CN': '下载客户端', 'zh-Hant': '下載客戶端', 'en-US': 'Download Client',
86
+ 'ja-JP': 'クライアントをダウンロード', 'ko-KR': '클라이언트 다운로드',
87
+ 'de-DE': 'Client herunterladen', 'fr-FR': 'Télécharger le client',
88
+ 'es-ES': 'Descargar cliente', 'pt-BR': 'Baixar cliente', 'ru-RU': 'Скачать клиент',
89
+ },
90
+ about: {
91
+ 'zh-CN': '关于 HagiCode', 'zh-Hant': '關於 HagiCode', 'en-US': 'About HagiCode',
92
+ 'ja-JP': 'HagiCode について', 'ko-KR': 'HagiCode 소개', 'de-DE': 'Über HagiCode',
93
+ 'fr-FR': 'À propos de HagiCode', 'es-ES': 'Acerca de HagiCode',
94
+ 'pt-BR': 'Sobre a HagiCode', 'ru-RU': 'О HagiCode',
95
+ },
96
+ dockerCompose: {
97
+ 'zh-CN': 'Docker Compose 安装', 'zh-Hant': 'Docker Compose 安裝',
98
+ 'en-US': 'Docker Compose Installation', 'ja-JP': 'Docker Compose インストール',
99
+ 'ko-KR': 'Docker Compose 설치', 'de-DE': 'Docker-Compose-Installation',
100
+ 'fr-FR': 'Installation de Docker Compose', 'es-ES': 'Instalación de Docker Compose',
101
+ 'pt-BR': 'Instalação do Docker Compose', 'ru-RU': 'Установка Docker Compose',
102
+ },
103
+ productDocs: {
104
+ 'zh-CN': '产品文档', 'zh-Hant': '產品文件', 'en-US': 'Product Docs',
105
+ 'ja-JP': '製品ドキュメント', 'ko-KR': '제품 문서', 'de-DE': 'Produktdokumentation',
106
+ 'fr-FR': 'Documentation produit', 'es-ES': 'Documentación del producto',
107
+ 'pt-BR': 'Documentação do produto', 'ru-RU': 'Документация продукта',
108
+ },
109
+ blogPosts: {
110
+ 'zh-CN': '博客文章', 'zh-Hant': '部落格文章', 'en-US': 'Blog Posts',
111
+ 'ja-JP': 'ブログ記事', 'ko-KR': '블로그 글', 'de-DE': 'Blogbeiträge',
112
+ 'fr-FR': 'Articles du blog', 'es-ES': 'Artículos del blog',
113
+ 'pt-BR': 'Posts do blog', 'ru-RU': 'Статьи блога',
114
+ },
115
+ rss: {
116
+ 'zh-CN': 'RSS 订阅', 'zh-Hant': 'RSS 訂閱', 'en-US': 'RSS Feed',
117
+ 'ja-JP': 'RSS 配信', 'ko-KR': 'RSS 구독', 'de-DE': 'RSS-Feed',
118
+ 'fr-FR': 'Flux RSS', 'es-ES': 'RSS', 'pt-BR': 'Feed RSS', 'ru-RU': 'RSS-лента',
119
+ },
120
+ costCalculator: {
121
+ 'zh-CN': '算一算,AI会不会淘汰我', 'zh-Hant': '算一算,AI 會不會淘汰我',
122
+ 'en-US': 'Will AI Replace Me?', 'ja-JP': 'AI に置き換えられるか診断',
123
+ 'ko-KR': 'AI가 나를 대체할까?', 'de-DE': 'Wird KI mich ersetzen?',
124
+ 'fr-FR': "L'IA va-t-elle me remplacer ?", 'es-ES': 'Me reemplazará la IA?',
125
+ 'pt-BR': 'A IA vai me substituir?', 'ru-RU': 'Заменит ли меня ИИ?',
126
+ },
127
+ github: {
128
+ 'zh-CN': 'GitHub', 'zh-Hant': 'GitHub', 'en-US': 'GitHub', 'ja-JP': 'GitHub',
129
+ 'ko-KR': 'GitHub', 'de-DE': 'GitHub', 'fr-FR': 'GitHub', 'es-ES': 'GitHub',
130
+ 'pt-BR': 'GitHub', 'ru-RU': 'GitHub',
131
+ },
132
+ discord: {
133
+ 'zh-CN': 'Discord', 'zh-Hant': 'Discord', 'en-US': 'Discord', 'ja-JP': 'Discord',
134
+ 'ko-KR': 'Discord', 'de-DE': 'Discord', 'fr-FR': 'Discord', 'es-ES': 'Discord',
135
+ 'pt-BR': 'Discord', 'ru-RU': 'Discord',
136
+ },
137
+ issueFeedback: {
138
+ 'zh-CN': '问题反馈', 'zh-Hant': '問題回饋', 'en-US': 'Issue Feedback',
139
+ 'ja-JP': '問題の報告', 'ko-KR': '문제 신고', 'de-DE': 'Probleme melden',
140
+ 'fr-FR': 'Signaler un problème', 'es-ES': 'Reportar un problema',
141
+ 'pt-BR': 'Relatar problema', 'ru-RU': 'Сообщить о проблеме',
142
+ },
143
+ contactEmail: {
144
+ 'zh-CN': '联系邮箱', 'zh-Hant': '聯絡信箱', 'en-US': 'Contact Email',
145
+ 'ja-JP': '連絡先メール', 'ko-KR': '문의 이메일', 'de-DE': 'Kontakt-E-Mail',
146
+ 'fr-FR': 'E-mail de contact', 'es-ES': 'Correo de contacto',
147
+ 'pt-BR': 'E-mail de contato', 'ru-RU': 'Контактный email',
148
+ },
149
+ qqGroup: {
150
+ 'zh-CN': 'QQ 群 610394020', 'zh-Hant': 'QQ 群 610394020',
151
+ 'en-US': 'QQ Group 610394020', 'ja-JP': 'QQ グループ 610394020',
152
+ 'ko-KR': 'QQ 그룹 610394020', 'de-DE': 'QQ-Gruppe 610394020',
153
+ 'fr-FR': 'Groupe QQ 610394020', 'es-ES': 'Grupo QQ 610394020',
154
+ 'pt-BR': 'Grupo QQ 610394020', 'ru-RU': 'Группа QQ 610394020',
155
+ },
156
+ steam: {
157
+ 'zh-CN': 'Steam', 'zh-Hant': 'Steam', 'en-US': 'Steam', 'ja-JP': 'Steam',
158
+ 'ko-KR': 'Steam', 'de-DE': 'Steam', 'fr-FR': 'Steam', 'es-ES': 'Steam',
159
+ 'pt-BR': 'Steam', 'ru-RU': 'Steam',
160
+ },
161
+ icpFiling: '闽ICP备2026004153号-1',
162
+ publicSecurityFiling: '闽公网安备35011102351148号',
73
163
  } satisfies Record<SiteLinkKey, LocalizedText>;
74
164
 
75
165
  const defaultLinks: Record<SiteLinkKey, LinkDefinition> = {
76
- home: { label: translations.home, href: 'https://www.hagicode.com/' },
166
+ home: { label: translations.home, href: (locale) => marketingPath(locale, '/') },
77
167
  blog: { label: translations.blog, href: (locale) => docsPath(locale, '/blog/') },
78
- support: { label: translations.support, href: 'https://www.hagicode.com/about/' },
79
- downloadClient: { label: translations.downloadClient, href: 'https://www.hagicode.com/desktop/' },
80
- about: { label: translations.about, href: 'https://www.hagicode.com/about/' },
168
+ support: { label: translations.support, href: (locale) => marketingPath(locale, '/about/') },
169
+ downloadClient: { label: translations.downloadClient, href: (locale) => marketingPath(locale, '/desktop/') },
170
+ about: { label: translations.about, href: (locale) => marketingPath(locale, '/about/') },
81
171
  dockerCompose: { label: translations.dockerCompose, href: (locale) => docsPath(locale, '/installation/docker-compose/') },
82
172
  productDocs: { label: translations.productDocs, href: (locale) => docsPath(locale, '/product-overview/') },
83
- rss: { label: translations.rss, href: (locale) => docsPath(locale, '/blog/rss.zh-CN.xml') },
173
+ blogPosts: { label: translations.blogPosts, href: (locale) => docsPath(locale, '/blog/') },
174
+ rss: { label: translations.rss, href: (locale) => `https://docs.hagicode.com/blog/rss.${locale}.xml` },
84
175
  costCalculator: { label: translations.costCalculator, href: 'https://cost.hagicode.com', external: true },
85
176
  github: { label: translations.github, href: 'https://github.com/HagiCode-org/site', external: true },
86
177
  discord: { label: translations.discord, href: 'https://discord.gg/qY662sJK', external: true },
178
+ issueFeedback: { label: translations.issueFeedback, href: 'https://github.com/HagiCode-org/site/issues', external: true },
179
+ contactEmail: { label: translations.contactEmail, href: 'mailto:support@hagicode.com' },
87
180
  qqGroup: { label: translations.qqGroup, href: 'https://qm.qq.com/q/Fwb0o094kw', external: true },
88
181
  steam: { label: translations.steam, href: 'https://store.steampowered.com/app/4625540/Hagicode/', external: true },
182
+ icpFiling: {
183
+ label: translations.icpFiling,
184
+ href: 'https://beian.miit.gov.cn/',
185
+ external: true,
186
+ ariaLabel: {
187
+ 'zh-CN': '查看 ICP 备案信息', 'zh-Hant': '查看 ICP 備案資訊',
188
+ 'en-US': 'View ICP filing information', 'ja-JP': 'ICP 登録情報を表示',
189
+ 'ko-KR': 'ICP 등록 정보 보기', 'de-DE': 'ICP-Registrierungsinformationen anzeigen',
190
+ 'fr-FR': 'Voir les informations de dépôt ICP', 'es-ES': 'Ver la información de registro ICP',
191
+ 'pt-BR': 'Ver informações de registro ICP', 'ru-RU': 'Показать информацию о регистрации ICP',
192
+ },
193
+ },
194
+ publicSecurityFiling: {
195
+ label: translations.publicSecurityFiling,
196
+ href: 'http://www.beian.gov.cn/portal/registerSystemInfo',
197
+ external: true,
198
+ ariaLabel: {
199
+ 'zh-CN': '查看公安备案信息', 'zh-Hant': '查看公安備案資訊',
200
+ 'en-US': 'View public security filing information', 'ja-JP': '公安登録情報を表示',
201
+ 'ko-KR': '공안 등록 정보 보기', 'de-DE': 'Informationen zur Sicherheitsregistrierung anzeigen',
202
+ 'fr-FR': 'Voir les informations de dépôt de sécurité publique',
203
+ 'es-ES': 'Ver la información de registro de seguridad pública',
204
+ 'pt-BR': 'Ver informações de registro de segurança pública',
205
+ 'ru-RU': 'Показать информацию о регистрации в органах общественной безопасности',
206
+ },
207
+ },
89
208
  };
90
209
 
91
210
  const groups: Record<LinkGroup, readonly SiteLinkKey[]> = {
92
211
  header: ['home', 'blog', 'support'],
93
- quick: ['downloadClient', 'about', 'dockerCompose', 'productDocs', 'blog', 'rss', 'costCalculator'],
94
- community: ['github', 'discord', 'qqGroup', 'steam'],
212
+ quick: ['downloadClient', 'about', 'dockerCompose', 'productDocs', 'blogPosts', 'rss'],
213
+ community: ['github', 'discord', 'issueFeedback', 'contactEmail', 'qqGroup', 'costCalculator', 'steam'],
214
+ filings: ['icpFiling', 'publicSecurityFiling'],
95
215
  };
96
216
 
97
217
  function normalizeLocale(locale?: string | null): string {
@@ -105,24 +225,19 @@ function normalizeLocale(locale?: string | null): string {
105
225
  if (normalized === 'en' || normalized === 'en-us' || !normalized) {
106
226
  return 'en-US';
107
227
  }
108
- const canonical = [
109
- 'ja-JP', 'ko-KR', 'de-DE', 'fr-FR', 'es-ES', 'pt-BR', 'ru-RU',
110
- ].find((candidate) => candidate.toLowerCase() === normalized);
228
+ const canonical = ['ja-JP', 'ko-KR', 'de-DE', 'fr-FR', 'es-ES', 'pt-BR', 'ru-RU']
229
+ .find((candidate) => candidate.toLowerCase() === normalized);
111
230
  if (canonical) return canonical;
112
- return locale!.trim();
231
+ return 'en-US';
113
232
  }
114
233
 
115
234
  function docsPath(locale: string, pathname: string): string {
116
- if (pathname.startsWith('/blog/rss.')) {
117
- const language = locale.startsWith('zh') ? 'zh-CN' : 'en-US';
118
- return `https://docs.hagicode.com/blog/rss.${language}.xml`;
119
- }
120
- const supportedRouteLocales = new Set([
121
- 'en-US', 'zh-CN', 'zh-Hant', 'ja-JP', 'ko-KR', 'de-DE', 'fr-FR', 'es-ES', 'pt-BR', 'ru-RU',
122
- ]);
123
- const routeLocale = supportedRouteLocales.has(locale) ? locale : 'en-US';
124
- const prefix = routeLocale === 'zh-CN' ? '' : `/${routeLocale}`;
125
- return `https://docs.hagicode.com${prefix}${pathname}`;
235
+ const routePrefix = locale === 'zh-CN' ? '' : `/${locale}`;
236
+ return `https://docs.hagicode.com${routePrefix}${pathname}`;
237
+ }
238
+
239
+ function marketingPath(locale: string, pathname: string): string {
240
+ return `https://www.hagicode.com/${locale}${pathname}`;
126
241
  }
127
242
 
128
243
  function resolveLocalized(value: LocalizedText, locale: string, fallback: string): string {
@@ -165,6 +280,9 @@ function resolveLink(
165
280
  id: key,
166
281
  label,
167
282
  href,
283
+ ...(definition.ariaLabel
284
+ ? { ariaLabel: resolveLocalized(definition.ariaLabel, locale, label) }
285
+ : {}),
168
286
  ...(external ? { target: '_blank', rel: 'noopener noreferrer' } : {}),
169
287
  };
170
288
  }
@@ -173,9 +291,11 @@ export function resolveSiteLinks(localeInput?: string | null, options: SiteLinks
173
291
  const locale = normalizeLocale(localeInput);
174
292
  const resolvedGroups = Object.fromEntries(
175
293
  (Object.keys(groups) as LinkGroup[]).map((group) => {
176
- const catalogLinks = groups[group].map((key) =>
177
- resolveLink(key, locale, defaultLinks[key], options.overrides?.[key]),
178
- );
294
+ const catalogLinks = groups[group].map((key) => {
295
+ const override = options.overrides?.[key]
296
+ ?? (key === 'blogPosts' ? options.overrides?.blog : undefined);
297
+ return resolveLink(key, locale, defaultLinks[key], override);
298
+ });
179
299
  const extraLinks = (options.extraLinks?.[group] ?? []).map((entry, index) => {
180
300
  const definition: LinkDefinition = {
181
301
  label: entry.label,
@@ -189,14 +309,18 @@ export function resolveSiteLinks(localeInput?: string | null, options: SiteLinks
189
309
  ) as Record<LinkGroup, SiteLink[]>;
190
310
 
191
311
  const renderedUrls = new Set(
192
- Object.values(resolvedGroups).flat().map((link) => normalizeUrl(link.href)),
312
+ [...resolvedGroups.quick, ...resolvedGroups.community, ...resolvedGroups.filings]
313
+ .map((link) => normalizeUrl(link.href)),
193
314
  );
194
315
  if (options.siteUrl) renderedUrls.add(normalizeUrl(options.siteUrl));
195
316
 
196
317
  const relatedIds = new Set<string>();
197
318
  const relatedUrls = new Set<string>();
198
- const relatedSites = (options.relatedSites ?? []).flatMap((site) => {
199
- const normalizedUrl = normalizeUrl(site.url);
319
+ const relatedSites = (options.relatedSites ?? docsRelatedSites).flatMap((site) => {
320
+ const href = site.id === 'hagicode-main' && !options.relatedSites
321
+ ? marketingPath(locale, '/')
322
+ : site.url;
323
+ const normalizedUrl = normalizeUrl(href);
200
324
  if (site.id === options.siteId || renderedUrls.has(normalizedUrl)
201
325
  || relatedIds.has(site.id) || relatedUrls.has(normalizedUrl)) {
202
326
  return [];
@@ -209,20 +333,68 @@ export function resolveSiteLinks(localeInput?: string | null, options: SiteLinks
209
333
  description: site.description
210
334
  ? resolveLocalized(site.description, locale, '')
211
335
  : undefined,
212
- href: site.url,
336
+ href,
337
+ target: '_blank' as const,
338
+ rel: 'noopener noreferrer' as const,
213
339
  }];
214
340
  });
215
341
 
216
342
  return {
217
343
  locale,
218
344
  labels: {
219
- relatedSites: resolveLocalized({ 'zh-CN': '相关站点', 'zh-Hant': '相關站點', 'en-US': 'Related sites' }, locale, 'Related sites'),
220
- quickLinks: resolveLocalized({ 'zh-CN': '快捷链接', 'zh-Hant': '快速連結', 'en-US': 'Quick links' }, locale, 'Quick links'),
221
- community: resolveLocalized({ 'zh-CN': '社区与支持', 'zh-Hant': '社群與支援', 'en-US': 'Community & support' }, locale, 'Community & support'),
345
+ relatedSites: resolveLocalized({
346
+ 'zh-CN': '生态站点', 'zh-Hant': '生態站點', 'en-US': 'Ecosystem Sites',
347
+ 'ja-JP': 'エコシステムサイト', 'ko-KR': '에코시스템 사이트',
348
+ 'de-DE': 'Ökosystem-Seiten', 'fr-FR': "Sites de l'écosystème",
349
+ 'es-ES': 'Sitios del ecosistema', 'pt-BR': 'Sites do ecossistema',
350
+ 'ru-RU': 'Сайты экосистемы',
351
+ }, locale, 'Ecosystem Sites'),
352
+ quickLinks: resolveLocalized({
353
+ 'zh-CN': '快速链接', 'zh-Hant': '快速連結', 'en-US': 'Quick Links',
354
+ 'ja-JP': 'クイックリンク', 'ko-KR': '빠른 링크', 'de-DE': 'Schnellzugriffe',
355
+ 'fr-FR': 'Liens rapides', 'es-ES': 'Enlaces rápidos',
356
+ 'pt-BR': 'Links rápidos', 'ru-RU': 'Быстрые ссылки',
357
+ }, locale, 'Quick Links'),
358
+ community: resolveLocalized({
359
+ 'zh-CN': '社区', 'zh-Hant': '社群', 'en-US': 'Community',
360
+ 'ja-JP': 'コミュニティ', 'ko-KR': '커뮤니티', 'de-DE': 'Community',
361
+ 'fr-FR': 'Communauté', 'es-ES': 'Comunidad',
362
+ 'pt-BR': 'Comunidade', 'ru-RU': 'Сообщество',
363
+ }, locale, 'Community'),
364
+ navigation: {
365
+ relatedSites: resolveLocalized({
366
+ 'zh-CN': '生态站点链接', 'zh-Hant': '生態站點連結', 'en-US': 'Ecosystem site links',
367
+ 'ja-JP': 'エコシステムサイトへのリンク', 'ko-KR': '에코시스템 사이트 링크',
368
+ 'de-DE': 'Links zu Ökosystem-Seiten', 'fr-FR': "Liens vers les sites de l'écosystème",
369
+ 'es-ES': 'Enlaces de sitios del ecosistema', 'pt-BR': 'Links dos sites do ecossistema',
370
+ 'ru-RU': 'Ссылки на сайты экосистемы',
371
+ }, locale, 'Ecosystem site links'),
372
+ quickLinks: resolveLocalized({
373
+ 'zh-CN': '快速链接', 'zh-Hant': '快速連結', 'en-US': 'Quick links',
374
+ 'ja-JP': 'クイックリンク', 'ko-KR': '빠른 링크', 'de-DE': 'Schnellzugriffe',
375
+ 'fr-FR': 'Liens rapides', 'es-ES': 'Enlaces rápidos',
376
+ 'pt-BR': 'Links rápidos', 'ru-RU': 'Быстрые ссылки',
377
+ }, locale, 'Quick links'),
378
+ community: resolveLocalized({
379
+ 'zh-CN': '社区链接', 'zh-Hant': '社群連結', 'en-US': 'Community links',
380
+ 'ja-JP': 'コミュニティリンク', 'ko-KR': '커뮤니티 링크',
381
+ 'de-DE': 'Community-Links', 'fr-FR': 'Liens communautaires',
382
+ 'es-ES': 'Enlaces de la comunidad', 'pt-BR': 'Links da comunidade',
383
+ 'ru-RU': 'Ссылки сообщества',
384
+ }, locale, 'Community links'),
385
+ filings: resolveLocalized({
386
+ 'zh-CN': '查看备案信息', 'zh-Hant': '查看備案資訊',
387
+ 'en-US': 'View filing information', 'ja-JP': '登録情報を表示',
388
+ 'ko-KR': '등록 정보 보기', 'de-DE': 'Registrierungsinformationen anzeigen',
389
+ 'fr-FR': 'Voir les informations de dépôt', 'es-ES': 'Ver información de registro',
390
+ 'pt-BR': 'Ver informações de registro', 'ru-RU': 'Показать информацию о регистрации',
391
+ }, locale, 'View filing information'),
392
+ },
222
393
  },
223
394
  header: resolvedGroups.header,
224
395
  quick: resolvedGroups.quick,
225
396
  community: resolvedGroups.community,
397
+ filings: resolvedGroups.filings,
226
398
  relatedSites,
227
399
  };
228
400
  }