> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vibestrap.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Bilingual content & i18n

> How Vibestrap handles English + Chinese copy, MDX variants, and locale-aware lookups.

Vibestrap ships fully bilingual (English + Chinese) out of the box. Two systems
work together: `next-intl` for UI strings (under `messages/`) and a thin
naming-convention layer for MDX content (under `content/`). Once you internalize
the conventions there is no extra ceremony — just write twice.

## Prerequisites

None. The plumbing is wired up; you only add files.

## How locales are routed

Routing is `as-needed` (`src/i18n/routing.ts`):

* English (the default) lives at the root: `/docs/quickstart`
* Chinese is prefixed: `/zh/docs/quickstart`
* Locale list comes from `siteConfig.i18n.locales` (`['en', 'zh']`)

Every locale-prefixed page must call `setRequestLocale(locale)` at the top —
without it, server components fall back to the default locale and your `t(...)`
calls go silent.

```tsx theme={null}
const { locale } = await params;
setRequestLocale(locale);
```

## Naming convention for MDX

For each piece of content, ship two files side by side:

```
content/blog/welcome-to-vibestrap.mdx       # English
content/blog/welcome-to-vibestrap.zh.mdx    # Chinese
```

The `.zh.mdx` suffix is the only signal — there is no separate folder, no
JSON manifest. The helpers in `src/lib/source-helpers.ts` strip the suffix to
compute the canonical slug.

## Locale-aware lookups

Three helpers cover everything:

```ts theme={null}
import { pickByLocale, findBySlug, canonicalSlugs } from '@/lib/source';

// All blog posts, locale-preferred (zh falls back to en if missing)
const posts = pickByLocale(blog.docs, locale);

// Single post by slug
const post = findBySlug(blog.docs, slug, locale);

// Build params for generateStaticParams (en files only)
const slugs = canonicalSlugs(blog.docs);
```

`pickByLocale('zh')` returns the `.zh.mdx` if it exists, otherwise the English
fallback so your /zh/ pages never 404 on missing translations.

## UI strings — both files, same keys

Translations live in `messages/en.json` and `messages/zh.json`. The two files
must have identical key shapes; the structural diff is enforced by
`scripts/check-i18n.mjs`.

```tsx theme={null}
import { useTranslations } from 'next-intl';

const t = useTranslations('Home.hero');
return <h1>{t('title')}</h1>;
```

When you add a new key to `en.json`, add it to `zh.json` in the same place —
even if the Chinese is a placeholder. The validator will yell otherwise.

## Adding a new locale

1. Append to `siteConfig.i18n.locales` (e.g. `['en', 'zh', 'ja']`).
2. Create `messages/ja.json` mirroring `en.json`.
3. For any MDX you want translated, ship `slug.ja.mdx`.
4. Add a new branch in `pickByLocale` if you want non-en fallbacks.

That's it — routing picks up the new locale automatically.

## Verify it works

```bash theme={null}
node scripts/check-i18n.mjs   # structural parity + literal t() validation
pnpm typecheck                # types are happy
pnpm dev                      # visit /docs and /zh/docs
```

The validator reports keys that exist in only one file plus any `t('foo.bar')`
call that doesn't resolve under the namespace declared in the same file.

## Common pitfalls

1. **Missing `.zh.mdx`** — the `/zh/` page silently falls back to English. Run
   the dev server and click around in Chinese mode to catch this.
2. **Date drift between variants** — when you update an English blog post, also
   bump the date in the `.zh.mdx`. Otherwise the index sort order diverges.
3. **Template `t()` calls** — `t(\`Foo.\${dyn}\`)\` cannot be statically
   verified. The validator only checks the prefix exists. Smoke-test the page.
4. **Missing `setRequestLocale(locale)`** — locale-prefixed pages without this
   call render with the default locale's messages. Easy to miss.
5. **Using `next/link` directly** — always import `Link` and `useRouter` from
   `@/i18n/navigation` so locale prefixes survive client navigation.

## Official docs

* [next-intl.dev](https://next-intl.dev/) — full reference for the i18n layer
* [next-intl App Router](https://next-intl.dev/docs/getting-started/app-router) — request config & routing
* [Next.js i18n routing](https://nextjs.org/docs/app/building-your-application/routing/internationalization) — Next.js fundamentals
