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 all 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 |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| import { useTranslations } from "next-intl"; | ||
| import { getTranslations } from "next-intl/server"; | ||
| import { Link } from "../../../../i18n/navigation"; | ||
|
|
||
| export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) { | ||
| const { locale } = await params; | ||
| const t = await getTranslations({ locale, namespace: "blog.cmdShiftU" }); | ||
|
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: Metadata translation namespace points to keys that are missing in non-English locales, so localized pages cannot resolve Prompt for AI agents |
||
| const url = locale === "en" ? "/blog/cmd-shift-u" : `/${locale}/blog/cmd-shift-u`; | ||
| return { | ||
| title: t("metaTitle"), | ||
| description: t("metaDescription"), | ||
| keywords: [ | ||
| "cmux", "terminal", "macOS", "notifications", "AI coding agents", | ||
| "keyboard shortcuts", "developer tools", "workflow", | ||
| ], | ||
| openGraph: { | ||
| title: t("metaTitle"), | ||
| description: t("metaDescription"), | ||
| type: "article", | ||
| publishedTime: "2026-03-04T00:00:00Z", | ||
| url, | ||
| }, | ||
| twitter: { | ||
| card: "summary", | ||
| title: t("metaTitle"), | ||
| description: t("metaDescription"), | ||
| }, | ||
| alternates: { canonical: url }, | ||
| }; | ||
| } | ||
|
|
||
| export default function CmdShiftUPage() { | ||
| const t = useTranslations("blog.posts.cmdShiftU"); | ||
| 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-03-04" className="text-sm text-muted"> | ||
| {t("date")} | ||
| </time> | ||
|
|
||
| <p className="mt-6">{t("p1")}</p> | ||
|
|
||
| <video | ||
| src="/blog/cmd-shift-u.mp4" | ||
| width={1824} | ||
| height={1080} | ||
| autoPlay | ||
| loop | ||
| muted | ||
| playsInline | ||
| className="my-6 rounded-lg w-full h-auto" | ||
| /> | ||
|
|
||
| <p> | ||
| {t.rich("p2", { | ||
| link: (chunks) => ( | ||
| <Link href="/docs/notifications">{chunks}</Link> | ||
| ), | ||
| })} | ||
| </p> | ||
| </> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| import { useTranslations } from "next-intl"; | ||
| import { getTranslations } from "next-intl/server"; | ||
| import { Link } from "../../../../i18n/navigation"; | ||
|
|
||
| export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) { | ||
| const { locale } = await params; | ||
| const t = await getTranslations({ locale, namespace: "blog.introducingCmux" }); | ||
|
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 |
||
| const url = locale === "en" ? "/blog/introducing-cmux" : `/${locale}/blog/introducing-cmux`; | ||
| return { | ||
| title: t("metaTitle"), | ||
| description: t("metaDescription"), | ||
| keywords: [ | ||
| "cmux", "terminal", "macOS", "Ghostty", "libghostty", | ||
| "AI coding agents", "Claude Code", "vertical tabs", "split panes", "socket API", | ||
| ], | ||
| openGraph: { | ||
| title: t("metaTitle"), | ||
| description: t("metaDescription"), | ||
| type: "article", | ||
| publishedTime: "2026-02-12T00:00:00Z", | ||
| url, | ||
| }, | ||
| twitter: { | ||
| card: "summary", | ||
| title: t("metaTitle"), | ||
| description: t("metaDescription"), | ||
| }, | ||
| alternates: { canonical: url }, | ||
| }; | ||
| } | ||
|
|
||
| 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>{t("featureVerticalTabsLabel")}</strong>: {t("featureVerticalTabsDesc")}</li> | ||
| <li><strong>{t("featureNotificationsLabel")}</strong>: {t("featureNotificationsDesc")}</li> | ||
| <li><strong>{t("featureSplitPanesLabel")}</strong>: {t("featureSplitPanesDesc")}</li> | ||
| <li><strong>{t("featureSocketApiLabel")}</strong>: {t("featureSocketApiDesc")}</li> | ||
| <li><strong>{t("featureGpuLabel")}</strong>: {t("featureGpuDesc")}</li> | ||
| </ul> | ||
|
coderabbitai[bot] marked this conversation as 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,60 @@ | ||||||
| import { useTranslations } from "next-intl"; | ||||||
| import { getTranslations } from "next-intl/server"; | ||||||
| import { Link } from "../../../i18n/navigation"; | ||||||
|
|
||||||
| export async function generateMetadata({ | ||||||
| params, | ||||||
| }: { | ||||||
| params: Promise<{ locale: string }>; | ||||||
| }) { | ||||||
| const { locale } = await params; | ||||||
| const t = await getTranslations({ locale, namespace: "blog" }); | ||||||
| return { | ||||||
| title: t("metaTitle"), | ||||||
| description: t("metaDescription"), | ||||||
| }; | ||||||
| } | ||||||
|
|
||||||
| 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The 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
privacyPolicy.Prompt for AI agents