How to: Translate products in Drupal Commerce

Translations have always been tricky business with Drupal, and the addition of Drupal Commerce is no exception. For multilingual content, the standard practice has been to use the Internationalization (i18n) module, which creates a new node for each translation and ties them together in a translation set. However, this procedure doesn't work for products because they have a unique SKU, so instead we go with the module Entity Translation (entity_translation) which allows us to translate specific fields such as the product title yet maintain the same SKU.

This tutorial assumes that you're using the Commerce Kickstart distribution, which among other things includes the module Title (title) to transform the title property of an entity into a proper field. The product display node and product entity I will use as an example are the ones that come with Commerce Kickstart.

So, here's what you need to do in order to achieve an adequate base system for translating products. After downloading and enabling Entity Translation, enter these settings:

/admin/config/regional/entity_translation - Add Commerce Product to Translatable entity types

/admin/structure/types/manage/product-display - Under Publishing options, select "Enabled, with field translation" for Multilingual support.

/admin/structure/types/manage/product-display/fields/body - Scroll down and enable field translation for this field since you're probably going to want different body texts for different languages.

That gets you started. However, what you will find is that after you create a translation for a product display and enter the translated title for its products, the original product display will also show the translated title so then you need to go back and type the original title again. In my eyes, that is not an acceptable usability level so I found out what causes this little problem and created a function that you can add to a module of your own to eliminate this problem:

//Implements hook_entity_presave().
function mymodule_entity_presave($entity, $type) {
  if ($type === 'node' && isset($entity->field_product)) {
    $product_ids = array();
    foreach($entity->field_product as $lang => $items) {
      foreach($items as $i => $values) {
        if(!empty($values['product_id'])) {
          $product_ids[] = $values['product_id'];
        }
      }
    }
    $products = entity_load('commerce_product', $product_ids);
    foreach($products as $id => $product) {
      $changed = false;
      //Find all translatable fields for the product
      $field_instances = field_info_instances('commerce_product', $product->type);
      foreach($field_instances as $field_name => $field_instance) {
        $field = field_info_field($field_name);
        if(!empty($field['translatable'])) {
          //Add a version of the field in the product display's language if it doesn't exist already
          if(!isset($product->{$field_name}[$entity->language])) {
            //Copy over the language-agnostic value.
            $product->{$field_name}[$entity->language] = $product->{$field_name}[LANGUAGE_NONE];
            $changed = true;
          }
        }
      }
      //Save the product.
      if($changed) {
        entity_save('commerce_product', $product);
      }
    }
  }
}

And that's it! A nice side-effect of this way to solve the problem is that if you don't enter a translated version of a field, it will still use the original value which can be desirable sometimes.