Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

Back to skills

I18n

ASecurity

Add full internationalization (i18n) to a Next.js project using next-intl. Supports 14+ languages, SEO-friendly locale routing, hreflang sitemaps, and bulk translation. Use when the user asks to "internationalize", "add i18n", "add translations", "multi-language", "localize", "add language support", or "translate my site".

7 stars
0 votes
0 copies
0 views
Added 9/20/2026
ai-agentstypescriptpythonbashnextjsgitapi

Works with

cliapi

Security Analysis

A96/100
mediumInstalls packages at runtime which could introduce malicious dependencies

Scanned 9/20/2026

Install to Claude Code

$npx -y skills add unempyd/revenueos --skill i18n --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of I18n?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for I18n
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/unempyd-i18n/badge)](https://www.skillsdirectory.com/skills/unempyd-i18n)

More formats (shields.io, HTML) on the badges page.

Download Zip
Files
SKILL.md
---
name: i18n
description: Add full internationalization (i18n) to a Next.js project using next-intl. Supports 14+ languages, SEO-friendly locale routing, hreflang sitemaps, and bulk translation. Use when the user asks to "internationalize", "add i18n", "add translations", "multi-language", "localize", "add language support", or "translate my site".
user_invocable: true
---

# Internationalize a Next.js Project

Add complete internationalization to a Next.js (App Router) project using **next-intl v4**. This skill handles routing, translation files, sitemap hreflang, and bulk translation across all locales.

## Step 1: Assess the Project

1. Check the Next.js version (`package.json`) — must be 13+ with App Router
2. Check if i18n is already partially set up (look for `next-intl`, `next-i18next`, `[locale]` routes)
3. Identify all pages/routes that need translation
4. Identify all user-facing strings (hardcoded text in components)
5. Ask the user which locales to support (default recommendation: en, es, fr, de, pt, ja, ar, zh, zh-tw, id, vi, ms, ru, hi)

## Step 2: Install Dependencies

```bash
npm install next-intl
```

## Step 3: Create i18n Configuration Files

Create 4 files under `src/i18n/`:

### `src/i18n/config.ts`
```typescript
export const locales = ['en', 'es', 'fr', 'de', 'pt', 'ja', 'ar', 'zh', 'zh-tw', 'id', 'vi', 'ms', 'ru', 'hi'] as const

export type Locale = (typeof locales)[number]
export const defaultLocale: Locale = 'en'

export const localeNames: Record<Locale, string> = {
  en: 'English',
  es: 'Espanol',
  fr: 'Francais',
  de: 'Deutsch',
  pt: 'Portugues',
  ja: '日本語',
  ar: 'العربية',
  zh: '简体中文',
  'zh-tw': '繁體中文',
  id: 'Bahasa Indonesia',
  vi: 'Tieng Viet',
  ms: 'Bahasa Melayu',
  ru: 'Русский',
  hi: 'हिन्दी',
}

export const rtlLocales: Locale[] = ['ar']
```

### `src/i18n/routing.ts`
```typescript
import { defineRouting } from 'next-intl/routing'
import { defaultLocale, locales } from './config'

export const routing = defineRouting({
  locales,
  defaultLocale,
  localePrefix: 'as-needed', // English URLs stay clean, other locales get /es/, /fr/, etc.
})
```

### `src/i18n/navigation.ts`
```typescript
import { createNavigation } from 'next-intl/navigation'
import { routing } from './routing'

export const { Link, redirect, usePathname, useRouter } = createNavigation(routing)
```

### `src/i18n/request.ts`
```typescript
import { getRequestConfig } from 'next-intl/server'
import { routing } from './routing'

export default getRequestConfig(async ({ requestLocale }) => {
  let locale = await requestLocale
  if (!locale || !routing.locales.includes(locale as any)) {
    locale = routing.defaultLocale
  }
  return {
    locale,
    messages: (await import(`../messages/${locale}.json`)).default,
  }
})
```

## Step 4: Create Middleware

Create `src/middleware.ts`:
```typescript
import createMiddleware from 'next-intl/middleware'
import { routing } from '@/i18n/routing'

export default createMiddleware({
  ...routing,
  localeDetection: false, // Don't auto-redirect based on Accept-Language
})

export const config = {
  matcher: ['/((?!_next|api|images|fonts|favicon|sitemap|robots).*)'],
}
```

**Key decision**: `localeDetection: false` prevents auto-redirecting users based on browser language. This keeps English URLs stable for SEO. Users can manually switch languages via a language selector.

## Step 5: Update next.config

Wrap the existing config with `createNextIntlPlugin`:

```typescript
import createNextIntlPlugin from 'next-intl/plugin'
const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts')

// ... existing config ...
export default withNextIntl(nextConfig)
```

## Step 6: Add `[locale]` Dynamic Route

Move all page content under `src/app/[locale]/`:

1. Create `src/app/[locale]/layout.tsx` with:
   - `generateStaticParams()` returning all locales
   - `setRequestLocale(locale)` call
   - `<NextIntlClientProvider>` wrapping children
   - `<html lang={locale} dir={rtlLocales.includes(locale) ? 'rtl' : 'ltr'}>`
   - Hreflang `<link>` tags in `<head>` for all locales + `x-default`

2. Move existing pages into `src/app/[locale]/`
3. Each page should call `setRequestLocale(locale)` for static generation

## Step 7: Extract Strings into Translation Files

1. Create `src/messages/en.json` with all user-facing strings organized by section:
   ```json
   {
     "common": { "signIn": "Sign In", ... },
     "tools": { "tool-slug": { "title": "...", "description": "..." } },
     "faq": { "tool-slug": [{ "question": "...", "answer": "..." }] }
   }
   ```

2. Replace all hardcoded strings in components with `useTranslations()`:
   ```typescript
   const t = useTranslations('common')
   return <button>{t('signIn')}</button>
   ```

3. For server components, use `getTranslations()`:
   ```typescript
   const t = await getTranslations('common')
   ```

## Step 8: Translate to All Locales

For each non-English locale, create `src/messages/{locale}.json` with the same structure as `en.json`.

### Translation Strategy

Use **parallel Codex agents** via the `codex-tasks` skill to save Claude credits:

1. Launch one Codex task per locale (up to 7 in parallel) using `/codex-tasks`
2. Each task reads `en.json`, translates all strings, writes `{locale}.json`
3. Codex prompt should include:
   - The full `en.json` content (or path to read it)
   - Target language name and locale code
   - Instructions:
     - Translate naturally, not literally
     - Keep technical terms in English (PowerPoint, PDF, API, etc.)
     - Preserve JSON structure exactly (same keys, same nesting)
     - Preserve interpolation variables like `{count}`, `{name}` unchanged
     - Write the result to `src/messages/{locale}.json`
4. After Codex tasks complete, **verify the results** using the verification script below — Codex output quality varies and must be checked

### Verification

After translation, run a verification script to catch issues:

```python
import json

locales = ['es', 'fr', 'de', 'pt', 'ja', 'ar', 'zh', 'zh-tw', 'id', 'vi', 'ms', 'ru', 'hi']
english_words = ['the ', 'and ', 'you ', 'your ', 'our ', 'this ', 'that ', 'with ', 'from ', 'will ']

with open('src/messages/en.json') as f:
    en = json.load(f)

for loc in locales:
    with open(f'src/messages/{loc}.json') as f:
        data = json.load(f)

    # Check: missing sections
    missing = [s for s in en if s not in data]

    # Check: residual English content
    eng_count = 0
    def check(d):
        nonlocal eng_count  # won't work in inline script; use list trick
        if isinstance(d, dict):
            for v in d.values(): check(v)
        elif isinstance(d, str):
            if sum(1 for w in english_words if w in d.lower()) >= 3:
                eng_count += 1
    check(data)

    status = 'OK' if not missing and eng_count == 0 else 'ISSUES'
    print(f'{loc}: {status} (missing={len(missing)}, english={eng_count})')
```

## Step 9: Update Sitemap with Hreflang

Update `src/app/sitemap.ts` to include hreflang alternates:

```typescript
import { MetadataRoute } from 'next'
import { locales } from '@/i18n/config'

const baseUrl = 'https://www.example.com'

function buildAlternates(path: string): Record<string, string> {
  const alternates: Record<string, string> = {}
  for (const locale of locales) {
    const prefix = locale === 'en' ? '' : `/${locale}`
    alternates[locale] = `${baseUrl}${prefix}${path}`
  }
  return alternates
}

export default function sitemap(): MetadataRoute.Sitemap {
  return pages.map((path) => ({
    url: `${baseUrl}${path}`,
    lastModified: new Date(),
    alternates: { languages: buildAlternates(path) },
  }))
}
```

**Important**: Generate one canonical URL per page with hreflang alternates, NOT one URL per locale. This prevents duplicate content in search results.

## Step 10: Add Language Selector (Optional)

Add a language switcher component that uses `useRouter` and `usePathname` from `@/i18n/navigation` to switch locales while preserving the current path.

## Step 11: Verify

1. Build the project: `npm run build` — check that all static pages generate correctly
2. Test English URLs have no prefix: `https://example.com/tools`
3. Test locale URLs have prefix: `https://example.com/es/tools`
4. Verify sitemap has hreflang alternates
5. Check RTL rendering for Arabic
6. Run the translation verification script from Step 8

## Common Pitfalls

- **`public/sitemap.xml`** conflicts with dynamic `src/app/sitemap.ts` in dev mode — delete the static one or rename it
- **Middleware matcher** must exclude `_next`, `api`, `sitemap`, `robots`, and static asset paths
- **`localePrefix: 'as-needed'`** is critical — it keeps default locale URLs clean for SEO continuity
- **`localeDetection: false`** prevents unwanted redirects that break SEO and confuse users
- **Large translation files** (5000+ lines per locale) can make git pushes fail — use `git config http.postBuffer 524288000`
- **Verify translations thoroughly** — automated translation often produces mixed-language output; always verify with the English word detection script after Codex tasks complete

## Locale Count Reference

- 14 locales x N pages = 14N static pages at build time
- Each locale JSON file is typically 2-5x the size of en.json (CJK characters, verbose languages)
- Build time increases linearly with locale count

Attribution

unempydunempyd
View sourceMore from unempyd →
SSkills DirectorySkills Directory

Your tool, in front of Claude Code builders.

3 founder slots · $299/mo · GSC-verified traffic · sponsors can never buy grades.

See placements

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Your tool, in front of Claude Code builders.

3 founder slots · $299/mo · GSC-verified traffic · sponsors can never buy grades.

See placements

Related Skills

Caveman

Ultra-compressed communication mode. Cuts token usage ~75% by speaking like caveman while keeping full technical accuracy. Supports intensity levels: lite, full (default), ultra, wenyan-lite, wenyan-full, wenyan-ultra. Use when user says "caveman mode", "talk like caveman", "use caveman", "less tokens", "be brief", or invokes /caveman. Also auto-triggers when token efficiency is requested.

1023331 votes

Hyperplan

Adversarial multi-agent planning skill. Self-orchestrates 5 hostile category members (unspecified-low, unspecified-high, deep, ultrabrain, artistry) via team-mode for ruthless cross-critique debate, distills only the defensible insights, then MANDATORILY hands the distilled insight bundle to the `plan` agent for executable plan formalization. Use when planning needs maximum rigor and surfacing of weak assumptions, blind spots, and over-engineering. Triggers: 'hyperplan', 'hpp', '/hyperplan', ...

686011 votes

Mcp Code Execution

Routes multi-tool workflows through MCP servers for large datasets and pipelines. Use when Bash tool overhead is limiting throughput on data-heavy tasks.

3331 votes

catchup

Recovers prior coding-agent session context by running `catchup <agent> --since-compact`, which extracts a clean summary of a previous Codex, Claude Code, Antigravity, OpenCode, or Pi Agent session. Use when the user says "catch up", "what did the last session do", "get me up to speed", "I switched agents", or asks to recover/summarize a previous session before continuing. Do NOT use for the current conversation, git history, or any non-agent log.

611 votes

math-skill

A comprehensive mathematical reasoning skill for AI assistants — handles arithmetic to research-level problems with rigorous step-by-step reasoning, systematic verification, and transparent uncertainty handling

381 votes
View all in ai-agents →