alengo/sulu-translated-media-bundle
Installation:
composer require alengo/sulu-translated-media-bundle
Add to config/bundles.php:
Alengo\SuluTranslatedMediaBundle\TranslatedMediaBundle::class => ['all' => true],
Route Configuration:
Add to config/routes/sulu_admin.yaml:
TranslatedMediaBundle:
resource: "@TranslatedMediaBundle/Resources/config/routing_admin_api.yaml"
prefix: /admin/api
Database Migration:
bin/adminconsole doctrine:schema:update --force
First Media Translation:
seoFilename (e.g., red-shoes-de), title, and description for a locale (e.g., de).First Twig Usage:
{{ sulu_translated_media_url(media, '800x', app.request.locale) }}
Verify the URL reflects the translated filename (e.g., /uploads/red-shoes-de.jpg).
sulu_translated_media_url() and sulu_translated_media_urls() are the core tools for frontend integration.Entity\Media and Entity\MediaTranslations define the data model. Extend these if custom logic is needed.TranslatedFormatManager replaces Sulu’s default FormatManager. Override this if you need custom format handling.red-shoes.jpg) to Sulu.seoFilename to red-shoes-de for German (de).title to Rote Schuhe and description to Bequeme Herbstschuhe.<img src="{{ sulu_translated_media_url(productImage, '600x', app.request.locale) }}"
alt="{{ productImage.title[app.request.locale] }}">
Result: /uploads/red-shoes-de.jpg is served for German users, with localized alt text.Upload Media:
Translate Metadata:
seoFilename: URL-safe filename (e.g., product-guide-fr).title: Human-readable name (e.g., Guide du Produit).description: SEO description (e.g., Découvrez les fonctionnalités de notre produit).verifyDownload (for gated content), aiGenerated (for metadata).Generate URLs in Twig:
{{ sulu_translated_media_url(media, '400x', 'de') }}
Output: /uploads/guide-du-produit-de.jpg (if seoFilename is guide-du-produit for de).{% set urls = sulu_translated_media_urls(media, '800x', app.request.locale) %}
<picture>
<source srcset="{{ urls.webp }}" type="image/webp">
<img src="{{ urls.default }}" alt="{{ media.title[app.request.locale] }}">
</picture>
Fallback Handling:
product-guide.jpg).TranslatedFormatManager.If you need custom fields beyond the bundle’s seoFilename, title, description, verifyDownload, or aiGenerated:
// src/Entity/CustomMedia.php
use Alengo\SuluTranslatedMediaBundle\Model\MediaAdditionalDataTrait;
use Alengo\SuluTranslatedMediaBundle\Model\MediaTranslationsTrait;
class CustomMedia extends \Alengo\SuluTranslatedMediaBundle\Entity\Media
{
use MediaTranslationsTrait;
use MediaAdditionalDataTrait;
// Add custom fields
private ?string $customField = null;
// Getters/setters for customField...
}
Update config/packages/sulu_media.yaml to point to your custom entity:
sulu_media:
objects:
media:
model: App\Entity\CustomMedia
The "Additional Data" tab is auto-registered but can be customized:
# config/packages/sulu_admin.yaml
sulu_admin:
navigation:
main:
media:
children:
additional_data:
label: "Custom Tab Name"
route: "sulu_media.media.additional_data"
icon: "cog"
To customize how translated URLs are generated (e.g., add a hash for cache busting):
// src/DependencyInjection/Compiler/OverrideTranslatedFormatManagerPass.php
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Alengo\SuluTranslatedMediaBundle\Format\TranslatedFormatManager;
class OverrideTranslatedFormatManagerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$definition = $container->findDefinition(TranslatedFormatManager::class);
$definition->setClass(CustomTranslatedFormatManager::class);
$definition->setArguments([...]);
}
}
Register the pass in src/Kernel.php:
protected function build(ContainerBuilder $container): void
{
$container->addCompilerPass(new OverrideTranslatedFormatManagerPass());
}
Use Sulu’s Data Importer to bulk-add translations:
# config/packages/sulu_data_importer.yaml
sulu_data_importer:
importers:
media_translations:
class: App\Importer\MediaTranslationImporter
label: "Media Translations"
Implement the importer to update me_media_translations in bulk.
Cache translated URLs to reduce database lookups:
// src/Service/CachedTranslatedMediaUrlGenerator.php
use Symfony\Contracts\Cache\CacheInterface;
class CachedTranslatedMediaUrlGenerator
{
public function __construct(
private TranslatedFormatManager $manager,
private CacheInterface $cache
) {}
public function generate(string $mediaId, string $format, string $locale): string
{
$key = "media_url_{$mediaId}_{$format}_{$locale}";
return $this->cache->get($key, fn() => $this->manager->generate($mediaId, $format, $locale));
}
}
Register as a Twig extension:
// src/Twig/CachedTranslatedMediaExtension.php
class CachedTranslatedMediaExtension extends \Twig\Extension\AbstractExtension
{
public function getFunctions(): array
{
return [
new \Twig\TwigFunction('sulu_cached_translated_media_url', [$this->generator, 'generate']),
];
}
}
| Pattern | Example |
|---|---|
| Locale-Aware URLs | {{ sulu_translated_media_url(media, '400x', app.request.locale) }} |
| Responsive Images | Use sulu_translated_media_urls() with <picture> tags. |
| Fallback Handling | Missing translations fall back to original filenames. |
| Admin Metadata | Edit seoFilename, title, description per locale in the UI. |
| Custom Entities | Extend Entity\Media for project-specific fields. |
| Cache Optimization | Cache translated URLs to reduce DB queries. |
seoFilename is empty, the bundle falls back to the original filename.seoFilename for all target locales in the admin UI.How can I help you explore Laravel packages today?