Modeling for multiple channels and localization
Modeling for multiple channels and localization
TL;DR
- Mark fields as non-localizable by default; opt in to localization only for text that editors actually translate.
- Design the fallback locale hierarchy before launch -- restructuring it later requires migrating existing entries.
- Keep field names channel-agnostic (short_description, not mobile_description) and let each frontend adapt the same structured data.
- Use JSON RTE + Image Delivery API transforms instead of per-channel HTML or per-channel image fields.
Content that is structurally bound to a single channel or a single language cannot scale. If your content types embed HTML markup, assume a specific screen width, or hard-code text strings without localization, every new channel or market launch becomes a rearchitecting project instead of a configuration change.
This lesson covers two interrelated concerns: how Contentstack's localization system works at the content modeling level, and how to design content types that serve multiple delivery channels without per-channel duplication.
How localization works in Contentstack
Localization in Contentstack operates at three levels: stack configuration, content type field settings, and entry-level data.
Stack-level language configuration
Languages are configured at the stack level under Settings > Languages. You add each locale your project needs - en-us, fr-fr, de-de, ja-jp, etc. - and define a fallback hierarchy. For example:
- fr-ca falls back to fr-fr, which falls back to en-us
- de-at falls back to de-de, which falls back to en-us
The fallback hierarchy determines what happens when an entry has not been localized for a particular language. If an editor has not translated a product description into fr-ca, the system returns the fr-fr version. If fr-fr also lacks a translation, it falls back to en-us.
This fallback behavior is automatic in the Delivery API. When you request an entry with locale=fr-ca, the response includes the best available version for each field based on the fallback chain. You do not need to implement fallback logic in your frontend.
Field-level localization settings
Not every field needs to be localized. In the content type builder, each field has a "Localize this field" checkbox. This is a critical modeling decision.
Fields that should typically be localized:
- Title, description, and any editorial text
- Rich text content
- Image alt text
- SEO metadata (meta_title, meta_description)
- CTA button labels
Fields that should typically NOT be localized:
- Dates and timestamps (December 15 is December 15 in every language)
- Numeric values like price, weight, or dimensions (unless currency conversion is involved)
- Reference fields (the relationship between a product and its category does not change by language)
- UIDs, slugs, or identifiers used for routing
- Boolean flags (featured, archived, etc.)
When you mark a field as non-localizable, editors cannot enter per-language values for that field. The base language value is used everywhere. This reduces editorial workload and prevents inconsistencies - an event date that accidentally differs between English and French is a data integrity problem, not a translation issue.
Here is a content type schema snippet showing localization configuration:
{
"display_name": "Product Name",
"uid": "product_name",
"data_type": "text",
"mandatory": true,
"field_metadata": {
"description": "Localized product display name"
},
"non_localizable": false
},
{
"display_name": "SKU",
"uid": "sku",
"data_type": "text",
"mandatory": true,
"unique": true,
"non_localizable": true
},
{
"display_name": "Release Date",
"uid": "release_date",
"data_type": "isodate",
"non_localizable": true
}The non_localizable: true property on SKU and Release Date means these fields carry the same value regardless of locale. Product Name, with non_localizable: false (or the property omitted, which defaults to localizable), can have different values per language.
Entry-level localization
When an editor opens an entry and switches to a different language using the locale selector in the Contentstack UI, they see the entry form filtered to localizable fields. Non-localizable fields are either hidden or shown as read-only. The editor translates the localizable fields, saves, and the localized version is stored.
In the API, you retrieve localized content by passing the locale query parameter:
# Fetch the French version of a product curl -X GET "https://cdn.contentstack.io/v3/content_types/product/entries?locale=fr-fr" \ -H "api_key: YOUR_API_KEY" \ -H "access_token: YOUR_DELIVERY_TOKEN"
The response includes the French values for localizable fields and the base-language values for non-localizable fields. If a localizable field has not been translated into fr-fr, the fallback chain determines which value is returned.
Fallback locales and the locale hierarchy
The fallback hierarchy is more than a convenience feature. It is a content strategy tool.
Consider a global brand launching in 8 markets: US English, UK English, Canadian English, Canadian French, France French, German, Austrian German, and Japanese. Without fallback, the editorial team must translate every entry into all 8 locales before launch. With a well-designed fallback hierarchy:
en-us (base) ├── en-gb (British English falls back to US English) ├── en-ca (Canadian English falls back to US English) ├── fr-fr (French falls back to US English) │ └── fr-ca (Canadian French falls back to France French) ├── de-de (German falls back to US English) │ └── de-at (Austrian German falls back to German) └── ja-jp (Japanese falls back to US English)
The editorial team can launch with only US English content. British, Canadian English, and Austrian German pages display intelligible content from day one, even before localization work begins. The team then prioritizes: Japanese first (because the fallback to English is least useful), then French and German, then regional variants.
This hierarchy must be planned during content modeling, not bolted on after launch. Adding or restructuring fallback locales after entries exist requires careful migration because existing localized entries reference the fallback chain that was active when they were created.
Multi-channel content modeling
Localization handles language variation. Multi-channel handles delivery variation: the same content served to a website, a mobile app, a digital kiosk, a voice assistant, or an email template.
The core principle: structure content for meaning, not for presentation. Channel-specific rendering is the frontend's job. The content model should provide clean, structured data that any channel can consume and render appropriately.
What channel-neutral content looks like
A channel-neutral product description:
{
"product_name": "Alpine Trail Runner",
"tagline": "Built for mountain terrain",
"description": {
"type": "doc",
"children": [
{
"type": "paragraph",
"children": [{ "text": "The Alpine Trail Runner features a reinforced toe cap and aggressive tread pattern designed for rocky trails." }]
}
]
},
"key_features": ["Reinforced toe cap", "Aggressive tread", "Waterproof membrane", "Vibram outsole"],
"weight_grams": 310,
"hero_image": { "url": "https://images.contentstack.io/v3/assets/.../alpine-trail.jpg" }
}This content works for:
- Web: render the full description with the hero image at 1200px width
- Mobile app: render the tagline and key_features as a bullet list, load the image at 400px width using Contentstack's Image Delivery API transforms
- Voice assistant: read the tagline and first two key_features aloud
- Email: use the tagline, first paragraph, and a resized image
No channel-specific data lives in the content model. Each frontend adapts the same structured content to its rendering context.
What channel-coupled content looks like (and why it fails)
A channel-coupled product description:
{
"product_name": "Alpine Trail Runner",
"web_hero_html": "<div class="hero-banner"><img src="..." style="width:100%"><h1>Alpine Trail Runner</h1></div>",
"mobile_short_description": "Built for mountain terrain",
"email_preview_text": "Check out the Alpine Trail Runner - built for mountain terrain",
"web_description": "<p>The Alpine Trail Runner features a <strong>reinforced toe cap</strong>...</p>",
"kiosk_display_mode": "fullscreen"
}This model has embedded HTML (web_hero_html), channel-specific fields (mobile_short_description, email_preview_text), and presentation instructions (kiosk_display_mode). Every new channel requires new fields. Every change to the marketing copy requires updating multiple fields. Every editor must understand which fields map to which channels.
Designing for channel flexibility
Practical rules for multi-channel content modeling:
- Store content as structured data, not markup. Use JSON Rich Text Editor for editorial content instead of HTML or Markdown fields. JSON RTE produces a structured document tree that renderers can transform per channel (see lesson 2.1.4 for JSON RTE details).
- Use Contentstack's Image Delivery API for responsive images. Store one high-resolution image. Let each channel request the appropriate size and format using URL parameters (?width=400&format=webp). Do not create separate image fields for web, mobile, and email.
- Separate content from layout. If you need per-channel layout differences, handle that in your page composition layer (Modular Blocks, page content types) rather than inside individual content entries. A product's description is the same everywhere; how it appears on screen is a frontend concern.
- Keep field names channel-agnostic. Name the field short_description, not mobile_description. Name it hero_image, not web_banner_image. If the content is the same data, it should have one field regardless of how many channels consume it.
- Use the include[] parameter strategically. Mobile apps may not need reference-resolved data that the web frontend uses. Rather than modeling different content types per channel, fetch the same content type with different include depths per consumer.
Worked example: global retail brand
A global retail brand sells products through a website (English, French, German, Japanese) and a mobile app that uses the same content types.
Content type: Product
| Field | Type | Localized | Rationale |
| product_name | Text | Yes | Translated per market |
| tagline | Text | Yes | Translated per market |
| description | JSON RTE | Yes | Translated per market |
| key_features | Group (repeatable) | Yes | Feature names translated |
| sku | Text | No | Same globally |
| price | Number | No | Managed in commerce system, not CMS |
| weight_grams | Number | No | Physical attribute, language-independent |
| hero_image | File | No | Same image globally (alt text is localized via a separate field) |
| image_alt_text | Text | Yes | Accessibility text translated |
| category | Reference | No | Categorization is structural, not linguistic |
| seo | Global Field (SEO Metadata) | Yes | meta_title and meta_description translated |
The website fetches locale=fr-fr and renders the French product name, description, and SEO metadata. The mobile app fetches the same entry with locale=ja-jp and renders the Japanese version. Both consume the same hero_image URL but request different sizes via Image Delivery API parameters.
The editorial workflow: content is authored in en-us first. A localization team opens each entry, switches to fr-fr, translates the localizable fields, and publishes. Then de-de, then ja-jp. Untranslated markets fall back through the hierarchy until translations are complete.
Common mistakes
Common pitfall: Making a reference field or date field localizable is one of the hardest bugs to catch. The French version of a product silently points to a different category than the English version, and nothing in the UI flags the inconsistency. Default every field to non_localizable: true and only enable localization for fields that contain translatable text.
Localizing fields that should be universal. Making a date field or a reference field localizable creates a scenario where the French version of a product points to a different category than the English version. Unless this is intentional (rare), it is a data integrity risk. Default to non-localizable and opt in to localization per field.
Embedding channel-specific markup in content fields. Storing <div class="web-hero"> in a CMS field locks the content to one rendering context. When the mobile team requests the same content, they receive HTML they cannot use. Use JSON RTE and let renderers handle markup generation.
Ignoring fallback hierarchy design until launch. Fallback locales are an architectural decision, not a post-launch configuration. A poorly designed hierarchy means editorial teams do unnecessary duplicate work or users see fallback content that does not make sense for their region.