
Varför ditt Contentful-innehåll ännu inte är faktiskt flerspråkigt
By Robert
Varför ditt Contentful-innehåll egentligen inte är flerspråkigt än
Contentful hanterar lokaler väl. Du kan definiera så många du behöver, ställa in en fallback-kedja, växla mellan dem i editorn och bygga ett frontend som serverar rätt språk per rutt. Infrastrukturen för flerspråkigt innehåll är verkligen bra.
Vad Contentful inte gör är att översätta ditt innehåll.
Det låter självklart när det sägs direkt, men det är förvånansvärt lätt att blanda ihop "vi har lokalfält inställda" med "vi är flerspråkiga." De två är inte samma sak. Lokalfält är en behållare. Flerspråkigt betyder att behållaren har innehåll i sig.
Om ditt Contentful-utrymme har franska och tyska lokaler konfigurerade men fälten i dessa lokaler är tomma, eller fyllda med den engelska texten som en platshållare, eller fyllda med ett grovt första utkast som ingen har granskat sedan webbplatsen lanserades — då är du inte flerspråkig. Du har stommen för flerspråkighet.
Det här inlägget handlar om att stänga den luckan.
Vad Contentful egentligen ger dig
Contentfuls lokalsystem är väl utformat. Varje innehållspost kan ha fältvärden per lokal. Du ställer in en standardlokal, konfigurerar fallback-alternativ, och din leverans-API returnerar rätt lokal när den efterfrågas. Innehållsmodelleringen är tillräckligt flexibel för att hantera komplexa flerspråkiga krav över olika innehållstyper.
Men systemet är helt neutralt när det gäller hur det översatta innehållet kommer in där. Contentful vet inte om dina franska lokalfält innehåller professionella översättningar, maskinöversatt text, engelska texter inklistrade av misstag eller ingenting alls. Det lagrar och levererar bara det du lägger in.
Översättningsproblemet är helt ditt att lösa.
Hur team vanligtvis hanterar det (och var det brister)
De flesta team som hanterar Contentful-översättning följer ett av några mönster.
Mönstret med manuell export. En utvecklare exporterar innehåll från Contentful, skickar det till en översättningsbyrå eller frilansare, väntar på att det ska komma tillbaka, formaterar om det och importerar det. Detta fungerar för en engångslansering men blir ohållbart när innehållet ändras. Varje uppdatering av källspråket innebär att hela cykeln måste göras om. I praktiken halkar det översatta innehållet snabbt efter källan och ingen har tid att ta igen det.
Mönstret med översättning i redigeraren. En redaktör öppnar varje post, byter till målplatsen och översätter fält för fält antingen manuellt eller genom att klistra in innehåll i ett översättningsverktyg. Detta är noggrant men långsamt. Det skalar inte heller — om du har hundratals poster över ett dussin innehållstyper är volymen manuellt arbete betydande.
"Det duger"-mönstret. Det översatta innehållet finns men har inte granskats sedan det först producerades. Källplatsen har uppdaterats flera gånger sedan dess. Den franska versionen av din prissida hänvisar fortfarande till en plan du lade ner för åtta månader sedan. Den tyska startsidan har fortfarande den gamla sloganen. Ingen har flaggat det eftersom ingen kontrollerar.
Alla tre mönster delar samma grundläggande problem: översättning behandlas som en engångsuppgift snarare än en pågående del av innehållsarbetsflödet.
Vad flerspråkighet egentligen kräver
Äkta flerspråkigt innehåll i Contentful kräver att tre saker fungerar tillsammans.
Noggrann initial översättning. Varje fält i varje målplats behöver översatt innehåll som är korrekt, lämpligt lokaliserat och som faktiskt läses som om det var skrivet för den marknaden snarare än att ha körts genom ett grundläggande översättningsverktyg och lämnats oöversett.
En process för att hålla översättningar aktuella. När källinnehållet ändras måste det översatta innehållet också ändras. Detta är den del som de flesta team underskattar. Ett innehållsteam som publicerar flera uppdateringar i veckan över ett Contentful-utrymme med fyra lokaler och femtio innehållstyper står inför en betydande pågående översättningsbörda om det hanteras manuellt.
Ett sätt att veta när översättningar är föråldrade. Contentfuls lokalsystem visar inte när innehåll är föråldrat. Om du uppdaterar den engelska kopian av en post och glömmer att uppdatera den franska kopian kommer den franska versionen tyst fortsätta att visa det gamla innehållet. Du behöver antingen en process eller verktyg för att upptäcka detta.
Var PolyLingo passar in
PolyLingos API översätter strukturerat innehåll samtidigt som dess struktur bevaras. För Contentful är det relevanta formatet JSON — en Contentful-posts fältvärden, extraherade och skickade till API:et, kommer tillbaka översatta med samma struktur intakt.
Det grundläggande flödet i 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')
// Hämta posten du vill översätta
const entry = await environment.getEntry('your-entry-id')
// PolyLingo använder BCP-47-koder utan region (fr, de).
// Mappa dessa till dina Contentful-lokaliserings-ID:n om de skiljer sig (t.ex. fr-FR, de-DE).
const localeMap = { fr: 'fr', de: 'de' } // justera för att matcha dina lokaliserings-ID:n i ditt space
// Extrahera endast engelska strängfält — hoppa över referenser, länkar, booleaner och nummer
const sourceFields = Object.fromEntries(
Object.entries(entry.fields)
.filter(([, value]) => typeof value['en-US'] === 'string')
.map(([key, value]) => [key, value['en-US']])
)
// Översätt till franska och tyska i en förfrågan
const result = await poly.translate({
content: JSON.stringify(sourceFields),
format: 'json',
targets: Object.keys(localeMap),
})
// Skriv tillbaka översatta värden till posten med Contentful-lokaliserings-ID:n
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('Post översatt och publicerad.')
Ett API-anrop returnerar både franska och tyska. Posten uppdateras och publiceras i samma skript.
En notering om lokaliserings-ID:n: PolyLingo använder BCP-47-koder utan regionsuffix (fr, de). Contentful-spaces använder ofta regionskvalificerade ID:n som fr-FR eller de-DE. Objektet localeMap ovan är där du anpassar dem — uppdatera det för att matcha vilka lokaliserings-ID:n ditt Contentful-space använder.
Samma mönster fungerar i 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')
# Extrahera engelska fältvärden
source_fields = {
key: value.get('en-US')
for key, value in entry.fields().items()
if value.get('en-US')
}
# Översätt till franska och tyska
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']
# Skriv tillbaka till posten
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('Post översatt och publicerad.')
Översättning i stor skala
Exemplen ovan hanterar en enda post. För ett Contentful-utrymme med många poster över flera innehållstyper hanterar batch-endpointen upp till 100 objekt per förfrågan:
// Hämta flera poster och översätt dem i ett batch-anrop
const entries = await environment.getEntries({
content_type: 'blogPost',
limit: 50,
})
// Mappa PolyLingo-lokalkoder till Contentful-lokal-ID:n
const localeMap = { fr: 'fr', de: 'de', es: 'es' } // justera för att matcha ditt utrymme
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()
}
Femtio poster, tre språk, en batch-förfrågan.
Hålla översättningar aktuella
Skripten ovan kan köras på begäran eller kopplas in i din publiceringsarbetsflöde. Ett vanligt mönster är att köra översättning som en del av ett CI-jobb som triggas när källinnehållet ändras, eller enligt ett schema som fångar upp alla poster som uppdaterats under de senaste 24 timmarna.
Contentful stöder också webhooks — du kan konfigurera en webhook som aktiveras när en post publiceras i standardlokalen, vilket kan trigga ett översättningsjobb automatiskt. Att koppla PolyLingo till den webhooken innebär att varje gång en redaktör publicerar uppdaterat engelskt innehåll, uppdateras de översatta lokalerna utan något manuellt steg. Detta finns med på färdplanen för en dedikerad PolyLingo-integration; under tiden ger API:et dig allt du behöver för att bygga det flödet själv.
En notering om fälttyper
Tillvägagångssättet ovan fungerar bra för kort text, lång text och rik text som lagras som strängar. Några Contentful-fälttyper behöver hanteras separat:
Riktextfält som lagras i Contentfuls dokumentformat (inte vanliga strängar) måste serialiseras till vanlig text eller HTML innan de skickas till PolyLingo, och sedan deserialiseras tillbaka. Paketet @contentful/rich-text-html-renderer hanterar serialiseringssteget, och format: "html" på PolyLingos sida bevarar markeringen korrekt.
Referensfält (länkar till andra poster eller tillgångar) bör inte översättas — de är ID:n, inte text. Exkludera dem från de fält du skickar till API:et.
Fält för nummer, boolean och datum är inte översättningsbara av naturen. Filtrera bort dem innan du bygger ditt källobjekt.
Kom igång
PolyLingos gratisnivå inkluderar 50 000 tokens per månad. För ett Contentful-utrymme med en måttlig mängd innehåll täcker det en initial översättningsomgång över flera poster i flera lokaler med utrymme kvar för löpande uppdateringar.
Fullständig API-dokumentation finns på usepolylingo.com/docs. SDK-paket finns tillgängliga för Node.js, Python, Ruby, PHP, Java och Go.
npm install polylingo