Installation:
composer require-dev c975l/xliff-bundle
Note: Install only in dev environment.
Enable Bundle:
Register in config/bundles.php (Symfony 4+) or AppKernel.php (Symfony 3):
// config/bundles.php
return [
// ...
c975L\XliffBundle\c975LXliffBundle::class => ['dev' => true, 'test' => true],
];
Add Routes:
In config/routes/dev.yaml (Symfony 4+):
c975_l_xliff:
resource: "@c975LXliffBundle/Controller/"
type: annotation
prefix: /_xliff
Database Table:
Create a table with columns for each language (e.g., en, fr) and a unique identifier (e.g., id or key).
Example schema:
CREATE TABLE translations (
key VARCHAR(255) PRIMARY KEY,
en TEXT,
fr TEXT,
de TEXT
);
First Export:
Visit /_xliff/export in your browser or trigger via CLI:
php bin/console xliff:export
Outputs .xlf files to var/xliff/ (default).
Define Table Structure:
Ensure your table has a column for each target language (e.g., en, fr) and a unique key column.
Example entity:
// src/Entity/Translation.php
#[ORM\Entity]
class Translation {
#[ORM\Id, ORM\Column]
private string $key;
#[ORM\Column]
private string $en;
#[ORM\Column]
private string $fr;
}
Customize Export: Override the default controller or service:
// src/Controller/XliffExportController.php
class XliffExportController extends AbstractXliffExportController {
protected function getEntityManager(): EntityManagerInterface {
return $this->container->get('doctrine')->getManager();
}
protected function getEntityClass(): string {
return Translation::class;
}
protected function getKeyColumn(): string {
return 'key';
}
protected function getLanguageColumns(): array {
return ['en', 'fr'];
}
}
Automate with CLI:
Schedule exports via cron or Symfony commands:
php bin/console xliff:export --entity=Translation --key=key --languages=en,fr
Integrate with CI/CD:
Add to deployment pipeline to generate .xlf files for translators:
# .github/workflows/translations.yml
jobs:
export-translations:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: composer install
- run: php bin/console xliff:export
- uses: actions/upload-artifact@v3
with:
name: xliff-files
path: var/xliff/
Import Workflow: After translations are updated, import back into the database:
php bin/console doctrine:fixtures:load --append
Note: Requires custom fixture logic to parse .xlf files.
Symfony Translation System:
Use exported .xlf files with tools like PoEditor or Crowdin.
Example Crowdin config:
# crowdin.yml
files:
- source: /var/xliff/translations.xlf
translation: /var/xliff/%locale%.xlf
Dynamic Language Handling: Fetch supported languages from the database:
$languages = $entityManager->getRepository(Translation::class)
->createQueryBuilder('t')
->select('DISTINCT COLUMN_GET(t, 2)') // Adjust for your DB
->getQuery()
->getScalarResult();
Partial Exports:
Filter by entity state (e.g., published = true):
protected function getQueryBuilder(EntityManagerInterface $em): QueryBuilder {
return $em->getRepository($this->getEntityClass())
->createQueryBuilder('t')
->where('t.published = :published')
->setParameter('published', true);
}
Dev-Only Bundle:
Forgetting to restrict the bundle to dev/test environments may expose sensitive routes in production.
Fix: Double-check config/bundles.php or AppKernel.php.
Column Naming:
The bundle assumes language columns are named exactly as ISO codes (e.g., en, fr).
Fix: Override getLanguageColumns() to match your schema:
protected function getLanguageColumns(): array {
return ['language_en', 'language_fr']; // Custom column names
}
File Overwrites:
By default, exports overwrite existing .xlf files.
Fix: Append timestamps to filenames:
protected function getOutputPath(): string {
return sprintf('var/xliff/%s_%s.xlf', $this->getKey(), (new \DateTime())->format('YmdHis'));
}
Memory Limits: Large tables may hit PHP memory limits during export. Fix: Use chunked queries:
protected function getQueryBuilder(EntityManagerInterface $em): QueryBuilder {
return $em->getRepository($this->getEntityClass())
->createQueryBuilder('t')
->setMaxResults(1000); // Process in batches
}
Doctrine Proxy Issues:
Lazy-loaded entities may cause errors in CLI exports.
Fix: Use EntityManager::createQueryBuilder() instead of Repository methods.
Check Routes: Ensure routes are loaded:
php bin/console debug:router | grep xliff
Validate Table Structure: Run a test export manually:
php bin/console xliff:export --dry-run
Output: Shows SQL query and expected structure.
Log Exports:
Enable debug mode to log generated .xlf content:
// config/packages/dev/monolog.yaml
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
channels: ["!event"]
Custom XLIFF Structure:
Extend the XliffGenerator service to modify the .xlf schema:
// src/Service/CustomXliffGenerator.php
class CustomXliffGenerator extends XliffGenerator {
public function generate(array $translations): string {
$xlf = parent::generate($translations);
// Add custom headers or namespaces
return str_replace('<xliff>', '<xliff version="2.1" custom="attribute">', $xlf);
}
}
Register as a service override:
# config/services.yaml
services:
c975_l_xliff.xliff_generator:
class: App\Service\CustomXliffGenerator
tags: ['xliff.generator']
Post-Export Hooks: Trigger actions after export (e.g., notify translators):
// src/EventListener/XliffExportListener.php
class XliffExportListener implements KernelEventSubscriberInterface {
public static function getSubscribedEvents() {
return [
KernelEvents::TERMINATE => 'onXliffExport',
];
}
public function onXliffExport(KernelEvent $event) {
if ($event->isMainRequest() && $event->getRequest()->get('_route') === 'xliff_export') {
// Send email, log, etc.
}
}
}
Multi-Database Support:
For multi-DB setups, override the EntityManager:
protected function getEntityManager(): EntityManagerInterface {
return $this->container->get('doctrine.orm.entity_manager.secondary');
}
Output Directory:
Default: var/xliff/. Change via:
# config/packages/c975_l_xliff.yaml
c975_l_xliff:
output_dir: '%kernel.project_dir%/public/translations'
Filename Template: Customize filenames
How can I help you explore Laravel packages today?