Skip to content

Commit f221a0f

Browse files
authored
fix(landing-page): polish alternative & agent page CTAs, footer layout + i18n (#5256)
* fix(landing-page): unify alternative-page CTA buttons and tidy the sub-page footer The /alternatives/* hero and final-CTA button pairs used mismatched box models: the filled button had a border-less box with white-space:nowrap while the outlined button had a 1px --line border, no nowrap, and less horizontal padding. On non-English locales the longer labels made the outlined button wrap and drift out of alignment with the filled one, and its faint --line border barely registered against the near-white paper. Both pairs now share one box (equal padding, a 1px border on each with the primary's matching its fill, nowrap) and the outlined border is lifted to --ink-faint / .55-alpha white so it reads as a real button edge. The sub-page footer packed eight nav sections into a 7-track grid, so Company orphaned onto a second row under Product. Community and Company — the two shortest sections — now share the last column stacked vertically (mirroring the reference footer), keeping everything on one clean row. * feat(landing-page): lead alternative-page CTAs with the desktop download The /alternatives/* hero and final CTAs showed a generic get-started button (the /quickstart/ action) as the emphasized primary, with Download demoted to an outlined secondary. On comparison pages the intended conversion is installing the desktop client, so drop the get-started CTA entirely and surface only two buttons: Download (now the emphasized primary) and Star on GitHub (secondary). Applied in the component by matching the locale-stable hrefs, so it holds uniformly across every translation. * refactor(landing-page): share download-first CTA logic across agent pages Extend the alternative-page CTA change to the 21 /agents/*-design/ guide pages, which used the same three-button hero (get-started + Star + Download). Factor the shaping into app/cta-actions.ts (downloadFirstCtas): drop the /quickstart/ get-started action and surface Download as the emphasized primary with Star on GitHub as the secondary. The alternatives component and every agent page now consume the shared helper for both the hero and final CTAs, and the compact/non-rich fallback CTA rows are aligned to match. * fix(landing-page): point every download CTA at the on-site download page The download CTA inherited whatever href the per-locale copy carried — the English base linked to /download/ but older translated copy linked straight to GitHub /releases, so the button target differed by language. Normalize it: downloadFirstCtas now always rewrites the download action to the localized /download/ page, and the non-rich fallback rows link there too. * fix(landing-page): localize the footer Company column for every locale The footer's Company column (Company / About / Careers / FAQ / Privacy / Terms) was driven by a small inline map that only carried en/zh/zh-tw, so every other locale — ko, ja, de, fr, ru, es, pt-br, it — silently rendered those labels in English while the rest of the footer was translated. Move the labels into a shared footer-legal-i18n.ts covering all landing locales (typed against LandingLocaleCode so a missing locale fails typecheck) and consume it from both the sub-page footer and the homepage footer, so the two can no longer drift. --------- Co-authored-by: Joey <276262049+xne998808-ai@users.noreply.github.com>
1 parent 02c6841 commit f221a0f

28 files changed

Lines changed: 349 additions & 151 deletions

File tree

apps/landing-page/app/_components/alternative-detail.astro

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import Layout from './sub-page-layout.astro';
1414
import { getInfoPageCopy, getAlternativeCopy } from '../info-page-i18n';
1515
import { localeFromPath, localizedHref } from '../i18n';
16+
import { downloadFirstCtas } from '../cta-actions';
1617
import '../_partials/comparison-v4.css';
1718
1819
interface Props {
@@ -39,6 +40,10 @@ const features = page.features;
3940
const ctaHref = (a: { href: string; external?: boolean }) =>
4041
a.external ? a.href : href(a.href);
4142
43+
// Comparison pages lead with the desktop download — see cta-actions.ts.
44+
const heroCtas = rich ? downloadFirstCtas(rich.heroCtaActions) : [];
45+
const finalCtas = rich ? downloadFirstCtas(rich.ctaActions) : [];
46+
4247
const faqEntries = (rich?.faq ?? page.faq).map(({ name, text }) => ({ name, text }));
4348
4449
const homeUrl = new URL(href('/'), SITE).toString();
@@ -93,7 +98,7 @@ const jsonLd = [
9398
<h1 class="v4h1">{page.heading}</h1>
9499
<p class="v4dek">{rich.heroCtaLead}</p>
95100
<div class="v4cta-row">
96-
{rich.heroCtaActions.map((a) => (
101+
{heroCtas.map((a) => (
97102
<a
98103
class={a.variant === 'primary' ? 'v4btn-p' : 'v4btn-g'}
99104
href={ctaHref(a)}
@@ -203,7 +208,7 @@ const jsonLd = [
203208
<h2 class="v4final-h">{rich.ctaTitle}</h2>
204209
<p class="v4final-p">{rich.ctaBody}</p>
205210
<div class="v4cta-row">
206-
{rich.ctaActions.map((a) => (
211+
{finalCtas.map((a) => (
207212
<a
208213
class={a.variant === 'primary' ? 'v4btn-coral' : 'v4btn-gd'}
209214
href={ctaHref(a)}
@@ -312,9 +317,8 @@ const jsonLd = [
312317
<p>{page.ctaBody}</p>
313318
</div>
314319
<div class="info-cta-actions">
315-
<a class="btn btn-primary" href={REPO} target="_blank" rel="noreferrer noopener">{common.starOnGithub}</a>
316-
<a class="btn btn-ghost" href={href('/quickstart/')}>{common.quickstart}</a>
317-
<a class="btn btn-ghost" href={REPO_RELEASES} target="_blank" rel="noreferrer noopener">{common.downloadDesktop}</a>
320+
<a class="btn btn-primary" href={href('/download/')}>{common.downloadDesktop}</a>
321+
<a class="btn btn-ghost" href={REPO} target="_blank" rel="noreferrer noopener">{common.starOnGithub}</a>
318322
</div>
319323
<div class="info-cta-meta">
320324
<span class="stamp">● {common.apache}</span>

apps/landing-page/app/_components/site-footer.astro

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
isLandingLocale,
99
localizedHref,
1010
} from '../i18n';
11+
import { getFooterLegalCopy } from '../footer-legal-i18n';
1112
1213
interface Props {
1314
counts: HeaderProps['counts'];
@@ -48,14 +49,9 @@ const FOOTER_AGENTS = [
4849
{ name: 'OpenCode', route: 'opencode-design' },
4950
] as const;
5051
51-
// Legal / company labels — small inline map (en/zh/zh-tw, fallback en) so
52-
// this stays out of the larger footer i18n surface.
53-
const L = {
54-
en: { company: 'Company', about: 'About', careers: 'Careers', faq: 'FAQ', privacy: 'Privacy Policy', terms: 'Terms', allAgents: 'All agents', allSolutions: 'All use cases' },
55-
zh: { company: '公司', about: '关于', careers: '招聘', faq: '常见问题', privacy: '隐私政策', terms: '服务条款', allAgents: '全部 Agent', allSolutions: '全部场景' },
56-
'zh-tw': { company: '公司', about: '關於', careers: '招聘', faq: '常見問題', privacy: '隱私政策', terms: '服務條款', allAgents: '全部 Agent', allSolutions: '全部場景' },
57-
} satisfies Record<string, Record<string, string>>;
58-
const l = L[locale as keyof typeof L] ?? L.en;
52+
// Legal / company labels — localized for every landing locale (see
53+
// footer-legal-i18n.ts), falling back to English for any unknown code.
54+
const l = getFooterLegalCopy(locale);
5955
---
6056

6157
<footer class='sub-footer' data-od-id='footer'>
@@ -126,26 +122,31 @@ const l = L[locale as keyof typeof L] ?? L.en;
126122
</ul>
127123
</div>
128124

129-
<div class='sub-footer-col'>
130-
<h5><a href={href('/community/')}>{menu.community}</a></h5>
131-
<ul>
132-
<li><a href={href('/community/contributors/')}>{menu.communityItems.contributors}</a></li>
133-
<li><a href={href('/community/ambassadors/')}>{menu.communityItems.ambassadors}</a></li>
134-
<li><a href={href('/community/moderators/')}>{menu.communityItems.moderators}</a></li>
135-
</ul>
136-
</div>
137-
138-
<div class='sub-footer-col'>
139-
<h5>{l.company}</h5>
140-
<ul>
141-
<li><a href={href('/about/')}>{l.about}</a></li>
142-
<li><a href={href('/careers/')}>{l.careers}</a></li>
143-
<li><a href={href('/faq/')}>{l.faq}</a></li>
144-
<li><a href={href('/privacy/')}>{l.privacy}</a></li>
145-
<li><a href={href('/terms/')}>{l.terms}</a></li>
146-
<li><a href={REPO} target='_blank' rel='noopener'>{ui.footer.github}</a></li>
147-
<li><a href={DISCORD} target='_blank' rel='noopener'>{ui.footer.discord}</a></li>
148-
</ul>
125+
{/* Community + Company share one grid column, stacked vertically. Eight
126+
nav sections into a 7-track grid used to orphan Company onto a second
127+
row; pairing the two shortest sections keeps everything on one row. */}
128+
<div class='sub-footer-col-stack'>
129+
<div class='sub-footer-col'>
130+
<h5><a href={href('/community/')}>{menu.community}</a></h5>
131+
<ul>
132+
<li><a href={href('/community/contributors/')}>{menu.communityItems.contributors}</a></li>
133+
<li><a href={href('/community/ambassadors/')}>{menu.communityItems.ambassadors}</a></li>
134+
<li><a href={href('/community/moderators/')}>{menu.communityItems.moderators}</a></li>
135+
</ul>
136+
</div>
137+
138+
<div class='sub-footer-col'>
139+
<h5>{l.company}</h5>
140+
<ul>
141+
<li><a href={href('/about/')}>{l.about}</a></li>
142+
<li><a href={href('/careers/')}>{l.careers}</a></li>
143+
<li><a href={href('/faq/')}>{l.faq}</a></li>
144+
<li><a href={href('/privacy/')}>{l.privacy}</a></li>
145+
<li><a href={href('/terms/')}>{l.terms}</a></li>
146+
<li><a href={REPO} target='_blank' rel='noopener'>{ui.footer.github}</a></li>
147+
<li><a href={DISCORD} target='_blank' rel='noopener'>{ui.footer.discord}</a></li>
148+
</ul>
149+
</div>
149150
</div>
150151
</div>
151152

