Internationalization in Next.js: 11-Locale Implementation

Build a production-ready 11-locale Next.js app with next-intl v4. Covers routing, RTL support, translation workflows, hreflang SEO, and server/client component patterns.

E
ECOSIRE Research and Development Team
|19 मार्च 202610 मिनट पढ़ें2.1k शब्द|

Next.js में अंतर्राष्ट्रीयकरण: 11-लोकेल कार्यान्वयन

वैश्विक दर्शकों के लिए निर्माण के लिए Google अनुवाद के माध्यम से अपनी स्ट्रिंग चलाने से कहीं अधिक की आवश्यकता होती है। उत्पादन अंतर्राष्ट्रीयकरण कार्यान्वयन में रूटिंग रणनीति, आरटीएल (दाएं से बाएं) लेआउट समर्थन, सर्वर-साइड लोकेल रिज़ॉल्यूशन, अनुवाद कुंजी संगठन, एसईओ ह्राफ्लैंग सिग्नल और एक स्थायी अनुवाद वर्कफ़्लो शामिल है। इनमें से कोई भी गलत होने पर आपके पास या तो अरबी/उर्दू उपयोगकर्ताओं के लिए टूटे हुए लेआउट होंगे, गायब hreflang टैग होंगे जो आपकी खोज रैंकिंग को विभाजित करेंगे, या एक अनुवाद पाइपलाइन जो सामग्री विकास के भार के तहत ध्वस्त हो जाएगी।

यह गाइड ECOSIRE.COM पर उपयोग किए गए संपूर्ण 11-लोकेल सेटअप का दस्तावेजीकरण करता है - अंग्रेजी प्लस चीनी, स्पेनिश, अरबी, पुर्तगाली, फ्रेंच, जर्मन, जापानी, तुर्की, हिंदी और उर्दू - जो नेक्स्ट-इंटल v4.8 पर बनाया गया है। यहां प्रत्येक पैटर्न का 5,577 एमडीएक्स सामग्री फ़ाइलों और 12,543 अनुवाद कुंजियों में उत्पादन-परीक्षण किया गया है।

मुख्य बातें

  • localePrefix: 'as-needed' का प्रयोग करें - अंग्रेजी में कोई उपसर्ग नहीं है, अन्य स्थानों को /zh/, /ar/ आदि मिलते हैं।
  • नेक्स्ट-इंटल v4 routing.ts + navigation.ts पैटर्न का उपयोग करता है - कभी भी स्थानीय-जागरूक घटकों में next/navigation से सीधे आयात न करें
  • अरबी (ar) और उर्दू (ur) के लिए <html> पर dir="rtl" और एक RTL-संगत फ़ॉन्ट (नोटो सैन्स अरबी) की आवश्यकता होती है।
  • en.json कुंजियाँ नेस्टेड ऑब्जेक्ट होनी चाहिए - फ्लैट डॉट-पृथक कुंजियाँ नेमस्पेस रिज़ॉल्यूशन को तोड़ देती हैं
  • प्रत्येक पृष्ठ पर hreflang के लिए alternates.languages के साथ generateMetadata() (कभी स्थिर export const metadata नहीं)
  • सर्वर घटक getTranslations('namespace') का उपयोग करते हैं, क्लाइंट घटक useTranslations('namespace') का उपयोग करते हैं
  • व्यवस्थापक पृष्ठ दो अनुवाद हुक का उपयोग करते हैं: मॉड्यूल-विशिष्ट कुंजियों के लिए t, साझा admin.common कुंजियों के लिए tc
  • Google Translate API के साथ नई कुंजियों का स्वचालित अनुवाद; मैन्युअल अनुवाद पर विकास को कभी भी अवरुद्ध न करें

प्रोजेक्ट सेटअप

pnpm add next-intl@4

प्रत्येक ऐप के लिए निर्देशिका संरचना:

src/
  i18n/
    routing.ts      # defineRouting() — locale list, prefix strategy
    navigation.ts   # createNavigation() — locale-aware Link, useRouter
    request.ts      # getRequestConfig() — server-side message loading
  messages/
    en.json         # Source of truth (~12,543 keys)
    zh.json
    es.json
    ar.json
    pt.json
    fr.json
    de.json
    ja.json
    tr.json
    hi.json
    ur.json

