18ways ब्लॉग

Next.js ऐप में कई भाषाएँ कैसे जोड़ें

मौजूदा Next.js ऐप में SEO को नुकसान पहुँचाए बिना या सब कुछ फिर से बनाए बिना बहुभाषी i18n और स्थानीयकरण कैसे जोड़ें।

I18n (अंतरराष्ट्रीयकरण), l10n (स्थानीयकरण), बहुभाषी समर्थन… आप चाहे इसे जैसे भी कहें, आखिरकार आपको अपने Next.js ऐप को एक से अधिक भाषा सपोर्ट करने के लिए अपडेट करना होगा।

कठिनाई एक वाक्य का अनुवाद करने में नहीं है। असल चुनौती है किसी वास्तविक Next.js ऐप में SEO को नुकसान पहुँचाए बिना, अपने कोडबेस को उलझाए बिना, या अपने लिए रखरखाव की समस्या बनाए बिना, कई भाषाएँ जोड़ना।

अगर आप सीधे कोड पर जाना चाहते हैं, तो 18ways-next GitHub उदाहरण देखें।

पहले अपना इन्फ्रा सेट अप करें

सबसे पहले, हमें अपने पैकेज इंस्टॉल करने होंगे। हम 18ways लाइब्रेरीज़ सेट अप करेंगे, लेकिन अगर आपका प्रोजेक्ट सरल है और उसमें सिर्फ़ स्थिर सामग्री है, तो i18next जैसे टूल भी अच्छी तरह काम करते हैं।

bash
npm install @18ways/next @18ways/react

अपनी कॉन्फ़िग फ़ाइल बनाएँ:

js
// 18ways.config.js
module.exports = {
  apiKey: 'pk_dummy_demo_token',
  baseLocale: 'en-GB',
  router: 'app', // 'app', or 'path' depending on which Next.js router you are using
};

अपनी Next.js कॉन्फ़िग को रैप करें:

js
// next.config.js
const { withWays } = require('@18ways/next/config');
 
const nextConfig = {
  /*
   * your normal Next.js config here
   */ 
};
 
module.exports = withWays(nextConfig);

एक रूट प्रॉक्सी जोड़ें ताकि / सही लोकेल पर रीडायरेक्ट कर सके:

js
// proxy.js
export { default, config } from '@18ways/next/proxy';

फिर एक स्थानीयकृत लेआउट जोड़ें:

jsx
// app/layout.jsx
import './styles.css';
import { WaysRoot } from '@18ways/next/server';
 
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className="next-demo-body">
        <WaysRoot>{children}</WaysRoot>
      </body>
    </html>
  );
}

और बस, हो गया! अब आपके पास है:

अपना पहला पेज अनुवाद करें

अब आप अपना पहला पेज अनुवाद कर सकते हैं। i18next जैसी टूल्स में आपको अपना सारा टेक्स्ट अलग करके अनुवाद कुंजियों में रखना होगा, और फिर अपने कोड में उन्हें संदर्भित करना होगा। यह ठीक-ठीक कैसा दिखेगा, यह आपके चुनी गई लाइब्रेरी पर निर्भर करेगा।

अगर आप 18ways का उपयोग कर रहे हैं, तो जिस टेक्स्ट का आप अनुवाद करना चाहते हैं, उसे बस <T> कॉम्पोनेंट में रैप करें:

jsx
// src/components/MyExampleComponent.jsx
import { useT, T } from '@18ways/react';
 
export default function MyExampleComponent() {
  const t = useT();
 
  return (
    <div>
      <T>Hello world!</T>
      <img
        src="https://example.com/image.png"
        alt={t('Example image')}
      />
    </div>
  );
}

आप शायद चाहते हैं कि उपयोगकर्ता अपनी भाषा की पसंद बदल सकें:

jsx
// src/components/Footer.jsx
import { T, LanguageSwitcher } from '@18ways/react';
 
