Skip to content

Commit 93be9fa

Browse files
feat(footer): localize footer and content layout toggle for all locales
Replace all hardcoded Chinese strings in StarlightFooter with locale-aware copy from getDocsFooterCopy and a per-locale link label map. Refactor DocsContentLayoutToggle to use getDocsContentLayoutToggle Copy. Remove the now-unnecessary SiteLocale cast in footer-site-links. Update integration test assertions to match the new localized output. Co-Authored-By: Hagicode <noreply@hagicode.com> Signed-off-by: newbe36524 <newbe36524@qq.com>
1 parent 76b2824 commit 93be9fa

4 files changed

Lines changed: 169 additions & 38 deletions

File tree

src/components/DocsContentLayoutToggle.astro

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,10 @@ import {
66
DOCS_CONTENT_LAYOUT_WIDE,
77
DEFAULT_DOCS_CONTENT_LAYOUT_MODE,
88
} from '../lib/docs-content-layout.mjs';
9+
import { getDocsContentLayoutToggleCopy } from '../lib/i18n';
910
1011
const { locale = 'root' } = Astro.props;
11-
const isEnglish = locale === 'en';
12-
13-
const copy = isEnglish
14-
? {
15-
label: 'Content layout',
16-
wide: 'Wide',
17-
narrow: 'Narrow',
18-
}
19-
: {
20-
label: '正文宽度',
21-
wide: '宽版',
22-
narrow: '窄版',
23-
};
12+
const copy = getDocsContentLayoutToggleCopy(locale);
2413
2514
const modes = [
2615
{ value: DOCS_CONTENT_LAYOUT_WIDE, label: copy.wide },

src/components/StarlightFooter.astro

Lines changed: 163 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,163 @@ import DocsPromoteInfoBanner from './DocsPromoteInfoBanner.astro';
1616
import { getLink, getLinkTarget, getLinkRel } from '@shared/links';
1717
import { resolveDocsFooterSiteLinks } from '@/lib/footer-site-links';
1818
import { BLOG_LANGUAGE_OPTIONS, getBlogLanguageByRouteLocale } from '@/lib/blog-i18n';
19+
import { buildDocsRoutePath, getDocsFooterCopy, resolveDocsLocale, type DocsLocale } from '@/lib/i18n';
1920
2021
// 从共享库获取链接
2122
const desktopLink = getLink('desktop');
2223
const githubLink = getLink('github');
2324
const qqGroupLink = getLink('qqGroup');
2425
const discordLink = getLink('discord');
25-
const blogLink = getLink('blog');
26-
const productOverviewLink = getLink('productOverview');
2726
const aboutLink = getLink('about');
2827
const costCalculatorLink = getLink('costCalculator');
2928
const steamLink = 'https://store.steampowered.com/app/4625540/Hagicode/';
3029
const currentRouteLocale = Astro.locals?.starlightRoute?.locale ?? 'root';
30+
const docsLocale = resolveDocsLocale(currentRouteLocale);
3131
const relatedSiteLinks = resolveDocsFooterSiteLinks(currentRouteLocale, [
3232
{ href: costCalculatorLink },
3333
]);
3434
const rssLink = getBlogLanguageByRouteLocale(currentRouteLocale)?.rssPath ?? BLOG_LANGUAGE_OPTIONS[0].rssPath;
35+
const footerCopy = getDocsFooterCopy(currentRouteLocale);
36+
37+
type FooterLinkCopy = Record<DocsLocale, string>;
38+
const footerLinkCopy = {
39+
downloadClient: {
40+
root: '下载客户端',
41+
'zh-Hant': '下載客戶端',
42+
en: 'Download Client',
43+
'ja-JP': 'クライアントをダウンロード',
44+
'ko-KR': '클라이언트 다운로드',
45+
'de-DE': 'Client herunterladen',
46+
'fr-FR': 'Télécharger le client',
47+
'es-ES': 'Descargar cliente',
48+
'pt-BR': 'Baixar cliente',
49+
'ru-RU': 'Скачать клиент',
50+
},
51+
about: {
52+
root: '关于 HagiCode',
53+
'zh-Hant': '關於 HagiCode',
54+
en: 'About HagiCode',
55+
'ja-JP': 'HagiCode について',
56+
'ko-KR': 'HagiCode 소개',
57+
'de-DE': 'Über HagiCode',
58+
'fr-FR': 'À propos de HagiCode',
59+
'es-ES': 'Acerca de HagiCode',
60+
'pt-BR': 'Sobre a HagiCode',
61+
'ru-RU': 'О HagiCode',
62+
},
63+
dockerComposeInstall: {
64+
root: 'Docker Compose 安装',
65+
'zh-Hant': 'Docker Compose 安裝',
66+
en: 'Docker Compose Installation',
67+
'ja-JP': 'Docker Compose インストール',
68+
'ko-KR': 'Docker Compose 설치',
69+
'de-DE': 'Docker-Compose-Installation',
70+
'fr-FR': 'Installation de Docker Compose',
71+
'es-ES': 'Instalación de Docker Compose',
72+
'pt-BR': 'Instalação do Docker Compose',
73+
'ru-RU': 'Установка Docker Compose',
74+
},
75+
productDocs: {
76+
root: '产品文档',
77+
'zh-Hant': '產品文件',
78+
en: 'Product Docs',
79+
'ja-JP': '製品ドキュメント',
80+
'ko-KR': '제품 문서',
81+
'de-DE': 'Produktdokumentation',
82+
'fr-FR': 'Documentation produit',
83+
'es-ES': 'Documentación del producto',
84+
'pt-BR': 'Documentação do produto',
85+
'ru-RU': 'Документация продукта',
86+
},
87+
blog: {
88+
root: '博客文章',
89+
'zh-Hant': '部落格文章',
90+
en: 'Blog Posts',
91+
'ja-JP': 'ブログ記事',
92+
'ko-KR': '블로그 글',
93+
'de-DE': 'Blogbeiträge',
94+
'fr-FR': 'Articles du blog',
95+
'es-ES': 'Artículos del blog',
96+
'pt-BR': 'Posts do blog',
97+
'ru-RU': 'Статьи блога',
98+
},
99+
rss: {
100+
root: 'RSS 订阅',
101+
'zh-Hant': 'RSS 訂閱',
102+
en: 'RSS Feed',
103+
'ja-JP': 'RSS 配信',
104+
'ko-KR': 'RSS 구독',
105+
'de-DE': 'RSS-Feed',
106+
'fr-FR': 'Flux RSS',
107+
'es-ES': 'RSS',
108+
'pt-BR': 'Feed RSS',
109+
'ru-RU': 'RSS-лента',
110+
},
111+
issueFeedback: {
112+
root: '问题反馈',
113+
'zh-Hant': '問題回饋',
114+
en: 'Issue Feedback',
115+
'ja-JP': '問題の報告',
116+
'ko-KR': '문제 신고',
117+
'de-DE': 'Probleme melden',
118+
'fr-FR': 'Signaler un problème',
119+
'es-ES': 'Reportar un problema',
120+
'pt-BR': 'Relatar problema',
121+
'ru-RU': 'Сообщить о проблеме',
122+
},
123+
contactEmail: {
124+
root: '联系邮箱',
125+
'zh-Hant': '聯絡信箱',
126+
en: 'Contact Email',
127+
'ja-JP': '連絡先メール',
128+
'ko-KR': '문의 이메일',
129+
'de-DE': 'Kontakt-E-Mail',
130+
'fr-FR': 'E-mail de contact',
131+
'es-ES': 'Correo de contacto',
132+
'pt-BR': 'E-mail de contato',
133+
'ru-RU': 'Контактный email',
134+
},
135+
qqGroup: {
136+
root: 'QQ 群 610394020',
137+
'zh-Hant': 'QQ 群 610394020',
138+
en: 'QQ Group 610394020',
139+
'ja-JP': 'QQ グループ 610394020',
140+
'ko-KR': 'QQ 그룹 610394020',
141+
'de-DE': 'QQ-Gruppe 610394020',
142+
'fr-FR': 'Groupe QQ 610394020',
143+
'es-ES': 'Grupo QQ 610394020',
144+
'pt-BR': 'Grupo QQ 610394020',
145+
'ru-RU': 'Группа QQ 610394020',
146+
},
147+
costCalculator: {
148+
root: '算一算,AI会不会淘汰我',
149+
'zh-Hant': '算一算,AI 會不會淘汰我',
150+
en: 'Will AI Replace Me?',
151+
'ja-JP': 'AI に置き換えられるか診断',
152+
'ko-KR': 'AI가 나를 대체할까?',
153+
'de-DE': 'Wird KI mich ersetzen?',
154+
'fr-FR': "L'IA va-t-elle me remplacer ?",
155+
'es-ES': 'Me reemplazará la IA?',
156+
'pt-BR': 'A IA vai me substituir?',
157+
'ru-RU': 'Заменит ли меня ИИ?',
158+
},
159+
steam: {
160+
root: 'Steam',
161+
'zh-Hant': 'Steam',
162+
en: 'Steam',
163+
'ja-JP': 'Steam',
164+
'ko-KR': 'Steam',
165+
'de-DE': 'Steam',
166+
'fr-FR': 'Steam',
167+
'es-ES': 'Steam',
168+
'pt-BR': 'Steam',
169+
'ru-RU': 'Steam',
170+
},
171+
} satisfies Record<string, FooterLinkCopy>;
172+
173+
function getFooterLinkLabel(key: keyof typeof footerLinkCopy) {
174+
return footerLinkCopy[key][docsLocale];
175+
}
35176
36177
// 外部链接的 target 和 rel 属性
37178
const externalLinkTarget = getLinkTarget('github');
@@ -68,7 +209,7 @@ const discordRel = getLinkRel('discord');
68209
<div class="unified-footer-logo-wrapper">
69210
<span class="unified-footer-logo">Hagicode</span>
70211
<span class="unified-footer-copyright">
71-
© {new Date().getFullYear()} Hagicode. All rights reserved.
212+
© {new Date().getFullYear()} Hagicode. {footerCopy.copyright}
72213
</span>
73214
</div>
74215
</div>
@@ -80,8 +221,8 @@ const discordRel = getLinkRel('discord');
80221
<div class="unified-footer-sections">
81222
{/* 产品信息 */}
82223
<div class="unified-footer-section">
83-
<h3 class="unified-footer-section-title">生态站点</h3>
84-
<nav class="unified-footer-section-links" aria-label="生态站点链接">
224+
<h3 class="unified-footer-section-title">{footerCopy.sections.ecosystemSites}</h3>
225+
<nav class="unified-footer-section-links" aria-label={footerCopy.navigation.ecosystemSites}>
85226
{
86227
relatedSiteLinks.map((link) => (
87228
<a href={link.href} class="unified-footer-link" target="_blank" rel="noopener noreferrer">
@@ -95,28 +236,28 @@ const discordRel = getLinkRel('discord');
95236

96237
{/* 快速链接 */}
97238
<div class="unified-footer-section">
98-
<h3 class="unified-footer-section-title">快速链接</h3>
99-
<nav class="unified-footer-section-links" aria-label="快速链接">
100-
<a href={desktopLink} class="unified-footer-link">下载客户端</a>
101-
<a href={aboutLink} class="unified-footer-link">关于 HagiCode</a>
102-
<a href="/installation/docker-compose" class="unified-footer-link">Docker Compose 安装</a>
103-
<a href={productOverviewLink} class="unified-footer-link">产品文档</a>
104-
<a href={blogLink} class="unified-footer-link">博客文章</a>
105-
<a href={rssLink} class="unified-footer-link">RSS 订阅</a>
239+
<h3 class="unified-footer-section-title">{footerCopy.sections.quickLinks}</h3>
240+
<nav class="unified-footer-section-links" aria-label={footerCopy.navigation.quickLinks}>
241+
<a href={desktopLink} class="unified-footer-link">{getFooterLinkLabel('downloadClient')}</a>
242+
<a href={aboutLink} class="unified-footer-link">{getFooterLinkLabel('about')}</a>
243+
<a href={buildDocsRoutePath(docsLocale, '/installation/docker-compose')} class="unified-footer-link">{getFooterLinkLabel('dockerComposeInstall')}</a>
244+
<a href={buildDocsRoutePath(docsLocale, '/product-overview/')} class="unified-footer-link">{getFooterLinkLabel('productDocs')}</a>
245+
<a href={buildDocsRoutePath(docsLocale, '/blog/')} class="unified-footer-link">{getFooterLinkLabel('blog')}</a>
246+
<a href={rssLink} class="unified-footer-link">{getFooterLinkLabel('rss')}</a>
106247
</nav>
107248
</div>
108249

109250
{/* 社区与支持 */}
110251
<div class="unified-footer-section">
111-
<h3 class="unified-footer-section-title">社区</h3>
112-
<nav class="unified-footer-section-links" aria-label="社区链接">
252+
<h3 class="unified-footer-section-title">{footerCopy.sections.community}</h3>
253+
<nav class="unified-footer-section-links" aria-label={footerCopy.navigation.community}>
113254
<a href={githubLink} class="unified-footer-link" target={externalLinkTarget} rel={externalLinkRel}>GitHub</a>
114255
<a href={discordLink} class="unified-footer-link" target={discordTarget} rel={discordRel}>Discord</a>
115-
<a href="https://github.com/HagiCode-org/site/issues" class="unified-footer-link" target="_blank" rel="noopener noreferrer">问题反馈</a>
116-
<a href="mailto:support@hagicode.com" class="unified-footer-link">联系邮箱</a>
117-
<a href={qqGroupLink} class="unified-footer-link" target={qqGroupTarget} rel={qqGroupRel}>QQ 群 610394020</a>
118-
<a href={costCalculatorLink} class="unified-footer-link" target="_blank" rel="noopener noreferrer">算一算,AI会不会淘汰我</a>
119-
<a href={steamLink} class="unified-footer-link" target="_blank" rel="noopener noreferrer">Steam</a>
256+
<a href="https://github.com/HagiCode-org/site/issues" class="unified-footer-link" target="_blank" rel="noopener noreferrer">{getFooterLinkLabel('issueFeedback')}</a>
257+
<a href="mailto:support@hagicode.com" class="unified-footer-link">{getFooterLinkLabel('contactEmail')}</a>
258+
<a href={qqGroupLink} class="unified-footer-link" target={qqGroupTarget} rel={qqGroupRel}>{getFooterLinkLabel('qqGroup')}</a>
259+
<a href={costCalculatorLink} class="unified-footer-link" target="_blank" rel="noopener noreferrer">{getFooterLinkLabel('costCalculator')}</a>
260+
<a href={steamLink} class="unified-footer-link" target="_blank" rel="noopener noreferrer">{getFooterLinkLabel('steam')}</a>
120261
</nav>
121262
</div>
122263
</div>
@@ -128,7 +269,7 @@ const discordRel = getLinkRel('discord');
128269
href="https://beian.miit.gov.cn/"
129270
target="_blank"
130271
rel="noopener noreferrer"
131-
aria-label="查看 ICP 备案信息"
272+
aria-label={footerCopy.filings.icpAriaLabel}
132273
class="unified-footer-icp-link"
133274
>
134275
闽ICP备2026004153号-1
@@ -137,7 +278,7 @@ const discordRel = getLinkRel('discord');
137278
href="http://www.beian.gov.cn/portal/registerSystemInfo"
138279
target="_blank"
139280
rel="noopener noreferrer"
140-
aria-label="查看公安备案信息"
281+
aria-label={footerCopy.filings.publicSecurityAriaLabel}
141282
class="unified-footer-icp-link"
142283
>
143284
闽公网安备35011102351148号

src/lib/footer-site-links.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ function resolveLocalizedField(field: LocalizedFooterField, locale: FooterCatalo
5252
}
5353

5454
for (const candidate of [locale, ...getFooterLocaleFallbackChain(locale)]) {
55-
const value = field[candidate as SiteLocale];
55+
const value = field[candidate];
5656
if (typeof value === 'string' && value.trim().length > 0) {
5757
return value;
5858
}

tests/about-footer-link.test.mjs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ test('docs footer exposes the local about link entry without importing the site
2929
assert.match(footerSource, /<DocsPromoteInfoBanner locale=\{Astro\.locals\?\.starlightRoute\?\.locale\} \/>/);
3030
assert.match(footerSource, /const aboutLink = getLink\('about'\);/);
3131
assert.match(footerSource, /const relatedSiteLinks = resolveDocsFooterSiteLinks/);
32-
assert.match(footerSource, /<h3 class="unified-footer-section-title"><\/h3>/);
32+
assert.match(footerSource, /const footerCopy = getDocsFooterCopy\(currentRouteLocale\);/);
33+
assert.match(footerSource, /<h3 class="unified-footer-section-title">\{footerCopy\.sections\.ecosystemSites\}<\/h3>/);
3334
assert.match(footerSource, /<span class="unified-footer-link-title">\{link\.title\}<\/span>/);
3435
assert.match(footerSource, /<span class="unified-footer-link-description">\{link\.description\}<\/span>/);
3536
assert.equal(footerSource.includes('@/components/home/Footer'), false);
@@ -51,7 +52,7 @@ test('docs header navigation reuses the shared about link registry and keeps dis
5152
assert.match(footerSource, /<a href=\{discordLink\} class="unified-footer-link" target=\{discordTarget\} rel=\{discordRel\}>Discord<\/a>/);
5253
assert.match(
5354
footerSource,
54-
/<a href=\{steamLink\} class="unified-footer-link" target="_blank" rel="noopener noreferrer">Steam<\/a>/,
55+
/<a href=\{steamLink\} class="unified-footer-link" target="_blank" rel="noopener noreferrer">\{getFooterLinkLabel\('steam'\)\}<\/a>/,
5556
);
5657
});
5758

0 commit comments

Comments
 (0)