Code below works on my site for preserving language and translation sets for taxonomy terms. First time playing with migrate and migrate d2d, so just adding here in hope of some feedback and maybe it's of some use to others....

class i18nTermMigration extends DrupalTerm6Migration {
  protected function query() {
    $query = parent::query();
    $query->fields('td', array('language', 'trid'));
    return $query;
  }  	  
	
  public function __construct(array $arguments) {
    $this->sourceFields['language'] = t('Language of the term');
    $this->sourceFields['trid'] = t('Translation group');
    parent::__construct($arguments);    
   
    // The example uses a SQL database as source.
    $this->addFieldMapping('language', 'language')->defaultValue('und');
    $this->addFieldMapping('i18n_tsid', 'trid');
  }
  
  // create a i18n translation set if is does not exsist   
  public function complete($entity, stdClass $row) {
    if (!is_null($entity->i18n_tsid) && !i18n_translation_set_load($entity->i18n_tsid) && $entity->i18n_tsid > 0) {
      $tsid = db_insert('i18n_translation_set')
        ->fields(array(
          'tsid' => $entity->i18n_tsid,
          'type' => 'taxonomy_term',
          'bundle' => $entity->vocabulary_machine_name,
          'master_id' => 0,
          'status'=> 0,
          'created' => REQUEST_TIME,
          'changed' => REQUEST_TIME,
        ))
      ->execute();
    }
  }
}

Comments

mikeryan’s picture

Component: Code » Documentation

'language' and 'trid' columns don't exist in the core term_data table in D6, are they added by the contrib i18n module?

When I do #2000160: Update documentation for migrate_d2d 2.1, I'll add an FAQ section, this can be posted beneath that.

mikeryan’s picture

Category: support » task
mikeryan’s picture

Actually, it can go under the Cookbook page: http://drupal.org/node/1931396

splash112’s picture

Hi Mike,

Yes, correct. They are added by the i18n module in D6, but becomes part of core in D7. The solution above works (as far as I know), but cannot be rolled back.

Will make a cookbook page later, thanks!

Mark

pablojais’s picture

Hi all,
Thanks splash112! I found your code very useful. I wanted to share a few additions I made to it. One makes the language and i18n_tsid fields to show as destinations, and the other maks sure no language group is overwritten.

By the way, mikeryan, is there a "right" way to add internationalization support to the rest of migrate_d2d?

Thanks,
Pablo

class i18nMigrateDestinationTerm extends MigrateDestinationTerm {
  public function fields($migration = NULL) {
    $fields = parent::fields($migration);
    if ( function_exists('i18n_taxonomy_vocabulary_mode') ) {
      $fields['language'] = t('Term: Language');
      $fields['i18n_tsid'] = t('Term: Language group');
    }
    return $fields;
  }
}


class i18nTermMigration extends DrupalTerm6Migration {
  protected function query() {
    $query = parent::query();
    $query->fields('td', array('language', 'trid'));
    return $query;
  }

  public function __construct(array $arguments) {
    $this->sourceFields['language'] = t('Language of the term');
    $this->sourceFields['trid'] = t('Translation group');
    parent::__construct($arguments);
    // Override destination given by the parent with our own
    $this->destination = new i18nMigrateDestinationTerm($this->destinationVocabulary, $arguments);
    $this->addFieldMapping('language', 'language')->defaultValue(LANGUAGE_NONE);
    $this->addFieldMapping('i18n_tsid', 'trid');

    // Try not to collide with existing translations
    $q = db_select('i18n_translation_set');
    $q->addExpression('max(tsid)');
    $this->min_tsid = $q->execute()->fetchField();
    if ( $this->min_tsid==NULL ) {
      $this->min_tsid = 0;
    }
  }

  public function complete($entity, stdClass $row) {
    if ( !is_null($entity->i18n_tsid)
          && !i18n_translation_set_load($entity->i18n_tsid)
          && $entity->i18n_tsid > 0 ) {
      $tsid = db_insert('i18n_translation_set')
        ->fields(array(
          'tsid' => $entity->i18n_tsid,
          'type' => 'taxonomy_term',
          'bundle' => $entity->vocabulary_machine_name,
          'master_id' => 0,
          'status'=> 0,
          'created' => REQUEST_TIME,
          'changed' => REQUEST_TIME,
        ))
      ->execute();
    }
  }

  public function prepareRow($row) {
    if (parent::prepareRow($row) === FALSE) {
      return FALSE;
    }
    $row->trid += $this->min_tsid;
    return TRUE;
  }
}
meichr’s picture

Thanks splash112 and pablojais,
I found your above postings very useful. When using the latest code of #5 I came across a problem, that terms with the same name in more than one language are merged during the migration.

