Drupal for multiple languages, part three: all language versions of a node at once

In the first and second articles of this series, we configured Drupal core and content types. Now we're doing something more special in a effort to make our site more usable for our customers and save time when creating new content.

Although the methods shown in the first two parts of this series are effective, that is, they work as expected, they are not the most efficient solution for all situations. An example:

You are creating a site for a Canada hockey club. Canada is a country with two official languages, French and English, so a fully bilingual site is expected. OK on that, no big deal. You create a new content type for the team members. Every node will be a player, with photos, bio, height, weight, position, age... It's cool, but please notice that if we apply the "create node, then translate" method explained in part two of this series, we'll have to upload all photos twice, write the player's name twice, and add the height, weight, age and so on twice. You see? The only field that changes from one language to the other is the bio. Everything else is duplicated, and we'll have to write it down twice!

We can do better than that.

Configuring our content type "players"

The plan is that we create one node "player", and this node "player" is valid for all languages. So, no matter we're seeing our site in one language or another, Drupal gets the same node. The trick is that we'll modify our theme so it will only show the proper info for every language, and the other one will not be shown. In this example, the player's bio is the only field that must be different on every language.

Configure the node type "player"

Our content type must be language neutral, that is, the same node will be used for all languages. Go to Administer > Content types > Edit (your content type).  Look at the following image:

Now, besides that, in the same screen, we keep editing the options. We don't need a "body" field for this content type, so we'll omit it:

There it is, if you leave the body field label blank, there won't be a Body field for this node type.

Configure language specific CCK fields

For our node type "player" we need a field for the player's bio, but as this will be a multilingual field, we need as many fields as languages. We go on editing our content type, but this time we'll go to Manage fields. You need to configure this screen about this way:

You see there are two Bio fields, one label "Bio (english)" and the other one "Bio (french)".

Please notice you need the almighty CCK module to do this.

Theming the node template to hide the content in the other language

We need a custom template for our node type "player". We go to our theme, open "node.tpl.php" and save it as "node-player.tpl.php". Now we must make Drupal aware of the new template we've just added, so we go Administer -> Site configuration -> Performance -> Clear cached data.

Now, we edit our template and look for the following code:

<?php print $content ?>

This code inserts all our node's content. All fields. The whole burrito. Unfortunately, this neat piece of code must be replaced by something more complicated:

<?php global $language; ?>

<?= $title ?>
<?= $field_height_rendered ?>
<?= $field_weight_rendered ?>
<?=($language->language == 'en') ? $field_bio_english_rendered :
 $field_bio_french_rendered ?>

You see, we put our content on our template, field by field. When it comes to the "bio" field, we use some PHP to select the appropiate one.

And that's it. You've done the trick. Good job.

Awesome article. The best on Drupal multi-language translation. Cheers

This is really really great job. Thank you very much indeed. It help me so much!

I was unaware about drupal.I got valuable information here.Really its nice topic.Thanks for nice sharing.

poker770

everest poker

You can extend this approach, especially if you have more then two languages it is a mess to go trough thousands of if-elseif-else or switches. Just do the following:

echo ${'field_bio_'. $language->name .'_rendered'};

Personally I use the language code in the field name, for example en and fr. Which would lead us to:

echo ${'field_bio_'. $language->language .'_rendered'};

This way there is no need for any if-elseif-else- or switch-construct and PHP has less work to do.

Yeah, Fleshgrinder, this is a much neater approach, thanks.

I recently came across your blog and have been reading along. I thought I would leave my first comment. I dont know what to say except that I have enjoyed reading. Nice blog. I will keep visiting this blog very often.

Lucy

Excellent post. Thanks. Would like to see a working example.

The working example is this very site, by the way. The node type "customer" in this site was built using the techniques explained in this article.

Thanks for reading.

To be quite honest I think the last part of this guide although a great idea appears to be very time consuming and a bit fiddly. There must be a better way to acheive this. I think in order to have 3 languages having to store 3 copies of the node is a poor way of implementing language translation, similarly I think having to create custom template for each node type and duplicate fields is all too much work, especially for someone like me who has 15+ content types with 1000's of nodes for each and now would like to implement language translation.

To be fair I dont have any alternative suggestions and not claiming to know best, but I do think this is a hell of a lot of work for somthing which in my eyes should be fairly straight forward. Drupal is so powerful...but not in this area.

I really like this article and you are given here really a wonderful tips.I am interested very much in the subject matter of your blog.Thanks for sharing information with us.Keep it up.

hi,

thanks great post.

I was wondering if I could extend it furthermore by replacing print $content with this (pseudo)-code

for each CCKfield:
if ($this.substring(-2) == $language->language .'_rendered') echo $this;

(Assuming that all my cck fields end with the matching language code (i.e. cckfield_de or cckfield_en)

In this way I don't have to update the code any time I add a new cck field to my content-type from back-end

thanks

You're right, your approach seems doable.

Thanks for reading.

Quite doable three language system achieved :) Thanks

Hi, in case someone is interested, I've implemented this in a custom module.
The assumption is that you name the "language specific" fields with "_en" or "_de" at the end.

And you don't have not "language specific" CCK fields with underscore as third last letter.

function myform_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {

switch ($op) {
  case 'presave':
  case 'insert':
  case 'update':
  case 'view':
	  
  global $language;

  foreach ($node->content as $CCKField) {
		
    //check if the field is a "language specific "
    if(substr($CCKField['#field_name'], -3,1) == '_') {		

    //if it is not the current language, remove it
    if (substr($CCKField['#field_name'], -2) !== $language->language)
      unset($node->content[$CCKField['#field_name']]);
    }
  }
  break;
}
}

Awesome work, Patrick. Thanks a lot.

Hi there,

Thanks a lot for this very usefull post!

I'm having trouble using the snippet given by Patrick Diviacco, it's working fine but when I save the node I get the following error:


warning: Invalid argument supplied for foreach() in /home2/site/public_html/sites/all/modules/traduction/traduction.module on line 15.
warning: Invalid argument supplied for foreach() in /home2/site/public_html/sites/all/modules/traduction/traduction.module on line 15.

If I refresh the page the error goes away.

Any idea?

Thanks again!

Very simple and clear, thanks!

Hey, guys!

Thank you all for the very useful tips on fields translation, especially topicstarter Ignacio. :)
This is ready-made working code for filtering fields by language postfix like "_en" in their names. It prints normal fields without language postfix as they are.

node.tpl.php

<?php 
  global $language;
  foreach ($node as $property_name => $property_value){  
    if (substr($property_name, 0, 5) == 'field' 
       && (substr($property_name, -3) == '_' . $language->language 
       || substr($property_name, -3, 1) <> '_'))  {
    	
      echo ${$property_name . '_rendered'} ; 
    
    }
  }
?>

It has not been optimized yet and is still understandable =)

Anyway, I haven't found the better way of iterating fields within node.tpl.php context. It seems to be too slow for nodes with lots of content. Any ideas?

Thanks for reading!

Unfortunately, I see no clear way to make your script run faster. I mean, hardcoding the fields to be printed like I explain in my example is much, much faster, but every modification in the node structure will require a change in the template, and that's not the idea of your code, your code aims to be versatile. I can only suggest that STRPOS is much faster than SUBSTR.

http://php.net/manual/en/function.strpos.php

hope it helps.

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <i> <b> <strong> <cite> <pre> <code> <ul> <ol> <li> <dl> <dt> <dd> <p> <img> <h2> <h3> <blockquote>
  • Lines and paragraphs break automatically.

More information about formatting options