Skip to content
translation.module 37.6 KiB
Newer Older
<?php
// $Id$

/**
 * Internationalization (i18n) package.
 * 
 * Translation module: translation
 *
 * @author Jose A. Reyero, 2004, http://www.reyero.net
 *
 */

// Status for translations
define('TRANSLATION_STATUS_NONE', 0);
define('TRANSLATION_STATUS_SOURCE', 1);
define('TRANSLATION_STATUS_WORKING', 2);
define('TRANSLATION_STATUS_TRANSLATED', 3);
define('TRANSLATION_STATUS_UPDATED', 4);

/**
 * Implementation of hook_help().
 */
function translation_help($section = 'admin/help#translation' ) {
  switch ($section) {
    case 'admin/help#translation' :
      $output = '<p>'.t('This module is part of i18n package and provides support for translation relationships.').'</p>';
      $output .= '<p>'.t('The objects you can define translation relationships for are:').'</p>';
      $output .= '<ul>';
      $output .= '<li>'.t('Nodes.').'</li>';
      $output .= '<li>'.t('Taxonomy Terms').'</li>';
      $output .= '</ul>';
      $output .= '<p>'.t('Additional features:').'</p>';
      $output .= '<ul>';      
      $output .= '<li>'.t('<i>Translations</i> block that looks like the language switcher provided by i18n module but also links to translations when available.').'</li>';
      $output .= '<li>'.t('Basic translation workflow and administration page for content translation.').'</li>';
      $output .= '<li>'.t('Links for node translations that can be displayed below each node, depending on module settings.').'</li>';
      $output .= '</ul>';      
      $output .= '<p>'.t('For more information please read the <a href="@i18n">on-line help pages</a>.', array('@i18n' =>'http://drupal.org/node/31631')) .'</p>';
      return $output;
      break;
    case 'admin/access#translation':
      $output = t('<h2>Translations</h2>');
      $output = t('<strong>translate nodes</strong> <p>This one, combined with create content permissions, will allow to create node translation</p>');
  }
  return $output;
}

/**
 * Implementation of hook_menu().
 */
