auto-claude: subtask-1-4 - Document request.ts configuration and next-intl setup
- Added comprehensive Configuration & Setup section - Documented getRequestConfig() function with full implementation - Explained locale validation logic with examples and security benefits - Detailed dynamic message loading mechanism and performance comparison - Documented createNextIntlPlugin() integration in next.config.ts - Explained plugin composition pattern with MDX - Added server-side vs client-side translation comparison - Included best practices for component splitting and performance optimization - Provided code examples for all patterns and troubleshooting guides Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
+525
-8
@@ -797,14 +797,531 @@ graph TD
|
|||||||
> - Best practices for key naming
|
> - Best practices for key naming
|
||||||
|
|
||||||
### ⚙️ Configuration & Setup
|
### ⚙️ Configuration & Setup
|
||||||
> Understanding request.ts and next-intl integration
|
|
||||||
>
|
This section explains how the i18n system integrates with Next.js through next-intl, covering the configuration in `request.ts` and `next.config.ts`.
|
||||||
> **Topics covered:**
|
|
||||||
> - `getRequestConfig()` function
|
#### `getRequestConfig()` Function
|
||||||
> - Locale validation and fallback logic
|
|
||||||
> - Dynamic message loading
|
**Location:** `src/i18n/request.ts`
|
||||||
> - `createNextIntlPlugin()` in next.config.ts
|
|
||||||
> - Server-side vs. client-side translations
|
The `getRequestConfig()` function is the **core integration point** between next-intl and the application. It handles locale resolution and message loading for every request.
|
||||||
|
|
||||||
|
**Full Implementation:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { getRequestConfig } from 'next-intl/server';
|
||||||
|
import { locales, type Locale } from './config';
|
||||||
|
|
||||||
|
export default getRequestConfig(async ({ requestLocale }) => {
|
||||||
|
let locale = await requestLocale;
|
||||||
|
|
||||||
|
// Validate locale and fallback to default if invalid
|
||||||
|
if (!locale || !locales.includes(locale as Locale)) {
|
||||||
|
locale = 'de';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
locale,
|
||||||
|
messages: (await import(`../messages/${locale}.json`)).default,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**How it works:**
|
||||||
|
|
||||||
|
1. **Receives `requestLocale`** - next-intl extracts the locale from the URL path (e.g., `/de/`, `/en/`, `/sr/`)
|
||||||
|
2. **Awaits the locale** - The locale is a promise that resolves to the URL segment
|
||||||
|
3. **Validates the locale** - Checks if it exists and is in the allowed `locales` array
|
||||||
|
4. **Falls back if invalid** - Defaults to `'de'` (German) for invalid/missing locales
|
||||||
|
5. **Returns configuration** - Provides the validated locale and dynamically loaded messages
|
||||||
|
|
||||||
|
**Key Features:**
|
||||||
|
|
||||||
|
- ✅ **Type-safe validation** - Uses the `Locale` type from `config.ts`
|
||||||
|
- ✅ **Automatic fallback** - Never breaks, always provides a valid locale
|
||||||
|
- ✅ **Dynamic imports** - Only loads the required translation file (better performance)
|
||||||
|
- ✅ **Server-side execution** - Runs on the server for every request
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Locale Validation Logic
|
||||||
|
|
||||||
|
**Purpose:** Ensure only valid locales are used, preventing errors and security issues.
|
||||||
|
|
||||||
|
**Validation Flow:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
if (!locale || !locales.includes(locale as Locale)) {
|
||||||
|
locale = 'de';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**What it checks:**
|
||||||
|
|
||||||
|
1. **Existence check** - `!locale` ensures a value was provided
|
||||||
|
2. **Whitelist check** - `!locales.includes(...)` verifies it's an allowed locale
|
||||||
|
3. **Fallback** - Defaults to `'de'` if either check fails
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
| URL Path | `requestLocale` | Valid? | Result | Reason |
|
||||||
|
|----------|----------------|--------|--------|--------|
|
||||||
|
| `/de/about` | `'de'` | ✅ Yes | `'de'` | Valid locale |
|
||||||
|
| `/en/contact` | `'en'` | ✅ Yes | `'en'` | Valid locale |
|
||||||
|
| `/sr/portfolio` | `'sr'` | ✅ Yes | `'sr'` | Valid locale |
|
||||||
|
| `/fr/home` | `'fr'` | ❌ No | `'de'` | Not in `locales` array |
|
||||||
|
| `/` | `null` | ❌ No | `'de'` | No locale provided |
|
||||||
|
| `/invalid123` | `'invalid123'` | ❌ No | `'de'` | Not in whitelist |
|
||||||
|
|
||||||
|
**Why this matters:**
|
||||||
|
|
||||||
|
- 🔒 **Security** - Prevents path traversal attacks (e.g., `../../etc/passwd`)
|
||||||
|
- 🛡️ **Reliability** - Guarantees the application always has a valid locale
|
||||||
|
- 📦 **Bundle safety** - Ensures only existing translation files are loaded
|
||||||
|
- 🎯 **User experience** - Provides predictable behavior for invalid URLs
|
||||||
|
|
||||||
|
**Alternative approaches (not used):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ❌ DON'T: No validation (unsafe)
|
||||||
|
export default getRequestConfig(async ({ requestLocale }) => {
|
||||||
|
const locale = await requestLocale;
|
||||||
|
return {
|
||||||
|
locale,
|
||||||
|
messages: (await import(`../messages/${locale}.json`)).default, // Could fail!
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// ❌ DON'T: Throwing errors (breaks user experience)
|
||||||
|
export default getRequestConfig(async ({ requestLocale }) => {
|
||||||
|
const locale = await requestLocale;
|
||||||
|
if (!locales.includes(locale as Locale)) {
|
||||||
|
throw new Error('Invalid locale'); // User sees error page
|
||||||
|
}
|
||||||
|
return { locale, messages: ... };
|
||||||
|
});
|
||||||
|
|
||||||
|
// ✅ DO: Validate and fallback (current implementation)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Dynamic Message Loading
|
||||||
|
|
||||||
|
**How translations are loaded:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
messages: (await import(`../messages/${locale}.json`)).default
|
||||||
|
```
|
||||||
|
|
||||||
|
**This line performs several critical functions:**
|
||||||
|
|
||||||
|
1. **Dynamic import** - Uses `import()` instead of static `import` statement
|
||||||
|
2. **Path interpolation** - Constructs the file path using the validated locale
|
||||||
|
3. **Async loading** - Waits for the file to be loaded
|
||||||
|
4. **Default export** - Extracts the JSON content from the module
|
||||||
|
|
||||||
|
**Benefits of dynamic imports:**
|
||||||
|
|
||||||
|
| Feature | Static Import | Dynamic Import (Current) |
|
||||||
|
|---------|--------------|--------------------------|
|
||||||
|
| **Bundle size** | All locales loaded upfront | Only requested locale loaded |
|
||||||
|
| **Initial load time** | Slower (3 files) | Faster (1 file) |
|
||||||
|
| **Memory usage** | Higher | Lower |
|
||||||
|
| **Server-side rendering** | Supported | ✅ Supported |
|
||||||
|
| **Code splitting** | No | ✅ Yes |
|
||||||
|
|
||||||
|
**Performance comparison:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ❌ Static imports (loads ALL locales on every request)
|
||||||
|
import deMessages from '../messages/de.json';
|
||||||
|
import enMessages from '../messages/en.json';
|
||||||
|
import srMessages from '../messages/sr.json';
|
||||||
|
|
||||||
|
const messageMap = { de: deMessages, en: enMessages, sr: srMessages };
|
||||||
|
return { locale, messages: messageMap[locale] };
|
||||||
|
// Bundle size: ~150KB (all locales)
|
||||||
|
|
||||||
|
// ✅ Dynamic import (loads ONLY requested locale)
|
||||||
|
messages: (await import(`../messages/${locale}.json`)).default
|
||||||
|
// Bundle size: ~50KB (single locale)
|
||||||
|
```
|
||||||
|
|
||||||
|
**File path resolution:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// If locale = 'de'
|
||||||
|
await import(`../messages/de.json`) // Resolves to: src/messages/de.json
|
||||||
|
|
||||||
|
// If locale = 'en'
|
||||||
|
await import(`../messages/en.json`) // Resolves to: src/messages/en.json
|
||||||
|
|
||||||
|
// If locale = 'sr'
|
||||||
|
await import(`../messages/sr.json`) // Resolves to: src/messages/sr.json
|
||||||
|
```
|
||||||
|
|
||||||
|
**Error handling:**
|
||||||
|
|
||||||
|
If a translation file is missing, Next.js will throw a build error:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
Error: Cannot find module '../messages/fr.json'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why this is safe:**
|
||||||
|
|
||||||
|
- ✅ Locale validation ensures only `de`, `en`, or `sr` are used
|
||||||
|
- ✅ All three files are required to exist for the build to succeed
|
||||||
|
- ✅ TypeScript catches typos in locale codes at compile time
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `createNextIntlPlugin()` in next.config.ts
|
||||||
|
|
||||||
|
**Location:** `next.config.ts`
|
||||||
|
|
||||||
|
The `createNextIntlPlugin()` function wraps the Next.js configuration to integrate next-intl into the build process.
|
||||||
|
|
||||||
|
**Implementation:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import type { NextConfig } from 'next';
|
||||||
|
import createNextIntlPlugin from 'next-intl/plugin';
|
||||||
|
|
||||||
|
// Create the next-intl plugin, pointing to request.ts
|
||||||
|
const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts');
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
// ... Next.js configuration options
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wrap the config with the plugin
|
||||||
|
export default withNextIntl(nextConfig);
|
||||||
|
```
|
||||||
|
|
||||||
|
**What `createNextIntlPlugin()` does:**
|
||||||
|
|
||||||
|
1. **Registers request.ts** - Tells next-intl where to find the `getRequestConfig` function
|
||||||
|
2. **Enables server-side translations** - Sets up the infrastructure for SSR
|
||||||
|
3. **Configures webpack** - Adds necessary loaders for JSON imports
|
||||||
|
4. **Optimizes builds** - Ensures proper code splitting for translation files
|
||||||
|
5. **Sets up middleware hooks** - Integrates with Next.js request lifecycle
|
||||||
|
|
||||||
|
**Plugin composition:**
|
||||||
|
|
||||||
|
This project uses **multiple plugins** together:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import createMDX from '@next/mdx';
|
||||||
|
import createNextIntlPlugin from 'next-intl/plugin';
|
||||||
|
|
||||||
|
// Create both plugins
|
||||||
|
const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts');
|
||||||
|
const withMDX = createMDX({ options: { ... } });
|
||||||
|
|
||||||
|
// Compose plugins: next-intl wraps MDX wraps config
|
||||||
|
export default withNextIntl(withMDX(nextConfig));
|
||||||
|
```
|
||||||
|
|
||||||
|
**Plugin order matters:**
|
||||||
|
|
||||||
|
```
|
||||||
|
nextConfig → withMDX → withNextIntl → Final Config
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why this pattern:**
|
||||||
|
|
||||||
|
- ✅ Modular configuration - Each plugin adds specific functionality
|
||||||
|
- ✅ Composable - Plugins can be added/removed independently
|
||||||
|
- ✅ Maintainable - Clear separation of concerns
|
||||||
|
- ✅ Type-safe - TypeScript validates the entire config chain
|
||||||
|
|
||||||
|
**Without the plugin:**
|
||||||
|
|
||||||
|
If you remove `withNextIntl`, the application would:
|
||||||
|
|
||||||
|
- ❌ Fail to resolve `useTranslations()` calls
|
||||||
|
- ❌ Not load translation messages
|
||||||
|
- ❌ Break server-side rendering of translated content
|
||||||
|
- ❌ Lose locale detection capabilities
|
||||||
|
|
||||||
|
**Plugin configuration path:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
createNextIntlPlugin('./src/i18n/request.ts')
|
||||||
|
↑
|
||||||
|
Relative to next.config.ts
|
||||||
|
Points to getRequestConfig export
|
||||||
|
```
|
||||||
|
|
||||||
|
**Troubleshooting:**
|
||||||
|
|
||||||
|
| Issue | Cause | Solution |
|
||||||
|
|-------|-------|----------|
|
||||||
|
| "Cannot find module 'next-intl/server'" | Plugin not installed | Run `npm install next-intl` |
|
||||||
|
| "getRequestConfig is not defined" | Wrong path in plugin | Check path matches `request.ts` location |
|
||||||
|
| Translations not loading | Plugin not applied | Ensure `withNextIntl(...)` wraps config |
|
||||||
|
| Build errors with MDX | Wrong plugin order | Place `withNextIntl` outermost |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Server-Side vs. Client-Side Translations
|
||||||
|
|
||||||
|
**How translations work in different contexts:**
|
||||||
|
|
||||||
|
##### Server Components (Recommended)
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
export default function ServerComponent() {
|
||||||
|
const t = useTranslations('common');
|
||||||
|
|
||||||
|
return <h1>{t('nav.home')}</h1>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**How it works:**
|
||||||
|
|
||||||
|
1. Component renders on the server
|
||||||
|
2. `useTranslations()` reads messages from the `getRequestConfig` result
|
||||||
|
3. Translation is resolved on the server
|
||||||
|
4. HTML with translated text is sent to the client
|
||||||
|
5. No translation data sent to the browser
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
|
||||||
|
- ✅ **Faster initial load** - No client-side JavaScript for translations
|
||||||
|
- ✅ **Better SEO** - Translated content available immediately
|
||||||
|
- ✅ **Smaller bundle** - Translations not included in client JS
|
||||||
|
- ✅ **Secure** - Translation logic stays on the server
|
||||||
|
|
||||||
|
##### Client Components
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
export default function ClientComponent() {
|
||||||
|
const t = useTranslations('common');
|
||||||
|
|
||||||
|
return <button>{t('actions.submit')}</button>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**How it works:**
|
||||||
|
|
||||||
|
1. Component marked with `'use client'`
|
||||||
|
2. Messages serialized and passed from server to client
|
||||||
|
3. `useTranslations()` reads from client-side context
|
||||||
|
4. Translations available for dynamic updates
|
||||||
|
|
||||||
|
**When to use:**
|
||||||
|
|
||||||
|
- ✅ Interactive components (forms, modals)
|
||||||
|
- ✅ Components with onClick handlers
|
||||||
|
- ✅ Real-time updates based on user actions
|
||||||
|
- ✅ Stateful UI elements
|
||||||
|
|
||||||
|
**Performance consideration:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// ❌ DON'T: Use client components unnecessarily
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
export default function StaticText() {
|
||||||
|
const t = useTranslations('common');
|
||||||
|
return <p>{t('siteDescription')}</p>; // This could be a server component
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ DO: Use server components for static content
|
||||||
|
export default function StaticText() {
|
||||||
|
const t = useTranslations('common');
|
||||||
|
return <p>{t('siteDescription')}</p>; // Server component (default)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Hybrid Pattern (Best Practice)
|
||||||
|
|
||||||
|
**Split components by interactivity:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// app/[locale]/page.tsx (Server Component)
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import ContactForm from '@/components/ContactForm'; // Client component
|
||||||
|
|
||||||
|
export default function ContactPage() {
|
||||||
|
const t = useTranslations('pages.contact');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>{t('hero.title')}</h1> {/* Server-rendered */}
|
||||||
|
<p>{t('hero.subtitle')}</p> {/* Server-rendered */}
|
||||||
|
<ContactForm /> {/* Client interactive */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// components/ContactForm.tsx (Client Component)
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
export default function ContactForm() {
|
||||||
|
const t = useTranslations('pages.contact.contactForm');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form>
|
||||||
|
<label>{t('name.label')}</label>
|
||||||
|
<input placeholder={t('name.placeholder')} />
|
||||||
|
<button type="submit">{t('submit')}</button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Translation data flow:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Request → getRequestConfig() → Messages loaded
|
||||||
|
↓
|
||||||
|
Server Components → useTranslations() → Direct access
|
||||||
|
↓
|
||||||
|
Client Components → Serialized via props → useTranslations() → Client access
|
||||||
|
```
|
||||||
|
|
||||||
|
**Bundle size impact:**
|
||||||
|
|
||||||
|
| Scenario | Server Component | Client Component |
|
||||||
|
|----------|------------------|------------------|
|
||||||
|
| **JavaScript sent** | 0 KB (translations) | ~50 KB (translations) |
|
||||||
|
| **HTML size** | Larger (translated text) | Smaller (placeholders) |
|
||||||
|
| **Time to Interactive** | Faster | Slower |
|
||||||
|
| **SEO crawlability** | ✅ Perfect | ⚠️ May need hydration |
|
||||||
|
|
||||||
|
**Best practices:**
|
||||||
|
|
||||||
|
1. ✅ **Default to server components** - Use client components only when necessary
|
||||||
|
2. ✅ **Split by interactivity** - Keep static content in server components
|
||||||
|
3. ✅ **Pass translations as props** - Avoid duplicating translation calls
|
||||||
|
4. ✅ **Minimize client boundaries** - Fewer `'use client'` directives = better performance
|
||||||
|
|
||||||
|
**Example optimization:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// ❌ BEFORE: Everything is a client component
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
export default function Dashboard() {
|
||||||
|
const t = useTranslations('dashboard');
|
||||||
|
const [count, setCount] = useState(0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>{t('title')}</h1> {/* Static - could be server */}
|
||||||
|
<p>{t('description')}</p> {/* Static - could be server */}
|
||||||
|
<button onClick={() => setCount(count + 1)}>
|
||||||
|
{t('increment')} {/* Interactive - needs client */}
|
||||||
|
</button>
|
||||||
|
<p>{count}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ AFTER: Split into server + client components
|
||||||
|
// app/[locale]/dashboard/page.tsx (Server)
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import Counter from '@/components/Counter';
|
||||||
|
|
||||||
|
export default function Dashboard() {
|
||||||
|
const t = useTranslations('dashboard');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>{t('title')}</h1> {/* Server-rendered */}
|
||||||
|
<p>{t('description')}</p> {/* Server-rendered */}
|
||||||
|
<Counter /> {/* Client component */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// components/Counter.tsx (Client)
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
export default function Counter() {
|
||||||
|
const t = useTranslations('dashboard');
|
||||||
|
const [count, setCount] = useState(0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button onClick={() => setCount(count + 1)}>
|
||||||
|
{t('increment')}
|
||||||
|
</button>
|
||||||
|
<p>{count}</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Result:**
|
||||||
|
|
||||||
|
- 📦 **Bundle size reduced** by ~40 KB (translations moved to server)
|
||||||
|
- ⚡ **Faster initial render** (server-rendered HTML)
|
||||||
|
- 🎯 **Better SEO** (title and description immediately available)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Summary: Configuration & Setup
|
||||||
|
|
||||||
|
**Key files:**
|
||||||
|
|
||||||
|
| File | Purpose | When to modify |
|
||||||
|
|------|---------|----------------|
|
||||||
|
| `src/i18n/request.ts` | Locale resolution & message loading | Rarely (fallback logic, custom validation) |
|
||||||
|
| `next.config.ts` | next-intl plugin integration | When adding/removing plugins |
|
||||||
|
| `src/i18n/config.ts` | Locale definitions | When adding new languages |
|
||||||
|
|
||||||
|
**Core concepts:**
|
||||||
|
|
||||||
|
- ✅ `getRequestConfig()` - Validates locale and loads messages
|
||||||
|
- ✅ Locale validation - Whitelist check with fallback to `'de'`
|
||||||
|
- ✅ Dynamic imports - Only load required translation file
|
||||||
|
- ✅ `createNextIntlPlugin()` - Integrates next-intl with Next.js build
|
||||||
|
- ✅ Server components - Preferred for better performance
|
||||||
|
- ✅ Client components - Use only for interactive elements
|
||||||
|
|
||||||
|
**Common patterns:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Pattern 1: Server component (default)
|
||||||
|
export default function Page() {
|
||||||
|
const t = useTranslations('namespace');
|
||||||
|
return <h1>{t('key')}</h1>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern 2: Client component (interactive)
|
||||||
|
'use client';
|
||||||
|
export default function Interactive() {
|
||||||
|
const t = useTranslations('namespace');
|
||||||
|
return <button>{t('action')}</button>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern 3: Multiple namespaces
|
||||||
|
export default function Complex() {
|
||||||
|
const tCommon = useTranslations('common');
|
||||||
|
const tPage = useTranslations('pages.home');
|
||||||
|
return <div>...</div>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### 🌐 Locale Configuration & Metadata
|
### 🌐 Locale Configuration & Metadata
|
||||||
> Detailed breakdown of locale settings and SEO data
|
> Detailed breakdown of locale settings and SEO data
|
||||||
|
|||||||
Reference in New Issue
Block a user