Repository navigation
Internationalize website with next-intl for 19 languages - #1216
Conversation
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).
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdd full i18n support: routing, middleware, request loader, navigation helpers, locale-aware layout, language switcher, translated pages/components, 18 message bundles, sitemap updates, and migrate pages into a Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Browser
participant MW as Middleware (next-intl)
participant Next as Next.js Router
participant Layout as LocaleLayout
participant Messages as getMessages
participant Page as Page Component
Client->>MW: Request /path or /[locale]/path
MW->>Next: normalize/ensure locale
Next->>Layout: render with params.locale
Layout->>Messages: setRequestLocale + import messages
Messages-->>Layout: messages JSON
Layout->>Page: provide NextIntlClientProvider + messages
Page->>Messages: useTranslations(namespace)
Messages-->>Page: translation function t
Page-->>Client: Render localized HTML
Client->>Page: LanguageSwitcher selects locale
Page->>Next: navigate to /[newLocale]/same-path
Next->>MW: new request (repeat)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
Greptile SummaryThis PR adds full i18n support to the cmux website using Key findings:
Confidence Score: 3/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Incoming Request] --> B{next-intl Middleware}
B -->|Accept-Language: ja| C[Redirect to /ja/...]
B -->|Accept-Language: en| D[No prefix — /...]
B -->|Unknown locale| E[Fallback to /en default]
C --> F["[locale] Layout\n(async)\n• validate locale\n• setRequestLocale\n• load messages\n• set dir= ltr/rtl"]
D --> F
E --> F
F --> G{locale === 'ar'?}
G -->|yes| H["dir='rtl'"]
G -->|no| I["dir='ltr'"]
H --> J[NextIntlClientProvider\nwraps children]
I --> J
J --> K[Page Component\nuseTranslations]
J --> L[SiteFooter\nLanguageSwitcher]
style G fill:#f96,stroke:#c00
style H fill:#f96,stroke:#c00
Last reviewed commit: 79eacda |
|
|
||
| const messages = await getMessages(); | ||
|
|
||
| const dir = locale === "ar" || locale === "km" ? "rtl" : "ltr"; |
There was a problem hiding this comment.
Khmer (km) is not an RTL language
The Khmer script is written left-to-right — it is not a right-to-left language. Setting dir="rtl" for km will mirror the entire page layout for Khmer users, causing text, navigation, and UI elements to render incorrectly.
Only Arabic (ar) uses RTL among the 19 supported locales.
| const dir = locale === "ar" || locale === "km" ? "rtl" : "ltr"; | |
| const dir = locale === "ar" ? "rtl" : "ltr"; |
| export default function Home({ | ||
| params, | ||
| }: { | ||
| params: Promise<{ locale: string }>; | ||
| }) { | ||
| return <HomeContent />; |
There was a problem hiding this comment.
params accepted but never used; setRequestLocale imported but never called
setRequestLocale is imported at line 2 and the component signature accepts params, but neither is ever used. The next-intl documentation recommends calling setRequestLocale(locale) in every page component (in addition to layouts) to correctly support Static Rendering — without it, the page's own rendering context is not initialised with the locale, and pages can fall back to the defaultLocale in edge-case SSG scenarios.
| export default function Home({ | |
| params, | |
| }: { | |
| params: Promise<{ locale: string }>; | |
| }) { | |
| return <HomeContent />; | |
| export default async function Home({ | |
| params, | |
| }: { | |
| params: Promise<{ locale: string }>; | |
| }) { | |
| const { locale } = await params; | |
| setRequestLocale(locale); | |
| return <HomeContent />; | |
| } |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 79eacda2e6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| const messages = await getMessages(); | ||
|
|
||
| const dir = locale === "ar" || locale === "km" ? "rtl" : "ltr"; |
There was a problem hiding this comment.
Restrict RTL direction to Arabic locale
This condition marks Khmer (km) pages as dir="rtl", but Khmer is a left-to-right script. For /km/* routes this flips document flow, alignment, and punctuation ordering across the entire page, making the localized site hard to read and navigate for Khmer users.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
18 issues found across 90 files
Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="web/app/[locale]/blog/zen-of-cmux/page.tsx">
<violation number="1" location="web/app/[locale]/blog/zen-of-cmux/page.tsx:19">
P2: Canonical/Open Graph URL is hard-coded to the English path in a locale route, so non-English pages publish incorrect metadata URLs.</violation>
</file>
<file name="web/app/[locale]/blog/page.tsx">
<violation number="1" location="web/app/[locale]/blog/page.tsx:17">
P2: `slugToPath` is typed too loosely. Use the `blogSlugs` union as map keys so missing slug mappings are caught at compile time.</violation>
</file>
<file name="web/app/[locale]/page.tsx">
<violation number="1" location="web/app/[locale]/page.tsx:13">
P2: The page ignores `params.locale` and never calls `setRequestLocale` before using translations, which can force this route into dynamic rendering instead of static i18n output.</violation>
<violation number="2" location="web/app/[locale]/page.tsx:110">
P2: This renders a nested `<a>` inside another `<a>`, which is invalid HTML and can break accessibility/click behavior.</violation>
</file>
<file name="web/app/[locale]/blog/cmd-shift-u/page.tsx">
<violation number="1" location="web/app/[locale]/blog/cmd-shift-u/page.tsx:27">
P2: Canonical metadata is hardcoded to the English URL in a locale route, so translated pages will point search engines to the wrong canonical.</violation>
</file>
<file name="web/app/[locale]/docs/api/page.tsx">
<violation number="1" location="web/app/[locale]/docs/api/page.tsx:36">
P2: The page body is localized, but metadata remains hardcoded in English. Use locale-aware metadata generation so non-English routes don't ship English SEO title/description.</violation>
</file>
<file name="web/app/[locale]/(legal)/eula/page.tsx">
<violation number="1" location="web/app/[locale]/(legal)/eula/page.tsx:13">
P2: Only the page title is localized; the EULA body remains hardcoded in English, so non-English locales won’t get a translated legal page.</violation>
</file>
<file name="web/app/[locale]/layout.tsx">
<violation number="1" location="web/app/[locale]/layout.tsx:78">
P2: `km` (Khmer) should not be treated as RTL. This condition forces Khmer pages to render with `dir="rtl"`, which breaks layout/text direction for that locale.</violation>
</file>
<file name="web/app/[locale]/community/page.tsx">
<violation number="1" location="web/app/[locale]/community/page.tsx:49">
P2: Do not force lowercase on translated UI text; pass the localized title directly to avoid locale-specific casing issues.</violation>
</file>
<file name="web/app/[locale]/components/docs-nav-items.ts">
<violation number="1" location="web/app/[locale]/components/docs-nav-items.ts:7">
P2: `/docs/browser-automation` is added to docs navigation but missing from sitemap paths, so this page (and its locale alternates) won't be emitted in the sitemap.</violation>
</file>
<file name="web/app/[locale]/docs/getting-started/page.tsx">
<violation number="1" location="web/app/[locale]/docs/getting-started/page.tsx:7">
P2: Metadata is hardcoded in English instead of being locale-aware, so translated routes still render English SEO/title tags.</violation>
</file>
<file name="web/app/[locale]/blog/show-hn-launch/page.tsx">
<violation number="1" location="web/app/[locale]/blog/show-hn-launch/page.tsx:8">
P2: Locale-specific routes are using hard-coded English metadata and canonical URL, so non-English pages publish incorrect SEO metadata (including canonical/OG URL) instead of locale-specific values.</violation>
</file>
<file name="web/app/[locale]/blog/introducing-cmux/page.tsx">
<violation number="1" location="web/app/[locale]/blog/introducing-cmux/page.tsx:19">
P2: Canonical and Open Graph URL are fixed to the English route in a `[locale]` page, so localized pages emit the wrong canonical/share URL.</violation>
<violation number="2" location="web/app/[locale]/blog/introducing-cmux/page.tsx:55">
P2: Avoid parsing translated strings with `.split(": ")` and hard-coded English labels in localized content; this breaks for non-English punctuation/order and can render mixed-language text.</violation>
</file>
<file name="web/app/[locale]/keyboard-shortcuts.tsx">
<violation number="1" location="web/app/[locale]/keyboard-shortcuts.tsx:213">
P3: Use the normalized query for jump-link visibility; whitespace-only input currently hides jump links while showing unfiltered results.</violation>
</file>
<file name="web/app/[locale]/components/site-footer.tsx">
<violation number="1" location="web/app/[locale]/components/site-footer.tsx:1">
P2: `"use client"` is unnecessary here and forces the whole footer to hydrate on the client. Remove it so only `LanguageSwitcher` remains client-side.</violation>
</file>
<file name="web/messages/en.json">
<violation number="1" location="web/messages/en.json:60">
P2: `keyboardShortcutsDesc` includes a colon and `<link>` wrapper even though the caller already adds both, causing `: :` text and nested `<a>` elements on the home page.</violation>
</file>
<file name="web/app/[locale]/components/blog-pager.tsx">
<violation number="1" location="web/app/[locale]/components/blog-pager.tsx:6">
P2: `blogSlugs` introduces a duplicate blog source-of-truth and leaves `blog-posts.ts` unused, which makes future blog updates easy to miss in one place.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| "introducingCmux", | ||
| ] as const; | ||
|
|
||
| const slugToPath: Record<string, string> = { |
There was a problem hiding this comment.
P2: slugToPath is typed too loosely. Use the blogSlugs union as map keys so missing slug mappings are caught at compile time.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/app/[locale]/blog/page.tsx, line 17:
<comment>`slugToPath` is typed too loosely. Use the `blogSlugs` union as map keys so missing slug mappings are caught at compile time.</comment>
<file context>
@@ -0,0 +1,52 @@
+ "introducingCmux",
+] as const;
+
+const slugToPath: Record<string, string> = {
+ cmdShiftU: "cmd-shift-u",
+ zenOfCmux: "zen-of-cmux",
</file context>
| const slugToPath: Record<string, string> = { | |
| const slugToPath: Record<(typeof blogSlugs)[number], string> = { |
There was a problem hiding this comment.
Actionable comments posted: 20
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
web/app/[locale]/wall-of-love/page.tsx (1)
6-10:⚠️ Potential issue | 🟠 MajorLocalize metadata along with the visible copy.
The page body now uses
wallOfLovetranslations, butmetadata.titleandmetadata.descriptionare still hard-coded English. Every localized route will still show English tab text and meta description, which undercuts the international SEO work in this PR.Also applies to: 13-23
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/app/`[locale]/wall-of-love/page.tsx around lines 6 - 10, metadata currently contains hard-coded English title/description; replace the static export const metadata with a dynamic generator that returns localized metadata using the same wallOfLove translations used in the page body. Implement an export async function generateMetadata({ params }) that loads the locale dictionary or translation helper (the same source used by wallOfLove in the page) and returns a Metadata object with title and description drawn from the wallOfLove keys; keep the Metadata type and ensure pages for all locales (including the code currently at lines 13-23) use the localized strings instead of literals.web/app/[locale]/community/page.tsx (1)
71-81:⚠️ Potential issue | 🟡 MinorInconsistent: GitHub name is hardcoded while other link names use translations.
Line 73 uses
name="GitHub"directly while Discord (line 61), Twitter (line 85), YouTube (line 97), and LinkedIn (line 109) all uset("...")for the name prop. For consistency, this should also use a translation key.🐛 Proposed fix
<CommunityLink href="https://github.com/manaflow-ai/cmux" - name="GitHub" + name={t("github")} action={t("githubAction")} description={t("githubDesc")}Note: Ensure the translation files include a
"github": "GitHub"key in thecommunitynamespace (it exists in pt-BR.json at line 83, but verify it's present in all locale files).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/app/`[locale]/community/page.tsx around lines 71 - 81, The GitHub CommunityLink is hardcoded as name="GitHub"; change it to use the i18n helper (name={t("github")}) to match other links (CommunityLink usage with t for "discord", "twitter", etc.), and ensure the "github" key exists in the community translation namespace for all locales.web/app/[locale]/docs/notifications/page.tsx (1)
46-67:⚠️ Potential issue | 🟡 MinorThese tables are still only partially localized.
Visible copy like
Variable,Description,Notification title ...,Title + body,Yes/No, andHigher/Loweris still hard-coded in English. That leaves the page half-translated on non-English locales. Move the human-readable headers/cells into translation keys; only the env var names and protocol identifiers need to stay literal.Also applies to: 107-136
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/app/`[locale]/docs/notifications/page.tsx around lines 46 - 67, The table contains hard-coded English strings ("Variable", "Description", "Notification title (workspace name or app name)", "Notification subtitle", "Notification body text", and other human-readable cells) causing partial localization; replace these literal strings with translation keys and call the repository's translation helper (the same i18n/translator used elsewhere in this page) for each header and cell while keeping the env var names (CMUX_NOTIFICATION_TITLE, CMUX_NOTIFICATION_SUBTITLE, CMUX_NOTIFICATION_BODY) and any protocol identifiers literal; apply the same change to the other table range mentioned (lines around the second table at 107-136) so all visible copy is pulled from translations rather than hard-coded English.
🧹 Nitpick comments (7)
web/app/sitemap.ts (1)
7-22: Consider using static dates or build-time constants instead ofnew Date().Using
new Date()forlastModifiedmeans the value changes on every build, which:
- Creates non-deterministic builds (different output each time)
- Signals to search engines that pages changed even when content is unchanged
- May trigger unnecessary re-crawling
For pages with infrequent changes (docs, community), consider using a static date string or a build-time constant that only updates when content actually changes.
♻️ Suggested approach
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: "", lastModified: "2026-03-12", changeFrequency: "weekly" as const, priority: 1 }, + { path: "/blog", lastModified: "2026-03-12", changeFrequency: "weekly" as const, priority: 0.8 }, { path: "/blog/show-hn-launch", lastModified: "2026-02-21", changeFrequency: "monthly" as const, priority: 0.7 }, // ... similar for other entries with new Date() ];Alternatively, use a build-time constant:
const BUILD_DATE = process.env.BUILD_DATE || "2026-03-12";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/app/sitemap.ts` around lines 7 - 22, The paths array currently sets lastModified using new Date(), which makes builds non-deterministic; update the lastModified entries in the paths array (and any other occurrences) to use a static build-time value (e.g., a BUILD_DATE constant sourced from process.env.BUILD_DATE or a fixed ISO date string) so only pages that actually change get a new date; locate the paths constant in sitemap.ts and replace new Date() uses with the BUILD_DATE constant (or explicit static date strings) and ensure BUILD_DATE is defined and used consistently.web/app/[locale]/docs/keyboard-shortcuts/page.tsx (1)
5-9: Static metadata not internationalized.The page content uses translations via
t("title")andt("description"), but themetadataexport remains hardcoded in English. For consistent i18n, consider usinggenerateMetadatato return localized metadata.♻️ Suggested approach using generateMetadata
-export const metadata: Metadata = { - title: "Keyboard Shortcuts", - description: - "All cmux keyboard shortcuts for workspaces, surfaces, split panes, browser, notifications, find, and window management on macOS.", -}; +import { getTranslations } from "next-intl/server"; + +export async function generateMetadata(): Promise<Metadata> { + const t = await getTranslations("docs.keyboardShortcuts"); + return { + title: t("title"), + description: t("metaDescription"), + }; +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/app/`[locale]/docs/keyboard-shortcuts/page.tsx around lines 5 - 9, The exported constant metadata is hardcoded in English; replace it with a localized metadata provider by implementing generateMetadata to return title/description using the existing t(...) translation function instead of the static export; update/remove the export const metadata and add an async function generateMetadata({ params, locale }) that calls your i18n t("title") and t("description") (or the same translation hook used in the page) and returns the localized Metadata object so metadata matches the page translations.web/app/[locale]/docs/api/page.tsx (1)
6-10: Static metadata not internationalized.Same issue as the keyboard-shortcuts page: static
metadataexport while page content uses translations. Consider usinggenerateMetadatafor localized SEO.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/app/`[locale]/docs/api/page.tsx around lines 6 - 10, The exported static constant metadata is not localized; replace the static export (export const metadata) with an async generateMetadata function that uses your i18n/translation helper to produce title and description per locale (matching how the page content is localized), e.g., implement generateMetadata({ params }) to read params.locale and return a Metadata object with translated title/description so SEO/meta tags match localized content; update any imports/types if needed to support async generateMetadata and remove the static metadata export.web/app/[locale]/blog/page.tsx (1)
5-8: Metadata is hardcoded in English while page content is translated.The
titleanddescriptionin metadata remain static English strings, which is inconsistent with the internationalized page content. For full i18n support, consider usinggenerateMetadatato return locale-specific metadata.♻️ Suggested approach using generateMetadata
-export const metadata: Metadata = { - title: "Blog", - description: "News and updates from the cmux team", -}; +import { getTranslations } from "next-intl/server"; + +export async function generateMetadata({ + params: { locale }, +}: { + params: { locale: string }; +}): Promise<Metadata> { + const t = await getTranslations({ locale, namespace: "blog" }); + return { + title: t("title"), + description: t("description"), + }; +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/app/`[locale]/blog/page.tsx around lines 5 - 8, The metadata object is hardcoded in English; replace the static export const metadata with an exported generateMetadata function that returns locale-specific Metadata (e.g., export async function generateMetadata({ params }) ...) and build title/description using the page's i18n lookup (use params.locale or your translation helper) so metadata.title and metadata.description are localized; update any imports or helpers used by the page component (the existing metadata symbol should be removed/replaced and generateMetadata should return the localized title/description).web/app/[locale]/components/blog-pager.tsx (1)
6-11: Consider extracting shared blog slug data to avoid duplication.The
blogSlugsarray here duplicates similar data inweb/app/[locale]/blog/page.tsx(which has separateblogSlugsandslugToPathobjects). Consider consolidating into a shared module (e.g.,blog-posts.ts) to ensure they stay in sync when adding or reordering posts.♻️ Suggested shared module
// web/app/[locale]/components/blog-data.ts export const blogPosts = [ { slug: "cmd-shift-u", key: "cmdShiftU" }, { slug: "zen-of-cmux", key: "zenOfCmux" }, { slug: "show-hn-launch", key: "showHnLaunch" }, { slug: "introducing-cmux", key: "introducingCmux" }, ] as const;Then import this in both
blog-pager.tsxandblog/page.tsx.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/app/`[locale]/components/blog-pager.tsx around lines 6 - 11, Extract the duplicated blog slugs into a single shared constant (e.g., export const blogPosts = [... ] as const) and replace local `blogSlugs` usages with an import of that shared constant in both the `blog-pager` component and the `blog/page` code; also derive the `slugToPath` mapping from this shared `blogPosts` (instead of duplicating it) so `blogSlugs` and `slugToPath` remain in sync—preserve the `as const` typing and update any references to `blogSlugs`/`slugToPath` to use the new exported symbol.web/app/[locale]/components/site-footer.tsx (1)
84-89: Consider memoizing the year value.
new Date().getFullYear()is computed on every render. While negligible in most cases, you could defineyearas a module-level constant since it won't change during a session.♻️ Optional refactor
+"use client"; + +import { useTranslations } from "next-intl"; +import { Link } from "../../../i18n/navigation"; +import { LanguageSwitcher } from "./language-switcher"; + +const CURRENT_YEAR = new Date().getFullYear(); + function isExternal(href: string) { return href.startsWith("http") || href.startsWith("mailto:"); } export function SiteFooter() { const t = useTranslations("footer"); - const year = new Date().getFullYear(); + const year = CURRENT_YEAR;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/app/`[locale]/components/site-footer.tsx around lines 84 - 89, Move the dynamic year computation out of the component render and memoize it as a module-level constant (e.g., const year = new Date().getFullYear()) so new Date().getFullYear() is not called on every render; update the usage in site-footer.tsx where {t("copyright", { year })} is referenced and leave LanguageSwitcher and the component unchanged.web/app/[locale]/docs/concepts/page.tsx (1)
5-9: Static metadata won't be translated across locales.The
metadataexport uses hardcoded English strings. For full i18n support, usegenerateMetadatawithgetTranslationsto provide locale-specific title and description. This pattern is supported by next-intl and allows metadata to adapt to each locale. Other pages in the PR also have this pattern, so this may be intentional for SEO consistency.♻️ Example using generateMetadata for locale-aware metadata
import { getTranslations } from "next-intl/server"; export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> { const { locale } = await params; const t = await getTranslations({ locale, namespace: "docs.concepts" }); return { title: t("title"), description: t("metaDescription"), }; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/app/`[locale]/docs/concepts/page.tsx around lines 5 - 9, Replace the static metadata export with a locale-aware generateMetadata function: remove or replace the exported const metadata and implement an exported async function generateMetadata({ params }) that awaits params.locale, calls getTranslations with the locale and the "docs.concepts" namespace (or appropriate namespace), and returns title and description from the translation keys (e.g., "title" and "metaDescription"); ensure you import getTranslations from "next-intl/server" and keep the returned type as Promise<Metadata> so metadata is generated per-locale.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@web/app/`[locale]/(legal)/eula/page.tsx:
- Around line 10-13: The page currently only localizes the EULA title via
useTranslations("legal") and t("eula") while the EULA body remains hard-coded
English; either remove the translation call for the title and render the title
as plain English to match the English-only body, or (preferred) add translation
keys for the entire EULA body in the "legal" namespace and replace the
hard-coded body with t(...) calls (or a single t("eula_full") that returns the
full localized text) so that useTranslations("legal") and t are used
consistently for both the title and the full document.
In `@web/app/`[locale]/(legal)/privacy-policy/page.tsx:
- Around line 11-15: The page currently uses useTranslations("legal") for the
heading but leaves the body copy and the inline "Terms of Service" link text
hard-coded English; update the component in page.tsx to replace the hard-coded
paragraph and the link text with translation lookups using the t function (e.g.,
t("privacyPolicyLastUpdated"), t("privacyPolicyBody"), and
t("termsOfServiceLinkText") or similar keys) and ensure those keys are added to
the locale resource files for all supported locales so the full body and link
are localized; keep the existing structure and link href but use t(...) for all
user-visible strings in the component (references: useTranslations, t in
page.tsx).
In `@web/app/`[locale]/(legal)/terms-of-service/page.tsx:
- Around line 10-13: The page uses useTranslations("legal") only for the <h1>
(t("termsOfService")) while the Terms of Service body remains hard-coded
English; update the component to source the full TOS content from your
localization system instead of inline English — either replace the hard-coded
text with translation keys accessed via useTranslations (e.g., t("termsBody", {
/* or t("termsParagraph1")... */ })) or load a locale-specific markdown/JSON
file and render it; ensure all text currently in the body is mapped to
translation keys or locale assets and rendered via the same translated-source
(symbols to change: useTranslations, t, and the page component rendering the
body).
In `@web/app/`[locale]/blog/introducing-cmux/page.tsx:
- Around line 53-60: The code is slicing translated strings with t(...).split(':
') (used for featureVerticalTabs, featureNotifications, featureSplitPanes,
featureSocketApi, featureGpu) which breaks non‑English locales; stop parsing
translations. Instead add separate translation keys for label and body (e.g.
featureVerticalTabs_label and featureVerticalTabs_text) or a single rich key
that contains the full <li> (e.g. featureVerticalTabs_full), then render using
t('featureVerticalTabs_label') + t('featureVerticalTabs_text') (or
dangerouslySetInnerHTML/Trans for rich HTML) and replace the split usage in the
list under the featuresTitle header so labels are not hard‑coded in English.
- Around line 5-27: Replace the static export const metadata with a
generateMetadata function that builds metadata per-locale: use the route param
locale passed into generateMetadata and call your i18n navigation helper
getPathname to produce the locale-aware path, then set openGraph.url and
alternates.canonical from that generated full URL; update any mentions of
title/description/keywords to reuse the existing values but ensure openGraph.url
and alternates.canonical are computed dynamically inside generateMetadata so
each locale returns the correct prefixed URL.
In `@web/app/`[locale]/blog/show-hn-launch/page.tsx:
- Around line 8-31: The metadata object currently hardcodes openGraph.url and
alternates.canonical to the English root, which breaks locale-aware canonical
URLs; replace the static export const metadata with an exported async
generateMetadata function that reads params.locale (or awaits params) and builds
baseUrl depending on whether locale === 'en' (no prefix) or includes
`/${locale}`, then return the metadata object with openGraph.url and
alternates.canonical constructed from that baseUrl (keep other fields the same);
update references to openGraph.url and alternates.canonical (and any similar
hardcoded URLs in this file) to use the generated values so non-default locales
produce locale-prefixed canonical and OG URLs.
In `@web/app/`[locale]/components/language-switcher.tsx:
- Around line 35-40: The select element in LanguageSwitcher
(language-switcher.tsx) removes the native focus indicator via the className
containing "focus:outline-none"; restore an accessible visible focus style by
removing that token and adding an explicit focus-visible style (e.g.
"focus-visible:outline" or "focus-visible:ring-2 focus-visible:ring-primary/75"
or similar) so keyboard users see a clear focus ring when using the select;
update the className on the <select> in the LanguageSwitcher component
accordingly.
- Around line 12-15: The locale switcher currently calls
router.replace(pathname) which drops the current query string and hash; update
the onChange handler to preserve them by appending the current URL suffix (query
+ hash) when navigating. Locate the onChange function and build the target URL
using either window.location.search and window.location.hash or derive the
suffix from router.asPath (e.g., router.asPath relative to pathname), then call
router.replace(targetUrl, undefined, { locale: newLocale }) so the query and
fragment are retained; keep references to onChange, router.replace, pathname,
and router.asPath/window.location in your change.
In `@web/app/`[locale]/docs/changelog/page.tsx:
- Around line 242-249: Replace the static export const metadata with a
server-side generateMetadata function that calls getTranslations from
next-intl/server using the incoming locale and returns title and description
using t("docs.changelog.title") and t("docs.changelog.metaDescription"); update
the page to accept locale params where needed. Modify formatDate to accept a
locale argument (instead of hardcoding "en-US") and use new
Intl.DateTimeFormat(locale, ...) inside formatDate; then call formatDate(locale,
date) wherever dates are rendered. Ensure imports include getTranslations and
adjust any uses of export const metadata and formatDate to the new
generateMetadata and locale-aware signature.
In `@web/app/`[locale]/keyboard-shortcuts.tsx:
- Around line 19-107: The ShortcutRow rendering is not passing the optional note
prop so scoped duplicates (e.g., shortcuts with id "ws-rename" and "wn-reload"
both using "⌘⇧R", and "br-open" and "nt-flash" both using "⌘⇧L") appear as
global conflicts; update the component usage where ShortcutRow is rendered (the
place that currently calls ShortcutRow without passing note) to include
note={s.note} (or the equivalent prop from the Shortcut object) so ShortcutRow
can display scoping notes; reference the Shortcut type's optional note field and
the variable s (shortcut) when adding the prop.
- Around line 109-110: The normalize function currently uses toLowerCase(),
which is not locale-aware; update normalize to perform locale-sensitive case
folding by calling toLocaleLowerCase with the active locale (e.g., change
signature to accept a locale param like normalize(s: string, locale: string) or
obtain the current locale from the enclosing scope) and use
s.toLocaleLowerCase(locale).keep the existing whitespace normalization
(.replace(/\s+/g, " ").trim()) unchanged so filtering still works but now
respects locale-specific casing (refer to the normalize function name when
making the change).
In `@web/app/`[locale]/layout.tsx:
- Line 78: The dir calculation currently treats locale "km" as RTL; update the
conditional that sets the dir variable so only Arabic ("ar") is considered RTL
(e.g., const dir = locale === "ar" ? "rtl" : "ltr";), leaving "km" (Khmer) and
all other locales as left-to-right; modify the expression or branching that
assigns dir (the dir variable and its locale check) to remove "km" from RTL
handling.
In `@web/app/`[locale]/page.tsx:
- Around line 108-121: Replace plain anchor elements used for internal docs (the
outer <a href="/docs/keyboard-shortcuts" className={linkClass}> and the nested
anchor returned inside t.rich(...) as well as other plain anchors pointing to
/docs/notifications and /docs/keyboard-shortcuts) with next-intl's locale-aware
Link component so client-side locale is preserved; remove the nested <a> (return
a fragment or a non-interactive span inside t.rich) and pass className to Link
(or to the inner element when using t.rich) so you avoid nested anchors and keep
styling via linkClass, updating the occurrences referenced by linkClass and
t.rich("feature.keyboardShortcutsDesc", ...) and the similar
notification/keyboard shortcut link usages.
In `@web/app/layout.tsx`:
- Around line 1-10: RootLayout currently returns only children; move the
document shell (<html> and <body>) into the RootLayout component so it renders
the top-level HTML document and wraps children, and remove those tags from the
nested locale layout (app/[locale]/layout.tsx). Specifically, update RootLayout
to render an <html> element (with appropriate lang/dir attributes if available)
containing a <body> that wraps children (and keep any global providers/metadata
here), then edit the nested layout (the file that currently renders
<html>/<body> and provides i18n) to remove the <html> and <body> elements so it
only provides locale-specific providers/wrappers (e.g., i18n provider) and
returns its children.
In `@web/i18n/routing.ts`:
- Around line 3-23: The Khmer locale "km" was incorrectly treated as RTL; keep
"km" in the locales constant if desired but remove it from any RTL handling —
locate the locales array (export const locales) and any RTL predicate/collection
(e.g., rtlLocales, isRtl, directionCheck) and ensure the RTL branch only
contains/compares to "ar" (Arabic) rather than "km"; update the RTL check to
explicitly use ['ar'] or locale === 'ar' and run the provided search to confirm
no remaining occurrences of "km" in RTL logic.
In `@web/messages/da.json`:
- Line 107: Two heading strings in the Danish locale are still English: replace
the values "The Zen of cmux" and "Wall of Love" in the da.json entries so they
are translated into Danish; locate the JSON objects where "title": "The Zen of
cmux" and "title": "Wall of Love" appear and update their "title" values to the
correct Danish translations (e.g., "Zen af cmux" or a preferred Danish phrasing,
and "Kærlighedens mur" or preferred localized phrasing) ensuring the quotes and
JSON formatting remain valid.
- Around line 203-233: The Danish file conflates "pane" and "panel" (making both
render as "Panel"); update the affected keys so the two concepts remain distinct
— specifically change paneTitle, paneDesc, paneNote, paneIdSocket to use a
distinct Danish term for "pane" (e.g., "Opdelingspanel" or "Pane" with
clarifying wording) while keeping panelTitle, panelDesc, panelNote,
panelTerminal, panelBrowser, panelIdInternal as the separate "panel" term;
ensure descriptions (paneDesc vs panelDesc) and the ID labels (paneIdSocket vs
panelIdInternal) clearly reflect their different hierarchy levels without
altering other keys.
In `@web/messages/km.json`:
- Around line 1-513: The layout.tsx sets page direction incorrectly by treating
Khmer (locale "km") as RTL; update the logic that computes the dir variable in
web/app/[locale]/layout.tsx (the line assigning dir based on locale) so only
"ar" yields "rtl" and all other locales (including "km") use "ltr". Locate the
const dir = ... expression and remove "km" from the RTL condition so Khmer
renders left-to-right.
In `@web/messages/pl.json`:
- Around line 106-108: The blog title key blog.posts.zenOfCmux.title is still in
English; update its value to a Polish translation so the entry is fully
localized (e.g., change "The Zen of cmux" to an appropriate Polish string) by
editing the JSON entry under "zenOfCmux" (keys: title, summary) and ensure
proper JSON string formatting and escaping.
In `@web/messages/th.json`:
- Around line 2-37: The Thai locale file contains several user-facing strings
still in English (e.g. "viewChangelog", nav key "changelog", footer key
"changelog" and other document titles like "The Zen of cmux" and "Browser
Automation"); update those keys' values to proper Thai translations so the UI is
fully localized—search for and replace occurrences of "Changelog", "The Zen of
cmux", "Browser Automation" (and any other English labels in the ranges
mentioned) with their Thai equivalents in web/messages/th.json and related
entries (preserving the JSON keys like viewChangelog, nav.changelog,
footer.changelog) ensuring placeholders like {year} remain intact.
---
Outside diff comments:
In `@web/app/`[locale]/community/page.tsx:
- Around line 71-81: The GitHub CommunityLink is hardcoded as name="GitHub";
change it to use the i18n helper (name={t("github")}) to match other links
(CommunityLink usage with t for "discord", "twitter", etc.), and ensure the
"github" key exists in the community translation namespace for all locales.
In `@web/app/`[locale]/docs/notifications/page.tsx:
- Around line 46-67: The table contains hard-coded English strings ("Variable",
"Description", "Notification title (workspace name or app name)", "Notification
subtitle", "Notification body text", and other human-readable cells) causing
partial localization; replace these literal strings with translation keys and
call the repository's translation helper (the same i18n/translator used
elsewhere in this page) for each header and cell while keeping the env var names
(CMUX_NOTIFICATION_TITLE, CMUX_NOTIFICATION_SUBTITLE, CMUX_NOTIFICATION_BODY)
and any protocol identifiers literal; apply the same change to the other table
range mentioned (lines around the second table at 107-136) so all visible copy
is pulled from translations rather than hard-coded English.
In `@web/app/`[locale]/wall-of-love/page.tsx:
- Around line 6-10: metadata currently contains hard-coded English
title/description; replace the static export const metadata with a dynamic
generator that returns localized metadata using the same wallOfLove translations
used in the page body. Implement an export async function generateMetadata({
params }) that loads the locale dictionary or translation helper (the same
source used by wallOfLove in the page) and returns a Metadata object with title
and description drawn from the wallOfLove keys; keep the Metadata type and
ensure pages for all locales (including the code currently at lines 13-23) use
the localized strings instead of literals.
---
Nitpick comments:
In `@web/app/`[locale]/blog/page.tsx:
- Around line 5-8: The metadata object is hardcoded in English; replace the
static export const metadata with an exported generateMetadata function that
returns locale-specific Metadata (e.g., export async function generateMetadata({
params }) ...) and build title/description using the page's i18n lookup (use
params.locale or your translation helper) so metadata.title and
metadata.description are localized; update any imports or helpers used by the
page component (the existing metadata symbol should be removed/replaced and
generateMetadata should return the localized title/description).
In `@web/app/`[locale]/components/blog-pager.tsx:
- Around line 6-11: Extract the duplicated blog slugs into a single shared
constant (e.g., export const blogPosts = [... ] as const) and replace local
`blogSlugs` usages with an import of that shared constant in both the
`blog-pager` component and the `blog/page` code; also derive the `slugToPath`
mapping from this shared `blogPosts` (instead of duplicating it) so `blogSlugs`
and `slugToPath` remain in sync—preserve the `as const` typing and update any
references to `blogSlugs`/`slugToPath` to use the new exported symbol.
In `@web/app/`[locale]/components/site-footer.tsx:
- Around line 84-89: Move the dynamic year computation out of the component
render and memoize it as a module-level constant (e.g., const year = new
Date().getFullYear()) so new Date().getFullYear() is not called on every render;
update the usage in site-footer.tsx where {t("copyright", { year })} is
referenced and leave LanguageSwitcher and the component unchanged.
In `@web/app/`[locale]/docs/api/page.tsx:
- Around line 6-10: The exported static constant metadata is not localized;
replace the static export (export const metadata) with an async generateMetadata
function that uses your i18n/translation helper to produce title and description
per locale (matching how the page content is localized), e.g., implement
generateMetadata({ params }) to read params.locale and return a Metadata object
with translated title/description so SEO/meta tags match localized content;
update any imports/types if needed to support async generateMetadata and remove
the static metadata export.
In `@web/app/`[locale]/docs/concepts/page.tsx:
- Around line 5-9: Replace the static metadata export with a locale-aware
generateMetadata function: remove or replace the exported const metadata and
implement an exported async function generateMetadata({ params }) that awaits
params.locale, calls getTranslations with the locale and the "docs.concepts"
namespace (or appropriate namespace), and returns title and description from the
translation keys (e.g., "title" and "metaDescription"); ensure you import
getTranslations from "next-intl/server" and keep the returned type as
Promise<Metadata> so metadata is generated per-locale.
In `@web/app/`[locale]/docs/keyboard-shortcuts/page.tsx:
- Around line 5-9: The exported constant metadata is hardcoded in English;
replace it with a localized metadata provider by implementing generateMetadata
to return title/description using the existing t(...) translation function
instead of the static export; update/remove the export const metadata and add an
async function generateMetadata({ params, locale }) that calls your i18n
t("title") and t("description") (or the same translation hook used in the page)
and returns the localized Metadata object so metadata matches the page
translations.
In `@web/app/sitemap.ts`:
- Around line 7-22: The paths array currently sets lastModified using new
Date(), which makes builds non-deterministic; update the lastModified entries in
the paths array (and any other occurrences) to use a static build-time value
(e.g., a BUILD_DATE constant sourced from process.env.BUILD_DATE or a fixed ISO
date string) so only pages that actually change get a new date; locate the paths
constant in sitemap.ts and replace new Date() uses with the BUILD_DATE constant
(or explicit static date strings) and ensure BUILD_DATE is defined and used
consistently.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: dbd7026b-411c-4e78-a6b6-910ef4418f05
⛔ Files ignored due to path filters (3)
web/app/[locale]/assets/landing-image.pngis excluded by!**/*.pngweb/app/[locale]/blog/show-hn-launch/star-history.pngis excluded by!**/*.pngweb/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (87)
web/app/[locale]/(legal)/eula/page.tsxweb/app/[locale]/(legal)/layout.tsxweb/app/[locale]/(legal)/privacy-policy/page.tsxweb/app/[locale]/(legal)/terms-of-service/page.tsxweb/app/[locale]/assets/images.d.tsweb/app/[locale]/blog/cmd-shift-u/page.tsxweb/app/[locale]/blog/introducing-cmux/page.tsxweb/app/[locale]/blog/layout.tsxweb/app/[locale]/blog/page.tsxweb/app/[locale]/blog/show-hn-launch/page.tsxweb/app/[locale]/blog/zen-of-cmux/page.tsxweb/app/[locale]/community/page.tsxweb/app/[locale]/components/blog-cta.tsxweb/app/[locale]/components/blog-pager.tsxweb/app/[locale]/components/blog-posts.tsweb/app/[locale]/components/callout.tsxweb/app/[locale]/components/code-block.tsxweb/app/[locale]/components/docs-nav-items.tsweb/app/[locale]/components/docs-pager.tsxweb/app/[locale]/components/docs-sidebar.tsxweb/app/[locale]/components/download-button.tsxweb/app/[locale]/components/fade-image.tsxweb/app/[locale]/components/github-button.tsxweb/app/[locale]/components/github-stars.tsxweb/app/[locale]/components/language-switcher.tsxweb/app/[locale]/components/mobile-drawer.tsxweb/app/[locale]/components/nav-links.tsxweb/app/[locale]/components/site-footer.tsxweb/app/[locale]/components/site-header.tsxweb/app/[locale]/components/spacing-control.tsxweb/app/[locale]/docs/api/page.tsxweb/app/[locale]/docs/browser-automation/page.tsxweb/app/[locale]/docs/changelog/changelog-media.tsweb/app/[locale]/docs/changelog/page.tsxweb/app/[locale]/docs/concepts/page.tsxweb/app/[locale]/docs/configuration/page.tsxweb/app/[locale]/docs/docs-nav.tsxweb/app/[locale]/docs/getting-started/page.tsxweb/app/[locale]/docs/keyboard-shortcuts/page.tsxweb/app/[locale]/docs/layout.tsxweb/app/[locale]/docs/notifications/page.tsxweb/app/[locale]/docs/page.tsxweb/app/[locale]/keyboard-shortcuts.tsxweb/app/[locale]/layout.tsxweb/app/[locale]/page.tsxweb/app/[locale]/posthog.tsxweb/app/[locale]/providers.tsxweb/app/[locale]/testimonials.tsxweb/app/[locale]/theme.tsxweb/app/[locale]/typing.tsxweb/app/[locale]/wall-of-love/page.tsxweb/app/blog/introducing-cmux/page.tsxweb/app/blog/page.tsxweb/app/blog/show-hn-launch/page.tsxweb/app/blog/zen-of-cmux/page.tsxweb/app/components/docs-nav-items.tsweb/app/docs/concepts/page.tsxweb/app/docs/getting-started/page.tsxweb/app/keyboard-shortcuts.tsxweb/app/layout.tsxweb/app/page.tsxweb/app/sitemap.tsweb/i18n/navigation.tsweb/i18n/request.tsweb/i18n/routing.tsweb/messages/ar.jsonweb/messages/bs.jsonweb/messages/da.jsonweb/messages/de.jsonweb/messages/en.jsonweb/messages/es.jsonweb/messages/fr.jsonweb/messages/it.jsonweb/messages/ja.jsonweb/messages/km.jsonweb/messages/ko.jsonweb/messages/no.jsonweb/messages/pl.jsonweb/messages/pt-BR.jsonweb/messages/ru.jsonweb/messages/th.jsonweb/messages/tr.jsonweb/messages/zh-CN.jsonweb/messages/zh-TW.jsonweb/middleware.tsweb/next.config.tsweb/package.json
💤 Files with no reviewable changes (9)
- web/app/components/docs-nav-items.ts
- web/app/blog/show-hn-launch/page.tsx
- web/app/keyboard-shortcuts.tsx
- web/app/blog/introducing-cmux/page.tsx
- web/app/docs/concepts/page.tsx
- web/app/blog/zen-of-cmux/page.tsx
- web/app/page.tsx
- web/app/blog/page.tsx
- web/app/docs/getting-started/page.tsx
| 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.
This policy is still only partially localized.
The heading moved to next-intl, but the body copy — including the inline “Terms of Service” link text you just touched — remains hard-coded English. That makes /[locale]/privacy-policy look translated when it is not.
Also applies to: 35-35
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/app/`[locale]/(legal)/privacy-policy/page.tsx around lines 11 - 15, The
page currently uses useTranslations("legal") for the heading but leaves the body
copy and the inline "Terms of Service" link text hard-coded English; update the
component in page.tsx to replace the hard-coded paragraph and the link text with
translation lookups using the t function (e.g., t("privacyPolicyLastUpdated"),
t("privacyPolicyBody"), and t("termsOfServiceLinkText") or similar keys) and
ensure those keys are added to the locale resource files for all supported
locales so the full body and link are localized; keep the existing structure and
link href but use t(...) for all user-visible strings in the component
(references: useTranslations, t in page.tsx).
| const t = useTranslations("legal"); | ||
| return ( | ||
| <> | ||
| <h1>Terms of Service</h1> | ||
| <h1>{t("termsOfService")}</h1> |
There was a problem hiding this comment.
Don’t expose a localized Terms route with English terms.
Only the <h1> is translated here; the actual Terms of Service remain hard-coded English. That makes the locale route look translated when it is not, which is especially risky for legal content.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/app/`[locale]/(legal)/terms-of-service/page.tsx around lines 10 - 13, The
page uses useTranslations("legal") only for the <h1> (t("termsOfService")) while
the Terms of Service body remains hard-coded English; update the component to
source the full TOS content from your localization system instead of inline
English — either replace the hard-coded text with translation keys accessed via
useTranslations (e.g., t("termsBody", { /* or t("termsParagraph1")... */ })) or
load a locale-specific markdown/JSON file and render it; ensure all text
currently in the body is mapped to translation keys or locale assets and
rendered via the same translated-source (symbols to change: useTranslations, t,
and the page component rendering the body).
| "p2": "Cmd+Shift+U springer til den nyeste ulæste <link>notifikation</link>. I praksis betyder det den sidste agent der blev færdig. Den skifter til det rigtige workspace, fokuserer det præcise panel, flasher det så du kan se hvor du skal kigge, og markerer det som læst. Hvis notifikationen kom fra et andet vindue, kommer det vindue forrest." | ||
| }, | ||
| "zenOfCmux": { | ||
| "title": "The Zen of cmux", |
There was a problem hiding this comment.
Finish the remaining English headings.
Line 107 (The Zen of cmux) and Line 507 (Wall of Love) are still English while the surrounding locale is Danish, so those titles will ship untranslated in the Danish UI.
Also applies to: 507-507
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/messages/da.json` at line 107, Two heading strings in the Danish locale
are still English: replace the values "The Zen of cmux" and "Wall of Love" in
the da.json entries so they are translated into Danish; locate the JSON objects
where "title": "The Zen of cmux" and "title": "Wall of Love" appear and update
their "title" values to the correct Danish translations (e.g., "Zen af cmux" or
a preferred Danish phrasing, and "Kærlighedens mur" or preferred localized
phrasing) ensuring the quotes and JSON formatting remain valid.
| "paneTitle": "Panel", | ||
| "paneDesc": "Et opdelt område inden for et workspace. Oprettes ved at opdele med {right} (højre) eller {down} (ned). Navigér mellem paneler med {nav} + piletaster.", | ||
| "paneNote": "Hvert panel kan indeholde flere surfaces (faner inden for panelet).", | ||
| "surfaceTitle": "Surface", | ||
| "surfaceDesc": "En fane inden for et panel. Hvert panel har sin egen fanebjælke og kan indeholde flere surfaces. Oprettes med {new}, navigeres med {prev} / {next} eller {jump}.", | ||
| "surfaceNote": "Surfaces er de individuelle terminal- eller browsersessioner du interagerer med. Hver surface har sin egen CMUX_SURFACE_ID-miljøvariabel.", | ||
| "panelTitle": "Panel", | ||
| "panelDesc": "Indholdet inde i en surface. Aktuelt to typer:", | ||
| "panelTerminal": "Terminal: en Ghostty-terminalsession", | ||
| "panelBrowser": "Browser: en indlejret webvisning", | ||
| "panelNote": "Panel er primært et internt koncept. I socket API og CLI interagerer du med surfaces snarere end paneler direkte.", | ||
| "visualExample": "Visuelt eksempel", | ||
| "visualExampleDesc": "I dette eksempel:", | ||
| "visualItem1": "Vinduet indeholder en sidebar med tre workspaces (dev, server, logs)", | ||
| "visualItem2": "Workspace \"dev\" er valgt og viser to paneler side om side", | ||
| "visualItem3": "Panel 1 har to surfaces ([S1] og [S2] i fanebjælken), med S1 aktiv", | ||
| "visualItem4": "Panel 2 har én surface", | ||
| "visualItem5": "Hver surface indeholder et panel (en terminal i dette tilfælde)", | ||
| "summary": "Oversigt", | ||
| "levelHeader": "Niveau", | ||
| "whatItIsHeader": "Hvad det er", | ||
| "createdByHeader": "Oprettet af", | ||
| "identifiedByHeader": "Identificeret ved", | ||
| "macosWindow": "macOS-vindue", | ||
| "sidebarEntry": "Sidebarpost", | ||
| "splitRegion": "Opdelt område", | ||
| "tabWithinPane": "Fane inden for panel", | ||
| "terminalOrBrowser": "Terminal eller browser", | ||
| "automatic": "Automatisk", | ||
| "paneIdSocket": "Panel-ID (socket API)", | ||
| "panelIdInternal": "Panel-ID (internt)" |
There was a problem hiding this comment.
Keep pane and panel distinct in the concepts section.
Line 203 and Line 209 both render as Panel, and Line 232 and Line 233 both become Panel-ID. The concepts page uses these as different hierarchy levels, so the Danish copy currently collapses an important distinction and makes the docs/API terminology ambiguous.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/messages/da.json` around lines 203 - 233, The Danish file conflates
"pane" and "panel" (making both render as "Panel"); update the affected keys so
the two concepts remain distinct — specifically change paneTitle, paneDesc,
paneNote, paneIdSocket to use a distinct Danish term for "pane" (e.g.,
"Opdelingspanel" or "Pane" with clarifying wording) while keeping panelTitle,
panelDesc, panelNote, panelTerminal, panelBrowser, panelIdInternal as the
separate "panel" term; ensure descriptions (paneDesc vs panelDesc) and the ID
labels (paneIdSocket vs panelIdInternal) clearly reflect their different
hierarchy levels without altering other keys.
| "zenOfCmux": { | ||
| "title": "The Zen of cmux", | ||
| "summary": "cmux to prymityw, nie rozwiązanie. Daje ci komponowalne elementy, a Twój workflow zależy od Ciebie.", |
There was a problem hiding this comment.
Localize this blog title as well.
blog.posts.zenOfCmux.title is still English while the rest of the entry is Polish, so this locale will render mixed-language copy in the blog UI.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/messages/pl.json` around lines 106 - 108, The blog title key
blog.posts.zenOfCmux.title is still in English; update its value to a Polish
translation so the entry is fully localized (e.g., change "The Zen of cmux" to
an appropriate Polish string) by editing the JSON entry under "zenOfCmux" (keys:
title, summary) and ensure proper JSON string formatting and escaping.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a8748f16b2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| <a href="/docs/keyboard-shortcuts" className={linkClass}> | ||
| {t.rich("feature.keyboardShortcutsDesc", { | ||
| link: (chunks) => ( | ||
| <a | ||
| href="/docs/keyboard-shortcuts" |
There was a problem hiding this comment.
Remove nested anchor in keyboard shortcuts feature text
t.rich("feature.keyboardShortcutsDesc", …) already renders a <link>…</link> placeholder as an anchor, but this block wraps that output in another <a>; this creates invalid nested anchors on the homepage for every locale, which can trigger React DOM nesting warnings and unreliable click behavior in browsers. It also duplicates punctuation because the message itself already includes the leading :.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@web/app/`[locale]/page.tsx:
- Around line 13-19: The Home component declares params but never awaits or uses
it; to enable SSG locale support call setRequestLocale with the resolved locale
before rendering. Update the Home function to await params (or accept it as an
awaited value), extract locale, call setRequestLocale(locale) (from next-intl)
and then return <HomeContent />; reference the Home component, its params
parameter, and setRequestLocale so the locale is set at build time for static
generation.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d5238d3f-dbb5-4751-845f-0cba5e690cc0
⛔ Files ignored due to path filters (1)
web/bun.lockis excluded by!**/*.lock
📒 Files selected for processing (1)
web/app/[locale]/page.tsx
| export default function Home({ | ||
| params, | ||
| }: { | ||
| params: Promise<{ locale: string }>; | ||
| }) { | ||
| return <HomeContent />; | ||
| } |
There was a problem hiding this comment.
Call setRequestLocale for SSG support.
The params prop is declared but never used. With next-intl v4, you must await the params and call setRequestLocale(locale) to enable static generation of locale-specific pages. Without this, next-intl cannot determine the locale during build time.
🛠️ Proposed fix
-export default function Home({
+export default async function Home({
params,
}: {
params: Promise<{ locale: string }>;
}) {
+ const { locale } = await params;
+ setRequestLocale(locale);
return <HomeContent />;
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/app/`[locale]/page.tsx around lines 13 - 19, The Home component declares
params but never awaits or uses it; to enable SSG locale support call
setRequestLocale with the resolved locale before rendering. Update the Home
function to await params (or accept it as an awaited value), extract locale,
call setRequestLocale(locale) (from next-intl) and then return <HomeContent />;
reference the Home component, its params parameter, and setRequestLocale so the
locale is set at build time for static generation.
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (4)
web/messages/th.json (1)
10-35:⚠️ Potential issue | 🟡 MinorFinish localizing the remaining Thai strings.
This catalog still exposes several non-brand English labels in the Thai locale, including
Changelog,The Zen of cmux,Browser Automation, and multiple subsection headings underdocs.browserAutomation. Those will surface as mixed-language UI in nav, footer, blog, and docs.Also applies to: 106-107, 447-499
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/messages/th.json` around lines 10 - 35, Several UI strings in the Thai locale remain untranslated (e.g., keys like "viewChangelog", "nav.changelog", footer keys "changelog", "blog", "github"/"twitter"/"discord" labels, and doc headings under docs.browserAutomation such as "The Zen of cmux" and "Browser Automation"), causing mixed-language UI; update the Thai JSON entries for those keys and any referenced subsection headings (including the repeated ranges noted) with proper Thai translations, ensuring consistency for nav.changelog, footer.changelog, viewChangelog, blog, community, docs.browserAutomation.* and the specific titles "The Zen of cmux" and "Browser Automation" so the UI is fully localized.web/app/[locale]/page.tsx (2)
13-18:⚠️ Potential issue | 🟠 MajorUse
setRequestLocale(locale)before rendering.
paramsis never awaited and the importedsetRequestLocalestays unused, so this page still isn't setting the request locale for locale-specific SSG.Suggested fix
-export default function Home({ +export default async function Home({ params, }: { params: Promise<{ locale: string }>; }) { + const { locale } = await params; + setRequestLocale(locale); return <HomeContent />; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/app/`[locale]/page.tsx around lines 13 - 18, The Home page never awaits the params Promise nor calls the imported setRequestLocale, so extract the locale by awaiting params in the Home function (params: Promise<{locale:string}>), then call setRequestLocale(locale) before rendering; i.e., await params to get {locale}, invoke setRequestLocale(locale), and only then return <HomeContent /> so SSG uses the correct request locale.
108-121:⚠️ Potential issue | 🟠 MajorUse the locale-aware
Linkfor internal docs links and remove the extra separator.These
/docs/...anchors still bypass the next-intl navigation wrapper, and Lines 110-121 still render an<a>inside another<a>. Also,feature.keyboardShortcutsDescalready includes the leading:, so the hardcoded separator here duplicates punctuation across locales.Suggested fix
- <span className="text-muted"> - :{" "} - <a href="/docs/keyboard-shortcuts" className={linkClass}> - {t.rich("feature.keyboardShortcutsDesc", { - link: (chunks) => ( - <a - href="/docs/keyboard-shortcuts" - className={linkClass} - > - {chunks} - </a> - ), - })} - </a> - </span> + <span className="text-muted"> + {t.rich("feature.keyboardShortcutsDesc", { + link: (chunks) => ( + <Link + href="/docs/keyboard-shortcuts" + className={linkClass} + > + {chunks} + </Link> + ), + })} + </span>- cliLink: (chunks) => ( - <a href="/docs/notifications" className={linkClass}> - {chunks} - </a> - ), - hooksLink: (chunks) => ( - <a href="/docs/notifications" className={linkClass}> - {chunks} - </a> - ), + cliLink: (chunks) => ( + <Link href="/docs/notifications" className={linkClass}> + {chunks} + </Link> + ), + hooksLink: (chunks) => ( + <Link href="/docs/notifications" className={linkClass}> + {chunks} + </Link> + ),- link: (chunks) => ( - <a href="/docs/keyboard-shortcuts" className={linkClass}> - {chunks} - </a> - ), + link: (chunks) => ( + <Link + href="/docs/keyboard-shortcuts" + className={linkClass} + > + {chunks} + </Link> + ),Also applies to: 177-205
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/app/`[locale]/page.tsx around lines 108 - 121, Replace the plain anchors with the locale-aware Link and remove the duplicated separator and nested anchors: remove the hardcoded ": " before t.rich (since feature.keyboardShortcutsDesc already contains the punctuation), change the outer <a href="/docs/keyboard-shortcuts" className={linkClass}> to a locale-aware <Link href="/docs/keyboard-shortcuts" className={linkClass}>, and update the t.rich link renderer so it returns a single <Link href="/docs/keyboard-shortcuts" className={linkClass}>{chunks}</Link> instead of an inner <a>; apply the same edits to the other occurrence noted (the block around lines 177-205). Use the existing linkClass and t.rich identifiers to locate the elements in page.tsx.web/messages/pl.json (1)
106-108:⚠️ Potential issue | 🟡 MinorTranslate
zenOfCmux.titletoo.This entry is still English, so the Polish blog card/page will render mixed-language copy.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/messages/pl.json` around lines 106 - 108, The title for the localization key zenOfCmux.title is still in English; update the value of zenOfCmux.title in the JSON to a proper Polish translation (matching the existing Polish summary), e.g., replace "The Zen of cmux" with an appropriate Polish string so the blog card/page no longer mixes languages.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@web/messages/de.json`:
- Around line 106-108: The title key zenOfCmux.title is still in English; update
its value to a German translation so the UI is fully localized (e.g., replace
"The Zen of cmux" with an appropriate German string such as "Die Zen von cmux"
or better "Das Zen von cmux" or another natural German phrasing) by editing the
zenOfCmux.title entry in the de.json translations.
In `@web/messages/en.json`:
- Around line 109-110: Update the English source string zenOfCmux.p1 to fix the
typo: change "how developers hold their tools" to "how developers use their
tools" (i.e., edit the value for the "p1" key under "zenOfCmux" so it reads
"...about how developers use their tools.").
In `@web/messages/es.json`:
- Around line 106-108: The translation for zenOfCmux.title is still in English;
update the value for the key "zenOfCmux.title" in the Spanish messages to a
Spanish translation (for example "El zen de cmux" or another appropriate
localized title) so the blog entry is fully localized and consistent with
"zenOfCmux.summary".
In `@web/messages/ja.json`:
- Around line 106-108: The JSON key "zenOfCmux" has an English "title" while its
"summary" and body are Japanese; update the "title" value for zenOfCmux to a
proper Japanese translation to avoid mixed-language rendering (locate the
"zenOfCmux" object and replace the "title" string with the Japanese equivalent).
---
Duplicate comments:
In `@web/app/`[locale]/page.tsx:
- Around line 13-18: The Home page never awaits the params Promise nor calls the
imported setRequestLocale, so extract the locale by awaiting params in the Home
function (params: Promise<{locale:string}>), then call setRequestLocale(locale)
before rendering; i.e., await params to get {locale}, invoke
setRequestLocale(locale), and only then return <HomeContent /> so SSG uses the
correct request locale.
- Around line 108-121: Replace the plain anchors with the locale-aware Link and
remove the duplicated separator and nested anchors: remove the hardcoded ": "
before t.rich (since feature.keyboardShortcutsDesc already contains the
punctuation), change the outer <a href="/docs/keyboard-shortcuts"
className={linkClass}> to a locale-aware <Link href="/docs/keyboard-shortcuts"
className={linkClass}>, and update the t.rich link renderer so it returns a
single <Link href="/docs/keyboard-shortcuts"
className={linkClass}>{chunks}</Link> instead of an inner <a>; apply the same
edits to the other occurrence noted (the block around lines 177-205). Use the
existing linkClass and t.rich identifiers to locate the elements in page.tsx.
In `@web/messages/pl.json`:
- Around line 106-108: The title for the localization key zenOfCmux.title is
still in English; update the value of zenOfCmux.title in the JSON to a proper
Polish translation (matching the existing Polish summary), e.g., replace "The
Zen of cmux" with an appropriate Polish string so the blog card/page no longer
mixes languages.
In `@web/messages/th.json`:
- Around line 10-35: Several UI strings in the Thai locale remain untranslated
(e.g., keys like "viewChangelog", "nav.changelog", footer keys "changelog",
"blog", "github"/"twitter"/"discord" labels, and doc headings under
docs.browserAutomation such as "The Zen of cmux" and "Browser Automation"),
causing mixed-language UI; update the Thai JSON entries for those keys and any
referenced subsection headings (including the repeated ranges noted) with proper
Thai translations, ensuring consistency for nav.changelog, footer.changelog,
viewChangelog, blog, community, docs.browserAutomation.* and the specific titles
"The Zen of cmux" and "Browser Automation" so the UI is fully localized.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a31e3438-a7a8-49b8-a93f-638d2a16263d
📒 Files selected for processing (20)
web/app/[locale]/page.tsxweb/messages/ar.jsonweb/messages/bs.jsonweb/messages/da.jsonweb/messages/de.jsonweb/messages/en.jsonweb/messages/es.jsonweb/messages/fr.jsonweb/messages/it.jsonweb/messages/ja.jsonweb/messages/km.jsonweb/messages/ko.jsonweb/messages/no.jsonweb/messages/pl.jsonweb/messages/pt-BR.jsonweb/messages/ru.jsonweb/messages/th.jsonweb/messages/tr.jsonweb/messages/zh-CN.jsonweb/messages/zh-TW.json
✅ Files skipped from review due to trivial changes (2)
- web/messages/it.json
- web/messages/ru.json
🚧 Files skipped from review as they are similar to previous changes (6)
- web/messages/zh-TW.json
- web/messages/fr.json
- web/messages/zh-CN.json
- web/messages/ar.json
- web/messages/bs.json
- web/messages/tr.json
| "date": "February 27, 2026", | ||
| "p1": "cmux is not prescriptive about how developers hold their tools. It's a terminal and browser with a CLI, and the rest is up to you.", |
There was a problem hiding this comment.
Fix the typo in zenOfCmux.p1.
“how developers hold their tools” reads wrong in the English source text; this likely meant “use their tools.”
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/messages/en.json` around lines 109 - 110, Update the English source
string zenOfCmux.p1 to fix the typo: change "how developers hold their tools" to
"how developers use their tools" (i.e., edit the value for the "p1" key under
"zenOfCmux" so it reads "...about how developers use their tools.").
There was a problem hiding this comment.
💡 Codex Review
https://github.com/manaflow-ai/cmux/blob/295c4f9e37198c53897045dd6b8753e735bd105a/web/app/[locale]/docs/page.tsx#L4
Preserve locale when redirecting docs index
Inside the localized docs route, redirecting to absolute /docs/getting-started drops the current locale from /<locale>/docs requests. This forces an extra middleware round-trip for non-default languages and can fall back to English when locale cookies are unavailable, so users entering a localized docs index may be sent to the default-language docs instead of staying in their selected locale.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| export default createMiddleware(routing); | ||
|
|
||
| export const config = { | ||
| matcher: ["/((?!api|_next|_vercel|.*\\..*).*)"], |
There was a problem hiding this comment.
Exclude PostHog proxy routes from locale middleware
The matcher currently internationalizes every non-file path, which includes /cmuxterm/*; however, web/next.config.ts depends on /cmuxterm/:path* rewrites to forward analytics traffic to PostHog. For non-default locales, middleware will redirect endpoints like /cmuxterm/e/ to /<locale>/cmuxterm/e/, and that prefixed path no longer matches the rewrite, so event ingestion requests can 404 and analytics/experiment data is lost for localized sessions.
Useful? React with 👍 / 👎.
…eholders
Changed {legacy}, {openShortcut}, {jumpShortcut} from plain variable
interpolation to <tag>content</tag> format so t.rich() gets proper
functions instead of values.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
web/app/[locale]/docs/notifications/page.tsx (2)
46-75:⚠️ Potential issue | 🟡 MinorSeveral labels in this page are still hard-coded English.
The custom-command table, the “Examples” title, and the OSC comparison rows remain English, so non-English locales will still see mixed-language docs in the middle of an otherwise translated page.
Also applies to: 107-137
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/app/`[locale]/docs/notifications/page.tsx around lines 46 - 75, The table headers and cell labels (the <th>Variable</th>, <th>Description</th> and the table rows showing CMUX_NOTIFICATION_TITLE/SUBTITLE/BODY) plus the CodeBlock title="Examples" and the OSC comparison rows (later in this file) are hard‑coded in English; replace these literal strings with calls to the existing i18n/translation helper used in this file (e.g., the getDictionary/t function or <Trans> wrapper used elsewhere in page.tsx) so the table headers, cell descriptions, the CodeBlock title, and the OSC comparison labels are rendered from the locale dictionary; update the strings keys and add corresponding entries to the locale JSONs as needed and ensure CodeBlock title prop receives the translated value (refer to the CodeBlock component and the table markup in page.tsx to locate each place to change).
6-10:⚠️ Potential issue | 🟠 MajorLocalize the metadata export for this route.
The page uses
useTranslations("docs.notifications")for visible content, but the static metadata at lines 6-10 remains hardcoded in English. Non-English routes will therefore display English<title>and description in the browser despite locale catalogs already containing localized values fordocs.notifications.titleanddocs.notifications.metaDescription.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/app/`[locale]/docs/notifications/page.tsx around lines 6 - 10, Replace the hardcoded export const metadata: Metadata with a locale-aware metadata generator: implement an async generateMetadata({ params }) that reads the route locale (params.locale) and resolves the localized strings for docs.notifications.title and docs.notifications.metaDescription (the same keys used by useTranslations("docs.notifications")), then return a Metadata object with those values; update any imports to use the server-side i18n/translator utility you use for other routes so the browser title and description are localized per-locale.web/app/[locale]/docs/api/page.tsx (1)
6-10:⚠️ Potential issue | 🟠 MajorLocalize metadata in all docs pages using
generateMetadata.The page body uses
useTranslations("docs.api"), but the exportedmetadataobject at lines 6-10 is hard-coded English. Non-English routes will render with English<title>and description in the HTML head, even though locale catalogs provide localizeddocs.api.titleanddocs.api.metaDescription(and equivalent strings for other docs sections).Replace the static
metadataexport with agenerateMetadatafunction that retrieves localized strings from the appropriate locale catalog. The same issue affects all other docs pages (api,browser-automation,changelog,concepts,configuration,notifications,keyboard-shortcuts,getting-started).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/app/`[locale]/docs/api/page.tsx around lines 6 - 10, The exported static metadata object (export const metadata) must be replaced with an async generateMetadata function that loads the locale-specific translations and returns localized title and description; specifically, remove export const metadata and implement export async function generateMetadata({ params }) { const t = await getTranslatorForLocale(params.locale) } (or use the same i18n helper used by useTranslations("docs.api")) and return { title: t("docs.api.title"), description: t("docs.api.metaDescription") } so the HTML head uses docs.api.title and docs.api.metaDescription for the current locale; apply the same pattern to the other docs pages (browser-automation, changelog, concepts, configuration, notifications, keyboard-shortcuts, getting-started).
♻️ Duplicate comments (5)
web/messages/es.json (1)
107-107:⚠️ Potential issue | 🟡 MinorTranslate
zenOfCmux.titlein the Spanish catalog.The summary and body are Spanish, but the title stays English, so this post renders mixed-language copy.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/messages/es.json` at line 107, The Spanish messages file contains an untranslated title for "zenOfCmux.title" — update the value for the "title" key under zenOfCmux in web/messages/es.json to its Spanish equivalent (e.g., translate "The Zen of cmux" into Spanish) so the title matches the existing Spanish summary/body; ensure the JSON string value is properly escaped and preserves the key "zenOfCmux.title".web/messages/ja.json (1)
107-107:⚠️ Potential issue | 🟡 MinorLocalize the
zenOfCmuxtitle as well.The summary and body are Japanese, but the title is still English, so this post renders mixed-language copy.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/messages/ja.json` at line 107, The "zenOfCmux" entry has its "title" still in English while "summary" and "body" are Japanese; update the zenOfCmux -> title value in the ja.json localization to a proper Japanese translation to avoid mixed-language rendering and keep the key name "zenOfCmux" and "title" unchanged.web/messages/en.json (1)
109-110:⚠️ Potential issue | 🟡 MinorFix the typo in
zenOfCmux.p1.“hold their tools” reads wrong in the source copy and should be “use their tools”.
✏️ Proposed fix
- "p1": "cmux is not prescriptive about how developers hold their tools. It's a terminal and browser with a CLI, and the rest is up to you.", + "p1": "cmux is not prescriptive about how developers use their tools. It's a terminal and browser with a CLI, and the rest is up to you.",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/messages/en.json` around lines 109 - 110, Update the translation string for zenOfCmux.p1 by replacing the incorrect phrase "hold their tools" with "use their tools" so the value of the "p1" key reads: "cmux is not prescriptive about how developers use their tools. It's a terminal and browser with a CLI, and the rest is up to you." Locate the "p1" key under zenOfCmux (or the matching JSON entry containing that sentence) and make the word substitution.web/messages/da.json (2)
203-233:⚠️ Potential issue | 🟠 MajorKeep
paneandpaneldistinct in the Danish concepts glossary.Both hierarchy levels render as
Panel, and both IDs becomePanel-ID. That collapses a real docs/API distinction and makes the concepts page ambiguous.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/messages/da.json` around lines 203 - 233, The Danish translation conflates the terms "pane" and "panel" making both display as "Panel" and both IDs as "Panel-ID"; update the entries to preserve the distinction by choosing distinct Danish terms (e.g., keep "pane" => "Panel" and translate "panel" => "Underpanel" or similar) and adjust ID labels accordingly so "paneIdSocket" and "panelIdInternal" reflect different names; modify the values for paneTitle, panelTitle, paneDesc, panelDesc, paneNote, panelNote, paneIdSocket, and panelIdInternal to use the distinct Danish words consistently across descriptions.
107-107:⚠️ Potential issue | 🟡 MinorTranslate the remaining English headings.
blog.posts.zenOfCmux.titleandwallOfLove.titleare still English, so the Danish locale will ship mixed-language headings in those views.Also applies to: 507-507
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/messages/da.json` at line 107, The Danish locale still contains English headings: update the locale keys "blog.posts.zenOfCmux.title" and "wallOfLove.title" to Danish strings so the views are fully localized; locate those keys in the translations file and replace the English values ("The Zen of cmux" and the current "wallOfLove" title) with appropriate Danish translations (e.g., translate to "Zen af cmux" or another correct Danish phrasing) ensuring the JSON remains valid.
🧹 Nitpick comments (1)
web/messages/it.json (1)
203-205: Use distinct Italian terms forPaneandPanel.Line 203 translates
PaneasPannello, but Lines 209-213 then keepPanel/panelin English. On this concepts page those are different objects, so the current wording makes the hierarchy harder to follow and weakens the API/docs mapping.💡 Example wording to keep the two concepts separate
- "paneTitle": "Pannello", + "paneTitle": "Riquadro", ... - "panelTitle": "Panel", + "panelTitle": "Pannello", ... - "paneIdSocket": "ID pannello (API socket)", + "paneIdSocket": "ID riquadro (API socket)", - "panelIdInternal": "ID panel (interno)" + "panelIdInternal": "ID pannello (interno)"Also applies to: 209-213, 220-233
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/messages/it.json` around lines 203 - 205, Summary: Use distinct Italian terms for the two concepts so "Pane" and "Panel" don't collapse into the same word. Fix: in the JSON entries related to the Pane concept (keys paneTitle, paneDesc, paneNote) replace the current translation "Pannello" with a distinct Italian term (e.g., use "Riquadro" or "Area del pannello" for Pane) and ensure all other keys that represent the separate Panel concept keep "Pannello" (or another consistently chosen term for Panel); update all occurrences of pane* and any other strings referring to Pane across the file (also where panel strings appear) so the two concepts use different, consistent Italian terms and preserve the original placeholders like {right}, {down}, {nav}.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@web/messages/es.json`:
- Line 507: The JSON value for wallOfLove.title is still in English; update the
"wallOfLove.title" entry in web/messages/es.json to its Spanish translation
(e.g., "Muro de amor" or your approved localized string) so the Spanish locale
no longer mixes languages and matches other localized keys like wallOfLove.*.
- Around line 203-233: The Spanish translations collapse two distinct
concepts—pane and panel—into the same words; update the JSON so keys referencing
the container region (paneTitle, paneDesc, paneNote, paneIdSocket) use a
distinct term (e.g., "Región" or "Panel dividido") and keys referencing the
content unit (panelTitle, panelDesc, panelTerminal, panelBrowser, panelNote,
panelIdInternal) use a different term (e.g., "Panel" or "Contenido de panel");
ensure pane* and panel* keys use consistent, different Spanish strings
throughout so the hierarchy remains unambiguous.
In `@web/messages/pt-BR.json`:
- Around line 209-233: The Portuguese text mixes English fragments for the
borrowed term "Panel" and inconsistent uses of "panel"/"painel"; update the
strings to be fully natural Portuguese while keeping "Panel" as the distinct
borrowed term (and explain it once), by editing keys like "panelTitle",
"panelDesc", "panelTerminal", "panelBrowser", "panelNote", "panelNote",
"visualItem5", and "panelIdInternal" to (1) introduce "Panel" as a borrowed term
in Portuguese (e.g. "Panel (termo emprestado, distinto de 'painel')"), (2)
translate remaining English fragments and plurals into Portuguese (e.g. replace
"panels" with "Panels" or "Panel(s)" consistently and render surrounding
verbs/nouns in Portuguese), and (3) make "panelIdInternal" a Portuguese label
that clarifies it's an internal ID for a Panel; ensure capitalization and
pluralization of "Panel" are consistent across these keys.
---
Outside diff comments:
In `@web/app/`[locale]/docs/api/page.tsx:
- Around line 6-10: The exported static metadata object (export const metadata)
must be replaced with an async generateMetadata function that loads the
locale-specific translations and returns localized title and description;
specifically, remove export const metadata and implement export async function
generateMetadata({ params }) { const t = await
getTranslatorForLocale(params.locale) } (or use the same i18n helper used by
useTranslations("docs.api")) and return { title: t("docs.api.title"),
description: t("docs.api.metaDescription") } so the HTML head uses
docs.api.title and docs.api.metaDescription for the current locale; apply the
same pattern to the other docs pages (browser-automation, changelog, concepts,
configuration, notifications, keyboard-shortcuts, getting-started).
In `@web/app/`[locale]/docs/notifications/page.tsx:
- Around line 46-75: The table headers and cell labels (the <th>Variable</th>,
<th>Description</th> and the table rows showing
CMUX_NOTIFICATION_TITLE/SUBTITLE/BODY) plus the CodeBlock title="Examples" and
the OSC comparison rows (later in this file) are hard‑coded in English; replace
these literal strings with calls to the existing i18n/translation helper used in
this file (e.g., the getDictionary/t function or <Trans> wrapper used elsewhere
in page.tsx) so the table headers, cell descriptions, the CodeBlock title, and
the OSC comparison labels are rendered from the locale dictionary; update the
strings keys and add corresponding entries to the locale JSONs as needed and
ensure CodeBlock title prop receives the translated value (refer to the
CodeBlock component and the table markup in page.tsx to locate each place to
change).
- Around line 6-10: Replace the hardcoded export const metadata: Metadata with a
locale-aware metadata generator: implement an async generateMetadata({ params })
that reads the route locale (params.locale) and resolves the localized strings
for docs.notifications.title and docs.notifications.metaDescription (the same
keys used by useTranslations("docs.notifications")), then return a Metadata
object with those values; update any imports to use the server-side
i18n/translator utility you use for other routes so the browser title and
description are localized per-locale.
---
Duplicate comments:
In `@web/messages/da.json`:
- Around line 203-233: The Danish translation conflates the terms "pane" and
"panel" making both display as "Panel" and both IDs as "Panel-ID"; update the
entries to preserve the distinction by choosing distinct Danish terms (e.g.,
keep "pane" => "Panel" and translate "panel" => "Underpanel" or similar) and
adjust ID labels accordingly so "paneIdSocket" and "panelIdInternal" reflect
different names; modify the values for paneTitle, panelTitle, paneDesc,
panelDesc, paneNote, panelNote, paneIdSocket, and panelIdInternal to use the
distinct Danish words consistently across descriptions.
- Line 107: The Danish locale still contains English headings: update the locale
keys "blog.posts.zenOfCmux.title" and "wallOfLove.title" to Danish strings so
the views are fully localized; locate those keys in the translations file and
replace the English values ("The Zen of cmux" and the current "wallOfLove"
title) with appropriate Danish translations (e.g., translate to "Zen af cmux" or
another correct Danish phrasing) ensuring the JSON remains valid.
In `@web/messages/en.json`:
- Around line 109-110: Update the translation string for zenOfCmux.p1 by
replacing the incorrect phrase "hold their tools" with "use their tools" so the
value of the "p1" key reads: "cmux is not prescriptive about how developers use
their tools. It's a terminal and browser with a CLI, and the rest is up to you."
Locate the "p1" key under zenOfCmux (or the matching JSON entry containing that
sentence) and make the word substitution.
In `@web/messages/es.json`:
- Line 107: The Spanish messages file contains an untranslated title for
"zenOfCmux.title" — update the value for the "title" key under zenOfCmux in
web/messages/es.json to its Spanish equivalent (e.g., translate "The Zen of
cmux" into Spanish) so the title matches the existing Spanish summary/body;
ensure the JSON string value is properly escaped and preserves the key
"zenOfCmux.title".
In `@web/messages/ja.json`:
- Line 107: The "zenOfCmux" entry has its "title" still in English while
"summary" and "body" are Japanese; update the zenOfCmux -> title value in the
ja.json localization to a proper Japanese translation to avoid mixed-language
rendering and keep the key name "zenOfCmux" and "title" unchanged.
---
Nitpick comments:
In `@web/messages/it.json`:
- Around line 203-205: Summary: Use distinct Italian terms for the two concepts
so "Pane" and "Panel" don't collapse into the same word. Fix: in the JSON
entries related to the Pane concept (keys paneTitle, paneDesc, paneNote) replace
the current translation "Pannello" with a distinct Italian term (e.g., use
"Riquadro" or "Area del pannello" for Pane) and ensure all other keys that
represent the separate Panel concept keep "Pannello" (or another consistently
chosen term for Panel); update all occurrences of pane* and any other strings
referring to Pane across the file (also where panel strings appear) so the two
concepts use different, consistent Italian terms and preserve the original
placeholders like {right}, {down}, {nav}.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 6d9422dc-903b-4e57-b253-96385d8eefe1
📒 Files selected for processing (21)
web/app/[locale]/docs/api/page.tsxweb/app/[locale]/docs/notifications/page.tsxweb/messages/ar.jsonweb/messages/bs.jsonweb/messages/da.jsonweb/messages/de.jsonweb/messages/en.jsonweb/messages/es.jsonweb/messages/fr.jsonweb/messages/it.jsonweb/messages/ja.jsonweb/messages/km.jsonweb/messages/ko.jsonweb/messages/no.jsonweb/messages/pl.jsonweb/messages/pt-BR.jsonweb/messages/ru.jsonweb/messages/th.jsonweb/messages/tr.jsonweb/messages/zh-CN.jsonweb/messages/zh-TW.json
✅ Files skipped from review due to trivial changes (2)
- web/messages/zh-TW.json
- web/messages/tr.json
🚧 Files skipped from review as they are similar to previous changes (10)
- web/messages/bs.json
- web/messages/de.json
- web/messages/pl.json
- web/messages/zh-CN.json
- web/messages/km.json
- web/messages/ko.json
- web/messages/th.json
- web/messages/ru.json
- web/messages/fr.json
- web/messages/ar.json
| "paneTitle": "Panel", | ||
| "paneDesc": "Una región dividida dentro de un workspace. Se crea dividiendo con {right} (derecha) o {down} (abajo). Navegue entre paneles con {nav} + teclas de flecha.", | ||
| "paneNote": "Cada panel puede contener múltiples superficies (pestañas dentro del panel).", | ||
| "surfaceTitle": "Superficie", | ||
| "surfaceDesc": "Una pestaña dentro de un panel. Cada panel tiene su propia barra de pestañas y puede contener múltiples superficies. Se crea con {new}, se navega con {prev} / {next} o {jump}.", | ||
| "surfaceNote": "Las superficies son las sesiones individuales de terminal o navegador con las que interactúa. Cada superficie tiene su propia variable de entorno CMUX_SURFACE_ID.", | ||
| "panelTitle": "Panel", | ||
| "panelDesc": "El contenido dentro de una superficie. Actualmente dos tipos:", | ||
| "panelTerminal": "Terminal: una sesión de terminal Ghostty", | ||
| "panelBrowser": "Navegador: una vista web integrada", | ||
| "panelNote": "Panel es principalmente un concepto interno. En la API de socket y la CLI, interactúa con superficies en lugar de directamente con paneles.", | ||
| "visualExample": "Ejemplo visual", | ||
| "visualExampleDesc": "En este ejemplo:", | ||
| "visualItem1": "La ventana contiene una barra lateral con tres workspaces (dev, server, logs)", | ||
| "visualItem2": "El workspace \"dev\" está seleccionado, mostrando dos paneles lado a lado", | ||
| "visualItem3": "El panel 1 tiene dos superficies ([S1] y [S2] en la barra de pestañas), con S1 activa", | ||
| "visualItem4": "El panel 2 tiene una superficie", | ||
| "visualItem5": "Cada superficie contiene un panel (una terminal en este caso)", | ||
| "summary": "Resumen", | ||
| "levelHeader": "Nivel", | ||
| "whatItIsHeader": "Qué es", | ||
| "createdByHeader": "Creado por", | ||
| "identifiedByHeader": "Identificado por", | ||
| "macosWindow": "Ventana de macOS", | ||
| "sidebarEntry": "Entrada en la barra lateral", | ||
| "splitRegion": "Región dividida", | ||
| "tabWithinPane": "Pestaña dentro del panel", | ||
| "terminalOrBrowser": "Terminal o navegador", | ||
| "automatic": "Automático", | ||
| "paneIdSocket": "ID de panel (API de socket)", | ||
| "panelIdInternal": "ID de panel (interno)" |
There was a problem hiding this comment.
Don’t collapse pane and panel into the same Spanish term.
paneTitle/panelTitle both become Panel, and paneIdSocket/panelIdInternal both become ID de panel. The concepts docs rely on those being separate hierarchy levels.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/messages/es.json` around lines 203 - 233, The Spanish translations
collapse two distinct concepts—pane and panel—into the same words; update the
JSON so keys referencing the container region (paneTitle, paneDesc, paneNote,
paneIdSocket) use a distinct term (e.g., "Región" or "Panel dividido") and keys
referencing the content unit (panelTitle, panelDesc, panelTerminal,
panelBrowser, panelNote, panelIdInternal) use a different term (e.g., "Panel" or
"Contenido de panel"); ensure pane* and panel* keys use consistent, different
Spanish strings throughout so the hierarchy remains unambiguous.
| "eula": "EULA" | ||
| }, | ||
| "wallOfLove": { | ||
| "title": "Wall of Love", |
There was a problem hiding this comment.
Localize wallOfLove.title too.
This header is still English, so the Spanish page will ship mixed-language UI.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/messages/es.json` at line 507, The JSON value for wallOfLove.title is
still in English; update the "wallOfLove.title" entry in web/messages/es.json to
its Spanish translation (e.g., "Muro de amor" or your approved localized string)
so the Spanish locale no longer mixes languages and matches other localized keys
like wallOfLove.*.
| "panelTitle": "Panel", | ||
| "panelDesc": "O conteúdo dentro de uma superfície. Atualmente dois tipos:", | ||
| "panelTerminal": "Terminal: uma sessão de terminal Ghostty", | ||
| "panelBrowser": "Navegador: uma visualização web integrada", | ||
| "panelNote": "Panel é majoritariamente um conceito interno. Na API de socket e CLI, você interage com superfícies em vez de panels diretamente.", | ||
| "visualExample": "Exemplo visual", | ||
| "visualExampleDesc": "Neste exemplo:", | ||
| "visualItem1": "A janela contém uma barra lateral com três workspaces (dev, server, logs)", | ||
| "visualItem2": "O workspace \"dev\" está selecionado, mostrando dois painéis lado a lado", | ||
| "visualItem3": "O Painel 1 tem duas superfícies ([S1] e [S2] na barra de abas), com S1 ativa", | ||
| "visualItem4": "O Painel 2 tem uma superfície", | ||
| "visualItem5": "Cada superfície contém um panel (um terminal neste caso)", | ||
| "summary": "Resumo", | ||
| "levelHeader": "Nível", | ||
| "whatItIsHeader": "O que é", | ||
| "createdByHeader": "Criado por", | ||
| "identifiedByHeader": "Identificado por", | ||
| "macosWindow": "Janela do macOS", | ||
| "sidebarEntry": "Entrada na barra lateral", | ||
| "splitRegion": "Região dividida", | ||
| "tabWithinPane": "Aba dentro do painel", | ||
| "terminalOrBrowser": "Terminal ou navegador", | ||
| "automatic": "Automático", | ||
| "paneIdSocket": "ID do painel (API de socket)", | ||
| "panelIdInternal": "ID do panel (interno)" |
There was a problem hiding this comment.
Finish the pt-BR prose around the internal panel term.
If Panel is the chosen borrowed term to keep it distinct from Painel, the surrounding sentences still have English fragments (Panel is..., panels, ID do panel), so this section reads unfinished.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/messages/pt-BR.json` around lines 209 - 233, The Portuguese text mixes
English fragments for the borrowed term "Panel" and inconsistent uses of
"panel"/"painel"; update the strings to be fully natural Portuguese while
keeping "Panel" as the distinct borrowed term (and explain it once), by editing
keys like "panelTitle", "panelDesc", "panelTerminal", "panelBrowser",
"panelNote", "panelNote", "visualItem5", and "panelIdInternal" to (1) introduce
"Panel" as a borrowed term in Portuguese (e.g. "Panel (termo emprestado,
distinto de 'painel')"), (2) translate remaining English fragments and plurals
into Portuguese (e.g. replace "panels" with "Panels" or "Panel(s)" consistently
and render surrounding verbs/nouns in Portuguese), and (3) make
"panelIdInternal" a Portuguese label that clarifies it's an internal ID for a
Panel; ensure capitalization and pluralization of "Panel" are consistent across
these keys.
- 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
There was a problem hiding this comment.
2 issues found across 5 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="web/app/[locale]/docs/layout.tsx">
<violation number="1" location="web/app/[locale]/docs/layout.tsx:14">
P2: `docs.layoutTitle` is referenced in metadata but missing in 18 locale message files, so non-English docs titles are not reliably translated and may resolve to missing-message output.</violation>
</file>
<file name="web/app/[locale]/layout.tsx">
<violation number="1" location="web/app/[locale]/layout.tsx:32">
P2: `generateMetadata` now depends on `meta.*` translations that are missing for 18 locales, so non-English pages cannot produce localized metadata correctly.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| const t = await getTranslations({ locale, namespace: "docs" }); | ||
| return { | ||
| title: { | ||
| template: `%s — ${t("layoutTitle")}`, |
There was a problem hiding this comment.
P2: docs.layoutTitle is referenced in metadata but missing in 18 locale message files, so non-English docs titles are not reliably translated and may resolve to missing-message output.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/app/[locale]/docs/layout.tsx, line 14:
<comment>`docs.layoutTitle` is referenced in metadata but missing in 18 locale message files, so non-English docs titles are not reliably translated and may resolve to missing-message output.</comment>
<file context>
@@ -1,20 +1,28 @@
+ const t = await getTranslations({ locale, namespace: "docs" });
+ return {
+ title: {
+ template: `%s — ${t("layoutTitle")}`,
+ default: t("layoutTitle"),
+ },
</file context>
| params: Promise<{ locale: string }>; | ||
| }): Promise<Metadata> { | ||
| const { locale } = await params; | ||
| const t = await getTranslations({ locale, namespace: "meta" }); |
There was a problem hiding this comment.
P2: generateMetadata now depends on meta.* translations that are missing for 18 locales, so non-English pages cannot produce localized metadata correctly.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/app/[locale]/layout.tsx, line 32:
<comment>`generateMetadata` now depends on `meta.*` translations that are missing for 18 locales, so non-English pages cannot produce localized metadata correctly.</comment>
<file context>
@@ -19,40 +23,47 @@ const geistMono = Geist_Mono({
+ params: Promise<{ locale: string }>;
+}): Promise<Metadata> {
+ const { locale } = await params;
+ const t = await getTranslations({ locale, namespace: "meta" });
+ const url =
+ locale === "en" ? "https://cmux.dev" : `https://cmux.dev/${locale}`;
</file context>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d08c8e8bbe
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| params: Promise<{ locale: string }>; | ||
| }): Promise<Metadata> { | ||
| const { locale } = await params; | ||
| const t = await getTranslations({ locale, namespace: "meta" }); |
There was a problem hiding this comment.
Add missing locale metadata messages before reading
meta
generateMetadata now reads meta.title/meta.description for every locale, but the non-English message bundles added in this commit (for example web/messages/ja.json and the other 17 localized files) do not define a top-level meta object or several *.metaTitle keys used across blog/docs/community pages. On localized routes this produces missing-message output (or intl errors, depending runtime config) in page titles/descriptions instead of translated metadata, which hurts SEO and localization quality.
Useful? React with 👍 / 👎.
- 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
There was a problem hiding this comment.
1 issue found across 19 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="web/messages/no.json">
<violation number="1" location="web/messages/no.json:186">
P2: Norwegian docs translations are missing required `metaTitle` keys for several docs namespaces that now call `t("metaTitle")` in metadata generation.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f9f77acb2c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| title: t("metaTitle"), | ||
| description: t("metaDescription"), |
There was a problem hiding this comment.
Add localized wall-of-love metadata keys
generateMetadata always reads wallOfLove.metaTitle and wallOfLove.metaDescription, but the non-English message bundles in this commit only define wallOfLove.title/description (for example web/messages/ja.json), so localized /wall-of-love routes emit missing-message fallbacks (or throw under stricter intl settings) and lose proper translated SEO metadata.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e6403898cd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| <p className="text-muted"> | ||
| {t.rich("faqNotificationsA", { | ||
| cliLink: (chunks) => ( | ||
| <a href="/docs/notifications" className={linkClass}> |
There was a problem hiding this comment.
Preserve locale when linking to internal docs from home FAQ
These FAQ links use a raw <a href="/docs/..."> instead of the locale-aware navigation helper, so on localized pages they drop the explicit locale prefix and rely on cookie/Accept-Language detection. A user who lands directly on a non-English URL (e.g. shared /ja/... link) without a NEXT_LOCALE cookie can be sent back to English when clicking this link, which breaks locale continuity for core navigation.
Useful? React with 👍 / 👎.
…#1216) * 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
Summary
next-intlframework with middleware-based locale detection fromAccept-Languageheaderas-needed(no/en/prefix for English, other locales get/ja/,/de/, etc.)dir="rtl"on<html>useTranslations(): home, blog posts, docs (8 pages), legal (3 pages), community, wall-of-love, keyboard shortcuts componentalternates.languagesfor international SEOgenerateStaticParams()for SSG across all 19 localesTesting
SKIP_ENV_VALIDATION=1 npx next buildpasses/ja/cmux.dev/docs/getting-startednotcmux.dev/en/docs/getting-started)Related
Summary by cubic
Internationalized the website with
next-intlfor 19 languages, adding auto locale detection, localized routing, and full-page translations. Localized all metadata (including docs pages), improved the language switcher and sitemap, and fixed i18n/accessibility issues.New Features
generateMetadata+getTranslationsfor root layout, docs/blog layouts (title templates), blog index, posts, community, and wall‑of‑love; docs pages now have per‑locale meta titles using new keys across all locales./docs/browser-automationand/wall-of-lovewith per‑locale alternates.Bug Fixes
.toLowerCase()on translations, typed phrase keys, fixed accents, corrected RTL (Arabic only; Khmer set to LTR), fixedt.rich()placeholders and ICU escapes; legal pages stay English‑only with localizedLink.Written for commit e640389. Summary will update on new commits.
Summary by CodeRabbit
New Features
Refactor