Localization with Contentful
If you are working with content that needs to be available in multiple languages, locales let you define localizations of content and select a specific locale when querying the Content Delivery API.
Every Space has its own set of locales, and each locale is uniquely identified by its ISO code (e.g., en-US
or de-AT
). There's always one default locale defined when you create a space, shown by default in the Contentful web app and used for Content Delivery API queries that do not request a specific locale.
Adding a locale
You can add a new locale to a space in the Contentful web app or by using the Content Management API.
With the Web App
To add a locale in the web app, open Settings -> Locales and click Add locale:
The list is quite extensive, but if you don’t see what you’re looking for (Klingon, perhaps?), you can always add your own via our Content Management API.
Next you will be able to configure the locale’s settings. Your default locale will always be listed first; this is the locale that will be used when other locale fields are empty. You can change the default locale by clicking on it and selecting a new language from the menu.
With the API
If you are writing scripts or applications, use the Content Management API to add a locale to a space use the following POST request with the name and ISO code contained within the body:
curl -X POST
-H "Authorization: Bearer <CONTENT_MANAGEMENT_KEY>"
-H "Content-Type: application/vnd.contentful.management.v1+json"
-d '{
"name":"English (British)",
"code":"en-GB"
}'
"https://api.contentful.com/spaces/<SPACE_ID>/locales"
There are a couple of other options you can send with the API call, read our API documentation to find out more. You can also enable or disable the locale in the API response and in the entry editor. If you disable the locale in the response, the content will still be visible and editable in the entry editor, but will not be delivered to your application. Disabling in the entry editor means the locale fields will not be visible or editable, but the content will continue to be delivered via the API.
Optional Locales
It may be the case that you want the default locale to be required but the other locales to be optional. For example, if you want to ensure that the English locale fields are always filled out but it’s okay to publish the entry with the Chinese field empty. In that case, remember to select the Allow empty fields for this locale checkbox.
This is a particularly useful setting for working with multiple translators. Is your Spanish translator a bit quicker than your French translator? If the Spanish market is more important to your business, you can publish the entry first in Spanish and let the French catch up later.
Enabling Locales
After adding a new locale and adjusting the settings, you must enable it. Locales are enabled on a per-space and per-field basis, and you can also toggle the visibility of the locale fields within your entries. This sounds complicated but it’s really a good thing! It gives you more control over what content is localized and when. After adding a locale to a space, you can define which fields in your content types you want localized. You can do this with the web app or the Content Management API.
Enabling on fields
You can do this with the web app or the Content Management API.
With the Web App
First open the content type of an entry that you would like to be localized. Go into the field settings for each field you need localized and check the "enable localization of this field" box. You will need to do this for each field, but again, this is to give you extra control over which fields are localized.
With the API
It's possible to use the Content Management API to update content types and localize fields.
The following PUT request enables localization for the fields productName
and productDescription
of the content type Product
by setting their localized
property to true
:
curl -X PUT
-H "Authorization: Bearer <CONTENT_MANAGEMENT_KEY>"
-H "Content-Type: application/vnd.contentful.management.v1+json"
-d '{
"name": "Product",
"displayField": "productName",
"fields": [
{
"name": "Description",
"id": "productDescription",
"type": "Text",
"localized": true,
"validations": []
},
{
"name": "Product name",
"id": "productName",
"type": "Text",
"localized": true,
"validations": []
},
// Other fields in content type
]
}'
"https://api.contentful.com/spaces/<SPACE_ID>/content_types/<CONTENT_TYPE_ID>"
Enabling in the entry editor
Another level of granularity is the ability to hide/show locales at the entry level. Say you have three locales enabled for your field but for your work you personally only need to see two of them. You can simply hide the locale fields of the language you don't need using the Translations controls in the sidebar! Keep in mind that this setting affects all of your entries and does not control what other users see.
Managing translators
Select which translations will be available for each entry:
After this step, entries will have different field values for each locale:
If you’re publishing content in more than one language, chances are you’ve got a trusted team of translators. However, as much as you trust your team, you might still want to limit their editing permissions within Contentful. You wouldn’t want your Italian translator to accidentally write “Ciao, bella!” in the field for Japanese. Control which locales your translator can work with using our custom roles and permissions feature. With this advanced feature you can set granular permissions for your members, including restricting their actions to one or more locales.
If you’re already working with a translation management system, you might be able to integrate it with Contentful. In theory, any platform with an API can interact with Contentful, it just takes some code work. To learn more, take a look at our API documentation.
Working with Multiple Locales
By now you might be wondering if having all of those extra locale fields becomes a problem when working with a large number of locales. The truth is, yes, if your entry requires many dozens of locales, it can be disruptive to your authoring environment. That being said, it definitely doesn’t stop our customers. Specialized Bicycle Components, for example, is using Contentful to publish in over 30 languages! There are ways to make working with many locales easier.
Control Content Visibility
First, you should make sure your translators know how to hide/show locales, as described above. This way they can limit the number of locales visible to them in the entry editor, which not only makes things more readable but also improves performance of the web app.
Another option is to use our roles and permissions feature to limit your editors to only editing or viewing certain locales. This way, when they open an entry in the editor, they would only see a few of your localized fields at a time. Does your Spanish translator really need to see the German version? Probably not. Make their life easier by only showing them the locales they really need to work with.
Tap into Localized Assets
Much as you can add localized content to your entries, you can create localized versions of your assets. The localization option extends to both - asset title and description, and the actual binary file. This allows you to use a single asset named flag, but display appropriate flags in different languages. Or you could re-use the same image, but add captions in multiple languages. In either case, you only need to add an asset to your entry once and from there on, our API will treat it according to the localization rules set out above.
Handling the Missing Translations
Now let's take a look at more advanced scenarios. The biggest problem editorial teams face is how to handle the missing translations for particular languages in a scalable and elegant way. To help you deal with that, you can turn to configuring custom fallback locales and understand the technical implications of leaving localized fields empty.
Custom fallback locales
By default, whenever Contentful comes across an entry with a missing localized content, it will revert to a default fallback locale (e.g. English). Alas, English is not always the most appropriate language to default to, because Swiss visitors usually prefer to see a German or French version of your website, while Argentinians are more comfortable with accessing content in standard Spanish. To accommodate the needs of your audience, you can define a custom fallback logic for your locales.
To construct a fallback tree, start with the lowest branch, say, Swiss German (locale code de-CH) and pick standard German (de-DE) as its fallback locale. You can repeat the same step for the Austrian German (de-AT), Luxembourgish German (de-LU), and Belgian German (de-BE), provided you actively use these locales. For the standard German, you can repeat the step and add another language as its fallback, or you can specify that no further fallback should be invoked.
To get the hang of it, imagine we have an entry with three locales - Spanish (Mexico)(es-MX), Spanish (Spain)(es-ES), and English (US)(en-US). The fallback logic is defined as (es-MX)
> (es-ES)
> (en-US)
and here is how API would respond in different scenarios:
CMA call | CDA call for locale es-MX |
Fallback logic |
---|---|---|
"title": { "en-US": "Hello NYC" } |
"title": "Hello NYC" | Falls back to es-ES which falls back to en-US which is "Hello NYC" |
"title": { "en-US": "Hello NYC", "es-ES": "Hola Barcelona" } |
"title": "Hola Barcelona" | Falls back to es-ES which is "Hola Barcelona" |
"title": { "en-US": "Hello NYC", "es-ES": "Hola Barcelona", "es-MX": "Hola Mexico" } |
"title": "Hola Mexico" | No fallback implied, returning es-MX value which is “Hola Mexico” |
When the same setup is modified to stop the fallback at (es-ES)
, the API output will behave as follows:
CMA call | CDA call for locale es-MX |
Fallback logic |
---|---|---|
"title": { "en-US": "Hello NYC" } |
No “title” field returned in the payload since no value present and the fallback chain stops at es-ES. |
Remember that once you designate a particular locale as a part of a fallback chain, you cannot disable it in a CDA response. We also didn't want you to accidentally get stuck in an infinite loop, so the dropdown for a new locale will automatically exclude all the locales already used in the current fallback chain.
Considerations on fallback locales:
- If you don't want a locale to fallback to anything, set its
fallbackCode
property tonull
. - You can create fallback chains. For example
de-CH
(Swiss German) fallbacks tode-AT
(Austrian German) andde-AT
fallbacks tode-DE
(German German) is a valid setup. If a locale in the chain doesn't have content, the API will try the next one. - Your fallback chain can't contain cycles, for example a fallback chain where
de-CH
fallbacks tode-AT
andde-AT
fallbacks tode-CH
. - Contentful will only use the content of the fallback locale when the requested one is not present in the entry or asset (i.e. you request 'en-US' but the entry has only content for 'de-DE'). This means that values like
null
or an empty string (""
) will not cause the system to use the value of the fallback locale. This can only be set via the API, and not with the Web app or SDKs. - If you don't specify a locale in your request, you will receive the entry from the default locale (
en-US
in this example):
curl -X GET "https://cdn.contentful.com/spaces/<SPACE_ID>/entries/<ENTRY_ID>?access_token=<CONTENT_DELIVERY_KEY>"
Empty fields
Another important nuance to master when handling edge cases in your web app is to understand when the CDA uses the defined fallback locale to return a value. The CDA fallback chain kicks in only when no value is defined for a given locale. To use the previous example, if es-MX locale has no value, then the CDA looks into es-ES. But what do we mean exactly by "no value"? Programmers know that this can take many shapes from undefined
to ""
to null
.
Whenever you interact with Contentful via the CMA, we follow the 'what you set is what you get' rule, meaning that setting either a null or an empty string value for the es-MX locale, will return this same value in the CDA payload and prevent the fallback mechanism from happening. By contrast, not setting any value for the locale will lead to the CDA returning the value of a fallback locale.
The Web Application takes a more conservative approach towards handling empty values. Whenever you have an entry with an empty field, it will trigger the fallback chain or, to put it in technical terms, editors cannot set a field value to ""
or null
from within the web app. This ensures a smoother editing experience and avoids accidental edits leading to undesired behavior. Also, note that any value set using the CMA will be preserved by the Web Application unless an editor edits the field.
Retrieving entries with a specific locale
If you want to retrieve fields from a specific locale (e.g de-AT
), use the locale=de-AT
parameter in your request:
curl -X GET "https://cdn.contentful.com/spaces/<SPACE_ID>/entries/<ENTRY_ID>?access_token=<CONTENT_DELIVERY_KEY>&locale=de-AT"
Retrieving all translations for an entry
You can retrieve all localized versions of an entry by using the 'wildcard' locale=*
parameter:
URL of request
curl -X GET "https://cdn.contentful.com/spaces/<SPACE_ID>/entries/<ENTRY_ID>?access_token=<CONTENT_DELIVERY_KEY>&locale=*"
Locales and the Sync API
No matter which locale you specify, the synchronization API always includes all localized content, using the same structure as the wildcard locale option above:
URL of request
curl -X GET "https://cdn.contentful.com/spaces/<SPACE_ID>/entries/<ENTRY_ID>sync?initial=true?access_token=<CONTENT_DELIVERY_KEY>&locale=de-AT"