Repository navigation
Internationalize website with next-intl for 19 languages #1216
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
33de0e0
6c3198b
d496fa3
9b801a3
b08bd7e
a85780c
79eacda
a8748f1
f22779a
295c4f9
a2231e0
cd3eefb
dc8543e
f724861
d08c8e8
f9f77ac
e640389
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,17 @@ | ||
| import type { Metadata } from "next"; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: This heading is hardcoded in English on a locale-scoped page, so non-English locales won’t get a translated title. Restore the translation key usage for Prompt for AI agents |
||
| import { useTranslations } from "next-intl"; | ||
| import { Link } from "../../../../i18n/navigation"; | ||
|
|
||
| export const metadata: Metadata = { | ||
| title: "Privacy Policy — cmux", | ||
| description: "Privacy policy for cmux", | ||
| }; | ||
|
|
||
| export default function PrivacyPolicyPage() { | ||
| const t = useTranslations("legal"); | ||
| return ( | ||
| <> | ||
| <h1>Privacy Policy</h1> | ||
| <h1>{t("privacyPolicy")}</h1> | ||
| <p>Last updated: December 2, 2025</p> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This policy is still only partially localized. The heading moved to Also applies to: 35-35 🤖 Prompt for AI Agents |
||
|
|
||
| <p> | ||
|
|
@@ -29,7 +32,7 @@ export default function PrivacyPolicyPage() { | |
| </p> | ||
| <p> | ||
| By using our Service, you accept this Privacy Policy and our{" "} | ||
| <a href="/terms-of-service">Terms of Service</a>, and you consent to | ||
| <Link href="/terms-of-service">Terms of Service</Link>, and you consent to | ||
| our collection, storage, use and disclosure of your information as | ||
| described here. | ||
| </p> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,16 @@ | ||
| import type { Metadata } from "next"; | ||
| import { useTranslations } from "next-intl"; | ||
|
|
||
| export const metadata: Metadata = { | ||
| title: "Terms of Service — cmux", | ||
| description: "Terms of service for cmux", | ||
| }; | ||
|
|
||
| export default function TermsOfServicePage() { | ||
| const t = useTranslations("legal"); | ||
| return ( | ||
| <> | ||
| <h1>Terms of Service</h1> | ||
| <h1>{t("termsOfService")}</h1> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don’t expose a localized Terms route with English terms. Only the 🤖 Prompt for AI Agents |
||
| <p>Last revised on: December 2, 2025</p> | ||
|
|
||
| <p> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| import type { Metadata } from "next"; | ||
| import { useTranslations } from "next-intl"; | ||
| import { Link } from "../../../../i18n/navigation"; | ||
|
|
||
| export const metadata: Metadata = { | ||
| title: "Introducing cmux", | ||
| description: | ||
| "A native macOS terminal built on Ghostty, designed for running multiple AI coding agents side by side.", | ||
| keywords: [ | ||
| "cmux", "terminal", "macOS", "Ghostty", "libghostty", | ||
| "AI coding agents", "Claude Code", "vertical tabs", "split panes", "socket API", | ||
| ], | ||
| openGraph: { | ||
| title: "Introducing cmux", | ||
| description: | ||
| "A native macOS terminal built on Ghostty, designed for running multiple AI coding agents side by side.", | ||
| type: "article", | ||
| publishedTime: "2026-02-12T00:00:00Z", | ||
| url: "https://cmux.dev/blog/introducing-cmux", | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
Resolved
|
||
| }, | ||
| twitter: { | ||
| card: "summary", | ||
| title: "Introducing cmux", | ||
| description: | ||
| "A native macOS terminal built on Ghostty, designed for running multiple AI coding agents side by side.", | ||
| }, | ||
| alternates: { canonical: "https://cmux.dev/blog/introducing-cmux" }, | ||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||
| }; | ||
|
|
||
| export default function IntroducingCmuxPage() { | ||
| const t = useTranslations("blog.posts.introducingCmux"); | ||
| const tc = useTranslations("common"); | ||
|
|
||
| return ( | ||
| <> | ||
| <div className="mb-8"> | ||
| <Link | ||
| href="/blog" | ||
| className="text-sm text-muted hover:text-foreground transition-colors" | ||
| > | ||
| ← {tc("backToBlog")} | ||
| </Link> | ||
| </div> | ||
|
|
||
| <h1>{t("title")}</h1> | ||
| <time dateTime="2026-02-12" className="text-sm text-muted">{t("date")}</time> | ||
|
|
||
| <p className="mt-6">{t("p1")}</p> | ||
|
|
||
| <h2>{t("whyTitle")}</h2> | ||
| <p>{t("whyP")}</p> | ||
|
|
||
| <h2>{t("featuresTitle")}</h2> | ||
| <ul> | ||
| <li><strong>Vertical tabs</strong>: {t("featureVerticalTabs").split(": ")[1] || t("featureVerticalTabs")}</li> | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
Resolved
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The feature bullets parse translated strings with Useful? React with 👍 / 👎. |
||
| <li><strong>Notification rings</strong>: {t("featureNotifications").split(": ")[1] || t("featureNotifications")}</li> | ||
| <li><strong>Split panes</strong>: {t("featureSplitPanes").split(": ")[1] || t("featureSplitPanes")}</li> | ||
| <li><strong>Socket API</strong>: {t("featureSocketApi").split(": ")[1] || t("featureSocketApi")}</li> | ||
| <li><strong>GPU-accelerated</strong>: {t("featureGpu").split(": ")[1] || t("featureGpu")}</li> | ||
| </ul> | ||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||
|
|
||
| <h2>{t("getStartedTitle")}</h2> | ||
| <p> | ||
| {t.rich("getStartedP", { | ||
| link: (chunks) => <Link href="/docs/getting-started">{chunks}</Link>, | ||
| })} | ||
| </p> | ||
| </> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,52 @@ | ||||||
| import type { Metadata } from "next"; | ||||||
| import { useTranslations } from "next-intl"; | ||||||
| import { Link } from "../../../i18n/navigation"; | ||||||
|
|
||||||
| export const metadata: Metadata = { | ||||||
| title: "Blog", | ||||||
| description: "News and updates from the cmux team", | ||||||
| }; | ||||||
|
|
||||||
| const blogSlugs = [ | ||||||
| "cmdShiftU", | ||||||
| "zenOfCmux", | ||||||
| "showHnLaunch", | ||||||
| "introducingCmux", | ||||||
| ] as const; | ||||||
|
|
||||||
| const slugToPath: Record<string, string> = { | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Prompt for AI agents
Suggested change
|
||||||
| cmdShiftU: "cmd-shift-u", | ||||||
| zenOfCmux: "zen-of-cmux", | ||||||
| showHnLaunch: "show-hn-launch", | ||||||
| introducingCmux: "introducing-cmux", | ||||||
| }; | ||||||
|
|
||||||
| export default function BlogPage() { | ||||||
| const t = useTranslations("blog"); | ||||||
|
|
||||||
| return ( | ||||||
| <> | ||||||
| <h1>{t("title")}</h1> | ||||||
| <div className="space-y-4 mt-6"> | ||||||
| {blogSlugs.map((slug) => ( | ||||||
| <article key={slug}> | ||||||
| <Link | ||||||
| href={`/blog/${slugToPath[slug]}`} | ||||||
| className="block group" | ||||||
| > | ||||||
| <h2 className="text-lg font-medium group-hover:underline"> | ||||||
| {t(`posts.${slug}.title`)} | ||||||
| </h2> | ||||||
| <time className="text-sm text-muted"> | ||||||
| {t(`posts.${slug}.date`)} | ||||||
| </time> | ||||||
| <p className="mt-1 text-muted"> | ||||||
| {t(`posts.${slug}.summary`)} | ||||||
| </p> | ||||||
| </Link> | ||||||
| </article> | ||||||
| ))} | ||||||
| </div> | ||||||
| </> | ||||||
| ); | ||||||
| } | ||||||
Uh oh!
There was an error while loading. Please reload this page.