Επιστροφή στο ιστολόγιο
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 διαχειρίζεται καλά τις τοπικές ρυθμίσεις. Μπορείτε να ορίσετε όσες χρειάζεστε, να ορίσετε μια αλυσίδα εφεδρείας, να εναλλάσσεστε μεταξύ τους στον επεξεργαστή και να δημιουργήσετε ένα frontend που εξυπηρετεί τη σωστή γλώσσα ανά διαδρομή. Η υποδομή για πολυγλωσσικό περιεχόμενο είναι πραγματικά καλή.

Αυτό που το Contentful δεν κάνει είναι να μεταφράζει το περιεχόμενό σας.

Ακούγεται προφανές όταν το λέμε απευθείας, αλλά είναι εκπληκτικά εύκολο να συγχέουμε το "έχουμε ρυθμίσει πεδία τοπικών ρυθμίσεων" με το "είμαστε πολυγλωσσικοί." Τα δύο δεν είναι το ίδιο πράγμα. Τα πεδία τοπικών ρυθμίσεων είναι ένα δοχείο. Πολυγλωσσικό σημαίνει ότι το δοχείο περιέχει περιεχόμενο.

Αν ο χώρος σας στο Contentful έχει ρυθμισμένες τις τοπικές ρυθμίσεις για τα γαλλικά και τα γερμανικά, αλλά τα πεδία σε αυτές τις τοπικές ρυθμίσεις είναι κενά, ή γεμάτα με το αγγλικό κείμενο ως κράτημα θέσης, ή γεμάτα με μια πρόχειρη πρώτη εκδοχή που κανείς δεν έχει ελέγξει από τότε που ξεκίνησε ο ιστότοπος — δεν είστε πολυγλωσσικοί. Έχετε τη δομή για πολυγλωσσικότητα.

Αυτή η ανάρτηση αφορά το κλείσιμο αυτού του κενού.


Τι σας προσφέρει πραγματικά το Contentful

Το σύστημα τοπικών ρυθμίσεων (locale) του Contentful είναι καλά σχεδιασμένο. Κάθε καταχώρηση περιεχομένου μπορεί να έχει τιμές πεδίων ανά locale. Ορίζετε μια προεπιλεγμένη locale, ρυθμίζετε εφεδρικές επιλογές (fallbacks), και το API παράδοσης επιστρέφει τη σωστή locale όταν ζητηθεί. Η μοντελοποίηση περιεχομένου είναι αρκετά ευέλικτη για να χειριστεί πολύπλοκες πολυγλωσσικές απαιτήσεις σε διαφορετικούς τύπους περιεχομένου.

Αλλά το σύστημα είναι εντελώς ουδέτερο ως προς το πώς εισάγεται το μεταφρασμένο περιεχόμενο. Το Contentful δεν γνωρίζει αν τα πεδία locale για τα γαλλικά σας περιέχουν επαγγελματικές μεταφράσεις, μηχανικά μεταφρασμένο κείμενο, αγγλικό κείμενο που επικολλήθηκε κατά λάθος ή τίποτα απολύτως. Απλώς αποθηκεύει και παραδίδει ό,τι βάλετε.

Το πρόβλημα της μετάφρασης είναι αποκλειστικά δικό σας να το λύσετε.


Πώς συνήθως το διαχειρίζονται οι ομάδες (και πού αποτυγχάνει)

Οι περισσότερες ομάδες που διαχειρίζονται μεταφράσεις στο Contentful ακολουθούν ένα από μερικά πρότυπα.

Το πρότυπο χειροκίνητης εξαγωγής. Ένας προγραμματιστής εξάγει περιεχόμενο από το Contentful, το στέλνει σε ένα πρακτορείο μετάφρασης ή σε ελεύθερο επαγγελματία, περιμένει να επιστραφεί, το αναμορφώνει και το εισάγει ξανά. Αυτό λειτουργεί για μια εφάπαξ εκκίνηση αλλά γίνεται μη βιώσιμο καθώς το περιεχόμενο αλλάζει. Κάθε ενημέρωση στην πηγή σημαίνει ότι πρέπει να περάσετε ξανά όλο τον κύκλο. Στην πράξη, το μεταφρασμένο περιεχόμενο γρήγορα μένει πίσω από την πηγή και κανείς δεν έχει χρόνο να το ενημερώσει.

