mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-11 01:51:34 -10:00
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:
1 parent
27ac0defac
commit
7d10db4850
34 files changed
+23
-256
No files matched your search
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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`);
|
||||
}
|
||||
@@ -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
@@ -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) =>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "更新日志",
|
||||
|
||||
@@ -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;
|
||||
@@ -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([
|
||||
"价格",
|
||||
"服务条款",
|
||||
"隐私政策",
|
||||
]);
|
||||
|
||||
@@ -58,7 +58,6 @@ const CHROME_PROSE_KEYS = [
|
||||
"skipToContent",
|
||||
"navDocs",
|
||||
"navProduct",
|
||||
"navPricing",
|
||||
"navCommunity",
|
||||
"navPrimaryAria",
|
||||
"navHomeAria",
|
||||
|
||||
@@ -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: "سجل التغييرات",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: "परिवर्तन लॉग",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: "変更履歴",
|
||||
|
||||
@@ -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: "변경 로그",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: "Журнал изменений",
|
||||
|
||||
@@ -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üğü",
|
||||
|
||||
@@ -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". */
|
||||
|
||||
@@ -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: "Журнал змін",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: "更新日志",
|
||||
|
||||
@@ -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 },
|
||||
];
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -112,7 +112,6 @@ describe("page metadata", () => {
|
||||
["faq", "/faq"],
|
||||
["feed", "/feed"],
|
||||
["roadmap", "/roadmap"],
|
||||
["pricing", "/pricing"],
|
||||
["legal/terms", "/legal/terms"],
|
||||
["legal/privacy", "/legal/privacy"],
|
||||
]) {
|
||||
|
||||
@@ -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`);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user