
আপনার 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 স্পেস যেসব লোকেল আইডি ব্যবহার করে তা মিলিয়ে এটি আপডেট করুন।
একই প্যাটার্ন পাইথনে কাজ করে:
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 স্পেসের জন্য যেখানে একাধিক কন্টেন্ট টাইপ জুড়ে অনেক এন্ট্রি রয়েছে, ব্যাচ এন্ডপয়েন্ট প্রতি অনুরোধে সর্বোচ্চ ১০০ আইটেম পরিচালনা করে:
// একাধিক এন্ট্রি আনুন এবং একবারে ব্যাচ কলের মাধ্যমে অনুবাদ করুন
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()
}
পঞ্চাশটি এন্ট্রি, তিনটি ভাষা, একটি ব্যাচ অনুরোধ।
অনুবাদগুলি আপ টু ডেট রাখা
উপরের স্ক্রিপ্টগুলি চাহিদা অনুযায়ী চালানো যেতে পারে বা আপনার প্রকাশনা ওয়ার্কফ্লোতে সংযুক্ত করা যেতে পারে। একটি সাধারণ প্যাটার্ন হল সোর্স কন্টেন্ট পরিবর্তিত হলে CI কাজের অংশ হিসেবে অনুবাদ চালানো, অথবা এমন একটি সময়সূচী যা গত ২৪ ঘণ্টায় আপডেট হওয়া যেকোনো এন্ট্রি ক্যাচ করে।
Contentful ওয়েবহুকও সমর্থন করে — আপনি একটি ওয়েবহুক কনফিগার করতে পারেন যা ডিফল্ট লোকেলে একটি এন্ট্রি প্রকাশিত হলে ট্রিগার হয়, যা স্বয়ংক্রিয়ভাবে একটি অনুবাদ কাজ শুরু করতে পারে। PolyLingo কে সেই ওয়েবহুকের সাথে সংযুক্ত করা মানে প্রতিবার একজন সম্পাদক আপডেট করা ইংরেজি কন্টেন্ট প্রকাশ করলে, অনুবাদকৃত লোকেলগুলি কোনো ম্যানুয়াল ধাপ ছাড়াই আপডেট হয়। এটি একটি নিবেদিত PolyLingo ইন্টিগ্রেশনের রোডম্যাপে রয়েছে; ইতিমধ্যে API আপনাকে সবকিছু দেয় যা আপনাকে সেই ফ্লো নিজে তৈরি করতে হবে।
ক্ষেত্রের ধরন সম্পর্কে একটি নোট
উপরের পদ্ধতিটি সংক্ষিপ্ত টেক্সট, দীর্ঘ টেক্সট এবং স্ট্রিং হিসাবে সংরক্ষিত রিচ টেক্সটের জন্য ভাল কাজ করে। কিছু Contentful ক্ষেত্রের ধরন আলাদাভাবে পরিচালনা করা প্রয়োজন:
রিচ টেক্সট ক্ষেত্রগুলি Contentful এর ডকুমেন্ট ফরম্যাটে সংরক্ষিত (সাধারণ স্ট্রিং নয়) PolyLingo তে পাঠানোর আগে প্লেইন টেক্সট বা HTML এ সিরিয়ালাইজ করতে হবে, তারপর আবার ডেসিরিয়ালাইজ করতে হবে। @contentful/rich-text-html-renderer প্যাকেজ সিরিয়ালাইজেশন ধাপটি পরিচালনা করে, এবং PolyLingo পাশের format: "html" মার্কআপ সঠিকভাবে সংরক্ষণ করে।
রেফারেন্স ক্ষেত্রগুলি (অন্যান্য এন্ট্রি বা অ্যাসেটের লিঙ্ক) অনুবাদ করা উচিত নয় — এগুলি আইডি, টেক্সট নয়। API তে পাঠানোর জন্য এগুলি ক্ষেত্র থেকে বাদ দিন।
সংখ্যা, বুলিয়ান, এবং তারিখ ক্ষেত্রগুলি স্বভাবতই অনুবাদযোগ্য নয়। আপনার সোর্স অবজেক্ট তৈরি করার আগে এগুলি ফিল্টার করুন।
শুরু করা
PolyLingo-এর ফ্রি টিয়ারে প্রতি মাসে ৫০,০০০ টোকেন অন্তর্ভুক্ত রয়েছে। একটি Contentful স্পেস যেখানে মাঝারি পরিমাণ কন্টেন্ট রয়েছে, সেখানে এটি একাধিক লোকেলে কয়েকটি এন্ট্রির উপর প্রাথমিক অনুবাদ প্রক্রিয়া কভার করে এবং চলমান আপডেটের জন্য পর্যাপ্ত জায়গা রাখে।
সম্পূর্ণ API ডকুমেন্টেশন পাওয়া যাবে usepolylingo.com/docs। Node.js, Python, Ruby, PHP, Java, এবং Go-এর জন্য SDK প্যাকেজ উপলব্ধ।
npm install polylingo