PHPackages                             terminal42/dc\_multilingual - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Localization &amp; i18n](/categories/localization)
4. /
5. terminal42/dc\_multilingual

ActiveContao-bundle[Localization &amp; i18n](/categories/localization)

terminal42/dc\_multilingual
===========================

A multilingual DC driver storing the translations in the same table for Contao Open Source CMS

4.7.0(2mo ago)1787.2k↓31.5%8[1 issues](https://github.com/terminal42/contao-DC_Multilingual/issues)[1 PRs](https://github.com/terminal42/contao-DC_Multilingual/pulls)12LGPL-3.0+PHPPHP ^8.3CI passing

Since Sep 2Pushed 2mo ago7 watchersCompare

[ Source](https://github.com/terminal42/contao-DC_Multilingual)[ Packagist](https://packagist.org/packages/terminal42/dc_multilingual)[ GitHub Sponsors](https://github.com/terminal42)[ Fund](https://ko-fi.com/terminal42)[ RSS](/packages/terminal42-dc-multilingual/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (9)Dependencies (12)Versions (53)Used By (12)

DC\_Multilingual
================

[](#dc_multilingual)

This is a standalone DC driver for Contao Open Source CMS that allows you to easily make your data translatable.

DCA configuration
-----------------

[](#dca-configuration)

```
// Set the driver
$GLOBALS['TL_DCA']['table']['config']['dataContainer'] = \Terminal42\DcMultilingualBundle\Driver::class;

// Languages you want to provide for translation (default: Languages of all root pages)
$GLOBALS['TL_DCA']['table']['config']['languages'] = ['en', 'de', 'pl'];

// Database column that contains the language keys (default: "language")
$GLOBALS['TL_DCA']['table']['config']['langColumnName'] = 'language';

// Database column that contains the reference id (default: "langPid")
$GLOBALS['TL_DCA']['table']['config']['langPid'] = 'langPid';

// Fallback language - if none is given then there will be another language "fallback" selectable from the dropdown
$GLOBALS['TL_DCA']['table']['config']['fallbackLang'] = 'en';

// Use '*' to make a field translatable for all languages
$GLOBALS['TL_DCA']['table']['fields']['username']['eval']['translatableFor'] = '*';

// Use an array of language keys to specify for which languages the field is translatable
$GLOBALS['TL_DCA']['table']['fields']['name']['eval']['translatableFor'] = ['de'];

// Note:
// If you don't use ['eval']['translatableFor'] and the user is not editing the fallback language, then the field will be hidden for all the languages
```

Example usage
-------------

[](#example-usage)

```
// Update tl_news configuration
$GLOBALS['TL_DCA']['tl_news']['config']['dataContainer'] = \Terminal42\DcMultilingualBundle\Driver::class;
$GLOBALS['TL_DCA']['tl_news']['config']['languages'] = ['en', 'de', 'pl'];
$GLOBALS['TL_DCA']['tl_news']['config']['langPid'] = 'langPid';
$GLOBALS['TL_DCA']['tl_news']['config']['langColumnName'] = 'language';
$GLOBALS['TL_DCA']['tl_news']['config']['fallbackLang'] = 'en';

// Add the language fields
$GLOBALS['TL_DCA']['tl_news']['config']['sql']['keys']['langPid'] = 'index';
$GLOBALS['TL_DCA']['tl_news']['config']['sql']['keys']['language'] = 'index';
$GLOBALS['TL_DCA']['tl_news']['fields']['langPid']['sql'] = "int(10) unsigned NOT NULL default '0'";
$GLOBALS['TL_DCA']['tl_news']['fields']['language']['sql'] = "varchar(2) NOT NULL default ''";

// Make some fields translatable
$GLOBALS['TL_DCA']['tl_news']['fields']['headline']['eval']['translatableFor'] = '*';
$GLOBALS['TL_DCA']['tl_news']['fields']['subheadline']['eval']['translatableFor'] = ['de'];
```

Querying using the model
------------------------

[](#querying-using-the-model)

```
class NewsModel extends Terminal42\DcMultilingualBundle\Model\Multilingual
{
    protected static $strTable = 'tl_news';

    public static function findPublished()
    {
        return static::findBy(['tl_news.published=?'], [1]);
    }
}
```

How does it work under the hood
-------------------------------

[](#how-does-it-work-under-the-hood)

Basically, the driver just stores translations into the same table, building up a relationship to its parent entry using the "langPid" (or whatever you configured it to be) column. In the back end list and tree view it makes sure translations are filtered so you only see the fallback language there. When querying using the `Multilingual` model or using the `MultilingualQueryBuilder`, the same table is simply joined so we have the fallback language aliased as `t1` and the target language (which you specify explicitly or it uses the current page's language) aliased as `translation`. Now, using MySQL's `IFNULL()` function, it checks whether there's a translated value and if not, automatically falls back to the fallback language. This allows you to translate only a subset of fields.

Alias handling
--------------

[](#alias-handling)

You can share the alias for all translations, so you'd have something like this:

```
* EN: domain.com/my-post/my-beautiful-alias.html
* DE: domain.de/mein-artikel/my-beautiful-alias.html
* FR: domain.fr/mon-post/my-beautiful-alias.html

```

This can be achieved by using the regular alias handling you may know from other modules such as news etc. in the back end and for the front end you simply use the `findByAlias()` method which the `Multilingual` model provides:

```
MyModel::findByAlias($alias);
```

However, there are many situations where you would like to have your aliases translated so you end up with something like this:

```
* EN: domain.com/my-post/my-beautiful-alias.html
* DE: domain.de/mein-artikel/mein-wunderschoenes-alias.html
* FR: domain.fr/mon-post/mon-alias-magnifique.html

```

In the back end it's slightly more difficult now because it does not make sense to check for duplicate aliases within the whole table but only within the whole table **and** the same language. To make this as easy as possible for you, simply use the following `eval` definitions on your `alias` field:

```
'eval'      => [
    'maxlength'                 => 255,
    'rgxp'                      => 'alias',
    'translatableFor'           => '*',
    'isMultilingualAlias'       => true,
    'generateAliasFromField'    => 'title' // optional ("title" is default)
],
```

It will automatically generate an alias if not present yet and check for duplicates within the same language.

In the front end you can then search by a multilingual alias like this:

```
MyModel::findByMultilingualAlias($alias);
```

Usage with Doctrine entities
----------------------------

[](#usage-with-doctrine-entities)

Since the driver only writes data to the database from the backend, it is fully compatible with using Doctrine entities in the frontend. For now, you must take care of translations yourself though. Here's how an entity could look like:

```
#[Entity()]
#[Table('tl_my_entity')]
class MyEntity
{
    #[Id]
    #[GeneratedValue('IDENTITY')]
    #[Column(options: ['unsigned' => true])]
    private int $id;
    #[Column(options: ['unsigned' => true, 'default' => 0])]
    private int $tstamp;

    #[OneToMany('parent', self::class)]
    protected $translations;
    #[ManyToOne(self::class, inversedBy: 'translations')]
    #[JoinColumn('langPid')]
    protected $parent;
    #[Column(length: 5, options: ['default' => ''])]
    protected string $language;

    // ... any other properties of your entity
}
```

Useful notes
------------

[](#useful-notes)

1. Sometimes a table you want to make multilingual already contains the `language` field (e.g. `tl_user`), which may lead to unexpected results. In such cases you have to make sure that data container's property `$GLOBALS['TL_DCA']['tl_table']['config']['langColumnName']` is set to something else than `language`. See [\#53](https://github.com/terminal42/contao-DC_Multilingual/issues/53) for more details.

###  Health Score

65

—

FairBetter than 99% of packages

Maintenance81

Actively maintained with recent releases

Popularity41

Moderate usage in the ecosystem

Community32

Small or concentrated contributor base

Maturity91

Battle-tested with a long release history

 Bus Factor2

2 contributors hold 50%+ of commits

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~91 days

Recently: every ~97 days

Total

47

Last Release

89d ago

Major Versions

2.1.6 → 3.0.02016-06-30

2.1.7 → 3.0.22017-11-24

3.0.8 → 4.0.02019-11-07

PHP version history (8 changes)2.0.0PHP &gt;=5.3.2

3.0.0PHP &gt;=5.4

3.0.1PHP &gt;=5.5

4.0.0PHP ^5.6 || ^7.0

4.1.2PHP ^5.6 || ^7.0 || ^8.0

4.2.0PHP ^7.1 || ^8.0

4.5.0PHP ^8.1

4.7.0PHP ^8.3

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/481937?v=4)[Yanick Witschi](/maintainers/Toflar)[@Toflar](https://github.com/Toflar)

---

Top Contributors

[![aschempp](https://avatars.githubusercontent.com/u/1073273?v=4)](https://github.com/aschempp "aschempp (97 commits)")[![qzminski](https://avatars.githubusercontent.com/u/193483?v=4)](https://github.com/qzminski "qzminski (79 commits)")[![Toflar](https://avatars.githubusercontent.com/u/481937?v=4)](https://github.com/Toflar "Toflar (61 commits)")[![richardhj](https://avatars.githubusercontent.com/u/1284725?v=4)](https://github.com/richardhj "richardhj (12 commits)")[![dmolineus](https://avatars.githubusercontent.com/u/1186266?v=4)](https://github.com/dmolineus "dmolineus (11 commits)")[![weyert](https://avatars.githubusercontent.com/u/7049?v=4)](https://github.com/weyert "weyert (8 commits)")[![psi-4ward](https://avatars.githubusercontent.com/u/1191572?v=4)](https://github.com/psi-4ward "psi-4ward (7 commits)")[![rabauss](https://avatars.githubusercontent.com/u/14016098?v=4)](https://github.com/rabauss "rabauss (3 commits)")[![fritzmg](https://avatars.githubusercontent.com/u/4970961?v=4)](https://github.com/fritzmg "fritzmg (2 commits)")[![lukasbableck](https://avatars.githubusercontent.com/u/42083846?v=4)](https://github.com/lukasbableck "lukasbableck (1 commits)")[![bytehead](https://avatars.githubusercontent.com/u/754921?v=4)](https://github.com/bytehead "bytehead (1 commits)")[![css-umsetzung](https://avatars.githubusercontent.com/u/1020304?v=4)](https://github.com/css-umsetzung "css-umsetzung (1 commits)")[![andreasisaak](https://avatars.githubusercontent.com/u/156767?v=4)](https://github.com/andreasisaak "andreasisaak (1 commits)")

---

Tags

multilingualdrivercontao

### Embed Badge

![Health badge](/badges/terminal42-dc-multilingual/health.svg)

```
[![Health](https://phpackages.com/badges/terminal42-dc-multilingual/health.svg)](https://phpackages.com/packages/terminal42-dc-multilingual)
```

###  Alternatives

[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.4k5.6M651](/packages/sylius-sylius)[shopware/platform

The Shopware e-commerce core

3.3k1.5M3](/packages/shopware-platform)[contao-community-alliance/dc-general

Universal data container for Contao

1578.3k86](/packages/contao-community-alliance-dc-general)[sulu/sulu

Core framework that implements the functionality of the Sulu content management system

1.3k1.3M152](/packages/sulu-sulu)[contao/core-bundle

Contao Open Source CMS

1231.6M2.4k](/packages/contao-core-bundle)[codefog/contao-haste

haste extension for Contao Open Source CMS

42650.8k139](/packages/codefog-contao-haste)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