रूटिंग कॉन्फ़िगरेशन

// src/i18n/routing.ts
import { defineRouting } from 'next-intl/routing';

export const locales = [
  'en', 'zh', 'es', 'ar', 'pt', 'fr', 'de', 'ja', 'tr', 'hi', 'ur',
] as const;

export type Locale = (typeof locales)[number];

export const rtlLocales: Locale[] = ['ar', 'ur'];

export const localeNames: Record<Locale, string> = {
  en: 'English',
  zh: '中文',
  es: 'Español',
  ar: 'العربية',
  pt: 'Português',
  fr: 'Français',
  de: 'Deutsch',
  ja: '日本語',
  tr: 'Türkçe',
  hi: 'हिन्दी',
  ur: 'اردو',
};

export const routing = defineRouting({
  locales,
  defaultLocale: 'en',
  localePrefix: 'as-needed', // /en/blog → /blog, /zh/blog → /zh/blog
});
// src/i18n/navigation.ts
import { createNavigation } from 'next-intl/navigation';
import { routing } from './routing';

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

मिडलवेयर/प्रॉक्सी

नेक्स्ट-इंटल v4 लोकेल डिटेक्शन और रूटिंग के लिए एक मिडलवेयर फ़ंक्शन का उपयोग करता है:

// src/proxy.ts (not middleware.ts — naming matters for Next.js 16)
import createMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';
import { NextRequest, NextResponse } from 'next/server';

const intlMiddleware = createMiddleware(routing);

export function middleware(request: NextRequest) {
  // Auth protection for dashboard routes
  const isDashboard = request.nextUrl.pathname.includes('/dashboard');
  const isPortal = request.nextUrl.pathname.includes('/portal');
  const authCookie = request.cookies.get('ecosire_auth');

  if ((isDashboard || isPortal) && !authCookie) {
    const loginUrl = new URL('/auth/login', request.url);
    // Prevent open redirect: only allow relative redirect paths
    const from = request.nextUrl.pathname;
    if (from.startsWith('/') && !from.startsWith('//')) {
      loginUrl.searchParams.set('from', from);
    }
    return NextResponse.redirect(loginUrl);
  }

  return intlMiddleware(request);
}

export const config = {
  matcher: [
    '/((?!api|_next/static|_next/image|.*\\..*|_vercel|favicon.ico).*)',
  ],
};

सर्वर-साइड संदेश लोड हो रहा है

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

export default getRequestConfig(async ({ requestLocale }) => {
  let locale = await requestLocale;

  // Validate locale is supported; fall back to default
  if (!locale || !routing.locales.includes(locale as any)) {
    locale = routing.defaultLocale;
  }

  return {
    locale,
    messages: (await import(`../messages/${locale}.json`)).default,
  };
});

आरटीएल और फ़ॉन्ट समर्थन के साथ लोकेल लेआउट

// src/app/[locale]/layout.tsx
import { NextIntlClientProvider } from 'next-intl';
import { getMessages, getTranslations } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { routing, rtlLocales, type Locale } from '@/i18n/routing';
import { Noto_Sans, Noto_Sans_Arabic } from 'next/font/google';

const notoSans = Noto_Sans({ subsets: ['latin'], variable: '--font-sans' });
const notoArabic = Noto_Sans_Arabic({
  subsets: ['arabic'],
  variable: '--font-arabic',
});

export async function generateStaticParams() {
  return routing.locales.map((locale) => ({ locale }));
}

export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
  const { locale } = await params;
  const t = await getTranslations({ locale, namespace: 'metadata' });
  return {
    title: { template: `%s | ${t('siteName')}`, default: t('defaultTitle') },
    other: { 'content-language': locale }, // For Bing/AI engine language detection
  };
}

