Remove public pricing from the Codewhale website

Redirect localized pricing URLs to installation; remove plan copy, navigation, footer, sitemap, and discovery references across all locales.

Validation: npm test (66 packaging + 9 SDK + 459 web tests passed, 0 failures); npm run check:web passed (2 existing image lint warnings); web check:locales passed.
This commit is contained in:
CodeWhale Bot authored and Hunter B committed 2026-09-13 00:19:35 -07:00
1 parent 27ac0defac
commit 7d10db4850
34 files changed
+23 -256

No files matched your search

-1
View File
@@ -44,7 +44,6 @@ export default async function PrivacyPage({ params }: { params: Promise<{ locale
</section>
<p className="legal-doc-nav">
<Link href={`/${locale}/legal/terms`}>{isZh ? "服务条款" : "Terms of service"}</Link>
<Link href={`/${locale}/pricing`}>{isZh ? "价格" : "Pricing"}</Link>
<Link href={`/${locale}`}>{isZh ? "返回首页" : "Back home"}</Link>
</p>
</article>
-1
View File
@@ -39,7 +39,6 @@ export default async function TermsPage({ params }: { params: Promise<{ locale:
))}
<p className="legal-doc-nav">
<Link href={`/${locale}/legal/privacy`}>{isZh ? "隐私政策" : "Privacy policy"}</Link>
<Link href={`/${locale}/pricing`}>{isZh ? "价格" : "Pricing"}</Link>
<Link href={`/${locale}`}>{isZh ? "返回首页" : "Back home"}</Link>
</p>
</article>
+4 -50
View File
@@ -1,54 +1,8 @@
import Link from "next/link";
import { PUBLIC_MEMBERSHIP_COPY } from "@/lib/content/membership";
import { pickText } from "@/lib/i18n/dictionaries";
import { APP_LOGIN_URL, APP_SIGNUP_URL } from "@/lib/i18n/links";
import { buildPageMetadata } from "@/lib/page-meta";
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params;
return buildPageMetadata({
path: "/pricing",
locale,
title: pickText(PUBLIC_MEMBERSHIP_COPY.metadata.title, locale),
description: pickText(PUBLIC_MEMBERSHIP_COPY.metadata.description, locale),
});
}
import { redirect } from "next/navigation";
// Public pricing is not offered. Preserve incoming links without advertising
// a dormant plan; use a temporary redirect so future availability is explicit.
export default async function PricingPage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params;
return (
<div className="portal-home">
<section className="portal-section">
<div className="portal-container pricing-page">
<p className="legal-doc-kicker">{pickText(PUBLIC_MEMBERSHIP_COPY.kicker, locale)}</p>
<h1>{pickText(PUBLIC_MEMBERSHIP_COPY.title, locale)}</h1>
<p className="portal-lede">
{pickText(PUBLIC_MEMBERSHIP_COPY.lead, locale)}
</p>
<ul className="pricing-list">
{PUBLIC_MEMBERSHIP_COPY.options.map((option) => (
<li key={option.id}>
<strong>{pickText(option.title, locale)}</strong>
<span>{pickText(option.body, locale)}</span>
</li>
))}
</ul>
<p className="pricing-note">
{pickText(PUBLIC_MEMBERSHIP_COPY.note, locale)}
</p>
<div className="portal-actions">
<a className="portal-button portal-button-primary" href={APP_SIGNUP_URL} data-usage="signup">
{pickText(PUBLIC_MEMBERSHIP_COPY.actions.createAccount, locale)}
</a>
<Link className="portal-button portal-button-secondary" href={`/${locale}/install`}>
{pickText(PUBLIC_MEMBERSHIP_COPY.actions.continueLocally, locale)}
</Link>
</div>
<p className="pricing-note">
<a href={APP_LOGIN_URL} className="body-link" data-usage="login">{pickText(PUBLIC_MEMBERSHIP_COPY.actions.signIn, locale)}</a>
</p>
</div>
</section>
</div>
);
redirect(`/${locale}/install`);
}
-26
View File
@@ -1257,10 +1257,6 @@ a.body-link:hover { background-size: 100% 6px; color: var(--ink); }
text-underline-offset: 0.2rem;
}
.pricing-page {
max-width: 42rem;
}
.public-account-entry {
max-width: 32rem;
}
@@ -1273,28 +1269,6 @@ a.body-link:hover { background-size: 100% 6px; color: var(--ink); }
border-radius: 14px;
}
.pricing-list {
list-style: none;
padding: 0;
margin: 1.5rem 0;
}
.pricing-list li {
display: grid;
gap: 0.35rem;
padding: 1rem 0;
border-top: 1px solid var(--hairline);
}
.pricing-list li:last-child {
border-bottom: 1px solid var(--hairline);
}
.pricing-note {
margin: 0 0 1.5rem;
line-height: 1.6;
}
.portal-section-grid {
display: grid;
grid-template-columns: minmax(14rem, 0.62fr) minmax(0, 1.38fr);
+1 -1
View File
@@ -4,7 +4,7 @@ import { SITE_URL } from "@/lib/page-meta";
// Public, indexable routes (locale-prefixed). /admin and /api are
// intentionally excluded; see app/robots.ts.
const PATHS = ["", "/product", "/install", "/constitution", "/models", "/runtime", "/docs", "/docs/auth", "/docs/computers", "/docs/configuration", "/docs/constitution", "/docs/guide", "/docs/hooks", "/docs/mcp", "/docs/modes", "/docs/fleet", "/docs/runtime-api", "/docs/sandbox", "/docs/subagents", "/docs/tools", "/docs/troubleshooting", "/docs/trust", "/docs/vocabulary", "/docs/web", "/docs/work", "/faq", "/roadmap", "/feed", "/digest", "/changelog", "/contribute", "/community", "/pricing", "/signin", "/signup", "/legal/terms", "/legal/privacy"];
const PATHS = ["", "/product", "/install", "/constitution", "/models", "/runtime", "/docs", "/docs/auth", "/docs/computers", "/docs/configuration", "/docs/constitution", "/docs/guide", "/docs/hooks", "/docs/mcp", "/docs/modes", "/docs/fleet", "/docs/runtime-api", "/docs/sandbox", "/docs/subagents", "/docs/tools", "/docs/troubleshooting", "/docs/trust", "/docs/vocabulary", "/docs/web", "/docs/work", "/faq", "/roadmap", "/feed", "/digest", "/changelog", "/contribute", "/community", "/signin", "/signup", "/legal/terms", "/legal/privacy"];
export default function sitemap(): MetadataRoute.Sitemap {
return [...PATHS, "/computer-use"].flatMap((path) =>
-3
View File
@@ -14,10 +14,8 @@
"navContributeSecondary": "贡献",
"navProduct": "Product",
"navModels": "Models",
"navPricing": "Pricing",
"navProductSecondary": "产品",
"navModelsSecondary": "模型",
"navPricingSecondary": "价格",
"skipToContent": "Skip to main content",
"navPrimaryAria": "Primary",
"navHomeAria": "Codewhale home",
@@ -61,7 +59,6 @@
"footerIssues": "Issues",
"footerContribute": "Contribute",
"footerLicense": "MIT license",
"footerPricing": "Pricing",
"footerTerms": "Terms",
"footerPrivacy": "Privacy",
"footerChangelog": "Changelog",
-3
View File
@@ -14,10 +14,8 @@
"navContributeSecondary": "Contribute",
"navProduct": "产品",
"navModels": "模型",
"navPricing": "价格",
"navProductSecondary": "Product",
"navModelsSecondary": "Models",
"navPricingSecondary": "Pricing",
"skipToContent": "跳转到主要内容",
"navPrimaryAria": "主导航",
"navHomeAria": "Codewhale 首页",
@@ -61,7 +59,6 @@
"footerIssues": "议题",
"footerContribute": "参与贡献",
"footerLicense": "MIT 许可证",
"footerPricing": "价格",
"footerTerms": "服务条款",
"footerPrivacy": "隐私政策",
"footerChangelog": "更新日志",
-75
View File
@@ -1,75 +0,0 @@
/**
* Public membership copy is intentionally separate from the billing catalog.
* The public site may describe what is usable now, but it must not promote a
* price or allowance until that commercial term has its own approval.
*/
import type { LocalizedText } from "./vocabulary";
export interface PublicMembershipOption {
id: "account" | "local" | "paid";
title: LocalizedText;
body: LocalizedText;
}
/** Machine-checkable launch state; copy tests assert these truths, not wording. */
export const PUBLIC_MEMBERSHIP_STATUS = {
checkout: "dormant",
paymentFromPage: false,
localUseRequiresPaidMembership: false,
commercialTerms: "not-published",
} as const;
export const PUBLIC_MEMBERSHIP_COPY = {
metadata: {
title: { en: "Pricing · Codewhale", zh: "价格 · Codewhale" },
description: {
en: "Install Codewhale free and use your own model provider. Explore local use and account options.",
zh: "免费安装 Codewhale,使用你选择的模型提供商。了解本地使用与账户选项。",
},
},
kicker: { en: "Pricing", zh: "价格" },
title: {
en: "Use Codewhale with your own models.",
zh: "用你自己的模型使用 Codewhale。",
},
lead: {
en: "Install Codewhale free and connect your model provider. Create an account to explore the web app development preview.",
zh: "免费安装 Codewhale,连接模型提供商。创建账户后可体验网页应用开发预览版。",
},
options: [
{
id: "account",
title: { en: "Codewhale account", zh: "Codewhale 账户" },
body: {
en: "Keep your conversations and work together in the app.",
zh: "在应用中集中管理对话与工作。",
},
},
{
id: "local",
title: { en: "Run locally", zh: "本地运行" },
body: {
en: "Install the open-source Codewhale runtime and connect your provider credentials. Local use is free.",
zh: "安装开源 Codewhale 运行时,连接你的提供商凭据。本地使用免费。",
},
},
{
id: "paid",
title: { en: "Paid plans", zh: "付费方案" },
body: {
en: "Paid plans are not available yet.",
zh: "付费方案暂未开放。",
},
},
] satisfies PublicMembershipOption[],
note: {
en: "Hosted model usage is billed by your provider.",
zh: "托管模型用量由模型提供商计费。",
},
actions: {
createAccount: { en: "Create account", zh: "创建账户" },
continueLocally: { en: "Install Codewhale", zh: "安装 Codewhale" },
signIn: { en: "Already have an account? Sign in", zh: "已有账户?登录" },
},
} as const;
+4 -6
View File
@@ -92,8 +92,9 @@ describe("sitemap and hreflang preservation", () => {
it("keeps sitemap and hreflang output aligned with real translation coverage", () => {
// 18 home locales + 10 guide locales + (en, zh) for every other route
// (including /product and /changelog, whose bodies ship en/zh only).
expect(sitemapEntries).toHaveLength(100);
for (const path of ["/product", "/computer-use", "/pricing", "/signin", "/signup", "/legal/terms", "/legal/privacy"]) {
expect(sitemapEntries).toHaveLength(98);
expect(sitemapEntries.some(entry => entry.url.endsWith("/pricing"))).toBe(false);
for (const path of ["/product", "/computer-use", "/signin", "/signup", "/legal/terms", "/legal/privacy"]) {
expect(
sitemapEntries.some((entry) => entry.url === `${SITE_URL}/en${path}`),
path,
@@ -190,7 +191,7 @@ describe("navigation parity and accessibility", () => {
const reference = buildNavLinks("en", getChrome("en")).map((l) =>
l.href.replace(/^\/en\//, ""),
);
expect(reference).toEqual(["product", "models", "pricing", "docs"]);
expect(reference).toEqual(["product", "models", "docs"]);
const moreReference = buildSecondaryNavLinks("en", getChrome("en")).map((l) =>
l.href.replace(/^\/en\//, ""),
);
@@ -236,14 +237,12 @@ describe("navigation parity and accessibility", () => {
]);
const legal = footerLegalLinks(locale, getChrome(locale));
expect(legal.map((l) => l.href), `${locale} footer legal`).toEqual([
`/${locale}/pricing`,
`/${locale}/legal/terms`,
`/${locale}/legal/privacy`,
]);
// Labels come from the dictionary, not hardcoded English, so every
// routed locale renders the footer legal links in its own language.
expect(legal.map((l) => l.label), `${locale} footer legal labels`).toEqual([
getChrome(locale).footerPricing,
getChrome(locale).footerTerms,
getChrome(locale).footerPrivacy,
]);
@@ -334,7 +333,6 @@ describe("navigation parity and accessibility", () => {
});
// zh gets the footer legal labels from its dictionary, not English.
expect(footerLegalLinks("zh", getChrome("zh")).map((l) => l.label)).toEqual([
"价格",
"服务条款",
"隐私政策",
]);
-1
View File
@@ -58,7 +58,6 @@ const CHROME_PROSE_KEYS = [
"skipToContent",
"navDocs",
"navProduct",
"navPricing",
"navCommunity",
"navPrimaryAria",
"navHomeAria",
-3
View File
@@ -29,10 +29,8 @@ export const chrome: ChromeDict = {
navProduct: "المنتج",
navModels: "النماذج",
navPricing: "الأسعار",
navProductSecondary: "Product",
navModelsSecondary: "Models",
navPricingSecondary: "Pricing",
skipToContent: "الانتقال إلى المحتوى الرئيسي",
@@ -89,7 +87,6 @@ export const chrome: ChromeDict = {
footerIssues: "Issues",
footerContribute: "المساهمة",
footerLicense: "رخصة MIT",
footerPricing: "الأسعار",
footerTerms: "شروط الخدمة",
footerPrivacy: "الخصوصية",
footerChangelog: "سجل التغييرات",
-3
View File
@@ -34,10 +34,8 @@ export const chrome: ChromeDict = {
navProduct: "Producte",
navModels: "Models",
navPricing: "Preus",
navProductSecondary: "Product",
navModelsSecondary: "Models",
navPricingSecondary: "Pricing",
skipToContent: "Salta al contingut principal",
@@ -94,7 +92,6 @@ export const chrome: ChromeDict = {
footerIssues: "Issues",
footerContribute: "Col·laborar",
footerLicense: "Llicència MIT",
footerPricing: "Preus",
footerTerms: "Termes del servei",
footerPrivacy: "Privadesa",
footerChangelog: "Registre de canvis",
-3
View File
@@ -34,10 +34,8 @@ export const chrome: ChromeDict = {
navProduct: "Produkt",
navModels: "Modelle",
navPricing: "Preise",
navProductSecondary: "Product",
navModelsSecondary: "Models",
navPricingSecondary: "Pricing",
skipToContent: "Zum Hauptinhalt springen",
@@ -94,7 +92,6 @@ export const chrome: ChromeDict = {
footerIssues: "Issues",
footerContribute: "Mitwirken",
footerLicense: "MIT-Lizenz",
footerPricing: "Preise",
footerTerms: "Nutzungsbedingungen",
footerPrivacy: "Datenschutz",
footerChangelog: "Änderungsprotokoll",
-3
View File
@@ -26,10 +26,8 @@ export const chrome: ChromeDict = {
navProduct: "Product",
navModels: "Models",
navPricing: "Pricing",
navProductSecondary: "产品",
navModelsSecondary: "模型",
navPricingSecondary: "价格",
skipToContent: "Skip to main content",
@@ -87,7 +85,6 @@ export const chrome: ChromeDict = {
footerIssues: "Issues",
footerContribute: "Contribute",
footerLicense: "MIT license",
footerPricing: "Pricing",
footerTerms: "Terms",
footerPrivacy: "Privacy",
footerChangelog: "Changelog",
-3
View File
@@ -34,10 +34,8 @@ export const chrome: ChromeDict = {
navProduct: "Producto",
navModels: "Modelos",
navPricing: "Precios",
navProductSecondary: "Product",
navModelsSecondary: "Models",
navPricingSecondary: "Pricing",
skipToContent: "Saltar al contenido principal",
@@ -95,7 +93,6 @@ export const chrome: ChromeDict = {
footerIssues: "Incidencias",
footerContribute: "Contribuir",
footerLicense: "Licencia MIT",
footerPricing: "Precios",
footerTerms: "Términos del servicio",
footerPrivacy: "Privacidad",
footerChangelog: "Registro de cambios",
-3
View File
@@ -34,10 +34,8 @@ export const chrome: ChromeDict = {
navProduct: "Produit",
navModels: "Modèles",
navPricing: "Tarifs",
navProductSecondary: "Product",
navModelsSecondary: "Models",
navPricingSecondary: "Pricing",
skipToContent: "Aller au contenu principal",
@@ -94,7 +92,6 @@ export const chrome: ChromeDict = {
footerIssues: "Issues",
footerContribute: "Contribuer",
footerLicense: "Licence MIT",
footerPricing: "Tarifs",
footerTerms: "Conditions d’utilisation",
footerPrivacy: "Confidentialité",
footerChangelog: "Journal des modifications",
-3
View File
@@ -32,10 +32,8 @@ export const chrome: ChromeDict = {
navProduct: "उत्पाद",
navModels: "मॉडल",
navPricing: "मूल्य",
navProductSecondary: "Product",
navModelsSecondary: "Models",
navPricingSecondary: "Pricing",
skipToContent: "मुख्य सामग्री पर जाएँ",
@@ -92,7 +90,6 @@ export const chrome: ChromeDict = {
footerIssues: "Issues",
footerContribute: "योगदान दें",
footerLicense: "MIT लाइसेंस",
footerPricing: "मूल्य",
footerTerms: "सेवा की शर्तें",
footerPrivacy: "गोपनीयता",
footerChangelog: "परिवर्तन लॉग",
-3
View File
@@ -32,10 +32,8 @@ export const chrome: ChromeDict = {
navProduct: "Produk",
navModels: "Model",
navPricing: "Harga",
navProductSecondary: "Product",
navModelsSecondary: "Models",
navPricingSecondary: "Pricing",
skipToContent: "Lewati ke konten utama",
@@ -93,7 +91,6 @@ export const chrome: ChromeDict = {
footerIssues: "Masalah",
footerContribute: "Kontribusi",
footerLicense: "Lisensi MIT",
footerPricing: "Harga",
footerTerms: "Ketentuan layanan",
footerPrivacy: "Privasi",
footerChangelog: "Catatan perubahan",
-3
View File
@@ -32,10 +32,8 @@ export const chrome: ChromeDict = {
navProduct: "Prodotto",
navModels: "Modelli",
navPricing: "Prezzi",
navProductSecondary: "Product",
navModelsSecondary: "Models",
navPricingSecondary: "Pricing",
skipToContent: "Vai al contenuto principale",
@@ -92,7 +90,6 @@ export const chrome: ChromeDict = {
footerIssues: "Issues",
footerContribute: "Contribuisci",
footerLicense: "Licenza MIT",
footerPricing: "Prezzi",
footerTerms: "Termini di servizio",
footerPrivacy: "Privacy",
footerChangelog: "Registro delle modifiche",
-3
View File
@@ -33,10 +33,8 @@ export const chrome: ChromeDict = {
navProduct: "製品",
navModels: "モデル",
navPricing: "料金",
navProductSecondary: "Product",
navModelsSecondary: "Models",
navPricingSecondary: "Pricing",
skipToContent: "メインコンテンツへスキップ",
@@ -94,7 +92,6 @@ export const chrome: ChromeDict = {
footerIssues: "Issues",
footerContribute: "貢献",
footerLicense: "MIT ライセンス",
footerPricing: "料金",
footerTerms: "利用規約",
footerPrivacy: "プライバシーポリシー",
footerChangelog: "変更履歴",
-3
View File
@@ -36,10 +36,8 @@ export const chrome: ChromeDict = {
navProduct: "제품",
navModels: "모델",
navPricing: "요금",
navProductSecondary: "Product",
navModelsSecondary: "Models",
navPricingSecondary: "Pricing",
skipToContent: "본문으로 건너뛰기",
@@ -99,7 +97,6 @@ export const chrome: ChromeDict = {
footerIssues: "이슈",
footerContribute: "기여",
footerLicense: "MIT 라이선스",
footerPricing: "가격",
footerTerms: "이용약관",
footerPrivacy: "개인정보처리방침",
footerChangelog: "변경 로그",
-3
View File
@@ -31,10 +31,8 @@ export const chrome: ChromeDict = {
navProduct: "Produkt",
navModels: "Modele",
navPricing: "Cennik",
navProductSecondary: "Product",
navModelsSecondary: "Models",
navPricingSecondary: "Pricing",
skipToContent: "Przejdź do treści głównej",
@@ -91,7 +89,6 @@ export const chrome: ChromeDict = {
footerIssues: "Issues",
footerContribute: "Współtwórz",
footerLicense: "Licencja MIT",
footerPricing: "Cennik",
footerTerms: "Warunki usługi",
footerPrivacy: "Prywatność",
footerChangelog: "Dziennik zmian",
@@ -23,10 +23,8 @@ export const chrome: ChromeDict = {
navProduct: "Produto",
navModels: "Modelos",
navPricing: "Preços",
navProductSecondary: "Product",
navModelsSecondary: "Models",
navPricingSecondary: "Pricing",
skipToContent: "Pular para o conteúdo principal",
@@ -84,7 +82,6 @@ export const chrome: ChromeDict = {
footerIssues: "Issues",
footerContribute: "Contribuir",
footerLicense: "Licença MIT",
footerPricing: "Preços",
footerTerms: "Termos de serviço",
footerPrivacy: "Privacidade",
footerChangelog: "Registro de alterações",
-3
View File
@@ -29,10 +29,8 @@ export const chrome: ChromeDict = {
navProduct: "Продукт",
navModels: "Модели",
navPricing: "Цены",
navProductSecondary: "Product",
navModelsSecondary: "Models",
navPricingSecondary: "Pricing",
skipToContent: "Перейти к основному содержимому",
@@ -90,7 +88,6 @@ export const chrome: ChromeDict = {
footerIssues: "Задачи",
footerContribute: "Участие",
footerLicense: "Лицензия MIT",
footerPricing: "Цены",
footerTerms: "Условия использования",
footerPrivacy: "Конфиденциальность",
footerChangelog: "Журнал изменений",
-3
View File
@@ -31,10 +31,8 @@ export const chrome: ChromeDict = {
navProduct: "Ürün",
navModels: "Modeller",
navPricing: "Fiyatlar",
navProductSecondary: "Product",
navModelsSecondary: "Models",
navPricingSecondary: "Pricing",
skipToContent: "Ana içeriğe geç",
@@ -91,7 +89,6 @@ export const chrome: ChromeDict = {
footerIssues: "Issues",
footerContribute: "Katkı",
footerLicense: "MIT lisansı",
footerPricing: "Fiyatlandırma",
footerTerms: "Hizmet şartları",
footerPrivacy: "Gizlilik",
footerChangelog: "Değişiklik günlüğü",
+1 -5
View File
@@ -48,16 +48,14 @@ export interface ChromeDict {
navContributeSecondary: string;
/**
* The primary strip: Product / Models / Pricing / Docs. The older six
* The primary strip: Product / Models / Docs. The older six
* (Start, Install, FAQ, Community, Contribute) stay in the dictionary for
* the compact sheet's second group and the footer.
*/
navProduct: string;
navModels: string;
navPricing: string;
navProductSecondary: string;
navModelsSecondary: string;
navPricingSecondary: string;
/**
* Skip-to-content link rendered before the nav in app/[locale]/layout.tsx.
@@ -165,8 +163,6 @@ export interface ChromeDict {
footerIssues: string;
footerContribute: string;
footerLicense: string;
/** Footer link to the pricing page, e.g. "Pricing". */
footerPricing: string;
/** Footer link to the terms route, e.g. "Terms". */
footerTerms: string;
/** Footer link to the privacy route, e.g. "Privacy". */
-3
View File
@@ -28,10 +28,8 @@ export const chrome: ChromeDict = {
navProduct: "Продукт",
navModels: "Моделі",
navPricing: "Ціни",
navProductSecondary: "Product",
navModelsSecondary: "Models",
navPricingSecondary: "Pricing",
skipToContent: "Перейти до основного вмісту",
@@ -89,7 +87,6 @@ export const chrome: ChromeDict = {
footerIssues: "Проблеми",
footerContribute: "Участь",
footerLicense: "Ліцензія MIT",
footerPricing: "Ціни",
footerTerms: "Умови використання",
footerPrivacy: "Приватність",
footerChangelog: "Журнал змін",
-3
View File
@@ -33,10 +33,8 @@ export const chrome: ChromeDict = {
navProduct: "Sản phẩm",
navModels: "Mô hình",
navPricing: "Giá",
navProductSecondary: "Product",
navModelsSecondary: "Models",
navPricingSecondary: "Pricing",
skipToContent: "Bỏ qua tới nội dung chính",
@@ -95,7 +93,6 @@ export const chrome: ChromeDict = {
footerIssues: "Issues",
footerContribute: "Đóng góp",
footerLicense: "Giấy phép MIT",
footerPricing: "Bảng giá",
footerTerms: "Điều khoản dịch vụ",
footerPrivacy: "Quyền riêng tư",
footerChangelog: "Nhật ký thay đổi",
-3
View File
@@ -24,10 +24,8 @@ export const chrome: ChromeDict = {
navProduct: "产品",
navModels: "模型",
navPricing: "价格",
navProductSecondary: "Product",
navModelsSecondary: "Models",
navPricingSecondary: "Pricing",
skipToContent: "跳转到主要内容",
@@ -85,7 +83,6 @@ export const chrome: ChromeDict = {
footerIssues: "议题",
footerContribute: "参与贡献",
footerLicense: "MIT 许可证",
footerPricing: "价格",
footerTerms: "服务条款",
footerPrivacy: "隐私政策",
footerChangelog: "更新日志",
+2 -8
View File
@@ -30,7 +30,7 @@ export interface ChromeLink {
}
/**
* The four primary nav links — Product / Models / Pricing / Docs — identical
* The primary nav links — Product / Models / Docs — identical
* in shape for every routed locale. This is the strip a serious product
* shows; discovery and community routes live in `secondaryNavLinks` (the
* compact sheet's second group) and the footer.
@@ -43,11 +43,6 @@ export function navLinks(locale: string, chrome: ChromeDict): ChromeLink[] {
secondary: chrome.navProductSecondary,
},
{ href: `/${locale}/models`, label: chrome.navModels, secondary: chrome.navModelsSecondary },
{
href: `/${locale}/pricing`,
label: chrome.navPricing,
secondary: chrome.navPricingSecondary,
},
{ href: `/${locale}/docs`, label: chrome.navDocs, secondary: chrome.navDocsSecondary },
];
}
@@ -104,10 +99,9 @@ export function footerProjectLinks(locale: string, chrome: ChromeDict): ChromeLi
];
}
/** Footer legal / pricing destinations — reachable, never a 404. */
/** Footer legal destinations — reachable, never a 404. */
export function footerLegalLinks(locale: string, chrome: ChromeDict): ChromeLink[] {
return [
{ href: `/${locale}/pricing`, label: chrome.footerPricing },
{ href: `/${locale}/legal/terms`, label: chrome.footerTerms },
{ href: `/${locale}/legal/privacy`, label: chrome.footerPrivacy },
];
+1 -1
View File
@@ -35,12 +35,12 @@ describe("llms.txt", () => {
"/en/community",
"/en/contribute",
"/en/docs",
"/en/pricing",
"/en/legal/terms",
"/en/legal/privacy",
]) {
expect(body, path).toContain(`${SITE_URL}${path}`);
}
expect(body).not.toContain("/pricing");
});
it("serves the generated body as text/plain from the well-known route", async () => {
-6
View File
@@ -1,5 +1,4 @@
import { DOC_TOPICS, REPO_DOCS_BASE, docTopicHref } from "./docs-map";
import { PUBLIC_MEMBERSHIP_COPY } from "./content/membership";
import { DISCORD_URL, REPO_URL } from "./i18n/links";
import { IDENTITY_PHRASE, SITE_NAME, SITE_URL } from "./page-meta";
@@ -63,11 +62,6 @@ const EXTRA_PAGES: readonly { path: string; title: string; description: string }
title: "Contribute",
description: "The pull-request workflow: scoped issue, fork, test the change, explain the result.",
},
{
path: "/pricing",
title: "Pricing",
description: PUBLIC_MEMBERSHIP_COPY.metadata.description.en,
},
{
path: "/legal/terms",
title: "Terms of service",
-1
View File
@@ -112,7 +112,6 @@ describe("page metadata", () => {
["faq", "/faq"],
["feed", "/feed"],
["roadmap", "/roadmap"],
["pricing", "/pricing"],
["legal/terms", "/legal/terms"],
["legal/privacy", "/legal/privacy"],
]) {
+10 -14
View File
@@ -1,16 +1,17 @@
import { existsSync } from "node:fs";
import { describe, expect, it } from "vitest";
import { PUBLIC_MEMBERSHIP_COPY, PUBLIC_MEMBERSHIP_STATUS } from "./content/membership";
import { describe, expect, it, vi } from "vitest";
import { redirect } from "next/navigation";
import PricingPage from "../app/[locale]/pricing/page";
import { footerLegalLinks } from "./i18n/links";
import { getChrome } from "./i18n/dictionaries";
import { LEGAL_UPDATED, PRIVACY_SECTIONS, TERMS_SECTIONS } from "./legal-copy";
const webRoot = new URL("../", import.meta.url);
vi.mock("next/navigation", () => ({ redirect: vi.fn() }));
describe("marketing pricing and legal routes", () => {
describe("public legal routes and retired pricing", () => {
it("ships real pages for the URLs that used to 404", () => {
for (const path of [
"app/[locale]/pricing/page.tsx",
"app/[locale]/legal/terms/page.tsx",
"app/[locale]/legal/privacy/page.tsx",
"app/[locale]/privacy/page.tsx",
@@ -22,7 +23,6 @@ describe("marketing pricing and legal routes", () => {
it("aliases /privacy and /terms onto the legal paths instead of inventing a second policy", () => {
expect(footerLegalLinks("en", getChrome("en")).map((l) => l.href)).toEqual([
"/en/pricing",
"/en/legal/terms",
"/en/legal/privacy",
]);
@@ -31,14 +31,10 @@ describe("marketing pricing and legal routes", () => {
expect(PRIVACY_SECTIONS.some((s) => s.title === "Retention and deletion")).toBe(true);
});
it("keeps dormant membership copy free of unapproved commercial terms", () => {
const copy = JSON.stringify(PUBLIC_MEMBERSHIP_COPY);
expect(copy).not.toMatch(/\$10|\$50|rollover hosted-compute/i);
expect(PUBLIC_MEMBERSHIP_STATUS).toEqual({
checkout: "dormant",
paymentFromPage: false,
localUseRequiresPaidMembership: false,
commercialTerms: "not-published",
});
it("takes incoming pricing links to installation in the visitor's locale", async () => {
for (const locale of ["en", "zh", "pt-BR"]) {
await PricingPage({ params: Promise.resolve({ locale }) });
expect(redirect).toHaveBeenLastCalledWith(`/${locale}/install`);
}
});
});