* Add i18n framework with next-intl for 19 languages
Set up complete internationalization infrastructure:
- Install next-intl v4 with App Router support
- Create i18n config (routing, request, navigation)
- Add middleware for automatic locale detection from Accept-Language
- Restructure all routes under app/[locale]/
- Extract UI strings to messages/en.json
- Update all components to use useTranslations()
- Add language switcher dropdown in footer
- Support RTL for Arabic and Khmer
- Update sitemap with locale alternates
- Add generateStaticParams for all 19 locales
Languages: en, ja, zh-CN, zh-TW, ko, de, es, fr, it, da, pl, ru, bs, ar, no, pt-BR, th, tr, km
Locale detection: auto-detect from browser Accept-Language header,
with cookie persistence and locale prefix only for non-default (en).
* Add translations for de, fr, it, ja, zh-CN, zh-TW
* Add translations for ar, bs, da, es, km, no, pl, pt-BR, ru, th, tr
* Convert docs and legal pages to use useTranslations()
* Add i18n to keyboard shortcuts component
* Add i18n to wall-of-love, add missing blog posts to sitemap
* Add keyboard shortcuts and wallOfLove translations to all locales
* Update bun lockfile for next-intl dependency
* Fix t.rich() configPath: pass ReactNode not function for {var} interpolation
* Fix configPath: use rich text tag instead of plain interpolation for ReactNode
* Fix t.rich() interpolation: use rich text tags for all ReactNode placeholders
Changed {legacy}, {openShortcut}, {jumpShortcut} from plain variable
interpolation to <tag>content</tag> format so t.rich() gets proper
functions instead of values.
* Escape ICU curly braces in socketCallout rich text across all locales
* Fix i18n issues: Khmer RTL, zh-CN quality, locale-aware testimonials, hardcoded strings
- Fix Khmer (km) incorrectly marked as RTL (it's LTR, only Arabic is RTL)
- Fix zh-CN/zh-TW taglinePrefix to mention terminals and open source
- Add locale-aware testimonial translations: show original text, translate
for non-matching locales, skip translation when locale matches original
- Translate hardcoded English table content in notifications page
- Add testimonial translations to all 19 locale files
- Remove unused setRequestLocale import and params from home page
* Address PR review comments: metadata localization, blog fixes, legal pages, accessibility
- Convert hardcoded metadata to generateMetadata with getTranslations on all docs, blog, community, and wall-of-love pages
- Fix blog canonical/OG URLs to be locale-aware
- Fix introducing-cmux .split(": ") by using separate label/desc translation keys
- Revert legal page titles to English (legal content stays English-only)
- Add focus-visible ring to language switcher for keyboard accessibility
- Preserve query string and hash when switching locale
- Convert site-footer to server component (remove unnecessary "use client")
- Remove .toLowerCase() on translated text in community page
- Add /docs/browser-automation and /wall-of-love to sitemap
- Fix keyboard-shortcuts jump link visibility with trimmed query
- Deduplicate blogSlugs by importing from blog-posts.ts
- Add typingCodingAgents/typingMultitasking translation keys to all locales
- Fix Spanish accent/tilde issues in es.json testimonials
- Fix nested <a> tag in homepage keyboard shortcuts feature
- Remove unused setRequestLocale import from homepage
* Convert remaining layout/index metadata to generateMetadata
- Root layout: locale-aware title, description, OG, and Twitter card metadata
- Docs layout: translated title template
- Blog layout: translated title template
- Blog index: locale-aware metadata
* Add translated metadata keys to all locales, fix docs redirect
- Add meta.title/description/ogDescription to all 18 non-English locales
- Add docs.layoutTitle, blog.layoutTitle/metaTitle/metaDescription to all locales
- Add blog post metadata (zenOfCmux, cmdShiftU, showHnLaunch, introducingCmux) to all locales
- Add community.metaTitle/metaDescription to all locales
- Fix docs index redirect to preserve locale prefix
* Add translated docs page metaTitle keys to all locales
45 lines
2.4 KiB
TypeScript
45 lines
2.4 KiB
TypeScript
import type { MetadataRoute } from "next";
|
|
import { locales } from "../i18n/routing";
|
|
|
|
export default function sitemap(): MetadataRoute.Sitemap {
|
|
const base = "https://cmux.dev";
|
|
|
|
const paths = [
|
|
{ path: "", lastModified: new Date(), changeFrequency: "weekly" as const, priority: 1 },
|
|
{ path: "/blog", lastModified: new Date(), changeFrequency: "weekly" as const, priority: 0.8 },
|
|
{ path: "/blog/show-hn-launch", lastModified: "2026-02-21", changeFrequency: "monthly" as const, priority: 0.7 },
|
|
{ path: "/blog/introducing-cmux", lastModified: "2026-02-12", changeFrequency: "monthly" as const, priority: 0.7 },
|
|
{ path: "/blog/zen-of-cmux", lastModified: "2026-02-27", changeFrequency: "monthly" as const, priority: 0.7 },
|
|
{ path: "/blog/cmd-shift-u", lastModified: "2026-03-04", changeFrequency: "monthly" as const, priority: 0.7 },
|
|
{ path: "/docs/getting-started", lastModified: new Date(), changeFrequency: "monthly" as const, priority: 0.9 },
|
|
{ path: "/docs/concepts", lastModified: new Date(), changeFrequency: "monthly" as const, priority: 0.8 },
|
|
{ path: "/docs/configuration", lastModified: new Date(), changeFrequency: "monthly" as const, priority: 0.8 },
|
|
{ path: "/docs/keyboard-shortcuts", lastModified: new Date(), changeFrequency: "monthly" as const, priority: 0.7 },
|
|
{ path: "/docs/api", lastModified: new Date(), changeFrequency: "monthly" as const, priority: 0.8 },
|
|
{ path: "/docs/notifications", lastModified: new Date(), changeFrequency: "monthly" as const, priority: 0.8 },
|
|
{ path: "/docs/changelog", lastModified: new Date(), changeFrequency: "weekly" as const, priority: 0.5 },
|
|
{ path: "/docs/browser-automation", lastModified: new Date(), changeFrequency: "monthly" as const, priority: 0.8 },
|
|
{ path: "/community", lastModified: new Date(), changeFrequency: "monthly" as const, priority: 0.5 },
|
|
{ path: "/wall-of-love", lastModified: new Date(), changeFrequency: "monthly" as const, priority: 0.5 },
|
|
];
|
|
|
|
const entries: MetadataRoute.Sitemap = [];
|
|
|
|
for (const { path, lastModified, changeFrequency, priority } of paths) {
|
|
const alternates: Record<string, string> = {};
|
|
for (const locale of locales) {
|
|
alternates[locale] =
|
|
locale === "en" ? `${base}${path}` : `${base}/${locale}${path}`;
|
|
}
|
|
|
|
entries.push({
|
|
url: `${base}${path}`,
|
|
lastModified,
|
|
changeFrequency,
|
|
priority,
|
|
alternates: { languages: alternates },
|
|
});
|
|
}
|
|
|
|
return entries;
|
|
}
|