Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions src/app/[lang]/about/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
"use client"

import Link from "next/link"
import { useLang } from "@/core/i18n/lang-provider"
import { notFound } from "next/navigation"
import { Github, Globe, Code2, Zap, Shield, ArrowUpRight } from "lucide-react"
import { getGrowthIndex } from "@/core/growth/growth-pages"
import { isValidLocale } from "@/core/i18n/i18n"
import { getTranslation } from "@/core/i18n/translations/catalog"

export default async function AboutPage({ params }: { params: Promise<{ lang: string }> }) {
const { lang } = await params
if (!isValidLocale(lang)) {
notFound()
}

export default function AboutPage() {
const { t, lang } = useLang()
const t = getTranslation(lang)
const p = t.pages
const compareIndex = getGrowthIndex("compare")
if (!compareIndex) {
throw new Error("[about] Missing compare growth index")
}

const highlights = [
{ icon: Shield, title: p.about_privacy_title, desc: p.about_privacy_desc },
Expand Down Expand Up @@ -84,6 +94,13 @@ export default function AboutPage() {
<Shield className="h-4 w-4" />
Self-hosting
</Link>
<Link
href={`/${lang}/compare`}
className="inline-flex items-center gap-2 rounded-xl border border-border/75 bg-background/55 px-3 py-2 text-sm text-muted-foreground transition-colors hover:text-foreground"
>
<ArrowUpRight className="h-4 w-4" />
{compareIndex.title[lang]}
</Link>
<Link
href={`/${lang}`}
className="inline-flex items-center gap-2 rounded-xl border border-border/75 bg-background/55 px-3 py-2 text-sm text-muted-foreground transition-colors hover:text-foreground"
Expand Down
24 changes: 15 additions & 9 deletions src/app/[lang]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { PopularToolsSection } from "@/features/home/components/popular-tools-se
import { HomeCategoryPreview } from "@/features/home/components/home-category-preview"
import { ALL_TOOLS_SECTION_ID } from "@/core/routing/all-tools-route"
import { buildHomepageCanonicalUrl, buildLocalizedAlternates } from "@/core/seo/urls"
import { getGrowthIndex } from "@/core/growth/growth-pages"

type FeatureCard = {
key: "privacy" | "speed" | "keyboard" | "tools"
Expand Down Expand Up @@ -62,7 +63,11 @@ export default async function Home({ params }: { params: Promise<{ lang: string
const registryStats = getToolRegistryStats()
const heroSearchLabel = t.site.hero_search
const commonLabels = t.common
const installAppLinkLabel = requireTranslationValue(commonLabels.install_as_app, "common.install_as_app")
const installAppLinkLabel = requireTranslationValue(commonLabels.install_as_app, "common.install_as_app")
const compareIndex = getGrowthIndex("compare")
if (!compareIndex) {
throw new Error("[home] Missing compare growth index")
}

const categoryLinks = MENU_GROUP_DEFS

Expand Down Expand Up @@ -199,20 +204,21 @@ export default async function Home({ params }: { params: Promise<{ lang: string
<Link
href={`/${locale}/pipeline-builder`}
prefetch={false}
className="inline-flex min-h-11 items-center rounded-lg border border-primary/35 bg-primary/12 px-5 text-sm font-medium text-primary backdrop-blur-sm transition-all hover:border-primary/50 hover:bg-primary/16 hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40"
className="inline-flex min-h-11 items-center rounded-lg border border-primary/35 bg-primary/12 px-5 text-sm font-medium text-primary hover:border-primary/50 hover:bg-primary/16 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40"
>
{getLocalizedToolTitle("pipeline_builder")}
</Link>
<Link
href={`/${locale}/install-app`}
prefetch={false}
className="inline-flex min-h-11 items-center rounded-lg border border-border/60 bg-background/80 px-5 text-sm font-medium text-foreground backdrop-blur-sm transition-all hover:border-primary/30 hover:bg-background hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40"
>
{installAppLinkLabel}
</Link>
</div>
</div>
</section>
className="inline-flex min-h-11 items-center rounded-lg border border-border/60 bg-background/80 px-5 text-sm font-medium text-foreground hover:border-primary/30 hover:bg-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40"
>
{installAppLinkLabel}
</Link>
<Link href={`/${locale}/compare`}>{compareIndex.eyebrow[locale]}</Link>
</div>
</div>
</section>

{/* Quick Navigation Cards - Compact Grid */}
<section
Expand Down
12 changes: 12 additions & 0 deletions src/app/[lang]/trust-center/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { notFound } from "next/navigation"
import { CheckCircle2, Database, ExternalLink, FileText, Github, LockKeyhole, Network, ShieldCheck, WifiOff } from "lucide-react"
import { isValidLocale, requireTranslationValue } from "@/core/i18n/i18n"
import { getTranslation } from "@/core/i18n/translations/catalog"
import { getGrowthIndex } from "@/core/growth/growth-pages"
import { TOOL_REGISTRY } from "@/core/registry"
import { getExternalRequestToolDisclosures } from "@/core/registry/privacy"
import { JsonLdScript } from "@/core/seo/components/json-ld-script"
Expand Down Expand Up @@ -50,6 +51,10 @@ export default async function TrustCenterPage({
const t = getTranslation(locale)
const p = t.pages
const common = t.common
const compareIndex = getGrowthIndex("compare")
if (!compareIndex) {
throw new Error("[trust-center] Missing compare growth index")
}
const toolCopy = t.tools as Record<string, { title?: string }>
const externalTools = getExternalRequestToolDisclosures(TOOL_REGISTRY)
.map((item) => ({
Expand Down Expand Up @@ -181,6 +186,13 @@ export default async function TrustCenterPage({
<Database className="h-4 w-4" aria-hidden="true" />
{common.local_data_controls.title}
</Link>
<Link
href={`/${locale}/compare`}
className="inline-flex min-h-10 items-center gap-2 rounded-md border border-border/75 bg-background/70 px-3 text-sm font-medium hover:border-primary/35 hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
<ExternalLink className="h-4 w-4" aria-hidden="true" />
{compareIndex.title[locale]}
</Link>
</div>
</section>

Expand Down
145 changes: 137 additions & 8 deletions src/core/growth/growth-pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,81 @@ export type GrowthUiCopy = {
trustBaselineDescription: string
}

const GROWTH_LOCALES: Locale[] = ["en", "zh-CN", "zh-TW", "ja", "ko", "de", "fr"]

const REQUIRED_COMPARISON_DECISION_ROWS: GrowthComparisonRow[] = [
{
factor: "Privacy",
byteflow: "Use the Trust Center, tool trust labels, and sample inputs to verify whether sensitive data stays in the browser.",
other: "Review each alternative's runtime behavior, storage policy, analytics, and deployment owner before using production data.",
note: "No comparison page should be treated as permission to paste secrets without verification.",
},
{
factor: "Local execution",
byteflow: "Prefer browser-local tools for parsing, formatting, encoding, hashing, redaction, and snippet generation when a network call is unnecessary.",
other: "Some hosted tools may proxy, store, or execute work on a server even when the UI feels instant.",
note: "Use DevTools Network with sample data when the processing boundary matters.",
},
{
factor: "Offline use",
byteflow: "Use installable PWA flows and cached tool shells for workflows that should remain available without a live connection.",
other: "Many single-purpose online tools require a fresh network request for the page, scripts, or processing endpoint.",
note: "Offline availability still depends on the route and assets having been cached first.",
},
{
factor: "Open source",
byteflow: "Review the public repository, issues, and implementation when a workflow needs inspectable behavior.",
other: "Closed or opaque tools require more vendor trust because runtime and storage behavior may not be independently reviewable.",
note: "Open source does not replace verification, but it makes claims easier to audit.",
},
{
factor: "Workflow composition",
byteflow: "Combine focused tools, related links, and Pipeline Builder when a task needs repeatable multi-step handling.",
other: "Recipe workbenches or specialized sites may be faster when their composition model already matches the task.",
note: "Choose the model your team can document and repeat with the least ambiguity.",
},
{
factor: "Platform coverage",
byteflow: "Run the web app in modern desktop and mobile browsers, with installable app behavior where supported.",
other: "Native apps, extensions, CLI tools, and hosted sites can cover different device or automation needs.",
note: "Check the target platform before standardizing a team workflow.",
},
{
factor: "Pricing",
byteflow: "Use the public site and source without an account for the comparison workflows described here.",
other: "Some alternatives may add paid tiers, account requirements, usage limits, or hosted-team features.",
note: "Verify current pricing and limits before making a procurement decision.",
},
]

function localizeRequiredComparisonDecisionRows(locale: Locale): GrowthComparisonRow[] {
if (locale === "en") return REQUIRED_COMPARISON_DECISION_ROWS
const copy = getComparisonLocaleCopy()[locale]

return REQUIRED_COMPARISON_DECISION_ROWS.map((row) => ({
factor: `${localizeComparisonTerms(row.factor, locale)}${copy.factorSuffix}`,
byteflow: `${copy.byteflowPrefix}${localizeComparisonTerms(row.byteflow, locale)}`,
other: `${copy.otherPrefix}${localizeComparisonTerms(row.other, locale)}`,
note: `${copy.notePrefix}${localizeComparisonTerms(row.note, locale)}`,
}))
}

function withRequiredComparisonDecisionRows(copy: GrowthPageCopy, locale: Locale): GrowthPageCopy {
return {
...copy,
comparisonRows: [
...(copy.comparisonRows ?? []),
...localizeRequiredComparisonDecisionRows(locale),
],
}
}

function withRequiredComparisonCopy(copyByLocale: Record<Locale, GrowthPageCopy>): Record<Locale, GrowthPageCopy> {
return Object.fromEntries(
GROWTH_LOCALES.map((locale) => [locale, withRequiredComparisonDecisionRows(copyByLocale[locale], locale)]),
) as Record<Locale, GrowthPageCopy>
}

export const GROWTH_UI_COPY: Record<Locale, GrowthUiCopy> = {
en: {
keyTakeaways: "Key takeaways",
Expand Down Expand Up @@ -333,7 +408,7 @@ export const GROWTH_PAGES: GrowthPage[] = [
slug: "compare/byteflow-vs-cyberchef",
kind: "comparison",
relatedToolKeys: ["json_formatter", "base64_encode_decode", "hash_generator", "url_encode_decode"],
copy: {
copy: withRequiredComparisonCopy({
en: {
eyebrow: "Comparison",
title: "Byteflow vs CyberChef",
Expand Down Expand Up @@ -768,21 +843,21 @@ export const GROWTH_PAGES: GrowthPage[] = [
},
],
},
},
}),
},
{
slug: "compare/byteflow-vs-jwt-io",
kind: "comparison",
relatedToolKeys: ["jwt_decoder", "jwt_workbench", "jwt_verifier", "base64_encode_decode"],
copy: {
copy: withRequiredComparisonCopy({
en: jwtPageCopy("Comparison", "Byteflow vs jwt.io"),
"zh-CN": jwtPageCopyZhCN(),
"zh-TW": jwtPageCopyZhTW(),
ja: jwtPageCopyJa(),
ko: jwtPageCopyKo(),
de: jwtPageCopyDe(),
fr: jwtPageCopyFr(),
},
}),
},
{
slug: "alternatives/json-formatter-privacy-first",
Expand All @@ -802,15 +877,15 @@ export const GROWTH_PAGES: GrowthPage[] = [
slug: "compare/md5-vs-sha256",
kind: "comparison",
relatedToolKeys: ["hash_generator", "md5_generator", "base64_encode_decode", "jwt_decoder"],
copy: {
copy: withRequiredComparisonCopy({
en: hashComparisonCopyEn(),
"zh-CN": hashComparisonCopyZhCN(),
"zh-TW": hashComparisonCopyZhTW(),
ja: hashComparisonCopyJa(),
ko: hashComparisonCopyKo(),
de: hashComparisonCopyDe(),
fr: hashComparisonCopyFr(),
},
}),
},
{
slug: "compare/json-formatter-vs-json-validator",
Expand Down Expand Up @@ -1401,6 +1476,15 @@ function getComparisonTermLocalization(): Record<Locale, Record<string, string>>
"SVG Converter": "SVG 转换器",
"Related tools": "相关工具",
"Related Tools": "相关工具",
"Trust Center": "隐私与信任中心",
"Open Source": "开源",
"Open source": "开源",
"open source": "开源",
"Pipeline Builder": "流水线构建器",
"Trust labels": "信任标签",
"trust labels": "信任标签",
"Browser-local tools": "浏览器本地工具",
"browser-local tools": "浏览器本地工具",
},
"zh-TW": {
"JSON Formatter": "JSON 格式化工具",
Expand All @@ -1418,6 +1502,15 @@ function getComparisonTermLocalization(): Record<Locale, Record<string, string>>
"SVG Converter": "SVG 轉換器",
"Related tools": "相關工具",
"Related Tools": "相關工具",
"Trust Center": "隱私與信任中心",
"Open Source": "開源",
"Open source": "開源",
"open source": "開源",
"Pipeline Builder": "流程建構器",
"Trust labels": "信任標籤",
"trust labels": "信任標籤",
"Browser-local tools": "瀏覽器本地工具",
"browser-local tools": "瀏覽器本地工具",
},
ja: {
"JSON Formatter": "JSON フォーマッター",
Expand All @@ -1435,6 +1528,15 @@ function getComparisonTermLocalization(): Record<Locale, Record<string, string>>
"SVG Converter": "SVG コンバーター",
"Related tools": "関連ツール",
"Related Tools": "関連ツール",
"Trust Center": "プライバシーと信頼センター",
"Open Source": "オープンソース",
"Open source": "オープンソース",
"open source": "オープンソース",
"Pipeline Builder": "パイプラインビルダー",
"Trust labels": "信頼ラベル",
"trust labels": "信頼ラベル",
"Browser-local tools": "ブラウザーローカルツール",
"browser-local tools": "ブラウザーローカルツール",
},
ko: {
"JSON Formatter": "JSON 포매터",
Expand All @@ -1452,6 +1554,15 @@ function getComparisonTermLocalization(): Record<Locale, Record<string, string>>
"SVG Converter": "SVG 변환기",
"Related tools": "관련 도구",
"Related Tools": "관련 도구",
"Trust Center": "개인정보 및 신뢰 센터",
"Open Source": "오픈 소스",
"Open source": "오픈 소스",
"open source": "오픈 소스",
"Pipeline Builder": "파이프라인 빌더",
"Trust labels": "신뢰 라벨",
"trust labels": "신뢰 라벨",
"Browser-local tools": "브라우저 로컬 도구",
"browser-local tools": "브라우저 로컬 도구",
},
de: {
"JSON Formatter": "JSON-Formatter",
Expand All @@ -1469,6 +1580,15 @@ function getComparisonTermLocalization(): Record<Locale, Record<string, string>>
"SVG Converter": "SVG-Konverter",
"Related tools": "Verwandte Tools",
"Related Tools": "Verwandte Tools",
"Trust Center": "Datenschutz- und Vertrauenszentrum",
"Open Source": "quelloffen",
"Open source": "quelloffen",
"open source": "quelloffen",
"Pipeline Builder": "Pipeline-Builder",
"Trust labels": "Vertrauenslabels",
"trust labels": "Vertrauenslabels",
"Browser-local tools": "browserlokale Tools",
"browser-local tools": "browserlokale Tools",
},
fr: {
"JSON Formatter": "Formateur JSON",
Expand All @@ -1486,6 +1606,15 @@ function getComparisonTermLocalization(): Record<Locale, Record<string, string>>
"SVG Converter": "Convertisseur SVG",
"Related tools": "Outils connexes",
"Related Tools": "Outils connexes",
"Trust Center": "Centre de confiance",
"Open Source": "open source",
"Open source": "open source",
"open source": "open source",
"Pipeline Builder": "Constructeur de pipeline",
"Trust labels": "labels de confiance",
"trust labels": "labels de confiance",
"Browser-local tools": "outils locaux dans le navigateur",
"browser-local tools": "outils locaux dans le navigateur",
},
}
}
Expand Down Expand Up @@ -1650,15 +1779,15 @@ function localizeComparisonSeed(seed: ComparisonSeed, locale: Locale): GrowthPag
}

function comparisonPageCopy(seed: ComparisonSeed): Record<Locale, GrowthPageCopy> {
return {
return withRequiredComparisonCopy({
en: seed,
"zh-CN": localizeComparisonSeed(seed, "zh-CN"),
"zh-TW": localizeComparisonSeed(seed, "zh-TW"),
ja: localizeComparisonSeed(seed, "ja"),
ko: localizeComparisonSeed(seed, "ko"),
de: localizeComparisonSeed(seed, "de"),
fr: localizeComparisonSeed(seed, "fr"),
}
})
}

export function getGrowthPage(slug: GrowthPageSlug) {
Expand Down
Loading