apps/landing-page/app/_partials/comparison-v4.css

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,16 @@
1717
.v4sub{font-size:18px;line-height:1.55;color:var(--ink-soft);font-weight:500;margin:0 0 24px;max-width:520px}
1818
.v4dek{font-size:18px;line-height:1.65;color:var(--ink-soft);max-width:660px;margin:0 0 34px}
1919
.v4cta-row{display:flex;gap:14px;flex-wrap:wrap;align-items:center}
20-
.v4btn-p{background:var(--ink);color:#fff;font-weight:600;font-size:15px;padding:15px 28px;border-radius:var(--spacing-8);text-decoration:none;display:inline-flex;align-items:center;white-space:nowrap}
21-
.v4btn-g{color:var(--ink);font-weight:600;font-size:15px;padding:15px 24px;border-radius:var(--spacing-8);border:1px solid var(--line);text-decoration:none;display:inline-flex;align-items:center}
20+
/* Both CTA buttons share one box model (equal padding + a 1px border on each,
21+
the primary's border matching its fill) so the filled and outlined buttons
22+
stay the same height/shape across every locale — long translated labels no
23+
longer make the outlined button wrap or drift out of alignment. The outlined
24+
border uses --ink-faint (not the faint --line) so it reads as a real button
25+
edge on the near-white paper, not a hairline. */
26+
.v4btn-p{background:var(--ink);color:#fff;border:1px solid var(--ink);font-weight:600;font-size:15px;padding:14px 27px;border-radius:var(--spacing-8);text-decoration:none;display:inline-flex;align-items:center;white-space:nowrap;transition:background .16s ease,border-color .16s ease}
27+
.v4btn-p:hover{background:#000;border-color:#000}
28+
.v4btn-g{color:var(--ink);background:transparent;border:1px solid var(--ink-faint);font-weight:600;font-size:15px;padding:14px 27px;border-radius:var(--spacing-8);text-decoration:none;display:inline-flex;align-items:center;white-space:nowrap;transition:background .16s ease,border-color .16s ease}
29+
.v4btn-g:hover{border-color:var(--ink);background:var(--paper-warm)}
2230
/* stats band */
2331
.v4stats{display:grid;grid-template-columns:repeat(4,1fr);gap:40px}
2432
.v4stats .n{font-size:clamp(34px,4.4vw,52px);line-height:1;font-weight:800;letter-spacing:-.035em;color:var(--ink)}
@@ -77,8 +85,12 @@
7785
.v4final>.v4in{position:relative;z-index:1}
7886
.v4final-h{font-size:clamp(30px,4vw,46px);line-height:1.1;letter-spacing:-.025em;font-weight:780;color:#fff;margin:0 0 16px;max-width:760px}
7987
.v4final-p{font-size:18px;line-height:1.6;color:rgba(255,255,255,.72);margin:0 0 30px;max-width:560px}
80-
.v4btn-coral{background:var(--coral);color:#0a0a0a;font-weight:700;font-size:15px;padding:15px 28px;border-radius:var(--spacing-8);text-decoration:none;display:inline-flex;align-items:center}
81-
.v4btn-gd{background:transparent;color:#fff;border:1px solid rgba(255,255,255,.35);font-weight:600;font-size:15px;padding:15px 26px;border-radius:var(--spacing-8);text-decoration:none;display:inline-flex;align-items:center}
88+
/* Final-CTA pair on the dark band: same equal-box treatment as the hero pair.
89+
The outlined button's border is lifted to .55 alpha so it stays legible
90+
against the ink background when a translated label makes it wider. */
91+
.v4btn-coral{background:var(--coral);color:#0a0a0a;border:1px solid var(--coral);font-weight:700;font-size:15px;padding:14px 27px;border-radius:var(--spacing-8);text-decoration:none;display:inline-flex;align-items:center;white-space:nowrap}
92+
.v4btn-gd{background:transparent;color:#fff;border:1px solid rgba(255,255,255,.55);font-weight:600;font-size:15px;padding:14px 27px;border-radius:var(--spacing-8);text-decoration:none;display:inline-flex;align-items:center;white-space:nowrap;transition:border-color .16s ease,background .16s ease}
93+
.v4btn-gd:hover{border-color:#fff;background:rgba(255,255,255,.08)}
8294
/* hero image (promo moved up) */
8395
.v4heroimg{margin:44px auto 0;max-width:740px}
8496
.v4heroimg img{width:100%;border-radius:var(--spacing-8);box-shadow:0 30px 64px -36px rgba(20,20,30,.5);display:block}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Shared CTA-action shaping for the comparison (/alternatives/*) and agent
2+
// (/agents/*-design/) landing pages.
3+
//
4+
// These pages lead with the desktop download: drop the generic "get started"
5+
// CTA (the /quickstart/ action) and surface only two buttons — Download,
6+
// emphasized as the primary, and Star on GitHub as the secondary. Actions are
7+
// matched by their locale-stable hrefs, so this holds across every translation
8+
// without editing per-locale copy.
9+
//
10+
// The download button always points at the on-site download page
11+
// (DOWNLOAD_HREF), regardless of what the (drifted) per-locale copy had — the
12+
// English base used /download/ while older translated copy linked straight to
13+
// GitHub /releases; both are normalised here to the localized download page.
14+
15+
export const CTA_REPO = 'https://github.com/nexu-io/open-design';
16+
export const CTA_REPO_RELEASES = `${CTA_REPO}/releases`;
17+
// On-site download page; ctaHref() localizes this per locale.
18+
export const DOWNLOAD_HREF = '/download/';
19+
const GET_STARTED_HREF = '/quickstart/';
20+
21+
export type CtaAction = {
22+
label: string;
23+
href: string;
24+
variant?: string;
25+
external?: boolean;
26+
};
27+
28+
const isStar = (a: CtaAction) => a.href === CTA_REPO;
29+
30+
export const downloadFirstCtas = (actions: readonly CtaAction[]): CtaAction[] => {
31+
const kept = actions.filter((a) => a.href !== GET_STARTED_HREF);
32+
const download = kept.find((a) => !isStar(a));
33+
const star = kept.find(isStar);
34+
if (!download) return [...actions]; // unexpected shape: leave untouched
35+
const out: CtaAction[] = [
36+
{ ...download, href: DOWNLOAD_HREF, external: false, variant: 'primary' },
37+
];
38+
if (star) out.push({ ...star, variant: 'ghost' });
39+
return out;
40+
};
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Localized labels for the footer's Company / legal column, shared by the
2+
// sub-page footer (site-footer.astro) and the homepage footer (page.tsx).
3+
//
4+
// These used to live in a small inline map that only covered en/zh/zh-tw, so
5+
// every other locale silently fell back to English in the Company column.
6+
// This module carries all landing locales; getFooterLegalCopy() falls back to
7+
// English for any code not present.
8+
import type { LandingLocaleCode } from './i18n';
9+
10+
export interface FooterLegalCopy {
11+
company: string;
12+
about: string;
13+
careers: string;
14+
faq: string;
15+
privacy: string;
16+
terms: string;
17+
allAgents: string;
18+
allSolutions: string;
19+
}
20+
21+
const EN: FooterLegalCopy = {
22+
company: 'Company',
23+
about: 'About',
24+
careers: 'Careers',
25+
faq: 'FAQ',
26+
privacy: 'Privacy Policy',
27+
terms: 'Terms',
28+
allAgents: 'All agents',
29+
allSolutions: 'All use cases',
30+
};
31+
32+
const FOOTER_LEGAL_COPY = {
33+
en: EN,
34+
zh: { company: '公司', about: '关于', careers: '招聘', faq: '常见问题', privacy: '隐私政策', terms: '服务条款', allAgents: '全部 Agent', allSolutions: '全部场景' },
35+
'zh-tw': { company: '公司', about: '關於', careers: '招聘', faq: '常見問題', privacy: '隱私政策', terms: '服務條款', allAgents: '全部 Agent', allSolutions: '全部場景' },
36+
ja: { company: '会社情報', about: '概要', careers: '採用情報', faq: 'よくある質問', privacy: 'プライバシーポリシー', terms: '利用規約', allAgents: 'すべてのエージェント', allSolutions: 'すべてのユースケース' },
37+
ko: { company: '회사', about: '소개', careers: '채용', faq: '자주 묻는 질문', privacy: '개인정보 처리방침', terms: '이용약관', allAgents: '모든 에이전트', allSolutions: '모든 사용 사례' },
38+
de: { company: 'Unternehmen', about: 'Über uns', careers: 'Karriere', faq: 'FAQ', privacy: 'Datenschutz', terms: 'Nutzungsbedingungen', allAgents: 'Alle Agents', allSolutions: 'Alle Anwendungsfälle' },
39+
fr: { company: 'Entreprise', about: 'À propos', careers: 'Carrières', faq: 'FAQ', privacy: 'Confidentialité', terms: 'Conditions', allAgents: 'Tous les agents', allSolutions: 'Tous les cas d’usage' },
40+
ru: { company: 'Компания', about: 'О нас', careers: 'Вакансии', faq: 'Частые вопросы', privacy: 'Конфиденциальность', terms: 'Условия', allAgents: 'Все агенты', allSolutions: 'Все сценарии' },
41+
es: { company: 'Empresa', about: 'Acerca de', careers: 'Empleo', faq: 'Preguntas frecuentes', privacy: 'Privacidad', terms: 'Términos', allAgents: 'Todos los agentes', allSolutions: 'Todos los casos de uso' },
42+
'pt-br': { company: 'Empresa', about: 'Sobre', careers: 'Carreiras', faq: 'Perguntas frequentes', privacy: 'Privacidade', terms: 'Termos', allAgents: 'Todos os agentes', allSolutions: 'Todos os casos de uso' },
43+
it: { company: 'Azienda', about: 'Chi siamo', careers: 'Lavora con noi', faq: 'FAQ', privacy: 'Privacy', terms: 'Termini', allAgents: 'Tutti gli agenti', allSolutions: 'Tutti i casi d’uso' },
44+
vi: { company: 'Công ty', about: 'Giới thiệu', careers: 'Tuyển dụng', faq: 'Câu hỏi thường gặp', privacy: 'Quyền riêng tư', terms: 'Điều khoản', allAgents: 'Tất cả agent', allSolutions: 'Tất cả trường hợp dùng' },
45+
pl: { company: 'Firma', about: 'O nas', careers: 'Kariera', faq: 'FAQ', privacy: 'Prywatność', terms: 'Regulamin', allAgents: 'Wszystkie agenty', allSolutions: 'Wszystkie zastosowania' },
46+
id: { company: 'Perusahaan', about: 'Tentang', careers: 'Karier', faq: 'FAQ', privacy: 'Privasi', terms: 'Ketentuan', allAgents: 'Semua agent', allSolutions: 'Semua kasus penggunaan' },
47+
nl: { company: 'Bedrijf', about: 'Over ons', careers: 'Vacatures', faq: 'FAQ', privacy: 'Privacy', terms: 'Voorwaarden', allAgents: 'Alle agents', allSolutions: 'Alle toepassingen' },
48+
ar: { company: 'الشركة', about: 'من نحن', careers: 'الوظائف', faq: 'الأسئلة الشائعة', privacy: 'سياسة الخصوصية', terms: 'الشروط', allAgents: 'كل الوكلاء', allSolutions: 'كل حالات الاستخدام' },
49+
tr: { company: 'Şirket', about: 'Hakkımızda', careers: 'Kariyer', faq: 'SSS', privacy: 'Gizlilik', terms: 'Şartlar', allAgents: 'Tüm agent’lar', allSolutions: 'Tüm kullanım senaryoları' },
50+
uk: { company: 'Компанія', about: 'Про нас', careers: 'Кар’єра', faq: 'Часті запитання', privacy: 'Конфіденційність', terms: 'Умови', allAgents: 'Усі агенти', allSolutions: 'Усі сценарії' },
51+
} satisfies Record<LandingLocaleCode, FooterLegalCopy>;
52+
53+
export function getFooterLegalCopy(locale: string): FooterLegalCopy {
54+
return (FOOTER_LEGAL_COPY as Record<string, FooterLegalCopy>)[locale] ?? EN;
55+
}

apps/landing-page/app/globals.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5191,6 +5191,15 @@ footer .container {
51915191
grid-template-columns: repeat(7, minmax(0, 1fr));
51925192
gap: 28px;
51935193
margin-bottom: 36px;
5194+
align-items: start;
5195+
}
5196+
/* A grid cell that stacks two short nav sections vertically so the sub-page
5197+
footer's eight sections fit the 7-track grid without a section orphaning
5198+
onto a second row (used for Community + Company in site-footer.astro). */
5199+
.sub-footer-col-stack {
5200+
display: flex;
5201+
flex-direction: column;
5202+
gap: 32px;
51945203
}
51955204
.sub-footer-col h5 {
51965205
font-family: var(--mono);

apps/landing-page/app/page.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
PRECISE_LAZY_PLACEHOLDER,
3333
} from './image-assets';
3434
import { getHomeExtra, getHomeCta } from './home-translations';
35+
import { getFooterLegalCopy } from './footer-legal-i18n';
3536

3637
/**
3738
* `<img>` wrapper for non-hero homepage images. Outputs `data-precise-src`
@@ -189,13 +190,6 @@ const FOOTER_AGENTS = [
189190
{ name: 'OpenCode', route: 'opencode-design' },
190191
] as const;
191192

192-
// Legal / company labels — small inline map (en/zh/zh-tw, fallback en) kept
193-
// identical to `site-footer.astro` so the two footers never drift.
194-
const FOOTER_LEGAL = {
195-
en: { company: 'Company', about: 'About', careers: 'Careers', faq: 'FAQ', privacy: 'Privacy Policy', terms: 'Terms', allAgents: 'All agents' },
196-
zh: { company: '公司', about: '关于', careers: '招聘', faq: '常见问题', privacy: '隐私政策', terms: '服务条款', allAgents: '全部 Agent' },
197-
'zh-tw': { company: '公司', about: '關於', careers: '招聘', faq: '常見問題', privacy: '隱私政策', terms: '服務條款', allAgents: '全部 Agent' },
198-
} satisfies Record<string, Record<string, string>>;
199193

200194
const ext = {
201195
target: '_blank',
@@ -334,8 +328,7 @@ export default function Page({
334328
const home = getHomePageCopy(locale);
335329
const ui = getLandingUiCopy(locale);
336330
const menu = getHeaderProductMenuCopy(locale);
337-
const footL =
338-
FOOTER_LEGAL[locale as keyof typeof FOOTER_LEGAL] ?? FOOTER_LEGAL.en;
331+
const footL = getFooterLegalCopy(locale);
339332
const localeDef = getLocaleDefinition(locale);
340333
const localeOptions = LANDING_LOCALES.map((entry) => ({
341334
...entry,

0 commit comments

Comments
 (0)