
Perché il tuo contenuto Contentful non è ancora realmente multilingue
By Robert
Perché il tuo contenuto Contentful non è ancora veramente multilingue
Contentful gestisce bene le località. Puoi definirne quante ne hai bisogno, impostare una catena di fallback, passare da una all'altra nell'editor e costruire un frontend che serve la lingua giusta per ogni percorso. L'infrastruttura per i contenuti multilingue è davvero buona.
Quello che Contentful non fa è tradurre i tuoi contenuti.
Questo sembra ovvio se detto direttamente, ma è sorprendentemente facile confondere "abbiamo configurato i campi delle località" con "siamo multilingue." I due non sono la stessa cosa. I campi delle località sono un contenitore. Multilingue significa che il contenitore contiene contenuti.
Se il tuo spazio Contentful ha configurato le località francese e tedesca ma i campi in quelle località sono vuoti, o riempiti con il testo inglese come segnaposto, o popolati con una prima bozza approssimativa che nessuno ha rivisto da quando il sito è stato lanciato — non sei multilingue. Hai solo l'impalcatura per il multilingue.
Questo post riguarda il colmare quel divario.
Cosa ti offre realmente Contentful
Il sistema di localizzazione di Contentful è ben progettato. Ogni voce di contenuto può avere valori di campo per locale. Imposti una locale predefinita, configuri fallback e la tua API di consegna restituisce la locale corretta quando richiesta. La modellazione dei contenuti è abbastanza flessibile da gestire requisiti multilingue complessi attraverso diversi tipi di contenuto.
Ma il sistema è completamente neutro su come il contenuto tradotto venga inserito. Contentful non sa se i campi della tua locale francese contengono traduzioni professionali, copie tradotte da macchina, testo in inglese incollato per errore o nulla. Memorizza e consegna semplicemente ciò che inserisci.
Il problema della traduzione è interamente tuo da risolvere.
Come i team di solito lo gestiscono (e dove falliscono)
La maggior parte dei team che gestiscono la traduzione di Contentful rientra in uno di pochi schemi.
Lo schema di esportazione manuale. Uno sviluppatore esporta contenuti da Contentful, li invia a un'agenzia di traduzione o a un freelance, aspetta che tornino, li riformatta e li importa. Questo funziona per un lancio una tantum ma diventa insostenibile man mano che i contenuti cambiano. Ogni aggiornamento della locale sorgente significa dover ripetere l'intero ciclo. In pratica, i contenuti tradotti rimangono rapidamente indietro rispetto alla sorgente e nessuno ha tempo per aggiornarli.
Lo schema di traduzione in-editor. Un editor apre ogni voce, passa alla locale di destinazione e traduce campo per campo manualmente o incollando il contenuto in uno strumento di traduzione. È accurato ma lento. Inoltre non scala — se hai centinaia di voci in una dozzina di tipi di contenuto, il volume di lavoro manuale è significativo.
Lo schema del "va bene così". Il contenuto tradotto esiste ma non è stato revisionato da quando è stato prodotto la prima volta. La locale sorgente è stata aggiornata più volte da allora. La versione francese della tua pagina dei prezzi fa ancora riferimento a un piano che hai ritirato otto mesi fa. La homepage tedesca ha ancora il vecchio slogan. Nessuno lo ha segnalato perché nessuno controlla.
Tutti e tre gli schemi condividono lo stesso problema di fondo: la traduzione è trattata come un compito una tantum piuttosto che come una parte continua del flusso di lavoro dei contenuti.
Cosa richiede realmente il multilingue
Il contenuto multilingue genuino in Contentful necessita di tre elementi che lavorano insieme.
Traduzione iniziale accurata. Ogni campo in ogni locale di destinazione necessita di contenuti tradotti che siano accurati, adeguatamente localizzati e che leggano effettivamente come se fossero stati scritti per quel mercato, piuttosto che passati attraverso uno strumento di traduzione base e lasciati senza revisione.
Un processo per mantenere le traduzioni aggiornate. Quando il contenuto sorgente cambia, anche il contenuto tradotto deve cambiare. Questa è la parte che la maggior parte dei team sottovaluta. Un team di contenuti che pubblica diversi aggiornamenti a settimana in uno spazio Contentful con quattro locali e cinquanta tipi di contenuto si trova di fronte a un carico di lavoro di traduzione significativo se gestito manualmente.
Un modo per sapere quando le traduzioni sono obsolete. Il sistema di localizzazione di Contentful non evidenzia l’obsolescenza. Se aggiorni la copia in inglese di una voce e dimentichi di aggiornare quella in francese, la versione francese continuerà silenziosamente a fornire il contenuto vecchio. Hai bisogno di un processo o di strumenti per rilevare questo.
Dove si colloca PolyLingo
L'API di PolyLingo traduce contenuti strutturati preservandone la struttura. Per Contentful, il formato rilevante è JSON — i valori dei campi di una voce Contentful, estratti e inviati all'API, tornano tradotti mantenendo intatta la stessa struttura.
Il flusso base in 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')
// Recupera la voce che vuoi tradurre
const entry = await environment.getEntry('your-entry-id')
// PolyLingo usa codici BCP-47 senza regione (fr, de).
// Mappali agli ID locali di Contentful se sono diversi (es. fr-FR, de-DE).
const localeMap = { fr: 'fr', de: 'de' } // adatta per corrispondere agli ID locali del tuo spazio
// Estrai solo i campi stringa in inglese — salta riferimenti, link, booleani e numeri
const sourceFields = Object.fromEntries(
Object.entries(entry.fields)
.filter(([, value]) => typeof value['en-US'] === 'string')
.map(([key, value]) => [key, value['en-US']])
)
// Traduci in francese e tedesco in una sola richiesta
const result = await poly.translate({
content: JSON.stringify(sourceFields),
format: 'json',
targets: Object.keys(localeMap),
})
// Scrivi i valori tradotti di nuovo nella voce usando gli ID locali di 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('Voce tradotta e pubblicata.')
Una chiamata API restituisce sia francese che tedesco. La voce viene aggiornata e pubblicata nello stesso script.
Nota sugli ID locali: PolyLingo usa codici BCP-47 senza suffisso di regione (fr, de). Gli spazi Contentful spesso usano ID qualificati con regione come fr-FR o de-DE. L'oggetto localeMap sopra è dove li allinei — aggiornalo per corrispondere agli ID locali usati dal tuo spazio Contentful.
Lo stesso schema funziona in 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')
# Estrai i valori dei campi in inglese
source_fields = {
key: value.get('en-US')
for key, value in entry.fields().items()
if value.get('en-US')
}
# Traduci in francese e tedesco
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']
# Scrivi di nuovo nella voce
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('Voce tradotta e pubblicata.')
Traduzione su larga scala
Gli esempi sopra gestiscono una singola voce. Per uno spazio Contentful con molte voci distribuite su più tipi di contenuto, l'endpoint batch gestisce fino a 100 elementi per richiesta:
// Recupera più voci e traducile in una singola chiamata batch
const entries = await environment.getEntries({
content_type: 'blogPost',
limit: 50,
})
// Mappa i codici locali di PolyLingo agli ID locali di Contentful
const localeMap = { fr: 'fr', de: 'de', es: 'es' } // adatta per corrispondere al tuo spazio
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()
}
Cinquanta voci, tre lingue, una richiesta batch.
Mantenere le traduzioni aggiornate
Gli script sopra possono essere eseguiti su richiesta o integrati nel tuo flusso di lavoro di pubblicazione. Un modello comune è eseguire la traduzione come parte di un job CI attivato quando il contenuto sorgente cambia, o su una pianificazione che cattura tutte le voci aggiornate nelle ultime 24 ore.
Contentful supporta anche i webhook — puoi configurare un webhook che si attiva quando una voce viene pubblicata nella lingua predefinita, che può attivare automaticamente un job di traduzione. Collegare PolyLingo a quel webhook significa che ogni volta che un editor pubblica contenuti aggiornati in inglese, le localizzazioni tradotte vengono aggiornate senza alcun passaggio manuale. Questo è nella roadmap per un'integrazione dedicata di PolyLingo; nel frattempo l'API ti offre tutto il necessario per costruire quel flusso da solo.
Una nota sui tipi di campo
L'approccio sopra funziona bene per testo breve, testo lungo e testo ricco memorizzati come stringhe. Alcuni tipi di campo di Contentful richiedono una gestione separata:
Campi di testo ricco memorizzati nel formato documento di Contentful (non stringhe semplici) devono essere serializzati in testo semplice o HTML prima di essere inviati a PolyLingo, quindi deserializzati nuovamente. Il pacchetto @contentful/rich-text-html-renderer gestisce la fase di serializzazione, e format: "html" sul lato PolyLingo preserva correttamente il markup.
Campi di riferimento (collegamenti ad altre voci o asset) non devono essere tradotti — sono ID, non testo. Escludili dai campi che invii all'API.
Campi numerici, booleani e di data non sono traducibili per natura. Filtrali prima di costruire il tuo oggetto sorgente.
Iniziare
Il piano gratuito di PolyLingo include 50.000 token al mese. Per uno spazio Contentful con una quantità moderata di contenuti, questo copre una prima traduzione su più voci in più località con spazio sufficiente per aggiornamenti continui.
La documentazione completa dell'API è disponibile su usepolylingo.com/docs. I pacchetti SDK sono disponibili per Node.js, Python, Ruby, PHP, Java e Go.
npm install polylingo