export default function Footer() {
  return (
    <footer>
      <T>My footer content</T>
      <LanguageSwitcher />
    </footer>
  );
}

इन नुकसानों से सावधान रहें

एक बार बुनियादी सेटअप हो जाने पर, इन प्रोजेक्ट्स में आम तौर पर कुछ चीज़ें गड़बड़ हो जाती हैं।

सर्वर रेंडरिंग

सर्वर-साइड रेंडरिंग (SSR) वही है जो आपकी Next.js ऐप को सर्वर पर पहले से रेंडर किया हुआ HTML देने देता है। यह SEO के लिए भी बेहद ज़रूरी है, और इसीलिए भी कि उपयोगकर्ता को खराब सामग्री या गलत भाषा की झलकियाँ न दिखें।

अगर आप i18next जैसी किसी लाइब्रेरी का उपयोग कर रहे हैं, तो आपको बहुत सावधानी रखनी होगी कि आपके अनुवाद सर्वर-साइड रेंडर के दौरान लोड और पॉप्युलेट हो रहे हों। आप अपने पेज के view-source: को देखकर इसका परीक्षण कर सकते हैं, जैसे view-source:http://localhost:3000/। आपको इसे प्रोडक्शन में भी जाँचना चाहिए, ताकि यह सुनिश्चित हो सके कि यह आपके प्रोडक्शन बिल्ड में भी काम करता है।

अगर आप 18ways का उपयोग कर रहे हैं, तो इसकी चिंता मत कीजिए। यह सब आपके लिए संभाला जाता है।

अनुवाद कुंजियाँ

अगर आप 18ways का उपयोग कर रहे हैं, तो आपको इसकी बिल्कुल चिंता करने की ज़रूरत नहीं है। 18ways को अनुवाद कुंजियों की आवश्यकता नहीं होती; आप अपना टेक्स्ट सामान्य की तरह उसी जगह रहने दे सकते हैं।

कई i18n सिस्टम में आपको अपने कोड को अनुवाद कुंजियों में बाँटना पड़ता है। इन कुंजियों के नाम सोच-समझकर रखने चाहिए।

खराब कुंजियाँ धुंधली होती हैं:

js
// bad-keys.en-GB.js
module.exports = {
  title: 'Continue',
  button: 'Pay now',
  label: 'Home',
};

ये कुंजियाँ अनुवादकों और डेवलपर्स को लगभग कुछ भी नहीं बतातीं कि टेक्स्ट कहाँ दिखता है।

बेहतर कुंजियों में संदर्भ शामिल होता है:

js
// better-keys.en-GB.js
module.exports = {
  'checkout.payment.primaryButton': 'Pay now',
  'checkout.payment.stepTitle': 'Complete your payment',
  'account.sidebar.homeLink': 'Home',
};

डायनामिक रूप से कुंजियाँ बनाना भी टालें:

js
// bad-dynamic-keys.js
const key = `checkout.${status}.${buttonType}`;
const translatedText = t(key);

यह उन IDE टूल्स को तोड़ देगा जो अनुवाद कुंजियों को कम बोझिल बनाने की कोशिश करते हैं। इससे पुराने अनुवाद कुंजियों को खोजना और साफ़ करना भी बेहद मुश्किल हो जाएगा।

js
// better-dynamic-keys.js
const keyMap = {
  success: {
    primary: t('checkout.success.primary'),
    default: t('checkout.success.default'),
  },
  error: {
    primary: t('checkout.error.primary'),
    default: t('checkout.error.default'),
  },
};
 
const translatedText = keyMap[status][buttonType];

यह DRY कम है, लेकिन अनुवाद कुंजियों को बेकाबू होने से रोकने का सबसे अच्छा तरीका है।

और भी बेहतर है 18ways जैसे टूल का उपयोग करना, तब आपको अनुवाद कुंजियों की ज़रूरत ही नहीं पड़ेगी:

jsx
// app/[lang]/checkout/page.jsx
<T>Pay now</T>

लोकेल का पता लगाना

लोकेल का पता लगाने का मतलब है कि उपयोगकर्ता के भाषा-स्विच करने से पहले ही तय करना कि उसे कौन-सी भाषा दिखानी चाहिए।

आम तौर पर इसमें इनमें से कुछ का संयोजन शामिल होता है:

साधारण Next.js मिडलवेयर में, यह अक्सर कुछ ऐसा दिखता है:

js
// middleware.js
 
/**
 * You don't need any of this if you're using 18ways 
 */
 
import { NextResponse } from 'next/server';
 
const acceptedLocales = ['en-GB', 'fr-FR'];
 
export function middleware(request) {
  const savedLocale =
    request.cookies.get('preferred-locale')?.value;
  const browserLocale =
    request.headers
      .get('accept-language')
      ?.split(',')[0] || 'en-GB';
 
  const locale = acceptedLocales.includes(savedLocale)
    ? savedLocale
    : acceptedLocales.includes(browserLocale)
      ? browserLocale
      : 'en-GB';
 
  if (request.nextUrl.pathname === '/') {
    return NextResponse.redirect(
      new URL(`/${locale}`, request.url)
    );
  }
 
  return NextResponse.next();
}

कुछ लाइब्रेरीज़ इसे आसान बनाने के लिए अलग-अलग स्तर तक सहायक फ़ंक्शन देती हैं। 18ways के साथ, शुरुआती पहचान और रीडायरेक्ट लेयर आपके लिए संभाली जाती है।

तिथियाँ और लोकेल-विशिष्ट प्रिमिटिव्स

अलग-अलग लोकेल तारीख़ों, संख्याओं और पैसे को अलग तरह से फ़ॉर्मैट करती हैं।

उदाहरण के लिए:

सामान्य JavaScript में, आप Intl के साथ इसे खुद संभालते हैं:

js
// formatting-dates-and-currency.js
const myLocale = getCurrentLocale(); // depends on your lib
 
const someTimestamp = new Date('2026-04-13T09:00:00Z');
const dateLabel = new Intl.DateTimeFormat('fr-FR', {
  dateStyle: 'long',
}).format(someTimestamp);
 
const someMoney = {
  amount: 1999,
  currency: 'EUR',
};
const moneyLabel = new Intl.NumberFormat('de-DE', {
  style: 'currency',
  currency: someMoney.currency,
}).format(someMoney.amount);
 
const translatedText = t(
  'my.translation.key',
  { dateLabel, moneyLabel }
);

अगर आप 18ways इस्तेमाल कर रहे हैं, तो यह आपके लिए संभाल लिया जाता है:

jsx
const someTimestamp = new Date('2026-04-13T09:00:00Z');
const someMoney = {
  amount: 1999,
  currency: 'EUR',
};
 
<T>My text with {{ someTimestamp }} and {{ someMoney }}</T>

स्ट्रिंग्स को जोड़ना

फ्रैगमेंट्स को जोड़कर अनुवादित UI न बनाएँ:

jsx
// bad-string-joining.jsx
const clickHereText = t('click.here');
const toGetStartedText = t('to.get.started')
<p><a href="#">{clickHereText}</a> {toGetStartedText}.</p>

यह कई भाषाओं में टूट जाएगा।

आप अंग्रेज़ी में कह सकते हैं “👉👉यहाँ क्लिक करें👈👈 और शुरू करें”, लेकिन फ़्रेंच में यह ज़्यादा स्वाभाविक है: “शुरू करने के लिए, 👉👉यहाँ क्लिक करें👈👈”. जापानी जैसी कुछ भाषाओं में तो “始めるには👉👉こちら👈👈をクリックしてください” जैसी रचना में पहले और बाद—दोनों तरफ़ शब्दों की ज़रूरत होगी।

वाक्य की संरचना बदल सकती है, इसलिए उसे हिस्सों में बाँटना अच्छा अनुवाद करना बहुत कठिन बना देता है।

पूरे वाक्य बेहतर अनुवादित होते हैं, क्योंकि अनुवादक शब्दों को स्वाभाविक रूप से फिर से व्यवस्थित कर सकते हैं और अर्थ को एक समग्र इकाई के रूप में देख सकते हैं।

अगर आप इस तरह JSX का अनुवाद कर रहे हैं:

jsx
// rich-text-message.jsx
<p>
  Something <strong>that we want to be bold</strong>
</p>

इसका प्रबंधन कैसे करना है, इसके लिए आपको अपनी i18n लाइब्रेरी से परामर्श करना होगा, क्योंकि अलग-अलग लाइब्रेरीज़ इसे बहुत अलग तरीकों से संभालती हैं।

अगर आप 18ways का उपयोग कर रहे हैं, तो आप पूरे JSX ब्लॉक का अनुवाद बस सामान्य तरीके से कर सकते हैं।

jsx
// rich-text-message.jsx
<p>
  <T>Something <strong>that we want to be bold</strong></T>
</p>

वैरिएबल्स

वैरिएबल्स आपको वाक्य को पूरा बनाए रखते हुए रनटाइम मान डालने देते हैं।

ज़्यादातर i18n लाइब्रेरीज़ किसी न किसी रूप में इसका समर्थन करती हैं। 18ways के साथ:

jsx
// app/[lang]/page.jsx
<T>Hello {{ name: 'Ada' }}</T>

यह पैटर्न इनके लिए अच्छी तरह काम करता है:

वाक्य पढ़ने योग्य बना रहता है, और मान स्पष्ट रहता है।

अगर आपको वैरिएबल को भी अनुवादित करना है, तो उसे t(...) में लपेटना सुनिश्चित करें:

jsx
const animal = t('dog');
<T>Favourite animal: {{ animal }}</T>

बहुवचन

बहुवचन के नियम भाषा के अनुसार अलग होते हैं। अंग्रेज़ी में एक ही बहुवचन रूप है (1 year, 2 years, आदि)। पोलिश में कई बहुवचन रूप हैं (1 rok, 2 lata, 5 lat)। जापानी में तो बहुवचन रूप होता ही नहीं (1 年, 2 年, 3 年)।

ज़्यादातर i18n लाइब्रेरीज़ बहुवचनों को संभालने के लिए ICU-जैसी सिंटैक्स निर्दिष्ट करने देती हैं:

jsx
// app/[lang]/page.jsx
<T>
  {{
    unreadCount,
    format:
      'plural, =0{No unread messages} =1{One unread message} other{{unreadCount} unread messages}',
  }}
</T>

ऊपर दिया गया तरीका 18ways में काम करेगा, लेकिन ज़्यादातर मामलों में आप बस यह भी कर सकते हैं:

jsx
// app/[lang]/page.jsx
<T>{{ unreadCount }} unread messages</T>

18ways आपके लिए बहुवचन रूपों को संभाल लेगा!

अभी शुरू करें

Next.js ऐप में कई भाषाएँ जोड़ना किसी रीराइट प्रोजेक्ट में बदलने की ज़रूरत नहीं है।

अगर आप 18ways जैसी किसी समाधान का उपयोग नहीं कर रहे हैं, तो सुनिश्चित करें कि आपका रूटिंग और SSR सही हो; फिर अपनी कॉपी को अनुवाद कुंजियों में स्थानांतरित करने की प्रक्रिया से गुज़रें, और सामान्य i18n pitfalls से बचने का ध्यान रखें।

अगर आप 18ways का उपयोग कर रहे हैं, तो यह सब आपके लिए संभाल लिया जाता है, और आपको बस अपने टेक्स्ट को <T> ब्लॉक्स में रैप करना शुरू करना है!