Το πρότυπο μετάφρασης μέσα στον επεξεργαστή. Ένας συντάκτης ανοίγει κάθε εγγραφή, αλλάζει στην επιθυμητή γλώσσα και μεταφράζει πεδίο προς πεδίο είτε χειροκίνητα είτε επικολλώντας περιεχόμενο σε ένα εργαλείο μετάφρασης. Αυτό είναι ακριβές αλλά αργό. Επίσης, δεν κλιμακώνεται – αν έχετε εκατοντάδες εγγραφές σε δώδεκα τύπους περιεχομένου, ο όγκος της χειροκίνητης εργασίας είναι σημαντικός.

Το πρότυπο "θα κάνει". Το μεταφρασμένο περιεχόμενο υπάρχει αλλά δεν έχει ελεγχθεί από τότε που δημιουργήθηκε αρχικά. Η πηγή έχει ενημερωθεί πολλές φορές από τότε. Η γαλλική έκδοση της σελίδας τιμολόγησής σας αναφέρεται ακόμα σε ένα πλάνο που αποσύρατε πριν από οκτώ μήνες. Η γερμανική αρχική σελίδα έχει ακόμα το παλιό σύνθημα. Κανείς δεν το έχει επισημάνει γιατί κανείς δεν το ελέγχει.

Και τα τρία πρότυπα μοιράζονται το ίδιο βασικό πρόβλημα: η μετάφραση αντιμετωπίζεται ως μια εφάπαξ εργασία και όχι ως ένα συνεχιζόμενο μέρος της ροής εργασίας περιεχομένου.


Τι απαιτεί πραγματικά η πολυγλωσσία

Το αυθεντικό πολυγλωσσικό περιεχόμενο στο Contentful χρειάζεται τρία πράγματα που λειτουργούν μαζί.

Ακριβής αρχική μετάφραση. Κάθε πεδίο σε κάθε γλώσσα-στόχο χρειάζεται μεταφρασμένο περιεχόμενο που είναι ακριβές, κατάλληλα τοπικοποιημένο και διαβάζεται πραγματικά σαν να έχει γραφτεί για εκείνη την αγορά, αντί να έχει περάσει από ένα βασικό εργαλείο μετάφρασης και να έχει μείνει χωρίς έλεγχο.

Μια διαδικασία για τη διατήρηση των μεταφράσεων ενημερωμένων. Όταν αλλάζει το αρχικό περιεχόμενο, πρέπει να αλλάζουν και οι μεταφράσεις. Αυτό είναι το μέρος που οι περισσότερες ομάδες υποτιμούν. Μια ομάδα περιεχομένου που δημοσιεύει αρκετές ενημερώσεις την εβδομάδα σε έναν χώρο Contentful με τέσσερις γλώσσες και πενήντα τύπους περιεχομένου αντιμετωπίζει σημαντικό συνεχιζόμενο φόρτο εργασίας μετάφρασης αν γίνεται χειροκίνητα.

Ένας τρόπος να γνωρίζετε πότε οι μεταφράσεις είναι ξεπερασμένες. Το σύστημα γλωσσών του Contentful δεν εμφανίζει την παλαιότητα. Αν ενημερώσετε το αγγλικό κείμενο μιας εγγραφής και ξεχάσετε να ενημερώσετε το γαλλικό, η γαλλική έκδοση θα συνεχίσει αθόρυβα να σερβίρει το παλιό περιεχόμενο. Χρειάζεστε είτε μια διαδικασία είτε εργαλεία για να το εντοπίσετε.


Πού ταιριάζει το PolyLingo

Το API του PolyLingo μεταφράζει δομημένο περιεχόμενο διατηρώντας τη δομή του. Για το 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 locale IDs σας αν διαφέρουν (π.χ. fr-FR, de-DE).
const localeMap = { fr: 'fr', de: 'de' } // προσαρμόστε για να ταιριάζει με τα locale IDs του χώρου σας

// Εξαγωγή μόνο των πεδίων κειμένου στα Αγγλικά — παραλείψτε αναφορές, συνδέσμους, boolean και αριθμούς
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 locale IDs
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 επιστρέφει και τα Γαλλικά και τα Γερμανικά. Η εγγραφή ενημερώνεται και δημοσιεύεται στο ίδιο σενάριο.

