Entity Translation Filter makes content listings language aware.

Submitted by Daniel Nolde on 2012-01-18 16:16

When managing Drupal content in multiple languages with the Entity Translation module, there's no way yet to filter content listings and views by the display language. See how we solved this problem for views queries, and benefit from the solution by using the Entity Translation Filter module.

THE PROBLEM

Entity Translation (ET) is a great new way to manage multilingual content, being an interface and workflow for Drupal 7’s field translations. (For details see our earlier blog article about Entity Translation integration with Search API).

The older and more seasoned way of handling translations, Drupal core's content translation mechanism, enables you to automatically have node listings and views filtered by the current display language, thus only seeing content in the appropriate language. ET lacks this filtering mechanism as of now: Node listings and views show nodes quite independent of their having an suitable translation. This is mainly because the underlying core mechanism is to manage all language versions of an entity (say a node) within this one entity, by managing multiple language versions of each translatable field. So when selecting content, the query does not know about what fields of which content may be available in what language version.

To add strangeness to the annoyance, content listings not only select an entity even if it doesn't have proper field translations for the display language, but shows the language neutral fields (like a date field), while leaving the translatable fields empty. The effect is that you get listings of content with rows or record that are almost, but now entirely empty. Confusing and very wrong, like this:

We needed to overcome this of course, and found a first quick but promising solution which we contributed – so you can solve this problem by just downloading and enabling a small module :)

THE SOLUTION

Adding language filtering to ET translated content queries is surprisingly straight forward, thanks to Drupal's database and hook systems, especially hook_query_alter, and the fact that ET keeps track of every language version (original version and translation) of every ET enabled node in the table {entity_translation}:

So basically, we have to join the {entity_translation} table into to query and select only those records that
a) have an {entity_translation} record
b) have status = 1 (published)
c) are matching the current display language
d) let's allow language neutral content entries (from the base table, e.g. {node}) to be selected, too.

This can be easily coded by implementing hook_query_alter to rewrite all content queries for adding some filtering conditions based on {entity_translation} to the existing query:

$et_table = $query->leftJoin('entity_translation', 'et', "et.entity_type = :base_table AND et.entity_id = $base_table.$base_field AND et.language = :language", array(':base_table' => $base_table, ':language' => $language));
$field_base_language = $query->addField($base_table, 'language');

$field_et_language   = $query->addField($et_table, 'language', 'et_language');
$field_et_status     = $query->addField($et_table, 'status', 'et_status');
$query->condition(db_or()->condition($base_table . '.' . $field_base_language, 'und')
                         ->condition($base_table . '.' . $field_base_language, $language)
                         ->condition(db_and()->condition($et_table   . '.' . 'language' , $language)
                                             ->condition($et_table   . '.' . 'status'   , 1)));

(Disclaimers: This is an shortened excerpt of the actual implementation in the module; in this early stage of the quick fix limited to node-table queries, but potentially working this way for all Entity Translation enabled entity types; stating the field names in the condition() statements this way is not recommended, however, the recommended way of using only the field aliases returned by the addField method is throwing SQL Exceptions)

IMPORTANT: The current version only filters node based views queries. This restriction can be easily removed (opening up the language filtering for any of Drupals node queries). This should be well thought over though – what side effects to expect, what kind of queries better not to change etc. So let's expand the rewriting to other Drupal queries besides views carefully and calmly after some tech discussion (be invited to the module's issue queue!).

Simply installing and enabling the module containing this few lines suddenly transforms your havocked content listings into the views output you expected. The output contains exactly those content items (entities, nodes, …) you want, but only in a language version that matches the current display language:

UNDER CONSTRUCTION

Like stated before, this first quick shot at solving the problem of language version aware listings for Drupal content managed via Entity Translation is far from being finished, let alone perfect. There are a lot of obvious improvements needed. The sandbox module we contributed should give you the possiblity to easily download and install this quick solution as a module. And this module exists for working out the list of ToDos in a closed environment. The goal then is to have the improved functionaliy included directly into the Entity Translation module itself.

The list of ToDos currently contains:

  • Add configurable options:
      * switch to selectively include or exclude language-neutral entities
      * switch to override filter for admins (via current user's permission)
      * switch to filter out unpublished translations (entity_translation.status=1)
      * switch to filter out stale translations (entity_translation.translate=0)
  • Implement a dedicated and explicitely addable views filter to do the same job selectively applicable for each view display
  • Consider and test broader application of rewriting beyond just views
  • Make conditional statement "$base_table == 'node'" for triggering rewrite generic and entity type agnostic, by somehow (?) making a connection between the queries' basetable, the associated entity type, and ET's translation support for that specific entity type.
  • Cache $base_field determination
  • Get rid of necessary but very dirty workaround in language conditions:
     tablealias.fieldalias / tablealias.fieldoriginalname
  • make status=published on translations automatically matching a status=published condition of (known) base table(s)

TRY AND USE IT!

Try and use this solution right away, by simply downloading the Entity Translation Filter sandbox module and installing/enabling it on your Drupal 7 installation. Enjoy language aware views listings with ET translated content!

Your feedback, suggestions and ideas for improvements are very welcome! Just post a comment, or create an issue on Drupal.org!

3 Comments:

1
2012-04-13 03:55 |

Hi,

I tried using the lines and error comes up and the site is broken :

PDOException: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'AND et.language = NULL WHERE (disabled = '0') AND( (.language = 'und') OR (.lan' at line 1: SELECT nt.*, language AS language, et.language AS et_language, et.status AS et_status FROM {node_type} nt LEFT OUTER JOIN {entity_translation} et ON et.entity_type = :base_table AND et.entity_id = . AND et.language = :language WHERE (disabled = :db_condition_placeholder_0) AND( (.language = :db_condition_placeholder_1) OR (.language IS NULL ) OR( (et.language IS NULL ) AND (et.status = :db_condition_placeholder_2) )) ORDER BY nt.type ASC; Array ( [:db_condition_placeholder_0] => 0 [:db_condition_placeholder_1] => und [:db_condition_placeholder_2] => 1 [:base_table] => [:language] => ) in _node_types_build() (line 703 of D:\AppServ\www\projects\salsa_digital\spring_vale\dev\modules\node\node.module).

can you please help,
this is the current code :
ethook.module :
<?php
/**
* Implementation of hook_query_alter().
*/
function ethook_query_alter(QueryAlterableInterface $query) {

$et_table = $query->leftJoin('entity_translation', 'et', "et.entity_type = :base_table AND et.entity_id = $base_table.$base_field AND et.language = :language", array(':base_table' => $base_table, ':language' => $language));
$field_base_language = $query->addField($base_table, 'language');

$field_et_language = $query->addField($et_table, 'language', 'et_language');
$field_et_status = $query->addField($et_table, 'status', 'et_status');
$query->condition(db_or()->condition($base_table . '.' . $field_base_language, 'und')
->condition($base_table . '.' . $field_base_language, $language)
->condition(db_and()->condition($et_table . '.' . 'language' , $language)
->condition($et_table . '.' . 'status' , 1)));

}
?>

Add new comment

Upcoming