export default async function LocaleLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: Promise<{ locale: string }>;
}) {
  const { locale } = await params;

  if (!routing.locales.includes(locale as Locale)) {
    notFound();
  }

  const messages = await getMessages();
  const isRtl = rtlLocales.includes(locale as Locale);

  return (
    <html
      lang={locale}
      dir={isRtl ? 'rtl' : 'ltr'}
      className={`${notoSans.variable} ${notoArabic.variable}`}
    >
      <body>
        <NextIntlClientProvider messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

अनुवाद प्रमुख संगठन

en.json संरचना में नेस्टेड ऑब्जेक्ट का उपयोग होना चाहिए, फ़्लैट डॉट से अलग की गई कुंजियों का नहीं:

// WRONG — flat keys break next-intl namespace resolution
{
  "nav.home": "Home",
  "nav.blog": "Blog",
  "admin.common.save": "Save"
}

// CORRECT — nested objects
{
  "nav": {
    "home": "Home",
    "blog": "Blog"
  },
  "admin": {
    "common": {
      "save": "Save",
      "cancel": "Cancel",
      "delete": "Delete",
      "edit": "Edit",
      "status": "Status"
    },
    "contacts": {
      "title": "Contacts",
      "addContact": "Add Contact",
      "searchPlaceholder": "Search contacts..."
    }
  }
}

सर्वर घटक

// Server component: use getTranslations (async)
import { getTranslations } from 'next-intl/server';

export default async function ServicesPage({ params }: Props) {
  const { locale } = await params;
  const t = await getTranslations({ locale, namespace: 'services.odoo' });

  return (
    <main>
      <h1>{t('hero.title')}</h1>
      <p>{t('hero.description')}</p>
    </main>
  );
}

// generateMetadata must also fetch translations for locale-aware metadata
export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { locale } = await params;
  const t = await getTranslations({ locale, namespace: 'services.odoo' });

  return {
    title: t('meta.title'),
    description: t('meta.description'),
    alternates: {
      canonical: `https://ecosire.com${locale === 'en' ? '' : `/${locale}`}/services/odoo`,
      languages: {
        'x-default': 'https://ecosire.com/services/odoo',
        en: 'https://ecosire.com/services/odoo',
        zh: 'https://ecosire.com/zh/services/odoo',
        ar: 'https://ecosire.com/ar/services/odoo',
        // ... all 11 locales
      },
    },
  };
}

ग्राहक घटक

// Client component: use useTranslations (synchronous)
'use client';

import { useTranslations } from 'next-intl';

// Admin pages use two hooks: module-specific + shared admin.common
export default function ContactsPage() {
  const t  = useTranslations('admin.contacts'); // Module-specific
  const tc = useTranslations('admin.common');   // Shared terms

  // IMPORTANT: stateConfig must be inside component (after hooks init)
  const stateConfig = {
    active:   { label: tc('statusActive'),   className: 'bg-green-100 text-green-800' },
    inactive: { label: tc('statusInactive'), className: 'bg-gray-100 text-gray-700'  },
  };

  return (
    <div>
      <h1>{t('title')}</h1>
      <button>{tc('addNew')}</button>
      {/* ... */}
    </div>
  );
}

भाषा स्विचर घटक

// components/language-switcher.tsx
'use client';

import { useLocale } from 'next-intl';
import { usePathname, useRouter } from '@/i18n/navigation'; // NOT next/navigation
import { locales, localeNames } from '@/i18n/routing';
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Globe } from 'lucide-react';

