Drupal is a registered trademark of Dries Buytaert
drupal 10.5.11 Update released for Drupal core (10.5.11)! drupal 11.3.11 Update released for Drupal core (11.3.11)! drupal 11.2.13 Update released for Drupal core (11.2.13)! drupal 10.6.10 Update released for Drupal core (10.6.10)! cms 2.1.2 Update released for Drupal core (2.1.2)! drupal 11.1.10 Update released for Drupal core (11.1.10)! drupal 10.5.10 Update released for Drupal core (10.5.10)! drupal 10.4.10 Update released for Drupal core (10.4.10)! drupal 11.2.12 Update released for Drupal core (11.2.12)! drupal 11.3.10 Update released for Drupal core (11.3.10)! drupal 10.6.9 Update released for Drupal core (10.6.9)! drupal 10.6.8 Update released for Drupal core (10.6.8)! drupal 11.3.9 Update released for Drupal core (11.3.9)! drupal 11.3.8 Update released for Drupal core (11.3.8)! drupal 11.3.7 Update released for Drupal core (11.3.7)! drupal 11.2.11 Update released for Drupal core (11.2.11)! drupal 10.6.7 Update released for Drupal core (10.6.7)! drupal 10.5.9 Update released for Drupal core (10.5.9)! cms 2.1.1 Update released for Drupal core (2.1.1)! drupal 11.3.6 Update released for Drupal core (11.3.6)!

Why does this module exist?

The philosophy of Drupal core translations is that a translation of a page (a node) is a one on one translation of the source. This can be a good philosophy for some projects: for example wikipedia.org would probably best provide the exact same information for every language.

However, in practice we often encounter scenarios where this is not completely applicable and we need to implement "dirty" workarounds to get what we want. Especially the fact that each and every paragraph of the paragraphs module needs to be there, often turns out to be quite a problem. For example, a node may contain a "product promotion" paragraph which at one time applies only to a specific country / language. Another scenario we often encounter is the fact that clients sometimes want a node in one language to have all bells and whistles, while in another language the page only needs a little bit of text.

In Drupal 7 one had the choice to set up the translations in one way or the other: it either worked pretty much the same as it now works in Drupal 8+, or you could set it up so that each translation had it's own node. Now this is where the Asymmetric Translations module comes in.

Module philosophy

The idea behind this module is that content itself is not translatable, but is created for a specific language. A parent entity (the so called Asymmetric Node Translation - or ANT in short) is translatable. Each ANT translation refers to a node of that same language, or does not refer to a node, meaning there is no translation in that specific language.

This approach offers the possibility for a node to have a different amount of paragraphs than a node in another language, or the node can even be of a whole different node bundle.

As for the HTML standards you could say that this module reckons a page translation as no more than a set of hreflang references telling the website visitor / bot where to find the translations.

How it works

This module overwrites the output of the entity.node.content_translation_overview route. It outputs a custom form in which you see the asymmetric translations of the current node and can add new ones.

When the module detects that a core translation exists for a node, it display a large warning message on this translations page. In theory it won't break anything, but we recommend using only core translations or asymmetric translations. Because the two however do not actually conflict with each other, it should be possible to transition quite smoothly from core translations to asymmetric translations in an early stage of the website. However you will have to delete the core translation before you can create the asymmetric translation (a nice feature would be to convert it).

Note that when you add a new translation (for example you add "Dutch", while on the "English" original version) this module will use it's dependency on the [quick_node_clone](https://www.drupal.org/project/quick_node_clone) module to clone the original version (the English version) and then override the language of that clone to the new language (in this example "Dutch").

The state of this module

This module has been developed to be used in (and is being used in) a production environment. While the module is being used actively already, it should not be considered as stable. The main features seem to be working well (refence nodes to other languages, have a working language dropdown, automatically overwrite the hreflang metatags). There are however still (plenty of) missing features, there are no tests yet, missing documentation, many todo's, etc. etc.

Sub modules

at_auto_fix_entity_references

Normally you probably will want to enable this module as well. After saving a new translation this module will recursively loop over the content (node reference fields, HTML fields, etc.) and try to find any references to nodes. Then it will lookup if such a referenced node has an asymmetric translation in the language we just created a translation for. If so, it will replace the node reference.

at_filter_node_reference_widgets

This module cannot be enabled at this time, because configuration is missing. Once fixed, it will filter link fields so that only nodes in the current language will be suggested.

at_frontpage_per_language

This module alters the frontpage, 404 and 403 settings so that they are translatable. Otherwise it won't be possible to set a different node for each language - something that we do need to do when working with asymmetric translations.

Module alternatives

Soft Translations

The Soft Translations module has the same goal as this module.
The Soft Translations module seem to be build around autodetection of the same URL.

When starting this module the consideration was to contribute to the Soft Translations module or create a new one.
Since the expected architecture of the module we needed was too far off, a new module seemed to be a better approach.
That said, the modules may lend from each other and even a merge may seem appropiate at one time.

Paragraphs Asymmetric Translation Widgets

The Paragraphs Asymmetric Translation Widgets module provides asymmetric translations for Paragraphs.

While the module is a great effort to achieve asymmetric translations, the possible impact of a bug can be huge and leading to data loss. While it works most of the time, we have encountered paragraphs detached from the parent node (and thus "lost") and paragraphs assigned to an incorrect node language.

The Asymmetric Translations module takes another approach in which the worst case scenario would be to lose the refences between one or more translations of the same page, but the nodes / paragraphs itself will never be impacted.

Good to know / considerations

No involvement in core translations

When calling `$node->getTranslations()` this module will not be involved, and thus this method call will not return any translations, unless there are core translations. This has been a design choice whether or not to involve in core functionality. Apart from the fact that it is really hard (or even impossible) for the module to hook into such functionality, it also seemed appropiate to not do that.

Choices when creating the Asymmetric Node Translations entity

- The entity type is not fieldable. The only field needed is an entity refence field to reference to the node, which is a basefield of the entity
- The entity type is not revisionable. Making the entity type revisionable would add a lot more complexity which we currently do not want.
- The entity type is translatable. Each language contains a single reference (or no reference) to a node in that specific language
- The entity type has no bundles. In the future we might want to create for example Asymmetric Taxonomy Term Translations (not sure if that would be a thing actually), but such a new feature would need a whole new submodule and entity type - not a bundle
- The entity type has no templates
- The entity type has it's own CRUD permissions. It probably would be better if the entity would follow the permissions of the referenced node types somehow.
- The entity type has no title. The title resides in the referenced node.
- The entity type has no status. The status resides in the referenced node. When there are no nodes referenced, the entity should no longer exist.
- The entity type has a "created" and a "changed" field, which might help with debugging some issues.
- The entity type has no author

(Known) Todo's

- Update this documentation with screenshots, how to use (code examples), etc.
- Update this documentation with dependency info (quick node clone)
- Update this documentation on how to use the submodules
- Update this documentation with info about conflicts between core translations and this module and how this module handles them
- Fix all "TODO" references in code
- Improve all texts and make them translatable
- Run against Drupal code standards (etc.)
- Write a cron function that checks for empty Asymmetric Translation entities (referencing to nothing) and remove them

Activity

Total releases
1
First release
May 2026
Latest release
19 hours ago
Release cadence
Stability
0% stable

Releases

Version Type Release date
1.0.0-alpha11 Pre-release May 29, 2026