Μια σημείωση για τα locale IDs: Το PolyLingo χρησιμοποιεί κωδικούς BCP-47 χωρίς επίθημα περιοχής (fr, de). Οι χώροι Contentful συχνά χρησιμοποιούν κωδικούς με περιοχή όπως fr-FR ή de-DE. Το αντικείμενο localeMap παραπάνω είναι όπου τα ευθυγραμμίζετε — ενημερώστε το ώστε να ταιριάζει με τα locale IDs που χρησιμοποιεί ο χώρος 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 με πολλές εγγραφές σε πολλούς τύπους περιεχομένου, το endpoint παρτίδας χειρίζεται έως και 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()
}

Πενήντα εγγραφές, τρεις γλώσσες, ένα αίτημα παρτίδας.


Διατήρηση των μεταφράσεων ενημερωμένων

Τα παραπάνω σενάρια μπορούν να εκτελεστούν κατόπιν ζήτησης ή να ενσωματωθούν στη ροή εργασίας δημοσίευσής σας. Ένα κοινό μοτίβο είναι να εκτελείτε τη μετάφραση ως μέρος μιας εργασίας CI που ενεργοποιείται όταν αλλάζει το αρχικό περιεχόμενο ή σε ένα πρόγραμμα που καλύπτει οποιεσδήποτε εγγραφές ενημερώθηκαν τις τελευταίες 24 ώρες.

Το Contentful υποστηρίζει επίσης webhooks — μπορείτε να ρυθμίσετε ένα webhook που ενεργοποιείται όταν μια εγγραφή δημοσιεύεται στην προεπιλεγμένη γλώσσα, το οποίο μπορεί να ενεργοποιήσει αυτόματα μια εργασία μετάφρασης. Η σύνδεση του PolyLingo με αυτό το webhook σημαίνει ότι κάθε φορά που ένας συντάκτης δημοσιεύει ενημερωμένο αγγλικό περιεχόμενο, οι μεταφρασμένες τοπικές εκδόσεις ενημερώνονται χωρίς κανένα χειροκίνητο βήμα. Αυτό βρίσκεται στον οδικό χάρτη για μια αφιερωμένη ενσωμάτωση PolyLingo· στο μεταξύ, το API σας παρέχει όλα όσα χρειάζεστε για να δημιουργήσετε αυτή τη ροή μόνοι σας.


Μια σημείωση σχετικά με τους τύπους πεδίων

Η παραπάνω προσέγγιση λειτουργεί καλά για σύντομο κείμενο, μακρύ κείμενο και πλούσιο κείμενο που αποθηκεύεται ως συμβολοσειρές. Ορισμένοι τύποι πεδίων του Contentful χρειάζονται ξεχωριστή διαχείριση:

Πεδία πλούσιου κειμένου που αποθηκεύονται στη μορφή εγγράφου του Contentful (όχι απλές συμβολοσειρές) πρέπει να σειριοποιηθούν σε απλό κείμενο ή HTML πριν αποσταλούν στο PolyLingo, και στη συνέχεια να αποσειριοποιηθούν ξανά. Το πακέτο @contentful/rich-text-html-renderer χειρίζεται το βήμα της σειριοποίησης, και το format: "html" στην πλευρά του PolyLingo διατηρεί σωστά τη μορφοποίηση.

Πεδία αναφοράς (σύνδεσμοι σε άλλες εγγραφές ή αρχεία) δεν πρέπει να μεταφράζονται — είναι αναγνωριστικά, όχι κείμενο. Αποκλείστε τα από τα πεδία που στέλνετε στο API.

Πεδία αριθμών, boolean και ημερομηνιών δεν είναι μεταφράσιμα από τη φύση τους. Φιλτράρετέ τα πριν δημιουργήσετε το αντικείμενο πηγής σας.


Ξεκινώντας

Το δωρεάν πακέτο του PolyLingo περιλαμβάνει 50.000 tokens ανά μήνα. Για έναν χώρο Contentful με μέτριο όγκο περιεχομένου, αυτό καλύπτει μια αρχική μετάφραση σε πολλές εγγραφές σε πολλαπλές τοπικές ρυθμίσεις, με περιθώριο για συνεχιζόμενες ενημερώσεις.

Η πλήρης τεκμηρίωση API βρίσκεται στο usepolylingo.com/docs. Πακέτα SDK είναι διαθέσιμα για Node.js, Python, Ruby, PHP, Java και Go.

npm install polylingo

Αποκτήστε το κλειδί API σας