Localization & Multi-Language
Build multi-language websites with BlocksWeb's built-in localization system. Manage translations for 76+ locales directly in the visual editor.
How Localization Works
BlocksWeb automatically handles translations for your content. Content editors can translate text without code changes.
What Can Be Translated
Only two option types are translatable:
type: 'text'- Short text fieldstype: 'richtext'- Formatted content
Other option types (images, colors, numbers, etc.) are not locale-specific - they remain the same across all languages.
Component Schema
import { IBlockswebComponent } from '@blocksweb/core';
const Hero: IBlockswebComponent = ({ title, subtitle, image }) => {
return (
<section>
<img src={image} alt="" />
<h1>{title}</h1>
<div dangerouslySetInnerHTML={{ __html: subtitle }} />
</section>
);
};
Hero.schema = {
displayName: 'Hero Section',
options: [
{
type: 'text', // ✅ Translatable
name: 'title',
label: 'Title',
default: 'Welcome'
},
{
type: 'richtext', // ✅ Translatable
name: 'subtitle',
label: 'Subtitle',
default: '<p>Get started today</p>'
},
{
type: 'image', // ❌ NOT translatable
name: 'image',
label: 'Background Image',
default: '/hero.jpg'
}
]
};
export default Hero;Page Content Structure
BlocksWeb stores translations in a structured format:
{
blocks: [
{
id: "hero-1",
type: "Hero Section",
props: {
title: "Welcome", // Default values
subtitle: "<p>Get started</p>",
image: "/hero.jpg"
}
}
],
locales: {
DEFAULT: {
"hero-1": {
title: "Welcome",
subtitle: "<p>Get started today</p>"
}
},
FR: {
"hero-1": {
title: "Bienvenue",
subtitle: "<p>Commencez aujourd'hui</p>"
}
},
ES: {
"hero-1": {
title: "Bienvenido",
subtitle: "<p>Comience hoy</p>"
}
}
}
}How It Works
- DEFAULT locale always exists - this is the fallback
- Each locale (FR, ES, etc.) stores translations per block
- Only text/richtext fields are stored in locales
- BlocksWeb automatically resolves the correct translation when rendering
Using the Editor
Adding Locales
- Click the locale selector in the editor toolbar
- Click "Add Locales"
- Select from 76+ supported locales
- Click "Add Selected Locales"
Supported Locales
BlocksWeb supports 76 locales including:
en-US- English (United States)en-GB- English (United Kingdom)es-ES- Spanish (Spain)fr-FR- French (France)de-DE- German (Germany)it-IT- Italian (Italy)pt-BR- Portuguese (Brazil)ja-JP- Japanese (Japan)zh-CN- Chinese (Simplified)nl-NL- Dutch (Netherlands)ar-SA- Arabic (Saudi Arabia)ru-RU- Russian (Russia)ko-KR- Korean (South Korea)- And 63 more...
Translation Panel
The Translation Panel appears when you select a component:
Features:
- Current Language Selector - Switch between locales
- Translatable Content List - Shows all text/richtext fields
- Default Value Reference - See the DEFAULT locale value
- Copy from Default - Quick copy button
- Translation Status Badges:
Translated ✓- Has translationMissing ⚠- No translation, using defaultEmpty- No content
Workflow:
- Select a component with text fields
- Switch to target locale (e.g., FR)
- See which fields need translation
- Click "Copy from Default" if you want to start with the default text
- Edit the translation
- Changes auto-save
Switching Locales
Use the locale selector in the editor toolbar to:
- Preview your site in different languages
- Edit translations for the selected locale
- See which content needs translation
Fallback Chain
BlocksWeb uses a smart fallback system:
Current Locale → DEFAULT Locale → Schema Default → Empty StringExample:
// Component schema default
{ type: 'text', name: 'title', default: 'Welcome' }
// Page locales
{
DEFAULT: { 'block-1': { title: 'Welcome to Our Site' } },
FR: { 'block-1': { title: 'Bienvenue' } },
ES: { 'block-1': {} } // No Spanish translation
}
// What users see:
// EN: "Welcome to Our Site" (from DEFAULT locale)
// FR: "Bienvenue" (from FR locale)
// ES: "Welcome to Our Site" (falls back to DEFAULT)
// IT: "Welcome to Our Site" (falls back to DEFAULT, IT not defined)Automatic Resolution
BlocksWeb automatically resolves translations:
// You don't write this code - BlocksWeb does it for you!
function resolveLocalizedProps(block, locales, currentLocale) {
let props = { ...block.props };
Object.keys(props).forEach((propName) => {
const option = findOption(block.type, propName);
// Only resolve text and richtext
if (!['text', 'richtext'].includes(option.type)) {
return;
}
// Fallback chain
if (locales[currentLocale]?.[block.id]?.[propName]) {
props[propName] = locales[currentLocale][block.id][propName];
} else if (locales.DEFAULT?.[block.id]?.[propName]) {
props[propName] = locales.DEFAULT[block.id][propName];
} else {
props[propName] = option.default || '';
}
});
return props;
}Your component simply receives the correct props:
const Hero = ({ title, subtitle }) => {
// title and subtitle are already translated!
return (
<section>
<h1>{title}</h1>
<p>{subtitle}</p>
</section>
);
};RichText Component
The RichText utility component handles translations automatically:
import { RichText } from '@blocksweb/core/client';
const BlogPost: IBlockswebComponent = ({ content }) => {
return (
<article>
<RichText
propName="content"
text={content} // Already translated by BlocksWeb!
/>
</article>
);
};
BlogPost.schema = {
displayName: 'Blog Post',
options: [
{
type: 'richtext',
name: 'content',
label: 'Post Content',
default: '<p>Write your post...</p>'
}
]
};Collections & Localization
Important: Collections are NOT locale-aware. They store data independently of the page locale system.
Option 1: Manual Locale Fields
Create separate fields for each language:
const productCollection: CollectionDefinition = {
name: 'products',
displayName: 'Products',
fields: [
{ name: 'nameEN', type: 'string', label: 'Name (English)' },
{ name: 'nameFR', type: 'string', label: 'Name (French)' },
{ name: 'nameES', type: 'string', label: 'Name (Spanish)' },
{ name: 'descriptionEN', type: 'richtext', label: 'Description (EN)' },
{ name: 'descriptionFR', type: 'richtext', label: 'Description (FR)' },
{ name: 'descriptionES', type: 'richtext', label: 'Description (ES)' }
]
};Component usage:
import { useEditorContext } from '@blocksweb/core/client';
const ProductCard: IBlockswebComponent = ({ product }) => {
const { locale } = useEditorContext();
// Select the right field based on locale
const name = product[`name${locale}`] || product.nameEN;
const description = product[`description${locale}`] || product.descriptionEN;
return (
<div>
<h3>{name}</h3>
<div dangerouslySetInnerHTML={{ __html: description }} />
</div>
);
};Option 2: JSON Locale Field
Store all translations in a single JSON field:
const productCollection: CollectionDefinition = {
name: 'products',
fields: [
{ name: 'name', type: 'string', label: 'Name (structured)' },
// Store as: { "EN": "Product", "FR": "Produit", "ES": "Producto" }
]
};Component usage:
const ProductCard: IBlockswebComponent = ({ product }) => {
const { locale } = useEditorContext();
const nameData = JSON.parse(product.name);
const name = nameData[locale] || nameData.EN;
return <div><h3>{name}</h3></div>;
};Best Practices
1. Always Provide DEFAULT Content
// ✅ Good - DEFAULT locale always has content
{
DEFAULT: { 'block-1': { title: 'Welcome' } },
FR: { 'block-1': { title: 'Bienvenue' } }
}
// ❌ Bad - DEFAULT locale empty
{
DEFAULT: { 'block-1': {} },
FR: { 'block-1': { title: 'Bienvenue' } }
}2. Use Clear Field Labels
// ✅ Good - Clear which language
{ name: 'nameEN', label: 'Product Name (English)' }
{ name: 'nameFR', label: 'Product Name (French)' }
// ❌ Bad - Unclear
{ name: 'name1', label: 'Name 1' }
{ name: 'name2', label: 'Name 2' }3. Test All Locales
Always test your site in:
- DEFAULT locale (fallback)
- All configured locales
- A locale you haven't added yet (tests fallback)
4. Keep Non-Text Content Shared
// ✅ Good - Image is NOT in locales
{
type: 'image',
name: 'heroImage',
label: 'Hero Image'
}
// ❌ Bad - Don't create multiple image fields per locale
{
type: 'image',
name: 'heroImageEN',
label: 'Hero Image (English)'
}Common Patterns
Multi-Language Navigation
const Navigation: IBlockswebComponent = ({ links }) => {
return (
<nav>
{links.map((link, index) => (
<a key={index} href={link.url}>
{link.label} {/* Automatically translated */}
</a>
))}
</nav>
);
};
Navigation.schema = {
displayName: 'Navigation',
options: [
{
type: 'component',
name: 'links',
label: 'Navigation Links',
multiple: true,
allowedComponents: ['NavLink']
}
]
};
// NavLink component
const NavLink: IBlockswebComponent = ({ label, url }) => {
return <span>{label}</span>;
};
NavLink.schema = {
displayName: 'Nav Link',
options: [
{ type: 'text', name: 'label', label: 'Label', default: 'Home' },
{ type: 'text', name: 'url', label: 'URL', default: '/' }
]
};Multi-Language Forms
const ContactForm: IBlockswebComponent = ({
title,
nameLabel,
emailLabel,
messageLabel,
submitButton
}) => {
return (
<form>
<h2>{title}</h2>
<label>{nameLabel}</label>
<input type="text" />
<label>{emailLabel}</label>
<input type="email" />
<label>{messageLabel}</label>
<textarea />
<button type="submit">{submitButton}</button>
</form>
);
};
ContactForm.schema = {
displayName: 'Contact Form',
options: [
{ type: 'text', name: 'title', default: 'Contact Us' },
{ type: 'text', name: 'nameLabel', default: 'Name' },
{ type: 'text', name: 'emailLabel', default: 'Email' },
{ type: 'text', name: 'messageLabel', default: 'Message' },
{ type: 'text', name: 'submitButton', default: 'Send' }
]
};
// Translations in editor:
// EN: "Contact Us", "Name", "Email", "Message", "Send"
// FR: "Nous Contacter", "Nom", "E-mail", "Message", "Envoyer"
// ES: "Contáctenos", "Nombre", "Correo", "Mensaje", "Enviar"Accessing Current Locale
Use the editor context to access the current locale:
import { useEditorContext } from '@blocksweb/core/client';
const MyComponent: IBlockswebComponent = ({ title }) => {
const { locale } = useEditorContext();
return (
<div>
<p>Current locale: {locale}</p>
<h1>{title}</h1>
</div>
);
};Next Steps
- RichText Component - Formatted content with translations
- Creating Components - Build translatable components
- Collections Overview - Work with structured data
Key Takeaways
✅ Only text and richtext options are translatable ✅ DEFAULT locale is always the fallback ✅ BlocksWeb automatically resolves translations ✅ 76+ locales supported out of the box ✅ Translation Panel makes managing translations easy ✅ Collections are NOT locale-aware (use manual fields) ✅ Components receive translated props automatically