Hi, is there a module that automatically translates to all active languages a new node on submit? I don't want actually to translate the content, just to create node duplicates to all languages. I mean to automatically create duplicate nodes, and to store in their "language" field the language they are and in "tnid" field the "nid" of the just created node.

This way the "Translate" button is useless and the content could change via the "Edit" button.

Comments

mishoboss’s picture

Anyone?

mishoboss’s picture

I assume this is not possible since there is no answer... :(
Anyone any idea?

mishoboss’s picture

I wrote an extension to Rules module to handle automatic node translation on new node creation. I post the code modifications bellow:

Edit the modules\rules\rules\modules\node.rules.inc

Find and replace the "node_rules_event_info" function with this:

----------------------
function node_rules_event_info() {
$items = array ('node_insert' => array ('label' => t ( 'After saving new content' ), 'module' => 'Node', 'arguments' => rules_events_node_arguments ( t ( 'created content' ), t ( "content's author" ) ), 'redirect' => TRUE ), 'node_update' => array ('label' => t ( 'After updating existing content' ), 'module' => 'Node', 'arguments' => rules_events_node_arguments ( t ( 'updated content' ), t ( "content's author" ), TRUE ), 'redirect' => TRUE ), 'node_presave' => array ('label' => t ( 'Content is going to be saved' ), 'module' => 'Node', 'arguments' => rules_events_node_arguments ( t ( 'saved content' ), t ( "content's author" ), TRUE ), 'redirect' => TRUE ), 'node_view' => array ('label' => t ( 'Content is going to be viewed' ), 'module' => 'Node', 'help' => t ( "Note that if drupal's page cache is enabled, this event won't be generated for pages served from cache." ), 'arguments' => rules_events_node_arguments ( t ( 'viewed content' ), t ( 'content author' ) ) + array ('teaser' => array ('type' => 'boolean', 'label' => t ( 'Content is displayed as teaser' ) ), 'page' => array ('type' => 'boolean', 'label' => t ( 'Content is displayed as page' ) ) ) ), 'node_delete' => array ('label' => t ( 'After deleting content' ), 'module' => 'Node', 'arguments' => rules_events_node_arguments ( t ( 'deleted content' ), t ( 'content author' ) ), 'redirect' => TRUE ) );
// Specify that on presave the node is saved anyway.
$items ['node_presave'] ['arguments'] ['node'] ['saved'] = TRUE;
return $items;
}

---------------------
---------------------

Add the following 2 functions:

function rules_action_translate_node($node) {
if (! $node->tnid) {
$node->language = i18n_get_lang ();
$node->tnid = $node->nid;

$lang_arr = array_keys ( i18n_node_language_list ( $node, true ) );
for($i = 1; $i < sizeof ( $lang_arr ); $i ++) {
if ($lang_arr [$i] != $node->language) {
addNewNode ( $lang_arr [$i], $node );
}
}

return array ('node' => $node );
}
}

function addNewNode($lang, $node) {
$edit = array ( );
$edit ['type'] = $node->type;
$edit ['uid'] = $node->uid;
$edit ['name'] = $node->name;
$edit ['promote'] = $node->promote;
$edit ['comment'] = $node->comment;
$edit ['revision'] = $node->revision;
$edit ['status'] = 1;
$edit ['title'] = $node->title;
$edit ['body'] = $node->body;
$edit ['language'] = $lang;
$edit ['tnid'] = $node->nid;

$new_node = node_submit ( $edit );
node_save ( $new_node );
}

-------------

That's it. Now in the Actions you will find a new position "Translate node". It must be added on "After saving new content" event. I don't think this is the right way of extending the Rules module, but I don't find a better one. Please test it and post feedback :)

Esnaque’s picture

Hi, I have added the extension to Rules, but action doesn't appear at list.

What I have make wrong?

Any solution?

Thanks and sorry for my english

mishoboss’s picture

Ahh, sorry I posted wrong function in my hurry... sorry again! The right function is node_rules_action_info() and it must be replaced with this:

function node_rules_action_info() {
return array ('rules_action_node_set_author' => array ('label' => t ( 'Set the content author' ), 'arguments' => array ('node' => array ('type' => 'node', 'label' => t ( 'Content' ) ), 'author' => array ('type' => 'user', 'label' => t ( 'User, who is set as author' ) ) ), 'module' => 'Node' ), 'rules_action_set_node_title' => array ('label' => t ( 'Set content title' ), 'arguments' => array ('node' => array ('type' => 'node', 'label' => t ( 'Content' ) ), 'title' => array ('type' => 'string', 'label' => t ( 'Title' ) ) ), 'module' => 'Node' ), 'rules_action_translate_node' => array ('label' => t ( 'Translate node' ), 'arguments' => array ('node' => array ('type' => 'node', 'label' => t ( 'Content' ) ) ), 'module' => 'Node' ), 'rules_action_add_node' => array ('label' => t ( 'Add new content' ), 'arguments' => array ('author' => array ('type' => 'user', 'label' => t ( 'User, who is set as author' ) ), 'title' => array ('type' => 'string', 'label' => t ( 'Title' ), 'description' => t ( 'The title of the newly created content.' ) ) ), 'new variables' => array ('node_added' => array ('type' => 'node', 'label' => t ( 'New content' ), 'save' => TRUE, 'label callback' => 'rules_action_add_node_variable_label' ) ), 'module' => 'Node' ), 'rules_action_load_node' => array ('label' => t ( 'Load content by id' ), 'arguments' => array ('nid' => array ('type' => 'number', 'label' => t ( 'Content ID' ) ), 'vid' => array ('type' => 'number', 'label' => t ( 'Content Revision ID' ), 'description' => t ( "If you want to load a specific revision, specify it's revision id. Else leave it empty to load the current revision." ), 'required' => FALSE ) ), 'new variables' => array ('node_loaded' => array ('type' => 'node', 'label' => t ( 'Loaded content' ), 'label callback' => 'rules_action_load_node_variable_label' ) ), 'module' => 'Node' ) );
}

cfmcoder’s picture

subscribing

momper’s picture

sub

ddorian’s picture

sub

Peacog’s picture

A better way to add your own action is to implement hook_rules_action_info(). Best practice is to add your hook implementation to a mymodule.rules.inc file in your custom modules folder. Here's my version of the hook

function mymodule_rules_action_info() {
  return array(
       'mymodule_action_node_translate' => array(
      'label' => t('Translate a node into every enabled language'),
      'arguments' => array(
        'node' => array('type' => 'node', 'label' => t('Content')),
      ),
      'module' => 'Node',
    ),
  );
}

/**
* Creates a translation for a Node in every enabled language
*/
function mymodule_action_node_translate($node) {

  // if this is not a source node, don't do automatic translations
  if (!$node->translation_source) {
    $node->language = i18n_get_lang (); // set language of source node to current language
    $node->tnid = $node->nid; // source node must point to itself

    // create a translation of the node for each enabled language
    // other than the source node language
    $languages = i18n_language_list();
    unset($languages[$node->language]); // exclude the language of the new node

    foreach ($languages as $langcode => $language) {
      module_load_include('inc', 'node', 'node.pages'); // needed for node_object_prepare()

      // prepare the new node
      $new_node = (object)array();
      node_object_prepare($new_node);

      $new_node->name  = $node->name;
      $new_node->type  = $node->type;
      $new_node->language = $langcode;
      $new_node->tnid = $node->nid;
      $new_node->title = $node->title;
      $new_node->body = $node->body;
      // copy any cck fields too.
      $new_node->myfield= $node->myfield;

      // create the new tranlation node
      node_save(node_submit($new_node));
    }
  }
  return array('node' => $node);
}

You can now create a new rule to be triggered by the After saving new content event. Your new action should be available for selection.

I made a quick stab at using i18n_synch to copy cck fields to the new node but it didn't work and I don't have time to look into it further. If anyone figures it out please share the solution. Be careful with recursive rules and remember to turn debugging on in Rules settings, so you can see what rules are being triggered.

Edit - just edited the source above to check if the new node is a source node. If it's not then don't create automatic translations of it.

mishoboss’s picture

Thanks for the code snippet. It's just perfect!

Peacog’s picture

I've recently come across a problem with automatic url aliases and the above rule. If you use Pathauto to automatically create urls, and your Pathauto Update action setting is set to Do nothing. Leave the old alias intact, the url alias for the source node does not get set correctly. The aliases for the translated nodes are OK.

The solution is to install the Path Redirect module and change Update action to Create a new alias. Redirect from old alias. In any case, this is the recommended update action for SEO purposes.

greg.harvey’s picture

I'm in the process of bundling this in to a proper module that includes a draft in the new language created using Google Translate. Will report back! Thanks for the head start... =)

greg.harvey’s picture

rtdean93’s picture

Thanks for this hidden gift. I've been working on this for a while and this simple rule really hit the spot.

  function blueline_extra_rules_action_info() {
  
  return array(
       'blueline_extra_action_node_translate' => array(
      'label' => t('Translate a node into every enabled language'),
      'arguments' => array(
        'node' => array('type' => 'node', 'label' => t('Content')),
      ),
   //   'module' => 'Node',
    ),
  );
}

/**
* Creates a translation for a Node in every enabled language
*/
function blueline_extra_action_node_translate($node) {

  // if this is not a source node, don't do automatic translations
  if (!$node->translation_source) {
    $node->language = i18n_language()->language; // set language of source node to current language
    $node->tnid = $node->nid; // source node must point to itself

    // create a translation of the node for each enabled language
    // other than the source node language
    $languages = i18n_language_list();
    unset($languages[$node->language]); // exclude the language of the new node

    foreach ($languages as $langcode => $language) {
      module_load_include('inc', 'node', 'node.pages'); // needed for node_object_prepare()

      // prepare the new node
      $new_node = (object)array();
      node_object_prepare($new_node);

      $new_node->name  = $node->name;
      $new_node->type  = $node->type;
      $new_node->language = $langcode;
      $new_node->tnid = $node->nid;
      $new_node->title = $node->title;
      $new_node->body = $node->body;
      // copy any cck fields too.
      $new_node->myfield= $node->myfield;

      // create the new tranlation node
      node_save(node_submit($new_node));
    }
  }
  return array('node' => $node);
}

Bobby Dean

bex’s picture

Thanks a lot for this. It's been very usefull.

I was also trying to create menu items automatically, but i'm new to Drupal and found myself a bit stuck. I'm trying to use the "menu_save_link" function.
Would you have any idea of how i should proceed?

Thank you

marcoka’s picture

be cautious with this code, i debugged around.

if (!$node->translation_source) {... this wa spossible in d6, in d7 that variable translation_source is not inside the node object

when running this rule with i18n sync enabled on the content type you get a lot of duplicate nodes like 5 times the same node in french

dcoulombe’s picture

Thanks for this snippet. I added a "Content is translation source" condition and the capability of setting the translation set.

/**
 * Implements hook_rules_action_info()
 */
function wr_temp_development_rules_action_info()
{
  return array(
    'wr_temp_development_action_node_translate' => array(
      'label' => t('Translate a node into every enabled language'),
      'group' => t('Node'),
      'arguments' => array(
        'node' => array(
          'type' => 'node', 
          'label' => t('Content')
        ),
      ),
    ),
  );
}

/**
 * Implements hook_rules_condition_info()
 */
function wr_temp_development_rules_condition_info() {
  return array(
    'wr_temp_development_action_node_is_translation_source' => array(
      'label' => t('Content is translation source'),
      'group' => t('Node'),
      'help' => t('Evaluates to TRUE if the given node is the translation source.'),
      'arguments' => array(
        'node' => array(
            'type' => 'node', 
            'label' => t('Current node')
        ),
      ),
      'module' => 'wr_temp_development',
    ),
  );
}

function wr_temp_development_action_node_is_translation_source($node)
{
  return ($node->language == $node->translations->original) ? TRUE : FALSE;
}

/**
 * Creates a translation for a node in every enabled language
 * 
 * @param $node (node object)
*/
function wr_temp_development_action_node_translate($node)
{
  $node->tnid = $node->nid;
  
  // GET THE ENABLED LANGUAGE LIST
  $languages = i18n_language_list();
  unset($languages[$node->language]);

  foreach ($languages as $langcode => $language)
  {
    // CLONE THE CURRENT NODE
    $new_node = clone $node;
    
    // CLEAR THE VALUES, SET THE LANGUAGE AND SAVE
    $new_node->nid = NULL;
    $new_node->vid = NULL;
    $new_node->tnid = $node->tnid;
    $new_node->created = NULL;
    $new_node->book['mlid'] = NULL;
    $new_node->path = NULL;
    $new_node->files = array();
    $new_node->language = $langcode;

    node_save($new_node);
  }

  // IF WE HAVE AT LEAST ONE ENABLED LANGUAGE, UPDATE THE TRANSLATION SET
  if(count($languages) > 0)
    db_update('node')
    ->fields(array(
      'tnid' => $node->nid,
    ))
    ->condition('nid', $node->nid, '=')
    ->execute();;

  return array('node' => $node);
}
minff’s picture

As the hooks of Rules module have changed in D7 (e.g., see http://drupalcontrib.org/api/drupal/contributions!rules!rules.api.php/fu...), I made some changes to the code snippets above to make it work.

/**
 * Implementation of hook_rules_action_info().
 */
function i18n_auto_translate_rules_action_info() {
  return array(
    'i18n_auto_translate_action_node_translate' => array(
      'label' => t('Translate a node into every enabled language'),
      'group' => t('Node'),
      'parameter' => array(
        'node' => array(
          'type' => 'node',
          'label' => t('Content')),
      ),
    )
  );
}

/**
 * Creates a translation for a node in every enabled language.
 */
function i18n_auto_translate_action_node_translate($node) {  
  // If this is not a source node, don't do automatic translations.
  if ($node->tnid == 0 or $node->tnid == $node->nid) {    
    // Create a translation of the node for each enabled language
    // other than the source node language.
    $languages = i18n_language_list();
    unset($languages[$node->language]);

    foreach ($languages as $langcode => $language) {
      // Prepare the new node.
      $new_node = clone $node;

      $new_node->language = $langcode;
      $new_node->tnid = $node->nid;
      $new_node->nid = null;
      $new_node->vid = null;
      $new_node->path = null;
      $new_node->created = null;
      
      // Create the new translation node.
      node_save(node_submit($new_node));
    }
    
    // Update tnid value of the source node to create a translation set.
    if (count($languages) > 0) {
      db_update('node')->fields(array(
        'tnid' => $node->nid,
      ))
      ->condition('nid', $node->nid, '=')
      ->execute();
    }
    
    return array('node' => $node);
  }
}
cesareaugusto’s picture

I just found this comment through Google... I would need this very same feature! Though I'm a kind of newbie in Drupal, am not much into the module creation... Where should I copy your code? Thanks for sharing!

PlayfulWolf’s picture

+1

vladimirkomarek’s picture

i have no custom action to use

darol100’s picture

Instead of doing it with Rules. You can use hook_entity_insert(). This will trigger you function when a node is inserted and you can limited by the content type.

- Darryl Norris
Be Connected: Website | Twitter | LinkendIn | GitHub

vladimirkomarek’s picture

unfortunately i'm not that good in scripting
could you write down an example for the community?
<3