This projects uses Next.js locale in middleware and client side translation.
Default locale is en (English) while zh (Simplified Chinese) is supported. The author can recognize both languages so can better support the translation accuracy there, but you are welcomed to contribute more languages following this doc.
apps/frontend/
├── app/
│ └── [locale]/ # Locale-based routing
│ ├── layout.tsx # Locale layout
│ ├── login/
│ ├── (sidebar)/ # Sidebar layout group
│ │ ├── layout.tsx
│ │ ├── mcp-servers/
│ │ ├── namespaces/
│ │ └── ...
│ └── ...
├── public/locales/
│ ├── en/ # English translations
│ │ ├── common.json
│ │ ├── auth.json
│ │ ├── navigation.json
│ │ ├── mcp-servers.json
│ │ ├── namespaces.json
│ │ ├── endpoints.json
│ │ ├── api-keys.json
│ │ ├── settings.json
│ │ ├── search.json
│ │ ├── inspector.json
│ │ └── logs.json
│ └── zh/ # Chinese translations
│ └── (auto-generated)
├── lib/
│ └── i18n.ts # Client-side i18n utilities
├── hooks/
│ ├── useLocale.ts # Hook to get current locale
│ └── useTranslations.ts # Hook for client-side translations
├── components/
│ └── language-switcher.tsx # Language switching component
└── middleware.ts # Locale detection and routing
For client components, use the useTranslations hook:
"use client";
import { useTranslations } from "@/hooks/useTranslations";
function ClientComponent() {
const { t, isLoading, locale } = useTranslations();
if (isLoading) return <div>Loading...</div>;
return (
<div>
<h1>{t('common:title')}</h1>
<button>{t('auth:signIn')}</button>
</div>
);
}// In translation file: "welcome": "Welcome, {{name}}!"
<span>{t('welcome', { name: 'John' })}</span>The application uses Next.js's App Router with locale segments:
- English:
/mcp-servers,/settings - Chinese:
/zh/mcp-servers,/zh/settings
The middleware in middleware.ts handles:
- Locale detection from URL, cookies, and Accept-Language header
- Automatic redirects to appropriate locale
- Authentication checks
Use the getLocalizedPath utility for navigation:
import { getLocalizedPath } from "@/lib/i18n";
const localizedPath = getLocalizedPath("/mcp-servers", "zh");
// Returns: "/zh/mcp-servers"- Add to English files first in
public/locales/en/ - Add other languages in new locale
Use colon-separated namespaces:
{
"server": {
"create": "Create Server",
"edit": "Edit Server",
"status": {
"online": "Online",
"offline": "Offline"
}
}
}Usage: t('mcp-servers:server.create')
- common: Shared UI elements (buttons, labels, etc.)
- auth: Authentication-related text
- navigation: Menu items, navigation text
- [feature]: Feature-specific translations
The LanguageSwitcher component provides a dropdown to switch between languages:
import { LanguageSwitcher } from "@/components/language-switcher";
function Header() {
return (
<header>
<LanguageSwitcher />
</header>
);
}- Use descriptive, hierarchical keys
- Use camelCase for consistency
- Group related translations together
- Use interpolation for dynamic content
- Keep variable names descriptive
{
"welcome": "Welcome, {{userName}}!",
"itemCount": "{{count}} items found"
}- Other language translations automatically fall back to English if missing
- Keys are returned as-is if no translation is found
- Missing translations: Check browser console for missing key warnings
- Hydration errors: Ensure consistent rendering between server and client
- Locale not detected: Check middleware configuration and URL structure
When adding new features:
- Add English translations first
- Add other languages (you can use agent like Cursor to generate other files for other locale)
- Test both locales thoroughly
- Update this documentation if adding new patterns