Skip to content

feat: i18n with next-intl + per-locale MDX content (content/<slug>/<locale>.mdx) #281

@bntvllnt

Description

@bntvllnt

Problem

Site is English-only. Two costs:

  1. SEO — non-English markets don't index us; `hreflang` alternates absent.
  2. AI agents — agents querying in non-English contexts get nothing back.
  3. Brand reach — VLLNT readership is global; English-only is a self-imposed ceiling.

Goal

First-class internationalization, leveraging existing MDX content pipeline. Co-locate translations next to source MDX — no separate translation tree.

Architecture (file layout)

apps/registry/
├── i18n/
│   ├── routing.ts        ← next-intl config: locales, defaultLocale, localePrefix
│   └── request.ts        ← message loader (per-request locale resolution)
├── messages/
│   ├── en.json           ← UI strings (chrome, buttons, search, errors)
│   ├── fr.json
│   └── es.json
├── app/
│   └── [locale]/         ← all routes under here
│       ├── page.tsx
│       ├── components/...
│       ├── docs/...
│       └── ...
└── content/
    └── pages/
        ├── home/
        │   ├── en.mdx
        │   ├── fr.mdx
        │   └── es.mdx
        ├── docs/installation/
        │   ├── en.mdx
        │   └── fr.mdx
        ├── philosophy/
        │   ├── en.mdx
        │   └── fr.mdx
        └── design/
            ├── en.mdx
            └── fr.mdx

Convention: every routable MDX page lives in a folder named for its slug, with one file per locale. The MDX loader in `page.tsx` resolves `/.mdx`, falling back to `/en.mdx` if the locale file is missing.

Library choice

`next-intl` — decision locked.

  • Native App Router support.
  • Locale-prefixed routes (`/fr/components/button`) with `localePrefix: 'as-needed'` (English at root, others prefixed).
  • Server Components first.
  • ``, `useRouter`, `usePathname` wrappers for locale-aware navigation.
  • Mature, actively maintained, used in production at scale.

Alternatives considered + rejected: `next-i18next` (Pages Router era), `@lingui/core` (heavier toolchain), DIY (not worth it).

Initial locales

Phase 1: `en` + `fr` (founder-quality translations possible).
Phase 2: `es`, `de`, `pt-BR`, `zh-CN` (community / AI-assisted, reviewed before merge).

Acceptance criteria

Routing & config

  • `i18n/routing.ts` defines `locales`, `defaultLocale: 'en'`, `localePrefix: 'as-needed'`.
  • `i18n/request.ts` loads messages per request locale.
  • All routes moved under `app/[locale]/`.
  • `middleware.ts` configured with `createMiddleware(routing)` for locale negotiation.
  • Custom ``, `useRouter` wrappers re-exported from `@/i18n/routing`.

MDX pipeline

  • `content/pages//` folder convention adopted; existing `home.mdx`, `philosophy.mdx`, `docs.mdx`, `components.mdx` moved to `/en.mdx`.
  • MDX loader resolves `content/pages//.mdx` with English fallback.
  • `generateStaticParams` enumerates `(locale, slug)` for static generation.

UI strings

  • `messages/en.json` extracted from current header/footer/search/buttons.
  • `messages/fr.json` translated.
  • All hardcoded JSX strings replaced with `useTranslations()` keys (codemod or manual).

SEO

Component pages

  • `/components/[slug]` works under each locale.
  • Component descriptions in `registry.json` get a `descriptions` map: `{ en, fr, ... }`. Falls back to `description` (English) when locale missing.
  • `/r/.json` keeps English as canonical (registry contract); locale UI text comes from a separate `/r/..json` overlay (additive, optional).

Locale switcher

  • Header gets a locale switcher (lucide `Languages` icon).
  • Switching preserves the current path.
  • Persisted via `NEXT_LOCALE` cookie + URL prefix.

CI

  • CI step verifies every `/en.mdx` has a non-empty translation in every active locale (or graceful fallback annotated).
  • Missing translation = warning (not error) for non-English locales.

Out of scope

Depends on

References

Success metrics

Metadata

Metadata

Assignees

Labels

discoverabilitySite/library discoverabilitydocumentationImprovements or additions to documentationenhancementNew feature or requestseoSearch engine optimization

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions