ब्लॉग पर वापस जाएं
A Contentful-style content entry card showing a filled English locale tab and an empty French locale tab with a warning icon.

आपकी Contentful सामग्री अभी तक वास्तव में बहुभाषी क्यों नहीं है

By Robert

क्यों आपका Contentful कंटेंट वास्तव में अभी तक बहुभाषी नहीं है

Contentful स्थानीयताओं को अच्छी तरह से संभालता है। आप जितनी चाहें उतनी स्थानीयताएँ परिभाषित कर सकते हैं, एक फॉलबैक चेन सेट कर सकते हैं, संपादक में उनके बीच स्विच कर सकते हैं, और एक फ्रंटेंड बना सकते हैं जो प्रत्येक मार्ग के लिए सही भाषा प्रदान करता है। बहुभाषी सामग्री के लिए बुनियादी ढांचा वास्तव में अच्छा है।

Contentful जो नहीं करता वह है आपकी सामग्री का अनुवाद करना।

यह सीधे कहने पर स्पष्ट लगता है, लेकिन यह आश्चर्यजनक रूप से आसान है कि "हमने स्थानीय फ़ील्ड सेट अप किए हैं" को "हम बहुभाषी हैं" के साथ भ्रमित कर लिया जाए। ये दोनों एक समान नहीं हैं। स्थानीय फ़ील्ड एक कंटेनर हैं। बहुभाषी का मतलब है कि कंटेनर में सामग्री है।

यदि आपके Contentful स्पेस में फ्रेंच और जर्मन स्थानीयताएँ कॉन्फ़िगर की गई हैं लेकिन उन स्थानीयताओं में फ़ील्ड खाली हैं, या अंग्रेज़ी कॉपी से भरे हुए हैं जो एक प्लेसहोल्डर के रूप में है, या एक मोटे प्रारंभिक संस्करण से भरे हुए हैं जिसे साइट लॉन्च के बाद से किसी ने समीक्षा नहीं किया है — तो आप बहुभाषी नहीं हैं। आपके पास बहुभाषी के लिए ढांचा है।

यह पोस्ट उस अंतर को बंद करने के बारे में है।


Contentful वास्तव में आपको क्या देता है

Contentful की लोकल सिस्टम अच्छी तरह से डिज़ाइन की गई है। प्रत्येक कंटेंट एंट्री के पास प्रति लोकल फील्ड वैल्यू हो सकती है। आप एक डिफ़ॉल्ट लोकल सेट करते हैं, फॉलबैक कॉन्फ़िगर करते हैं, और आपकी डिलीवरी API अनुरोध पर सही लोकल लौटाती है। कंटेंट मॉडलिंग विभिन्न कंटेंट प्रकारों में जटिल बहुभाषी आवश्यकताओं को संभालने के लिए पर्याप्त लचीली है।

लेकिन सिस्टम पूरी तरह से तटस्थ है कि अनुवादित कंटेंट वहां कैसे आता है। Contentful नहीं जानता कि आपकी फ्रेंच लोकल फील्ड्स में पेशेवर अनुवाद हैं, मशीन-अनुवादित कॉपी है, गलती से चिपका हुआ अंग्रेजी टेक्स्ट है, या कुछ भी नहीं। यह केवल वह स्टोर और डिलीवर करता है जो आप डालते हैं।

अनुवाद की समस्या पूरी तरह से आपकी है।


टीमें आमतौर पर इसे कैसे संभालती हैं (और कहाँ यह टूटता है)

अधिकांश टीमें जो Contentful अनुवाद को संभालती हैं, कुछ पैटर्न में से एक में आती हैं।

मैनुअल एक्सपोर्ट पैटर्न। एक डेवलपर Contentful से सामग्री एक्सपोर्ट करता है, इसे एक अनुवाद एजेंसी या फ्रीलांसर को भेजता है, वापस आने का इंतजार करता है, इसे पुनः स्वरूपित करता है, और आयात करता है। यह एक बार के लॉन्च के लिए काम करता है लेकिन जैसे-जैसे सामग्री बदलती है यह असंभव हो जाता है। स्रोत भाषा में हर अपडेट का मतलब है पूरे चक्र को फिर से करना। व्यवहार में, अनुवादित सामग्री जल्दी ही स्रोत से पीछे रह जाती है और किसी के पास इसे पकड़ने का समय नहीं होता।

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

"यह चलेगा" पैटर्न। अनुवादित सामग्री मौजूद है लेकिन इसे पहली बार बनाए जाने के बाद से समीक्षा नहीं की गई है। स्रोत भाषा तब से कई बार अपडेट हो चुकी है। आपकी मूल्य निर्धारण पृष्ठ का फ्रेंच संस्करण अभी भी उस योजना का संदर्भ देता है जिसे आपने आठ महीने पहले बंद कर दिया था। जर्मन होमपेज में अभी भी पुराना टैगलाइन है। किसी ने इसे फ्लैग नहीं किया क्योंकि कोई जांच नहीं करता।

तीनों पैटर्न में एक ही मूल समस्या साझा होती है: अनुवाद को एक बार का कार्य माना जाता है न कि सामग्री कार्यप्रवाह का एक सतत हिस्सा।


वास्तव में बहुभाषीकरण के लिए क्या आवश्यक है

Contentful में वास्तविक बहुभाषी सामग्री के लिए तीन चीजें एक साथ काम करती हैं।

सटीक प्रारंभिक अनुवाद। प्रत्येक लक्ष्य भाषा में प्रत्येक फ़ील्ड में ऐसा अनुवादित सामग्री होनी चाहिए जो सटीक, उपयुक्त रूप से स्थानीयकृत हो, और वास्तव में ऐसा लगे जैसे वह उस बाजार के लिए लिखा गया हो, न कि केवल एक बुनियादी अनुवाद उपकरण से गुजरा हो और बिना समीक्षा के छोड़ा गया हो।

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

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


PolyLingo कहाँ फिट बैठता है

PolyLingo का API संरचित सामग्री का अनुवाद करता है जबकि उसकी संरचना को बरकरार रखता है। Contentful के लिए, संबंधित प्रारूप JSON है — Contentful प्रविष्टि के फ़ील्ड मान, जिन्हें निकाला जाता है और API को भेजा जाता है, उसी संरचना के साथ अनुवादित होकर वापस आते हैं।

Node.js में मूल प्रवाह:

import PolyLingo from 'polylingo'
import { createClient } from 'contentful-management'

const poly = new PolyLingo({ apiKey: process.env.POLYLINGO_API_KEY })
const contentful = createClient({
  accessToken: process.env.CONTENTFUL_MANAGEMENT_TOKEN,
})

const space = await contentful.getSpace(process.env.CONTENTFUL_SPACE_ID)
const environment = await space.getEnvironment('master')

// उस प्रविष्टि को प्राप्त करें जिसे आप अनुवादित करना चाहते हैं
const entry = await environment.getEntry('your-entry-id')

// PolyLingo BCP-47 कोड का उपयोग करता है बिना क्षेत्र के (fr, de)।
// यदि वे भिन्न हैं तो इन्हें अपने Contentful लोकल आईडी से मैप करें (जैसे fr-FR, de-DE)।
const localeMap = { fr: 'fr', de: 'de' } // अपने स्पेस के लोकल आईडी से मेल खाने के लिए समायोजित करें

// केवल अंग्रेज़ी स्ट्रिंग फ़ील्ड निकालें — संदर्भ, लिंक, बूलियन, और नंबर छोड़ें
const sourceFields = Object.fromEntries(
  Object.entries(entry.fields)
    .filter(([, value]) => typeof value['en-US'] === 'string')
    .map(([key, value]) => [key, value['en-US']])
)

// एक अनुरोध में फ्रेंच और जर्मन में अनुवाद करें
const result = await poly.translate({
  content: JSON.stringify(sourceFields),
  format: 'json',
  targets: Object.keys(localeMap),
})

// Contentful लोकल आईडी का उपयोग करके अनुवादित मानों को प्रविष्टि में वापस लिखें
for (const [polyLocale, translated] of Object.entries(result.translations)) {
  const contentfulLocale = localeMap[polyLocale]
  const parsed = JSON.parse(translated)
  for (const [field, value] of Object.entries(parsed)) {
    if (!entry.fields[field]) entry.fields[field] = {}
    entry.fields[field][contentfulLocale] = value
  }
}

await entry.update()
await entry.publish()

console.log('प्रविष्टि का अनुवाद किया गया और प्रकाशित किया गया।')

एक API कॉल फ्रेंच और जर्मन दोनों लौटाता है। प्रविष्टि को उसी स्क्रिप्ट में अपडेट और प्रकाशित किया जाता है।

लोकल आईडी पर एक नोट: PolyLingo BCP-47 कोड का उपयोग करता है बिना क्षेत्र प्रत्यय के (fr, de)। Contentful स्पेस अक्सर क्षेत्र-योग्य आईडी जैसे fr-FR या de-DE का उपयोग करते हैं। ऊपर दिया गया localeMap ऑब्जेक्ट वह जगह है जहाँ आप उन्हें संरेखित करते हैं — इसे अपने Contentful स्पेस के लोकल आईडी से मेल खाने के लिए अपडेट करें।

वही पैटर्न Python में काम करता है:

import os, json, requests
from contentful_management import Client

poly_key = os.environ['POLYLINGO_API_KEY']
contentful = Client(os.environ['CONTENTFUL_MANAGEMENT_TOKEN'])

space = contentful.spaces().find(os.environ['CONTENTFUL_SPACE_ID'])
environment = space.environments().find('master')
entry = environment.entries().find('your-entry-id')

# अंग्रेज़ी फ़ील्ड मान निकालें
source_fields = {
    key: value.get('en-US')
    for key, value in entry.fields().items()
    if value.get('en-US')
}

# फ्रेंच और जर्मन में अनुवाद करें
r = requests.post(
    'https://api.usepolylingo.com/v1/translate',
    headers={'Authorization': f"Bearer {poly_key}"},
    json={
        'content': json.dumps(source_fields),
        'format': 'json',
        'targets': ['fr', 'de'],
    },
    timeout=120,
)
r.raise_for_status()
translations = r.json()['translations']

# प्रविष्टि में वापस लिखें
for locale, translated in translations.items():
    parsed = json.loads(translated)
    for field, value in parsed.items():
        if field in entry.fields():
            entry.fields()[field][locale] = value

entry.save()
entry.publish()
print('प्रविष्टि का अनुवाद किया गया और प्रकाशित किया गया।')

बड़े पैमाने पर अनुवाद

ऊपर दिए गए उदाहरण एक ही प्रविष्टि को संभालते हैं। एक Contentful स्पेस के लिए जिसमें कई प्रविष्टियाँ कई सामग्री प्रकारों में फैली हुई हैं, बैच एंडपॉइंट प्रति अनुरोध 100 आइटम तक संभालता है:

// कई प्रविष्टियाँ प्राप्त करें और उन्हें एक बैच कॉल में अनुवादित करें
const entries = await environment.getEntries({
  content_type: 'blogPost',
  limit: 50,
})
 
// PolyLingo लोकल कोड को Contentful लोकल आईडी से मैप करें
const localeMap = { fr: 'fr', de: 'de', es: 'es' } // अपने स्पेस के अनुसार समायोजित करें
 
const items = entries.items.map(entry => ({
  id: entry.sys.id,
  content: JSON.stringify(
    Object.fromEntries(
      Object.entries(entry.fields)
        .filter(([, value]) => typeof value['en-US'] === 'string')
        .map(([k, v]) => [k, v['en-US']])
    )
  ),
  format: 'json',
}))
 
const batch = await poly.batch({
  items,
  targets: Object.keys(localeMap),
})
 
for (const result of batch.results) {
  const entry = entries.items.find(e => e.sys.id === result.id)
  for (const [polyLocale, translated] of Object.entries(result.translations)) {
    const contentfulLocale = localeMap[polyLocale]
    const parsed = JSON.parse(translated)
    for (const [field, value] of Object.entries(parsed)) {
      if (!entry.fields[field]) entry.fields[field] = {}
      entry.fields[field][contentfulLocale] = value
    }
  }
  await entry.update()
  await entry.publish()
}

पचास प्रविष्टियाँ, तीन भाषाएँ, एक बैच अनुरोध।


अनुवादों को अद्यतित रखना

ऊपर दिए गए स्क्रिप्ट्स को मांग पर चलाया जा सकता है या आपके प्रकाशन वर्कफ़्लो में जोड़ा जा सकता है। एक सामान्य पैटर्न यह है कि स्रोत सामग्री में बदलाव होने पर या पिछले 24 घंटों में अपडेट किए गए किसी भी प्रविष्टि को पकड़ने वाले शेड्यूल पर CI जॉब के हिस्से के रूप में अनुवाद चलाया जाए।

Contentful वेबहुक्स का भी समर्थन करता है — आप एक वेबहुक कॉन्फ़िगर कर सकते हैं जो डिफ़ॉल्ट लोकल में किसी प्रविष्टि के प्रकाशित होने पर सक्रिय होता है, जो स्वचालित रूप से अनुवाद जॉब को ट्रिगर कर सकता है। उस वेबहुक में PolyLingo को जोड़ने का मतलब है कि हर बार जब कोई संपादक अपडेट की गई अंग्रेज़ी सामग्री प्रकाशित करता है, तो अनुवादित लोकल्स बिना किसी मैनुअल कदम के अपडेट हो जाते हैं। यह एक समर्पित PolyLingo इंटीग्रेशन के लिए रोडमैप पर है; इस बीच API आपको वह सब कुछ देता है जिसकी आपको उस फ्लो को स्वयं बनाने के लिए आवश्यकता है।


फ़ील्ड प्रकारों पर एक नोट

ऊपर दिया गया तरीका छोटे टेक्स्ट, लंबे टेक्स्ट, और स्ट्रिंग के रूप में संग्रहीत रिच टेक्स्ट के लिए अच्छा काम करता है। कुछ Contentful फ़ील्ड प्रकारों को अलग से संभालने की आवश्यकता होती है:

रिच टेक्स्ट फ़ील्ड जो Contentful के दस्तावेज़ प्रारूप में संग्रहीत होते हैं (साधारण स्ट्रिंग्स नहीं), उन्हें PolyLingo को भेजने से पहले प्लेन टेक्स्ट या HTML में सीरियलाइज़ किया जाना चाहिए, फिर वापस डीसिरियलाइज़ किया जाना चाहिए। @contentful/rich-text-html-renderer पैकेज सीरियलाइज़ेशन चरण को संभालता है, और PolyLingo पक्ष पर format: "html" मार्कअप को सही ढंग से संरक्षित करता है।

रेफरेंस फ़ील्ड (अन्य प्रविष्टियों या एसेट्स के लिंक) का अनुवाद नहीं किया जाना चाहिए — वे आईडी हैं, टेक्स्ट नहीं। उन्हें उन फ़ील्ड्स से बाहर रखें जिन्हें आप API को भेजते हैं।

संख्या, बूलियन, और तारीख फ़ील्ड स्वभावतः अनुवाद योग्य नहीं होते। इन्हें अपने स्रोत ऑब्जेक्ट को बनाने से पहले फ़िल्टर करें।


शुरुआत करना

PolyLingo की मुफ्त योजना में प्रति माह 50,000 टोकन शामिल हैं। एक Contentful स्पेस जिसमें मध्यम मात्रा में सामग्री होती है, उसके लिए यह कई प्रविष्टियों में कई स्थानीय भाषाओं में प्रारंभिक अनुवाद पास को कवर करता है और निरंतर अपडेट के लिए भी जगह बचाता है।

पूर्ण API दस्तावेज़ usepolylingo.com/docs पर उपलब्ध है। SDK पैकेज Node.js, Python, Ruby, PHP, Java, और Go के लिए उपलब्ध हैं।

npm install polylingo

अपनी API कुंजी प्राप्त करें