export function LanguageSwitcher() {
  const locale = useLocale();
  const pathname = usePathname();
  const router = useRouter();

  const handleLocaleChange = (newLocale: string) => {
    router.replace(pathname, { locale: newLocale });
  };

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <button aria-label="Change language">
          <Globe className="h-4 w-4" />
          <span>{localeNames[locale as keyof typeof localeNames]}</span>
        </button>
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end">
        {locales.map((loc) => (
          <DropdownMenuItem
            key={loc}
            onClick={() => handleLocaleChange(loc)}
            className={loc === locale ? 'font-bold' : ''}
          >
            {localeNames[loc]}
          </DropdownMenuItem>
        ))}
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

अनुवाद कार्यप्रवाह

नई कुंजियाँ जोड़ना

  1. पहले en.json में जोड़ें (सच्चाई का स्रोत)
  2. अनुवाद स्क्रिप्ट चलाएँ:
node apps/web/scripts/translate-missing.mjs

स्क्रिप्ट en.json में मौजूद लेकिन अन्य स्थानीय फ़ाइलों से गायब कुंजियाँ ढूंढती है और Google Translate API को कॉल करती है:

// scripts/translate-missing.mjs (simplified)
import { Translate } from '@google-cloud/translate/build/src/v2/index.js';
import { readFileSync, writeFileSync } from 'fs';

const translate = new Translate({ key: process.env.GOOGLE_TRANSLATE_API_KEY });

async function translateMissingKeys(sourceLocale, targetLocale, sourceMessages, targetMessages) {
  const missing = findMissingKeys(sourceMessages, targetMessages);

  for (const [keyPath, value] of missing) {
    const [translated] = await translate.translate(value, targetLocale);
    setNestedKey(targetMessages, keyPath, translated);
  }

  return targetMessages;
}

अनुवाद कवरेज को मान्य करना

# Check for missing keys across all locales
node apps/web/scripts/validate-blog-i18n.mjs

# Fix flat keys accidentally added
node apps/web/scripts/fix-en-flat-keys.mjs

ब्लॉग सामग्री अनुवाद

एमडीएक्स सामग्री फ़ाइलों के लिए, फ्रंटमैटर शीर्षक अंग्रेजी में रहते हैं (शीर्षक i18n JSON में हैं), लेकिन मुख्य सामग्री का अनुवाद प्रति स्थान पर किया जाता है:

src/content/blog/
  en/        # English originals (or root directory)
    odoo-erp-guide.mdx
  zh/
    odoo-erp-guide.mdx  # Translated body
  ar/
    odoo-erp-guide.mdx
  # ... 10 locale directories
// src/lib/blog.ts — locale-aware loader
export async function getPost(slug: string, locale: string) {
  // Try locale-specific file first
  const localePath = path.join(contentDir, locale, `${slug}.mdx`);
  const englishPath = path.join(contentDir, `${slug}.mdx`);

  const filePath = fs.existsSync(localePath) ? localePath : englishPath;

  const raw = fs.readFileSync(filePath, 'utf8');
  const { data: frontmatter, content } = matter(raw);
  return { frontmatter, content, locale };
}

अक्सर पूछे जाने वाले प्रश्न

always के बजाय localePrefix: 'as-needed' का उपयोग क्यों करें?

as-needed के साथ, अंग्रेजी यूआरएल में कोई उपसर्ग (/blog/post) नहीं होता है, जबकि अन्य स्थानों में एक उपसर्ग (/zh/blog/post) मिलता है। यह मानक परिपाटी है - अंग्रेजी डिफ़ॉल्ट है और इसमें साफ़ यूआरएल हैं। always के साथ, अंग्रेजी को /en/blog/post भी मिलता है, जो अनावश्यक रीडायरेक्ट बनाता है और यदि आप माइग्रेट कर रहे हैं तो आपकी मौजूदा अंग्रेजी लिंक इक्विटी को विभाजित करता है।

en.json कुंजियाँ नेस्टेड ऑब्जेक्ट क्यों होनी चाहिए न कि फ़्लैट डॉट-सेपरेटेड स्ट्रिंग्स?

नेक्स्ट-इंटल का नेमस्पेस रिज़ॉल्यूशन JSON ट्री को पार करने के लिए नेमस्पेस स्ट्रिंग को डॉट्स द्वारा विभाजित करता है। यदि आपकी कुंजियाँ "admin.contacts.title" जैसी फ्लैट डॉट-पृथक स्ट्रिंग हैं, तो नेक्स्ट-इंटल नेस्टेड पथ के रूप में messages.admin.contacts.title की तलाश करता है, लेकिन पाता है कि कुंजी वस्तुतः मूल स्तर पर स्ट्रिंग "admin.contacts.title" है, जिससे लुकअप विफलता हो जाती है। हमेशा वास्तविक नेस्टेड वस्तुओं का उपयोग करें।

मैं विभिन्न भाषाओं में बहुवचन को कैसे संभालूं?

नेक्स्ट-इंटल बहुवचनीकरण के लिए आईसीयू संदेश प्रारूप का उपयोग करता है। अंग्रेजी में दो बहुवचन रूप हैं (एक/अन्य), अरबी में छह हैं (शून्य, एक, दो, कुछ, अनेक, अन्य)। अपने अनुवाद मूल्यों में {count, plural, one {# item} other {# items}} आईसीयू सिंटैक्स का उपयोग करें और नेक्स्ट-इंटल स्वचालित रूप से प्रत्येक स्थान के लिए सही बहुवचन नियम लागू करता है।

क्या मैं पेज राउटर के साथ नेक्स्ट-इंटल का उपयोग कर सकता हूं?

नेक्स्ट-इंटल v4 मुख्य रूप से ऐप राउटर के लिए डिज़ाइन किया गया है। पेज राउटर के लिए, नेक्स्ट-इंटल v2 या v3 का उपयोग करें। पैटर्न काफी भिन्न हैं: पेज राउटर लेआउट में getStaticProps + NextIntlProvider का उपयोग करता है, जबकि ऐप राउटर लेआउट में getRequestConfig + NextIntlClientProvider का उपयोग करता है। यदि आप पेज राउटर पर हैं, तो i18n को बड़े पैमाने पर लागू करने से पहले ऐप राउटर पर माइग्रेट करने पर विचार करें।

मैं अरबी और उर्दू के लिए आरटीएल समर्थन कैसे स्थापित करूं?

जब स्थान ar या ur हो तो <html> तत्व पर dir="rtl" सेट करें। दिशा-विशिष्ट शैलियों के लिए टेलविंड के rtl: वैरिएंट उपसर्ग का उपयोग करें (rtl:mr-4 RTL में ml-4 बन जाता है)। एक संगत फ़ॉन्ट लोड करें - नोटो सैन्स अरबी में अरबी और उर्दू दोनों लिपियाँ शामिल हैं। हार्डकोडेड left/right CSS मानों से बचें; start/end तार्किक गुणों (ms-4, me-4 Tailwind v4 में) का उपयोग करें।

बड़ी मात्रा में सामग्री का शीघ्रता से अनुवाद करने का सबसे अच्छा तरीका क्या है?

सभी कुंजियों और सामग्री के प्रारंभिक मशीनी अनुवाद के लिए Google अनुवाद एपीआई का उपयोग करें, फिर देशी वक्ताओं से उच्च-दृश्यता सामग्री (मुखपृष्ठ, उत्पाद विवरण, सीटीए कॉपी) की समीक्षा कराएं। ब्लॉग सामग्री के लिए, मशीनी अनुवाद शुरुआती बिंदु के रूप में स्वीकार्य है लेकिन तकनीकी सटीकता के लिए इसकी समीक्षा की जानी चाहिए। ट्रैफ़िक के आधार पर कम से कम शीर्ष 3-5 स्थानों की देशी वक्ता समीक्षा के लिए बजट।


अगले चरण

सही ढंग से किया गया अंतर्राष्ट्रीयकरण एक प्रतिस्पर्धी खाई है - आपकी सामग्री 11 गुना अधिक संभावित ग्राहकों तक पहुंचती है, आपके एसईओ ह्राफ्लैंग सिग्नल स्प्लिट लिंक इक्विटी के बजाय समेकित होते हैं, और उपयोगकर्ता अपनी मूल भाषा में काफी उच्च दरों पर परिवर्तित होते हैं।

ECOSIRE ने 5,577 सामग्री फ़ाइलों, 12,543 अनुवाद कुंजियों और RTL लेआउट समर्थन को कवर करते हुए पूर्ण 11-लोकेल कार्यान्वयन का निर्माण और रखरखाव किया है। यदि आपको अपने नेक्स्ट.जेएस एप्लिकेशन के लिए अंतर्राष्ट्रीयकरण की आवश्यकता है, तो यह जानने के लिए कि हम इसे आपके कोडबेस के लिए कैसे कार्यान्वित कर सकते हैं, हमारी फ्रंटएंड इंजीनियरिंग सेवाओं का पता लगाएं

शेयर करें:
E

लेखक

ECOSIRE Research and Development Team

ECOSIRE में एंटरप्राइज़-ग्रेड डिजिटल उत्पाद बना रहे हैं। Odoo एकीकरण, ई-कॉमर्स ऑटोमेशन, और AI-संचालित व्यावसायिक समाधानों पर अंतर्दृष्टि साझा कर रहे हैं।

WhatsApp पर चैट करें