Astro/Next.js i18n Skill
Complete i18n infrastructure. SEO-first, static-first, Cloudflare Pages compatible.
Quick Start
- •Copy
assets/boilerplate/tosrc/i18n/ - •Configure locales in
config.ts - •Add
<HreflangHead />to base layout - •Use
t(locale, 'key')for all strings - ZERO hardcoded text
Directory Structure
code
src/i18n/
├── config.ts # Locales, default, siteUrl
├── routing.ts # URL helpers
├── utils.ts # t(), formatDate(), formatPrice()
├── translations/
│ ├── en.json
│ └── hu.json
└── components/
├── HreflangHead.astro
├── LanguageSwitcher.astro
└── LocaleLink.astro
Configuration
typescript
// src/i18n/config.ts
export const i18nConfig = {
defaultLocale: 'en' as const,
locales: ['en', 'hu'] as const,
localeStates: { // Lifecycle management
en: 'active', // index + hreflang
hu: 'active',
// de: 'paused', // noindex, no hreflang
// sk: 'deprecated', // 301 redirect
},
xDefaultLocale: 'en' as const,
routing: 'prefix-except-default',
siteUrl: 'https://example.com',
localeLabels: { /* ... */ },
} as const;
Features
- •Scroll preservation - Position maintained on locale switch
- •Locale lifecycle - active/paused/deprecated states
- •Namespace support - Split translations for large projects
- •SEO lint CI - Build-time validation script
Hreflang Rules (SEO Critical)
- •Self-referencing - Every page links to itself
- •Bidirectional - If A→B, then B→A
- •x-default - Always include
- •Absolute URLs - Full URLs with protocol
- •Canonical alignment - Must match one hreflang
html
<link rel="canonical" href="https://example.com/about" /> <link rel="alternate" hreflang="en" href="https://example.com/about" /> <link rel="alternate" hreflang="hu" href="https://example.com/hu/about" /> <link rel="alternate" hreflang="x-default" href="https://example.com/about" />
Usage
astro
---
// Layout
import HreflangHead from '@/i18n/components/HreflangHead.astro';
import { getCurrentLocale, getPathWithoutLocale } from '@/i18n/routing';
const locale = getCurrentLocale(Astro.url);
---
<html lang={locale}>
<head><HreflangHead currentLocale={locale} currentPath={getPathWithoutLocale(Astro.url)} /></head>
---
// Page - use t() for ALL strings
import { t } from '@/i18n/utils';
---
<h1>{t(Astro.params.locale, 'hero.title', { company: 'Acme' })}</h1>
Astro Config
javascript
export default defineConfig({
site: 'https://example.com',
i18n: { defaultLocale: 'en', locales: ['en', 'hu'], routing: { prefixDefaultLocale: false } },
});
Next.js → See references/nextjs.md
References
- •Hreflang: references/hreflang.md - SEO rules, x-default, common mistakes
- •Next.js: references/nextjs.md - App Router, static export
- •Translations: references/translations.md - Namespaces, validation, CI lint
- •Cloudflare: references/cloudflare.md - Cache headers, Accept-Language
Incomplete Translations & Indexing
typescript
// Exclude incomplete locales from hreflang
<HreflangHead currentLocale={locale} currentPath={path} availableLocales={['en']} />
| Completeness | Action |
|---|---|
| 100% | index, follow |
| <80% | noindex or redirect |
| Machine-translated | noindex |
Forbidden Patterns
❌ Hardcoded strings · ❌ Missing lang on <html> · ❌ Hreflang without self-reference · ❌ Relative hreflang URLs · ❌ Emoji flags without text (a11y) · ❌ Missing translation shows key
Build Validation
Run npm run i18n:check before deploy. Build fails if: missing keys, broken placeholders, canonical≠hreflang mismatch. See references/translations.md.
Checklist
- •
i18nConfigwith all locales - • Translation JSON per locale (validated)
- •
<HreflangHead>in layout - •
langon<html> - • x-default set
- • Zero hardcoded strings
- • Incomplete pages excluded from hreflang
- • Build validation passes