function translation_menu($may_cache) {
  $items = array();

  if ($may_cache) {
    $items[] = array(
      'path' => 'admin/settings/i18n/translation',
      'title' => t('Translation'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array('translation_admin_settings'),
      'access' => user_access('administer site configuration'),
      'type' => MENU_LOCAL_TASK,
    );
    $items[] = array(
      'path' => 'admin/content/translation',
      'title' => t('Translations'),
      'description' => t("Manage content translations."),
      'callback' => 'translation_admin_content',
      'position' => 'left',
      'access' => user_access('translate nodes'));
    $items[] = array('path' => 'admin/content/translation/overview', 'title' => t('List'),
      'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10);
  }
  else {
    if (arg(0) == 'node' && is_numeric(arg(1)) && variable_get('i18n_node_'.translation_get_node_type(arg(1)), 0)) {
      $access = user_access('translate nodes');
      $type = MENU_LOCAL_TASK;      
      $items[] = array(
        'path' => 'node/'. arg(1) .'/translation', 
        'title' => t('Translation'),
        'callback' => 'translation_node_page',
        'access' => $access,
        'type' => $type,
        'weight' => 3);
    }
    if(arg(0) == 'admin' && arg(1) == 'content' && arg(2) == 'taxonomy' && is_numeric(arg(3)) ) {
      $items[] = array(
        'path' => 'admin/content/taxonomy/'.arg(3).'/translation', 
        'title' => t('Translation'),
        'callback' => 'translation_taxonomy_admin',
        'access' => user_access('administer taxonomy'),
        'type' => MENU_LOCAL_TASK);
      
    }
    // Special redirections and rewrite conditions
    if( arg(0) == 'node' && arg(1) == 'add' && isset($_GET['translation']) && ($source_nid = $_GET['translation'])  && isset($_GET['language']) && ($lang = $_GET['language']) && array_key_exists($lang, i18n_supported_languages()) ) {
      // Special redirection for product types
      if (arg(2) == 'product' && !arg(3) && is_numeric($source_nid) && $ptype = db_result(db_query("SELECT ptype FROM {ec_product} WHERE nid = %d", $source_nid))) {
        drupal_goto(url("node/add/product/$ptype", "translation=$source_nid&language=$lang", NULL, TRUE));
      }
      // Change rewrite conditions when translating node
      i18n_selection_mode('translation', db_escape_string($lang));
    }
  }

  return $items;
}

/**
 * Implementation of hook_perm
 */
function translation_perm(){
  return array('translate nodes');
}

/**
 * Form builder function: settings form
function translation_admin_settings(){
  $form['i18n_translation_links'] = array(
    '#type' => 'radios',
    '#title' => t('Language Management'),
    '#default_value' => variable_get('i18n_translation_links', 0),
    '#options' => array(t('Interface language depends on content.'), t('Interface language is independent')),
    '#description' => t("Whether the whole page should switch language when clicking on a node translation or not."),
  );
  $form['i18n_translation_node_links'] = array(
    '#type' => 'radios',
    '#title' => t('Links to node translations'),
    '#default_value' => variable_get('i18n_translation_node_links', 0),
    '#options' => array(t('None.'), t('Main page only'), t('Teaser and Main page')),
    '#description' => t("Links from nodes to translated versions."),
  );
  $form['i18n_translation_workflow'] = array(
    '#type' => 'radios',
    '#title' => t('Translation workflow'),
    '#default_value' => variable_get('i18n_translation_workflow', 1),
    '#options' => array(t('Disabled'), t('Enabled')),
    '#description' => t("If enabled some worklow will be provided for content translation."),
  );  
  return system_settings_form($form);  
 * Implementation of hook_block().
 * 
 * This is a simple language switcher which knows nothing about translations
 */
function translation_block($op = 'list', $delta = 0) {
  if ($op == 'list') {
    $blocks[0]['info'] = t('Translations');
  }
  elseif($op == 'view') {
    $blocks['subject'] = t('Languages');
    $query = drupal_query_string_encode($_GET, array('q'));
    $blocks['content'] = theme('item_list', translation_get_links($_GET['q'], empty($query) ? NULL : $query));
  }

  return $blocks;
}

/**
 * Implementation of hook_form_alter().
 */
function translation_form_alter($form_id, &$form) {
  // drupal_set_message('translation_form_alter');
  // Node edit form
  if (isset($form['type']) && $form['type']['#value'] .'_node_form' == $form_id && variable_get('i18n_node_'.$form['type']['#value'], 0)) {
    $node = $form['#node'];
    $languages = i18n_supported_languages();
    // Translation workflow, default
    if ($node->nid && $node->translation) {
      $translations = $node->translation;
    }
    elseif (isset($_GET['translation']) && user_access('translate nodes')) {
      // We are translating a node: node/add/type/translation/nid/lang
      $translation_nid = $_GET['translation'];
      $language = $_GET['language'];
      // Load the node to be translated and populate fields
      $trans = node_load($translation_nid);
      $form['i18n']['translation_nid'] = array('#type' => 'hidden', '#value' => $translation_nid);
      $form['i18n']['language']['#default_value'] = $language;
      // Do not allow language change
      $form['i18n']['language']['#disabled'] = TRUE;
      $form['i18n']['language']['#description'] = t('Language cannot be changed while creating a translation.');
      translation_node_populate_fields($trans, $form, $node);      
      if($trans->trid){
        $form['i18n']['trid'] = array('#type' => 'hidden', '#value' => $trans->trid);
      }
      // Translations are taken from source node          
      $translations = $trans->translation;
      $translations[$trans->language] = $trans;
    }
    if ($translations) {
      // Unset invalid languages
      foreach(array_keys($translations) as $lang) {
          unset($form['i18n']['language']['#options'][$lang]);
      }
      // Add translation list
      $form['i18n']['#title'] = t('Language and translations');
      $form['i18n']['translations'] = array(
        '#type' => 'markup',
        '#value' => theme('translation_node_list', $translations, FALSE)
      );
    }
    // Translation workflow
    if (variable_get('i18n_translation_workflow', 1) && (user_access('translate nodes') || user_access('administer nodes'))) {
      $form['i18n']['i18n_status'] = array(
        '#type' => 'select', 
        '#title' => t('Translation workflow'), 
        '#options' => _translation_status(),
        '#description' => t('Use the translation workflow to keep track of content that needs translation.'));
      if($node->nid) {
      	$form['i18n']['i18n_status']['#default_value'] = isset($node->i18n_status) ? $node->i18n_status : TRANSLATION_STATUS_NONE;
      } elseif(isset($trans)) {
        $form['i18n']['i18n_status']['#default_value'] = TRANSLATION_STATUS_WORKING;      	
      }
    } else {
    	$form['i18n']['i18n_status'] = array('#type' => 'value', '#value' => isset($node->i18n_status) ? $node->i18n_status : TRANSLATION_STATUS_NONE);
    }
    // Clone files for original node ?
    if (isset($trans) && is_array($trans->files) && count($trans->files)) {
      $form['i18n']['translation_files'] = array(
        '#type' => 'fieldset', 
        '#title' => t('Files from translated content'), 
        '#tree' => TRUE,
        '#prefix' => '<div class="attachments">',
        '#suffix' => '</div>',
        '#theme' => 'upload_form_current',
        '#description' => t('You can remove the files for this translation or keep the original files and translate the description.')
        );
      foreach($trans->files as $key => $file) {
        $description = file_create_url((strpos($file->fid, 'upload') === false ? $file->filepath : file_create_filename($file->filename, file_create_path())));
        $description = "<small>". check_plain($description) ."</small>";
        $form['i18n']['translation_files'][$key]['description'] = array('#type' => 'textfield', '#default_value' => (strlen($file->description)) ? $file->description : $file->filename, '#maxlength' => 256, '#description' => $description );
        $form['i18n']['translation_files'][$key]['size'] = array('#type' => 'markup', '#value' => format_size($file->filesize));
        $form['i18n']['translation_files'][$key]['remove'] = array('#type' => 'checkbox', '#default_value' => 0);
        $form['i18n']['translation_files'][$key]['list'] = array('#type' => 'checkbox',  '#default_value' => $file->list);
        $form['i18n']['translation_files'][$key]['fid'] = array('#type' => 'value',  '#value' => $file->fid);
      }
    }
    // Translate taxonomy terms
    // TO-DO: take care of module order. Default ordering is filename so it depends on install which is too bad!!
    if(isset($trans->taxonomy) && is_array($trans->taxonomy)) {
      // Set translated taxonomy terms
      $form['#node']->taxonomy = array();
      foreach ($trans->taxonomy as $tid => $term) {
        $translated_terms = translation_term_get_translations(array('tid' =>$tid));
        if ($translated_terms && $newterm = $translated_terms[$language]) {
          $form['#node']->taxonomy[$newterm->tid] = $newterm;
          //drupal_set_message("DEBUG: Translated term $tid to ". $newterm->tid);
        }
        // Run again taxonomy_form_alter ?
        if(isset($form['taxonomy'])) {
          unset($form['taxonomy']);
          taxonomy_form_alter($form_id, $form);
        }
      }        
    }
  }    
}

/**
 * Implementation of hook_nodeapi().
 * 
 * Delete case is now handled in i18n_nodeapi
 */
function translation_nodeapi(&$node, $op, $arg = 0) {
  if (variable_get("i18n_node_$node->type", 0)) {
    switch ($op) {
      case 'load':
        $node->translation = translation_node_get_translations(array('nid' =>$node->nid), FALSE);
        break;
      case 'insert':
        if($node->translation_nid) {
            // If not existing translation set, update both nodes. Otherwise trid is saved by i18n module
            if(!$node->trid){
            $node->trid = db_next_id('{i18n_node}_trid');
            db_query("UPDATE {i18n_node} SET trid = %d WHERE nid=%d OR nid=%d", $node->trid, $node->nid, $node->translation_nid);
          }
          // Clone files for node attachments
          if(isset($node->translation_files)) {
            foreach($node->translation_files as $fid => $file) {
                if(!$file['remove']) {
                // We are using revisions to have a file linked to different nodes, different descriptions
                db_query("INSERT INTO {file_revisions} (fid, vid, list, description) VALUES (%d, %d, %d, '%s')", $file['fid'], $node->vid, $file['list'], $file['description']);
                }
            }
          }
        }
        break;
    }
  }
}

/**
 * Fills up some fields from source node
 */
function translation_node_populate_fields($source, &$form, &$node, $level = 0){
  $fields = array();
  $language = $form['i18n']['language']['#default_value'];  
  foreach(element_children($form) as $key) {
    if($key == 'nid' || $key == 'vid' || $key == 'i18n'){
      continue;
    } elseif(isset($source->$key) && !isset($node->$key)) {
      if($level == 0 && $key == 'parent' && is_numeric($source->parent)) {
        // Translate book outline
      	$trans = translation_node_get_translations(array('nid' => $source->parent));
        if(isset($trans[$language])) {
         $form['parent']['#default_value'] = $trans[$language]->nid;
        }
      } else {
        $node->$key = $form[$key]['#default_value'] = $source->$key;
        $fields[] = $key;
      }
    } elseif(!isset($form[$key]['#tree'])) {
      translation_node_populate_fields($source, $form[$key], $node, $level +1);
    }
  }
  // For debugging
  //if($fields) drupal_set_message("Populated fields ($level): ". implode(', ', $fields));
}

/**
 * Multilingual Nodes support
 */

/**
 * This is the callback for the tab 'translation' for nodes
 */
function translation_node_page() {
  $args = func_get_args();
  $op = $args[0];
  $nid = arg(1);
  $node = node_load($nid);
  // If node has no language, just warning message. Function returns here
  if(!$node->language) {
  	form_set_error('language', t("You need to set a language before creating a translation."));
    drupal_goto("node/$nid/edit");
  }
  drupal_set_title($node->title);
  $output = '';
  switch($op){
    case 'select':
      $output .= translation_node_overview($node);
      $output .= translation_node_select($node, $args[1]);
      break;
    case 'remove':
    case t('Remove'):
      db_query("UPDATE {i18n_node} SET trid = NULL WHERE nid=%d", $node->nid);
      drupal_set_message("The node has been removed from the translation set");
      drupal_goto("node/$node->nid/translation");
    default:
      $output .= translation_node_overview($node);
  }
  return $output;  
}

/**
 * Form builder function
 */
function translation_node_form($node, $lang, $list){
  $form['node'] = array('#type' => 'value', '#value' =>$node);
  $form['language'] = array('#type' => 'hidden', '#value' => $lang);
  $form['source_nid'] = array('#type' => 'hidden', '#value' => $node->nid);
  $form['trid'] = array('#type' => 'hidden', '#value' => $node->trid);
  $languages = i18n_supported_languages();

  $form['nodes']['nid'] = array(
   '#type' => 'radios', '#title' => t('Select translation for %language', array('%language' => $languages[$lang])), 
       '#default_value' => isset($node->translation[$lang]) ? $node->translation[$lang]->nid : '',
       '#options' => $list);
  $form['pager'] = array('#value' => theme('pager'));
  $form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
  return $form;
}

/**
 * Menu callback: administration page for node translations
 */
function translation_admin_content() {
  $output = '';
  $defaults = array('translation_language' => i18n_get_lang(), 'source_status' => TRANSLATION_STATUS_SOURCE);
  // Filter forms
  $output .= drupal_get_form('node_filter_form');
  $output .= drupal_get_form('translation_filter_form', $defaults);
  // Node listing depending on previous forms
  $output .= drupal_get_form('translation_admin_nodes');

  return $output;
} 

/**
 * Form builder. Administrative form for node translations
 */
function translation_admin_nodes() {
  // First, translation filter because it may need parameters for join conditions
  $filter = translation_build_filter_query();
  $where = $filter['where'];
  $params = $filter['args'];
  $join = $filter['join'];
  
  $filter = node_build_filter_query();
  // Remove WHERE 
  if($filter['where']) {
  	$where += array(str_replace('WHERE', '', $filter['where']));
  }

  $params = array_merge($params, $filter['args']);

  $join .= $filter['join'];
  
  $sql = "SELECT n.nid, n.type, n.title, n.status, u.name, u.uid, i.language, i2.nid AS translation_nid, i2.status AS translation_status ".
    "FROM {node} n INNER JOIN {users} u ON n.uid = u.uid INNER JOIN {i18n_node} i ON n.nid = i.nid ".
    "LEFT JOIN {i18n_node} i2 ON i.trid = i2.trid AND 0 != i2.trid AND i.language != i2.language $join".
    (count($where) ? ' WHERE '.implode(' AND ', $where) : '');
 
  // Fetch data
  //drupal_set_message("DEBUG:query: $sql");
  //drupal_set_message("DEBUG:params: ".implode(', ', $params));
  i18n_selection_mode('off');
  $result = pager_query(db_rewrite_sql($sql), 50, 0, NULL, $params);
  i18n_selection_mode('reset');

  $languages = i18n_supported_languages();
  $translation_status = _translation_status();

  // Fetch language for translations
  $language = isset($_SESSION['translation_filter']['translation_language']) ? $_SESSION['translation_filter']['translation_language'] : i18n_get_lang();
  $destination = drupal_get_destination();
  while ($node = db_fetch_object($result)) {
    $nodes[$node->nid] = '';
    $form['language'][$node->nid] = array('#value' => $languages[$node->language]);
    $form['title'][$node->nid] = array('#value' => l($node->title, 'node/'. $node->nid) .' '. theme('mark', node_mark($node->nid, $node->changed)));
    $form['name'][$node->nid] =  array('#value' => node_get_types('name', $node));
    $form['username'][$node->nid] = array('#value' => theme('username', $node));
    $form['status'][$node->nid] =  array('#value' =>  ($node->status ? t('published') : t('not published')));
    if ($node->translation_nid) {
      $form['translation_status'][$node->nid] = array('#value' => $translation_status[$node->translation_status]);
      $form['operations'][$node->nid] = array('#value' => l(t('edit translation'), 'node/'. $node->translation_nid .'/edit', array(), $destination));
    } else {
    	$form['translation_status'][$node->nid] = array('#value' => '--');
      if($language == $node->language) {
      	$form['operations'][$node->nid] = array('#value' => '--');
      } else {
        $form['operations'][$node->nid] = array('#value' => l(t('create translation'), 'node/add/'.$node->type, array(), "translation=$node->nid&language=$language").
        ' | '.l(t('select node'), "node/$node->nid/translation/select/$language", array(), $destination));   
      }
    }
  }
  /*
  $form['nodes'] = array('#type' => 'checkboxes', '#options' => $nodes);
  */
  $form['pager'] = array('#value' => theme('pager', NULL, 50, 0));
  return $form; 
}


/**
 * Build query for node administration filters based on session.
 */
function translation_build_filter_query() {

  // Build query
  $where = $args = array();
  $join = '';
  // This will produce an empty join
  if (!is_array($_SESSION['translation_filter'])) {
  	$_SESSION['translation_filter'] = array();
  }
  foreach ($_SESSION['translation_filter'] as $type => $value) {
    switch($type) {
      case 'source_language':
        $where[] = "i.language = '%s'";
        $args[] = $value;
        break;
      case 'translation_language':
        $join .= " AND i2.language ='".db_escape_string($value)."' ";
        break;
      case 'source_status':
        $where[] = "i.status = %d";
        $args[] = $value;
        break;
      case 'translation_status':
        $join .= " AND i2.status = ".db_escape_string($value);
        break;
    }
  }

  return array('where' => $where, 'join' => $join, 'args' => $args);
}

/**
 * Returns form for translation administration filters.
 */
function translation_filter_form($defaults = array()) {
  $session = &$_SESSION['translation_filter'];
  $session = is_array($session) ? $session : $defaults;

  // Save defaults for form reset
  $form['_defaults'] = array('#type' => 'value', '#value' => $defaults);
  $form['filters'] = array('#type' => 'fieldset',
    '#title' => t('And translation conditions are'),
  );
  $languages = i18n_supported_languages();
  // Translation and language conditions

  $form['filters']['source_language'] = array('#type' => 'select', '#title' => t('source language'),
    '#options' => array('' => '') + $languages, '#default_value' => $session['source_language']);
  $form['filters']['source_status'] = array('#type' => 'select', '#title' => t('source status'),
    '#options' => array('' => '') + _translation_status(), '#default_value' => $session['source_status']);
  $form['filters']['translation_language'] = array('#type' => 'select', '#title' => t('translation language'),
    '#options' => array('' => '') + $languages, '#default_value' => $session['translation_language']);      
  $form['filters']['translation_status'] = array('#type' => 'select', '#title' => t('translation status'),
    '#options' => array('' => '') + _translation_status(), '#default_value' => $session['translation_status']);
  $form['filters']['buttons']['submit'] = array('#type' => 'submit', '#value' =>  t('Filter'));
  if (count($session)) {
    $form['filters']['buttons']['reset'] = array('#type' => 'submit', '#value' => t('Reset'));
  }
  return $form;
}

function theme_translation_filter_form($form) {
	$form['filters']['source_language']['#prefix'] = '<table><tr><td>';
  $form['filters']['source_status']['#suffix'] = '</td><td>';
  $form['filters']['buttons']['#prefix'] = '</td><td>';
  $form['filters']['buttons']['#suffix'] = '</td></tr></table>';
  return drupal_render($form);
}
/**
 * Builds translation admin filters
 */
function translation_filter_form_submit($form_id, $form_values) {
  $op = $_POST['op'];
  $session = &$_SESSION['translation_filter'];
  
  switch ($op) {
    case t('Filter'):
      $session = array();
      foreach($form_values as $name => $value) {
      	if($value) $session[$name] = $value;
      }
      break;
    case t('Reset'):
      $session = $form_values['_defaults'];
      break;
  }

}
/**
 * Theme node administration overview.
 */
function theme_translation_admin_nodes($form) {
  // Overview table:
  $header = array(t('Language'), t('Title'), t('Type'), t('Author'), t('Status'), t('Translation status'), t('Operations'));

  $output .= drupal_render($form['options']);
  if (isset($form['title']) && is_array($form['title'])) {
    foreach (element_children($form['title']) as $key) {
      $row = array();
      $row[] = drupal_render($form['language'][$key]);
      $row[] = drupal_render($form['title'][$key]);
      $row[] = drupal_render($form['name'][$key]);
      $row[] = drupal_render($form['username'][$key]);
      $row[] = drupal_render($form['status'][$key]);
      $row[] = drupal_render($form['translation_status'][$key]);
      $row[] = drupal_render($form['operations'][$key]);
      $rows[] = $row;
    }

  }
  else  {
    $rows[] = array(array('data' => t('No posts available.'), 'colspan' => '6'));
  }

  $output .= theme('table', $header, $rows);
  if ($form['pager']['#value']) {
    $output .= drupal_render($form['pager']);
  $output .= drupal_render($form);
/**
 * Shows overview of current translations plus remove button
 */
function translation_node_overview($node) {
  $languages = i18n_supported_languages();
  unset($languages[$node->language]);
  // $output = t('<h2>Current translations</h2>');
  $header = array(t('Language'), t('Title'), t('Status'), t('Options'));
  // Special links for product nodes
  $createlink = $node->type == 'product' ? "node/add/$node->type/$node->ptype" : "node/add/$node->type";
  foreach($languages as $lang => $langname){
	$options = array();
	if(isset($node->translation[$lang])){
	  $trnode = $node->translation[$lang];
	  $title = l($trnode->title, 'node/'.$trnode->nid);
      $status = $trnode->status ? t('Published') : t('Not published');
	} else {
	  $title = t('Not translated');
	  $options[] = l(t('create translation'), $createlink, array(), "translation=$node->nid&language=$lang");
	  $status = '--';
	$options[] = l(t('select node'), "node/$node->nid/translation/select/$lang");
	$rows[] = array($langname, $title, $status, implode(" | ", $options));
  }
  $output .= theme('table', $header, $rows);
  if($node->trid){
    $output .= drupal_get_form('translation_node_remove', $node);
  }
  return theme('box', t('Current translations'), $output);

/**
 * Form to remove node from translation set
 */
function translation_node_remove($node) {
  $form['submit'] = array('#type' => 'submit', '#value' => t('Remove'), '#suffix' => t('Remove node from this translation set'));
  $form['#action'] = url('node/'.$node->nid.'/translation/remove');
  return $form;
}

/**
 * Form to select a translation from existing nodes
 */
function translation_node_select($node, $lang) {
  $languages = i18n_supported_languages();
  // Disable i18n rewrite. Order by trid to show first nodes with no translation
  i18n_selection_mode('off');
  $result = pager_query(db_rewrite_sql("SELECT n.nid, n.title FROM {node} n INNER JOIN {i18n_node} i ON n.nid = i.nid WHERE i.language = '%s' ORDER BY i.trid"), 40, 0, NULL, $lang);
  i18n_selection_mode('reset');
  while($trnode = db_fetch_object($result)){
    $list[$trnode->nid] = l($trnode->title, "node/$trnode->nid") ;
  }
  if($list){
    return drupal_get_form('translation_node_form', $node, $lang, $list);
  } else {
    return t("<p>No nodes available in %language</p>",  array('%language' => $languages[$lang]) );
  }  
}

/**
 * Process translation node form
 */
function translation_node_form_submit($form_id, $form_values){
  $op = $_POST['op'];
  $source_nid = $form_values['source_nid'];
  $language = $form_values['language'];
  $nid = $form_values['nid'];
  if( $source_nid && $language && $nid ) {
   if($trid = $form_values['trid']){
      // Delete old translations
      db_query("UPDATE {i18n_node} SET trid = 0 WHERE trid = %d AND language = '%s'", $trid, $language);
    } else {
      $trid = db_next_id('{i18n_node}_trid');
    }
    db_query("UPDATE {i18n_node} SET trid = %d WHERE nid=%d OR nid=%d", $trid, $source_nid, $nid); 
    drupal_set_message(t('The translation has been saved'));
  
  } 
}

/**
 * Taxonomy hook
 * $edit parameter is an array, not an object !!
 */
 // $op = insert, update, delete
function translation_taxonomy($op, $type = NULL, $edit = NULL) {
  if (!is_array($op)) {
    switch ("$type/$op") {
      case 'term/insert':
      case 'term/update':
        if (!$edit['language'] && $edit['trid']) {
          // Removed language, remove trid
          db_query('UPDATE {term_data} SET trid=0 WHERE tid=%d', $edit['tid']);
          if(db_affected_rows()) drupal_set_message(t('Removed translation information from term'));
        }
        break;
    }
  } else {
    return $op;
  }
}
/**
 * Implementation of hook_link().
 */
function translation_link($type, $node = NULL, $teaser = FALSE) {
  $languages = i18n_supported_languages();
  $links = array();
  if ($type == 'node' && variable_get('i18n_translation_node_links', 0) > ($teaser ? 1 : 0) && $node->translation) {
    foreach ($node->translation as $lang => $trnode) {
      // Add node link if published
      if($trnode->status) {
        $baselang = variable_get('i18n_translation_links', 0) ? i18n_get_lang() : $lang;
        $links['translation-'.$lang]= theme('translation_node_link', $trnode , $lang, $baselang);
      }
    }
  }
  return $links;
}

/**
 * Returns a list for terms for vocabulary, language
 */
function translation_vocabulary_get_terms($vid, $lang, $status = 'all') {
  switch($status){
    case 'translated':
      $andsql = ' AND trid > 0';
      break;
    case 'untranslated':
      $andsql = ' AND trid = 0';
      break;
    default:
      $andsql = '';
  }
  $result = db_query("SELECT * FROM {term_data} WHERE vid=%d AND language='%s' $andsql", $vid, $lang);
  $list = array();
  while ($term = db_fetch_object($result)) {
     $list[$term->tid] = $term->name;
  }
  return $list;  
}

/**
	*	Get translations
  *
	* @param $params
  *   array of parameters
	* @param $getall
  *   TRUE to get the also node itself
	*/
function translation_node_get_translations($params, $getall = TRUE) {
  foreach($params as $field => $value) {
    $conds[] = "b.$field = '%s'";
    $values[] = $value;
  }
  if(!$getall){ // If not all, a parameter must be nid
    $conds[] = "n.nid != %d";
    $values[] = $params['nid'];
  }
  $conds[] = "b.trid != 0";
  $sql = 'SELECT n.nid, n.title, n.status, a.language FROM {node} n INNER JOIN {i18n_node} a ON n.nid = a.nid INNER JOIN {i18n_node} b ON a.trid = b.trid WHERE '. implode(' AND ', $conds);

  i18n_selection_mode('off');
  $result = db_query(db_rewrite_sql($sql), $values);
  i18n_selection_mode('reset');
    
  $items = array();
  while ($node = db_fetch_object($result)) {
    $items[$node->language] = $node;
  }
  return $items;
}

/**
 * Returns node type for nid
 */
function translation_get_node_type($nid) {
  return db_result(db_query('SELECT type FROM {node} WHERE nid=%d', $nid));
}

/**
 * Multilingual Taxonomy
 *
 */
 
/**
 * This is the callback for taxonomy translations
 * 
 * Gets the urls:
 * 		admin/taxonomy/i18n/term/xx
 * 		admin/taxonomy/i18n/term/new/xx
 * 		admin/taxonomy/vid/translation/op/trid
 */

function translation_taxonomy_admin() {
  $vid = arg(3);
  $op = $_POST['op'] ? $_POST['op'] : arg(5);
  $edit = $_POST['edit'];

  switch ($op) {
    case t('Save'):    
    case 'edit':
      drupal_set_title(t('Edit term translations'));
      $output = translation_taxonomy_form($vid, arg(6), $edit);
      break;
    case t('Submit'):
      drupal_set_title(t('Submit'));
      translation_taxonomy_term_save($edit);
      $output = translation_taxonomy_overview($vid);
      break;
    case 'delete':
      //print theme('page', node_delete($edit), t('Delete'));
      break;
    default:
      $output = translation_taxonomy_overview($vid);
  }
  return $output;   
}  
    
/**
 * Generate a tabular listing of translations for vocabularies.
 */
function translation_taxonomy_overview($vid) {
  $vocabulary = taxonomy_get_vocabulary($vid);
  drupal_set_title(check_plain($vocabulary->name));
 
  $languages = i18n_supported_languages();
  $header = array_merge($languages, array(t('Operations')));
  $links = array();
  $types = array();
	// Get terms/translations for this vocab 
	$result = db_query('SELECT * FROM {term_data} t WHERE vid=%d',$vocabulary->vid);
	$terms = array();
	while ($term = db_fetch_object($result)) {
	  if($term->trid && $term->language) {
	    $terms[$term->trid][$term->language] = $term;
	  }
	}
	// Reorder data for rows and languages
  foreach ($terms as $trid => $terms) {
    $thisrow = array();
    foreach ($languages as $lang => $name) {
      if (array_key_exists($lang, $terms)) {
        $thisrow[] = $terms[$lang]->name;
       }
       else {
        $thisrow[] = '--';
       }
    }
    $thisrow[] = l(t('edit'), "admin/content/taxonomy/$vid/translation/edit/$trid");
    $rows[] = $thisrow;
  }
  $output .= theme('table', $header, $rows);
  $output .= l(t('new translation'), "admin/content/taxonomy/$vid/translation/edit/new");
  return $output;
}

/**
 * Produces a vocabulary translation form
 */
function translation_taxonomy_form($vid, $trid = NULL, $edit = array()) {
  $languages = i18n_supported_languages();
  if ($trid == 'new') {
    $translations = array();
  } else {
    $form['trid'] = array('#type' => 'hidden', '#value' => $trid);
    $translations = translation_term_get_translations(array('trid' =>$trid));
  } 
  //var_dump($translations);
  $vocabulary = taxonomy_get_vocabulary($vid);
  
  // List of terms for languages
  foreach ($languages as $lang => $langname) {
    $current = isset($translations[$lang]) ? $translations[$lang]->tid : '';
    $list = translation_vocabulary_get_terms($vid, $lang, 'all');
    $list[''] = '--';
    $form[$lang] = array('#type' => 'fieldset', '#tree' => TRUE);
    $form[$lang]['tid'] = array(
      '#type' => 'select', 
      '#title' => $langname,
    	'#default_value' => $current,
    	'#options' => $list
    );
    $form[$lang]['old'] = array('#type' => 'hidden', '#value' =>$current);
  }

  $form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
  return drupal_get_form('translation_taxonomy', $form);
}

/**
 * Process vocabulary translation form
 */
function translation_taxonomy_submit($form_id, $form_values) {
  $trid = $form_values['trid'];
  $languages = i18n_supported_languages();
  $translations = array();
  // Delete old translations
  if($trid){
    db_query("UPDATE {term_data} SET trid = 0 WHERE trid=%d", $trid);
  }
  foreach ($languages as $lang => $name) {
    if (is_numeric($form_values[$lang]['tid'])) {
      $translations[$lang] = $form_values[$lang]['tid'];
    }
  }
  if(count($translations)) {
    $trid = is_numeric($trid) ? $trid : db_next_id('{term_data}_trid');
    db_query('UPDATE {term_data} SET trid=%d WHERE tid IN(%s)', $trid, implode(',',$translations));
  }
  drupal_set_message(t('Term translations have been updated'));
}

/**
 * Converts a list of arrays to an array of the form keyfield => namefield
 */
function translation_array2list($data, $keyfield, $namefield = 'name') {
  foreach ($data as $key => $value) {
    if (is_array($data)) {
      $list[$value[$keyfield]] = $value[$namefield];
    }
    else {
      $list[$value->$keyfield] = $value->$namefield;
    }
  }
  return $list;
}

/**
 * Get term translations
 * 
 * @return
 *   An array of the from lang => Term
 */ 
function translation_term_get_translations($params, $getall = TRUE) {
  foreach($params as $field => $value) {
    $conds[] = "i.$field = '%s'";
    $values[] = $value;
  }
  if(!$getall){ // If not all, a parameter must be tid
    $conds[] = "t.tid != %d";
    $values[] = $params['tid'];
  }
  $conds[] = "t.trid != 0";
  $sql = 'SELECT t.* FROM {term_data} t INNER JOIN {term_data} i ON t.trid = i.trid WHERE '. implode(' AND ', $conds);;
  $result = db_query($sql, $values);
  $items = array();
  while ($data = db_fetch_object($result)) {
    $items[$data->language] = $data;
  }
  return $items;  
}

/**
 * Produces url of translated page
 */
function translation_url($url, $lang) {
  global $i18n_langpath;
  // If !url get from original request
  if (!$url) {
    $url = _i18n_get_original_path();
  }
  // If url has lang_prefix, remove it
  i18n_get_lang_prefix($url, true);
  // are we looking at a node?
  if (preg_match("/^(node\/)([0-9]*)$/",$url,$matches)) {
    if ($nid = translation_node_nid($matches[2], $lang)) {
      $url = "node/$nid";
    }
  }
  // or a taxonomy term
  elseif (preg_match("/^(taxonomy\/term\/)([^\/]*)$/",$url,$matches)) {//or at a taxonomy-listing?
    if ($str_tids = translation_taxonomy_tids($matches[2], $lang)) {
      $url = "taxonomy/term/$str_tids";
    }
  }
  
  return $url;
}

/**
 *  Returns an url-part, pointing to the translated node, if exists 
 */
function translation_node_nid($nid, $lang) {
  $sql = 'SELECT n.nid FROM {i18n_node} n INNER JOIN {i18n_node} a ON n.nid = a.nid INNER JOIN {i18n_node} b ON a.trid = b.trid AND b.nid =%d WHERE n.nid != %d AND n.language = \'%s\' AND a.trid != 0';
  return db_result(db_query($sql, $nid, $nid, $lang));
}
/**
 *  Returns an url for the translated taxonomy-page, if exists 
 */
function translation_taxonomy_tids($str_tids, $lang) {
  if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str_tids)) {
    $separator = '+';
    // The '+' character in a query string may be parsed as ' '.
    $tids = preg_split('/[+ ]/', $str_tids);
  }
  else if (preg_match('/^([0-9]+,)*[0-9]+$/', $str_tids)) {
    $separator = ',';
    $tids = explode(',', $str_tids);
  }
  else {
    return;
  }
  $translated_tids = array();
  foreach ($tids as $tid) {
    if ($translated_tid = translation_term_get_translations(array('tid' =>$tid))) {
      $translated_tids[] = $translated_tid[$lang]->tid;
    }
  }
  return implode($separator, $translated_tids);
}

/**
 * function translation_get_links
 *