Configure Multi-Language Content and Webspace Routing in Sulu CMS 2.x
Step-by-step guide to configuring multi-language content structures, webspace routing, locale detection, and cache invalidation in Sulu CMS 2.x.
29 Jul 2025, 11:10 UTC

Desired Outcome
A Sulu 2.x installation serving localized content across multiple languages using either distinct domains (example.com, example.de) or path prefixes (/en/, /de/), with correct route generation, admin UI locale restrictions, and cache invalidation per language variant.
Prerequisites
- Sulu 2.x project with Symfony 6.x or 7.x
- PHP 8.1+ with required extensions (intl, pdo_mysql)
- Database configured and migrations applied
- Admin bundle enabled (sulu/admin-bundle)
- CLI access to run console commands as the project user (typically www-data or your deploy user)
Procedure
1. Define Webspaces in config/sulu.yaml
Each webspace represents a logical site or language entry point. Configure defaultLocale, supported locales, and portalUrl for absolute URL generation. Use consistent schemes (all https) to avoid mixed-content warnings.
# config/sulu.yaml
sulu_website:
webspaces:
en:
key: en
name: English Site
defaultLocale: en
locales: [en, de]
portalUrl: https://example.com
# or for path-prefix routing:
# portalUrl: https://example.com/en
de:
key: de
name: German Site
defaultLocale: de
locales: [de, en]
portalUrl: https://example.de
# or: https://example.com/deRisk: Changing locales after content exists requires manual migration of document translations. Plan all locales upfront.
2. Create Structure XML with Translatable Properties
In config/templates/pages/, define page templates. Mark fields that vary by language with translatable="true"; non-translatable fields share values across locales.
<?xml version="1.0" encoding="UTF-8"?>
<structure xmlns="http://schemas.sulu.io/template/template"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://schemas.sulu.io/template/template http://schemas.sulu.io/template/template-1.0.xsd">
<key>default</key>
<properties>
<property name="title" type="text_line" translatable="true">
<tag>sulu.rte.title</tag>
</property>
<property name="body" type="text_editor" translatable="true">
<tag>sulu.rte.body</tag>
</property>
<property name="author" type="text_line" translatable="false">
<tag>sulu.rte.author</tag>
</property>
</properties>
</structure>Caution: The translatable attribute cannot be toggled after initial schema creation without a database migration. Run bin/console sulu:content:initialize carefully.
3. Map Webspaces to Hostnames or Path Prefixes
Configure config/packages/sulu_route.yaml to bind webspace keys to hosts or path prefixes. Locale detection prioritizes path prefix over host mapping; avoid conflicting rules.
# config/packages/sulu_route.yaml
sulu_route:
mappings:
en:
host: example.com
# or for path prefix:
# path: /en
de:
host: example.de
# or: path: /de
# Optional: enable locale detection from Accept-Language header as fallback
locale_detector:
enabled: true4. Verify Generated Routes
Run the route dump in the target environment (prod) because router cache warming changes output between dev and prod.
# Run from project root as the deploy user
bin/console sulu:website:dump-routes --env=prodExpected output shows each webspace with correct locale prefixes and host mappings. Example snippet:
en_homepage ANY /en/ {_locale=en, _webspace=en}
de_homepage ANY /de/ {_locale=de, _webspace=de}5. Implement Language Switcher in Twig
Inject the sulu_router service and generate localized URLs for the current content.
{# templates/partials/language_switcher.html.twig #}
{% set currentLocale = app.request.locale %}
{% set otherLocales = ['en', 'de']|filter(l => l != currentLocale) %}
<ul class="language-switcher">
{% for locale in otherLocales %}
<li>
<a href="{{ sulu_router.generate(content, { locale: locale }) }}">
{{ locale|upper }}
</a>
</li>
{% endfor %}
</ul>6. Persist Translations via Document Manager
When creating or updating content programmatically, set the locale before assigning translatable values.
$document = $documentManager->create('default');
$document->setStructureType('default');
$document->setTitle('English Title');
$document->setBody('English body');
$documentManager->persist($document);
// Create German translation
$document->setLocale('de');
$document->setTitle('Deutscher Titel');
$document->setBody('Deutscher Text');
// author remains shared (translatable=false)
$documentManager->flush();7. Restrict Admin UI Locales per Webspace
Limit editor-visible locales in config/packages/sulu_admin.yaml. This only affects UI display; API endpoints still accept any configured locale unless explicitly restricted.
# config/packages/sulu_admin.yaml
sulu_admin:
localization:
en:
locales: [en]
de:
locales: [de]8. Configure Cache Invalidation for Localized Responses
Tag responses with webspace and locale, then purge specific variants on content updates.
# config/packages/sulu_http_cache.yaml
sulu_http_cache:
invalidator:
enabled: true
tags:
webspace: true
locale: trueIn your controller or subscriber, the sulu_http_cache.invalidator service will automatically tag responses. To manually purge:
$invalidator = $container->get('sulu_http_cache.invalidator');
$invalidator->invalidate(['webspace-en', 'locale-en']);Expected Checks
- Route verification: Run
bin/console sulu:website:dump-routes --env=prodand confirm each webspace shows routes with correct locale prefixes and host mappings. - Admin UI test: Create a test page, switch to a secondary locale, modify translatable fields, save, then verify both language versions render correctly on the frontend.
- Locale detection: Use curl with headers to test automatic selection:
Should serve German content without explicit path prefix.curl -H "Host: example.de" -H "Accept-Language: de-DE" https://example.de/ - Database inspection: Check
sulu_documenttable for separate rows per locale with matchinguuidbut differentlocaleand translatable field values. - Cache tags: Inspect
X-Cache-Tagsheader on localized responses; confirmwebspace-enandlocale-en(or de) tags are present. - Schema validation: After structure changes, run
bin/console doctrine:schema:validateto ensure translatable column mappings match entity definitions.
Recovery Options
Rollback Configuration Changes
If route mappings or webspace config cause issues, revert YAML files and clear caches:
bin/console cache:clear --env=prod
bin/console sulu:website:dump-routes --env=prodMigrate Locales After Content Exists
If you must add a locale post-launch:
locales array in sulu.yaml.bin/console sulu:content:initialize (review caution above).setLocale() before setting translatable fields.Fix Mixed-Content Warnings
Ensure all portalUrl entries use the same scheme (https). Update and clear cache:
bin/console cache:clear --env=prodLimitations
- Admin locale filter does not restrict API access; implement custom voters or firewall rules if API locale restriction is required.
- Path-prefix and host-based routing cannot be mixed for the same webspace key.
- Translatable property changes require schema migration; not supported via
sulu:content:initializealone. - Route dump output is environment-specific; always verify in prod-like environment.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.