The feature of merging terms with the same name makes sense within the same language only, e.g. to clean the vocabulary. By adding the parameter 'allow_duplicate_terms = TRUE' to the arguments array in the instantiation of class 'i18nTermMigration' you can avoid this. But if you still need the feature of merging terms of the same name within a language you need a better solution.

I've taken the code of #5 and added the method 'FindMatchingTerm' from migrate module with an added check, whether the term not only matches by name, but also by language:

class i18nMigrateDestinationTerm extends MigrateDestinationTerm {
  public function fields($migration = NULL) {
    $fields = parent::fields($migration);
    if ( function_exists('i18n_taxonomy_vocabulary_mode') ) {
      $fields['language'] = t('Term: Language');
      $fields['i18n_tsid'] = t('Term: Language group');
    }
    return $fields;
  }
  public function findMatchingTerm($term) {
    // See if the term with the same language and parentage, already exists -
    // if so, load it
    $candidates = taxonomy_term_load_multiple(array(),
      array('name' => trim($term->name), 'vid' => $term->vid));
    foreach ($candidates as $candidate) {
      if ($term->language != $candidate->language) continue;
      $parents = taxonomy_get_parents($candidate->tid);
      // We need to set up $parents as a simple array of tids
      if (empty($parents)) {
        $parents = array(0);
      }
      else {
        // Parents array is tid => term object, make into list of tids
        $new_parents = array();
        foreach ($parents as $parent) {
          $new_parents[] = $parent->tid;
        }
        $parents = $new_parents;
      }
      if ($term->parent == $parents) {
        // We've found a matching term.
        return $candidate;
      }
    }
    return FALSE;
  }
}
class i18nTermMigration extends DrupalTerm6Migration {
  protected function query() {
    $query = parent::query();
    $query->fields('td', array('language', 'trid'));
    return $query;
  }
  public function __construct(array $arguments) {
    $this->sourceFields['language'] = t('Language of the term');
    $this->sourceFields['trid'] = t('Translation group');
    parent::__construct($arguments);
    // Override destination given by the parent with our own
    $this->destination = new i18nMigrateDestinationTerm($this->destinationVocabulary, $arguments);
    $this->addFieldMapping('language', 'language')->defaultValue(LANGUAGE_NONE);
    $this->addFieldMapping('i18n_tsid', 'trid');
    // Try not to collide with existing translations
    $q = db_select('i18n_translation_set');
    $q->addExpression('max(tsid)');
    $this->min_tsid = $q->execute()->fetchField();
    if ( $this->min_tsid==NULL ) {
      $this->min_tsid = 0;
    }
  }
  public function complete($entity, stdClass $row) {
    if ( !is_null($entity->i18n_tsid)
          && !i18n_translation_set_load($entity->i18n_tsid)
          && $entity->i18n_tsid > 0 ) {
      $tsid = db_insert('i18n_translation_set')
        ->fields(array(
          'tsid' => $entity->i18n_tsid,
          'type' => 'taxonomy_term',
          'bundle' => $entity->vocabulary_machine_name,
          'master_id' => 0,
          'status'=> 0,
          'created' => REQUEST_TIME,
          'changed' => REQUEST_TIME,
        ))
      ->execute();
    }
  }
  public function prepareRow($row) {
    if (parent::prepareRow($row) === FALSE) {
      return FALSE;
    }
    $row->trid += $this->min_tsid;
    return TRUE;
  }
}

By this version of the code terms are only merged if the name matches another term within the same language. Unless the argument array contains the 'allow_duplicate_terms = TRUE' parameter setting, of course.

lmingarro’s picture

Hey guys, thanks for the code really useful. Apparently some languages code was changed form d6 to d7 for example
Portuguese changed from pt-br to pt. If there is some other languages with the same problem probably will be great create a mapping between d6 and d7 languages codes. Bu meanwhile the following hack should be enough :-)

 public function prepareRow($row) {
    if (parent::prepareRow($row) === FALSE) {
      return FALSE;
    }
    // pt-br was replaced with pt
    if ($row->language == 'pt-br') {
      $row->language = 'pt';
    } elseif ($row->language == '') {
      $row->language == NULL;
    }
    // We add min_tsid to avoid collisions
    $row->trid += $this->min_tsid;
    return TRUE;
  }
Luke_Nuke’s picture

Translations sets technically are entities so they really should be a separated destination. I started to work on i18n_migrate module that already handles this properly. This is going to be (hopefully) a set of modules that will easily handle migration of internationalized Drupal core content/taxonomy. Any help will be appreciated :) .