=== modified file 'payment/uc_google_checkout/uc_google_checkout.module'
--- payment/uc_google_checkout/uc_google_checkout.module	2009-06-22 18:24:47 +0000
+++ payment/uc_google_checkout/uc_google_checkout.module	2009-07-06 21:48:20 +0000
@@ -115,6 +115,11 @@ function uc_google_checkout_theme() {
 
 function uc_google_checkout_form_alter(&$form, &$form_state, $form_id) {
   $node = $form['#node'];
+  // Use the translation's source node if any.
+  if (isset($node->translation_source)) {
+    $node = $node->translation_source;
+  }
+  
   if (is_object($node) && $form_id == $node->type .'_node_form' && uc_product_is_product($node->type)) {
     $policy_url = 'https://checkout.google.com/support/sell/bin/answer.py?answer=75724';
     $form['google_checkout'] = array('#type' => 'fieldset',
@@ -151,6 +156,10 @@ function uc_google_checkout_form_alter(&
 function uc_google_checkout_nodeapi(&$node, $op) {
   if (uc_product_is_product($node->type)) {
     switch ($op) {
+      case 'prepare translation':
+        $node->gc_salable = $node->translation_source->gc_salable;
+      break;
+        
       case 'insert':
       case 'update':
         if (isset($node->gc_salable)) {

=== modified file 'shipping/uc_flatrate/uc_flatrate.module'
--- shipping/uc_flatrate/uc_flatrate.module	2009-05-06 18:27:07 +0000
+++ shipping/uc_flatrate/uc_flatrate.module	2009-06-11 08:05:56 +0000
@@ -50,6 +50,12 @@ function uc_flatrate_menu() {
  */
 function uc_flatrate_form_alter(&$form, &$form_state, $form_id) {
   if (uc_product_is_product_form($form)) {
+    $node = $form['#node'];
+    // Use the translation's source node if any.
+    if (isset($node->translation_source)) {
+      $node = $node->translation_source;
+    }
+    
     $sign_flag = variable_get('uc_sign_after_amount', FALSE);
     $currency_sign = variable_get('uc_currency_sign', '$');
     $enabled = variable_get('uc_quote_enabled', array());
@@ -76,8 +82,8 @@ function uc_flatrate_form_alter(&$form, 
       $form['shipping']['flatrate'][$method->mid] = array(
         '#type' => 'textfield',
         '#title' => $method->title,
-        '#default_value' => $form['#node']->flatrate[$method->mid],
-          '#description' => t('Default rate: %price', array('%price' => uc_price($method->product_rate, $context))),
+        '#default_value' => $node->flatrate[$method->mid],
+        '#description' => t('Default rate: %price', array('%price' => uc_price($method->product_rate, $context))),
         '#size' => 16,
         '#field_prefix' => $sign_flag ? '' : $currency_sign,
         '#field_suffix' => $sign_flag ? $currency_sign : '',
@@ -93,6 +99,10 @@ function uc_flatrate_form_alter(&$form, 
 function uc_flatrate_nodeapi(&$node, $op) {
   if (uc_product_is_product($node->type)) {
     switch ($op) {
+      case 'prepare translation':
+        $node->flatrate = $node->translation_source->flatrate;
+      break;
+      
       case 'insert':
       case 'update':
         if (is_array($node->flatrate)) {

=== modified file 'shipping/uc_quote/uc_quote.module'
--- shipping/uc_quote/uc_quote.module	2009-07-02 18:50:17 +0000
+++ shipping/uc_quote/uc_quote.module	2009-07-06 21:48:20 +0000
@@ -89,6 +89,12 @@ function uc_quote_theme() {
   );
 }
 
+function uc_quote_node_fields() {
+  return array(
+    'first_name', 'last_name', 'company', 'street1', 'street2', 'city', 
+    'zone', 'postal_code', 'country', 'phone',          
+  );
+}
 /**
  * Implementation of hook_nodeapi().
  *
@@ -97,6 +103,14 @@ function uc_quote_theme() {
 function uc_quote_nodeapi(&$node, $op, $arg3 = NULL, $arg4 = NULL) {
   if (uc_product_is_product($node->type)) {
     switch ($op) {
+      case 'prepare translation':
+        $node->shipping_type = $node->translation_source->shipping_type;
+        
+        foreach (uc_quote_node_fields() as $field) {
+          $node->$field = $node->translation_source->shipping_address->$field;
+        }
+      break;
+       
       case 'insert':
       case 'update':
         if (isset($node->shipping_type)) {
@@ -136,9 +150,15 @@ function uc_quote_nodeapi(&$node, $op, $
 function uc_quote_form_alter(&$form, &$form_state, $form_id) {
   // Alter the product node form.
   if (uc_product_is_product_form($form)) {
+    $node = $form['#node'];
+    // Use the translation's source node if any.
+    if (isset($node->translation_source)) {
+      $node = $node->translation_source;
+    }
+    
     // Get the shipping address.
-    $address = $form['#node']->shipping_address;
-
+    $address = $node->shipping_address;
+    
     // Use the store default if the product does not have an address set.
     if (empty($address)) {
       $address = variable_get('uc_quote_store_default_address', new stdClass());

=== modified file 'shipping/uc_ups/uc_ups.module'
--- shipping/uc_ups/uc_ups.module	2009-05-06 18:27:07 +0000
+++ shipping/uc_ups/uc_ups.module	2009-06-11 08:07:22 +0000
@@ -76,6 +76,10 @@ function uc_ups_theme() {
 function uc_ups_form_alter(&$form, &$form_state, $form_id) {
   if (uc_product_is_product_form($form)) {
     $node = $form['#node'];
+    // Use the translation's source node if any.
+    if (isset($node->translation_source)) {
+      $node = $node->translation_source;
+    }
     $enabled = variable_get('uc_quote_enabled', array());
     $weight = variable_get('uc_quote_method_weight', array('ups' => 0));
     $ups = array(
@@ -117,6 +121,10 @@ function uc_ups_product_alter_validate($
 function uc_ups_nodeapi(&$node, $op) {
   if (uc_product_is_product($node->type)) {
     switch ($op) {
+      case 'prepare translation':
+        $node->ups = $node->translation_source->ups;
+      break;
+        
       case 'insert':
       case 'update':
         if (isset($node->ups)) {

=== modified file 'shipping/uc_usps/uc_usps.module'
--- shipping/uc_usps/uc_usps.module	2009-06-22 15:13:26 +0000
+++ shipping/uc_usps/uc_usps.module	2009-07-06 21:48:20 +0000
@@ -39,6 +39,10 @@ function uc_usps_menu() {
 function uc_usps_form_alter(&$form, &$form_state, $form_id) {
   if (uc_product_is_product_form($form)) {
     $node = $form['#node'];
+    // Use the translation's source node if any.
+    if (isset($node->translation_source)) {
+      $node = $node->translation_source;
+    }
     $enabled = variable_get('uc_quote_enabled', array());
     $weight = variable_get('uc_quote_method_weight', array('usps' => 0, 'usps_intl' => 1));
     $form['shipping']['usps'] = array(
@@ -64,6 +68,10 @@ function uc_usps_form_alter(&$form, &$fo
 function uc_usps_nodeapi(&$node, $op) {
   if (uc_product_is_product($node->type)) {
     switch ($op) {
+      case 'prepare translation':
+        $node->usps = $node->translation_source->usps;
+      break;
+      
       case 'insert':
       case 'update':
         if (isset($node->usps)) {

=== modified file 'shipping/uc_weightquote/uc_weightquote.module'
--- shipping/uc_weightquote/uc_weightquote.module	2009-05-06 18:27:07 +0000
+++ shipping/uc_weightquote/uc_weightquote.module	2009-06-11 08:08:40 +0000
@@ -37,6 +37,11 @@ function uc_weightquote_menu() {
  */
 function uc_weightquote_form_alter(&$form, &$form_state, $form_id) {
   if (uc_product_is_product_form($form)) {
+    $node = $form['#node'];
+    // Use the translation's source node if any.
+    if (isset($node->translation_source)) {
+      $node = $node->translation_source;
+    }
     $enabled = variable_get('uc_quote_enabled', array());
     $weight = variable_get('uc_quote_method_weight', array('weightquote' => 0));
     $form['shipping']['weightquote'] = array(
@@ -51,7 +56,7 @@ function uc_weightquote_form_alter(&$for
       '#type' => 'textfield',
       '#title' => t('Shipping cost per !unit', array('!unit' => variable_get('uc_weight_unit', 'lb'))),
       '#description' => t('The amount per weight unit to add to the shipping cost for an item.<br />Example: to add $5 per pound, put 5 in here.'),
-      '#default_value' => $form['#node']->weightquote,
+      '#default_value' => $node->weightquote,
       '#size' => 16,
       '#field_prefix' => variable_get('uc_sign_after_amount', FALSE) ? '' : variable_get('uc_currency_sign', '$'),
       '#field_suffix' => (variable_get('uc_sign_after_amount', FALSE) ? variable_get('uc_currency_sign', '$') : '') . t('/!unit', array('!unit' => variable_get('uc_weight_unit', 'lb'))),
@@ -65,6 +70,10 @@ function uc_weightquote_form_alter(&$for
 function uc_weightquote_nodeapi(&$node, $op) {
   if (uc_product_is_product($node->type)) {
     switch ($op) {
+      case 'prepare translation':
+        $node->weightquote = $node->translation_source->weightquote;
+      break;
+      
       case 'insert':
       case 'update':
         if ($node->weightquote !== '') {

=== modified file 'uc_attribute/uc_attribute.admin.inc'
--- uc_attribute/uc_attribute.admin.inc	2009-06-09 15:01:27 +0000
+++ uc_attribute/uc_attribute.admin.inc	2009-06-11 08:02:03 +0000
@@ -499,6 +499,10 @@ function uc_object_attributes_form($form
         );
 
         $form['attributes'][$attribute->aid] = array(
+          'aid' => array(
+            '#type' => 'hidden',
+            '#default_value' => $attribute->aid,
+          ),
           'remove' => array(
             '#type' => 'checkbox',
             '#default_value' => 0,
@@ -623,78 +627,58 @@ function theme_uc_object_attributes_form
   return $output;
 }
 
+/**
+ * Submit handler for the product/class attributes form.
+ */
 function uc_object_attributes_form_submit($form, &$form_state) {
-  if ($form_state['values']['type'] == 'product') {
-    $attr_table = '{uc_product_attributes}';
-    $opt_table = '{uc_product_options}';
-    $id = 'nid';
-    $sql_type = '%d';
-  }
-  elseif ($form_state['values']['type'] == 'class') {
-    $attr_table = '{uc_class_attributes}';
-    $opt_table = '{uc_class_attribute_options}';
-    $id = 'pcid';
-    $sql_type = "'%s'";
-  }
-
+  $type = $form_state['values']['type'];
+  $id = $form_state['values']['id'];
+  
+  // The attribute edit form.
   if ($form_state['values']['view'] == 'overview' && is_array($form_state['values']['attributes'])) {
+    
+    $remove_count = 0;
     foreach ($form_state['values']['attributes'] as $aid => $attribute) {
+      // Remove was checked.
       if ($attribute['remove']) {
-        $remove_aids[] = $aid;
+        uc_attribute_subject_delete($aid, $id, $type);
+        $remove_count++;
       }
+      
+      // Update the attribute.
       else {
-        db_query("UPDATE $attr_table SET label = '%s', ordering = %d, required = %d, display = %d WHERE aid = %d AND $id = $sql_type", $attribute['label'], $attribute['ordering'], $attribute['required'], $attribute['display'], $aid, $form_state['values']['id']);
+        uc_attribute_subject_save($attribute, $id, $type);
         $changed = TRUE;
       }
     }
-
-    if (count($remove_aids) > 0) {
-      $id_value = $form_state['values']['id'];
-      $remove_aids_value = implode(', ', $remove_aids);
-
-      db_query("DELETE FROM $opt_table WHERE EXISTS (SELECT * FROM {uc_attribute_options} AS ao WHERE $opt_table.oid = ao.oid AND ao.aid IN (%s)) AND $opt_table.$id = $sql_type", $remove_aids_value, $id_value);
-      db_query("DELETE FROM $attr_table WHERE $id = $sql_type AND aid IN (%s)", $id_value, $remove_aids_value);
-      if ($form_state['values']['type'] == 'product') {
-        db_query("DELETE FROM {uc_product_adjustments} WHERE nid = %d", $id_value);
-      }
-
-      drupal_set_message(format_plural(count($remove_aids), '@count attribute has been removed.', '@count attributes have been removed.'));
+    
+    if ($remove_count) {
+      drupal_set_message(format_plural($remove_count, '@count attribute has been removed.', '@count attributes have been removed.'));
     }
-
+    
     if ($changed) {
       drupal_set_message(t('The changes have been saved.'));
     }
   }
+  
+  // The attribute add form.
   elseif ($form_state['values']['view'] == 'add') {
     foreach ($form_state['values']['add_attributes'] as $aid) {
-      // Enable all options for added attributes.
+      // Load the base attribute, and save it with all its options.
       $attribute = uc_attribute_load($aid);
-      foreach ($attribute->options as $option) {
-        db_query("INSERT INTO $opt_table ($id, oid, cost, price, weight, ordering) VALUES ($sql_type, %d, %f, %f, %f, %d)", $form_state['values']['id'], $option->oid, $option->cost, $option->price, $option->weight, $option->ordering);
-      }
-      // Make the first option (if any) the default.
-      $option = reset($attribute->options);
-      if ($option) {
-        $oid = $option->oid;
-      }
-      else {
-        $oid = 0;
-      }
-      db_query("INSERT INTO $attr_table ($id, aid, label, ordering, default_option, required, display) SELECT $sql_type, aid, label, ordering, %d, required, display FROM {uc_attributes} WHERE aid = %d", $form_state['values']['id'], $oid, $aid);
+      uc_attribute_subject_save($attribute, $id, $type, TRUE);
     }
+    
     if (count($form_state['values']['add_attributes']) > 0) {
-      if ($form_state['values']['type'] == 'product') {
-        db_query("DELETE FROM {uc_product_adjustments} WHERE nid = %d", $form_state['values']['id']);
-      }
       drupal_set_message(format_plural(count($form_state['values']['add_attributes']), '@count attribute has been added.', '@count attributes have been added.'));
     }
   }
 
   if ($form_state['values']['type'] == 'product') {
-    $form_state['redirect'] = 'node/'. $form_state['values']['id'] .'/edit/attributes';
+    $form_state['redirect'] = 'node/'. $id .'/edit/attributes';
   }
   else {
-    $form_state['redirect'] = 'admin/store/products/classes/'. $form_state['values']['id'] .'/attributes';
+    $form_state['redirect'] = 'admin/store/products/classes/'. $id .'/attributes';
   }
 }
 
@@ -771,7 +755,7 @@ function uc_object_options_form($form_st
         );
       }
 
-      $form['attributes'][$aid]['default'] = array(
+      $form['attributes'][$aid]['default_option'] = array(
         '#type' => 'radios',
         '#options' => $options,
         '#default_value' => /* $attribute->required ? NULL : */ $attribute->default_option,
@@ -779,7 +763,7 @@ function uc_object_options_form($form_st
       );
     }
     else {
-      $form['attributes'][$aid]['default'] = array(
+      $form['attributes'][$aid]['default_option'] = array(
         '#value' => t('This attribute does not have any options.'),
       );
     }
@@ -820,12 +804,12 @@ function theme_uc_object_options_form($f
 
     $rows = array();
 
-    if (element_children($form['attributes'][$key]['default'])) {
+    if (element_children($form['attributes'][$key]['default_option'])) {
 
-      foreach (element_children($form['attributes'][$key]['default']) as $oid) {
+      foreach (element_children($form['attributes'][$key]['default_option']) as $oid) {
         $row = array(
           drupal_render($form['attributes'][$key]['options'][$oid]['select']),
-          drupal_render($form['attributes'][$key]['default'][$oid]),
+          drupal_render($form['attributes'][$key]['default_option'][$oid]),
           drupal_render($form['attributes'][$key]['options'][$oid]['cost']),
           drupal_render($form['attributes'][$key]['options'][$oid]['price']),
           drupal_render($form['attributes'][$key]['options'][$oid]['weight']),
@@ -842,7 +826,7 @@ function theme_uc_object_options_form($f
     }
     else {
       $row = array();
-      $row[] = array('data' => drupal_render($form['attributes'][$key]['default']), 'colspan' => 6);
+      $row[] = array('data' => drupal_render($form['attributes'][$key]['default_option']), 'colspan' => 6);
       $rows[] = $row;
     }
 
@@ -883,8 +867,8 @@ function uc_object_options_form_validate
           }
         }
       }
-      if (!empty($selected_opts) && !$form['attributes'][$aid]['default']['#disabled'] && !in_array($attribute['default'], $selected_opts)) {
-        form_set_error($attribute['default']);
+      if (!empty($selected_opts) && !$form['attributes'][$aid]['default_option']['#disabled'] && !in_array($attribute['default_option'], $selected_opts)) {
+        form_set_error($attribute['default_option']);
         $error = TRUE;
       }
     }
@@ -895,41 +879,36 @@ function uc_object_options_form_validate
   }
 }
 
+/**
+ * Submit handler for the product/class attribute options form.
+ */
 function uc_object_options_form_submit($form, &$form_state) {
-  if ($form_state['values']['type'] == 'product') {
-    $attr_table = '{uc_product_attributes}';
-    $opt_table = '{uc_product_options}';
-    $id = 'nid';
-    $sql_type = '%d';
-  }
-  elseif ($form_state['values']['type'] == 'class') {
-    $attr_table = '{uc_class_attributes}';
-    $opt_table = '{uc_class_attribute_options}';
-    $id = 'pcid';
-    $sql_type = "'%s'";
-  }
-
+  $id = $form_state['values']['id'];
+  $type = $form_state['values']['type'];
+  
   foreach ($form_state['values']['attributes'] as $attribute) {
-    db_query("UPDATE $attr_table SET default_option = %d WHERE $id = $sql_type AND aid = %d", $attribute['default'], $form_state['values']['id'], $attribute['aid']);
-
+    // Save the default option.
+    uc_attribute_subject_save($attribute, $id, $type);
+    
+    // Deal with options if they exist.
     if (is_array($attribute['options'])) {
       foreach ($attribute['options'] as $oid => $option) {
-        db_query("DELETE FROM $opt_table WHERE $id = $sql_type AND oid = %d", $form_state['values']['id'], $oid);
-
+        
+        // Checked?
         if ($option['select']) {
-          db_query("INSERT INTO $opt_table ($id, oid, cost, price, weight, ordering) VALUES ($sql_type, %d, %f, %f, %f, %d)",
-                   $form_state['values']['id'], $oid, $option['cost'], $option['price'], $option['weight'], $option['ordering']);
+          $option['oid'] = $oid;
+          uc_attribute_subject_option_save($option, $id, $type);
         }
-        elseif ($form_state['values']['type'] == 'product') {
-          $aid = $attribute['aid'];
-          $match = 'i:'. $aid .';s:'. strlen($oid) .':"'. $oid .'";';
-          db_query("DELETE FROM {uc_product_adjustments} WHERE nid = %d AND combination LIKE '%%%s%%'", $form_state['values']['id'], $match);
+        
+        // Unchecked means delete the option.
+        else {
+          uc_attribute_subject_option_delete($oid, $id, $type);
         }
       }
     }
   }
 
-  drupal_set_message(t('The !type options have been saved.', array('!type' => $form_state['values']['type'] == 'product' ? t('product') : t('product class'))));
+  drupal_set_message(t('The !type options have been saved.', array('!type' => $type == 'product' ? t('product') : t('product class'))));
 }
 
 /**

=== modified file 'uc_attribute/uc_attribute.module'
--- uc_attribute/uc_attribute.module	2009-07-02 18:22:43 +0000
+++ uc_attribute/uc_attribute.module	2009-07-06 21:48:20 +0000
@@ -300,6 +300,21 @@ function uc_attribute_nodeapi(&$node, $o
         }
         break;
       case 'insert':
+        // Handle translations.
+        if (isset($node->translation_source) && is_array($node->translation_source->attributes)) {
+          foreach ($node->translation_source->attributes as $attribute) {
+            // Copy the product attributes over to the new nid.
+            uc_attribute_subject_save($attribute, $node->nid, 'product', TRUE);
+          }
+          
+          // Also, the adjustments need to be copied.
+          $result = db_query("SELECT * FROM {uc_product_adjustments} WHERE nid = %d", $node->translation_source->nid);
+          while ($adjustment = db_fetch_array($result)) {
+            $adjustment['nid'] = $node->nid;
+            drupal_write_record('uc_product_adjustments', $adjustment);
+          }
+        }
+        
         switch ($GLOBALS['db_type']) {
           case 'mysqli':
           case 'mysql':
@@ -778,3 +793,210 @@ function _uc_attribute_get_name($attribu
     return (empty($attribute->label) || ($attribute->label == '<none>' && $title) ? $attribute->name : $attribute->label);
   }
 }
+
+/**
+ * Delete a product/class attribute.
+ * 
+ * @param $aid
+ *   The base attribute ID.
+ * @param $id
+ *   The product/class ID.
+ * @param $type
+ *   Is this a product or a class?
+ */
+function uc_attribute_subject_delete($aid, $id, $type) {
+  $sql = uc_attribute_type_info($type);
+  
+  // Delete all the options associated with this product/class attribute, and
+  // then the attribute itself.
+  $result = db_query("SELECT oid FROM {uc_attributes} WHERE aid = %d", $aid);
+  while ($oid = db_result($result)) {
+    // Don't delete the adjustments one at a time. We'll do it in bulk soon for
+    // efficiency.
+    uc_attribute_subject_option_delete($oid, $id, $type, FALSE);
+  }
+  db_query("DELETE FROM {$sql['attr_table']} WHERE {$sql['id']} = {$sql['placeholder']} AND aid = %d", $id, $aid);
+  
+  // If this is a product attribute, wipe any associated adjustments.
+  if ($type == 'product') {
+    db_query("DELETE FROM {uc_product_adjustments} WHERE nid = %d", $id);
+  }
+}
+
+/**
+ * Save a product/class attribute.
+ * 
+ * @param &$attribute
+ *   The product/class attribute.
+ * @param $id
+ *   The product/class ID.
+ * @param $type
+ *   Is this a product or a class?
+ * @param $save_options
+ *   Save the product/class attribute's options, too?
+ */
+function uc_attribute_subject_save(&$attribute, $id, $type, $save_options = FALSE) {
+  $sql = uc_attribute_type_info($type);
+  
+  // Convert to an array if needed.
+  if (is_object($attribute)) {
+    $attribute = (array) $attribute;
+    $object = TRUE;
+  }
+  else {
+    $object = FALSE;
+  }
+  
+  // Insert or update?
+  $key = uc_attribute_subject_exists($attribute['aid'], $id, $type) ? array('aid', $sql['id']) : NULL;
+  
+  // First, save the options. First because if this is an insert, we'll set
+  // a default option for the product/class attribute.
+  if ($save_options) {
+    foreach ($attribute['options'] as $option) {
+      uc_attribute_subject_option_save($option, $id, $type);
+    }
+    
+    // Is this an insert? If so, we'll set the default option.
+    if (empty($key)) {
+      $default_option = 0;
+      // Make the first option (if any) the default.
+      if (is_array($attribute['options'])) {
+        $option = (array) reset($attribute['options']);
+        $default_option = $option['oid'];
+      }
+      $attribute['default_option'] = $default_option;
+    }
+  }
+  
+  // Merge in the product/class attribute's ID and save.
+  $attribute = array($type == 'product' ? 'nid' : 'pcid' => $id) + $attribute;
+  drupal_write_record(trim($sql['attr_table'], '{}'), $attribute, $key);
+  
+  // Convert back if necessary, for the caller.
+  if ($object) {
+    $attribute = (object) $attribute;
+  }
+}
+
+/**
+ * Delete a product/class attribute option.
+ * 
+ * @param $oid
+ *   The base attribute's option ID.
+ * @param $id
+ *   The product/class ID.
+ * @param $type
+ *   Is this a product or a class?
+ */
+function uc_attribute_subject_option_delete($oid, $id, $type, $adjustments = TRUE) {
+  $sql = uc_attribute_type_info($type);
+  
+  // Delete the option.
+  db_query("DELETE FROM {$sql['opt_table']} WHERE {$sql['id']} = {$sql['placeholder']} AND oid = %d", $id, $oid);
+    
+  // If this is a product, clean up the associated adjustments.
+  if ($adjustments && $type == 'product') {
+    $aid = db_result(db_query("SELECT aid FROM {uc_attribute_options} WHERE oid = %d", $oid));
+    
+    $match = 'i:'. $aid .';s:'. strlen($oid) .':"'. $oid .'";';
+    db_query("DELETE FROM {uc_product_adjustments} WHERE nid = %d AND combination LIKE '%%%s%%'", $form_state['values']['id'], $match);
+  }
+}
+
+/**
+ * Save a product/class attribute option.
+ * 
+ * @param &$option
+ *   The product/class attribute option.
+ * @param $id
+ *   The product/class ID.
+ * @param $type
+ *   Is this a product or a class?
+ */
+function uc_attribute_subject_option_save(&$option, $id, $type) {
+  $sql = uc_attribute_type_info($type);
+
+  // Convert to an array if needed.
+  if (is_object($option)) {
+    $option = (array) $option;
+    $object = TRUE;
+  }
+  else {
+    $object = FALSE;
+  }
+  
+  // Insert or update?
+  $key = uc_attribute_subject_option_exists($option['oid'], $id, $type) ? array('oid', $sql['id']) : NULL;
+  
+  // Merge in the product/class attribute option's ID, and save.
+  $option = array($type == 'product' ? 'nid' : 'pcid' => $id) + $option;
+  drupal_write_record(trim($sql['opt_table'], '{}'), $option, $key);
+  
+  // Convert back if necessary, for the caller.
+  if ($object) {
+    $option = (object) $option;
+  }
+}
+
+/**
+ * Check if a product/class attribute exists.
+ * 
+ * @param $aid
+ *   The base attribute ID.
+ * @param $id
+ *   The product/class attribute's ID.
+ * @param $type
+ *   Is this a product or a class?
+ * @return (bool)
+ */
+function uc_attribute_subject_exists($aid, $id, $type) {
+  $sql = uc_attribute_type_info($type);
+  return FALSE !== db_result(db_query("SELECT aid FROM {$sql['attr_table']} WHERE aid = %d AND {$sql['id']} = {$sql['placeholder']}", $aid, $id));
+}
+
+/**
+ * Check if a product/class attribute option exists.
+ * 
+ * @param $oid
+ *   The base attribute option ID.
+ * @param $id
+ *   The product/class attribute option's ID.
+ * @param $type
+ *   Is this a product or a class?
+ * @return (bool)
+ */
+function uc_attribute_subject_option_exists($oid, $id, $type) {
+  $sql = uc_attribute_type_info($type);
+  return FALSE !== db_result(db_query("SELECT oid FROM {$sql['opt_table']} WHERE oid = %d AND {$sql['id']} = {$sql['placeholder']}", $oid, $id));
+}
+
+/**
+ * Return a list of db helpers to abstract the queries between products/classes.
+ * @param $type
+ *   Is this a product or a class?
+ * @return (array)
+ */
+function uc_attribute_type_info($type) {
+  switch ($type) {
+    case 'product':
+      return array(
+        'attr_table' => '{uc_product_attributes}',
+        'opt_table' => '{uc_product_options}',
+        'id' => 'nid',
+        'placeholder' => '%d',
+      );
+    break;
+    
+    case 'class':
+      return array(
+        'attr_table' => '{uc_class_attributes}',
+        'opt_table' => '{uc_class_attribute_options}',
+        'id' => 'pcid',
+        'placeholder' => "'%s'",
+      );
+    break;
+  }
+}
+
+

=== modified file 'uc_file/uc_file.module'
--- uc_file/uc_file.module	2009-06-17 15:27:58 +0000
+++ uc_file/uc_file.module	2009-07-06 21:48:20 +0000
@@ -45,6 +45,34 @@ function uc_file_form_alter(&$form, &$fo
 }
 
 /**
+ * Implementation of hook_nodeapi().
+ */
+function uc_file_nodeapi(&$node, $op, $arg3 = NULL, $arg4 = NULL) {
+  
+  switch ($op) {
+    case 'insert':
+      // Handle translation copying.
+      if (isset($node->translation_source)) {
+        // Copy over all file product features.
+        $result = db_query("SELECT *, upf.fid as feature_id, ufp.fid as file_id, upf.description as p_desc, ufp.description as f_desc FROM {uc_product_features} upf INNER JOIN {uc_file_products} ufp ON upf.pfid = ufp.pfid WHERE upf.nid = %d AND upf.fid = 'file'", $node->translation_source->nid);
+        while ($feature = db_fetch_array($result)) {
+          $feature['nid'] = $node->nid;
+          
+          $feature['fid'] = $feature['feature_id'];
+          $feature['description'] = $feature['p_desc'];
+          drupal_write_record('uc_product_features', $feature);
+          
+          $feature['fid'] = $feature['file_id'];
+          $feature['description'] = $feature['f_desc'];
+          drupal_write_record('uc_file_products', $feature);
+        }
+      }
+      
+    break;
+  }
+}
+
+/**
  * Implementation of hook_menu().
  */
 function uc_file_menu() {

=== modified file 'uc_product/uc_product.module'
--- uc_product/uc_product.module	2009-06-22 15:10:29 +0000
+++ uc_product/uc_product.module	2009-07-06 21:48:20 +0000
@@ -197,6 +197,39 @@ function uc_product_perm() {
 }
 
 /**
+ * Implementation of hook_nodeapi()
+ * 
+ * For now, we're just cloning some fields to make node translation work.
+ */
+function uc_product_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
+  if (!uc_product_is_product($node)) return;
+  
+  switch ($op) {
+    
+    // Node created by a new translation. Copy the product fields.
+    case 'prepare translation':
+      foreach (uc_product_node_fields() as $field) {
+        $node->$field = $node->translation_source->$field;
+      }
+    break;
+    
+  }
+}
+
+/**
+ * Returns an array of strings describing fields Ubercart adds to nodes.
+ * 
+ * @return array
+ */
+function uc_product_node_fields() {
+  return array(
+    "model", "list_price", "cost", "sell_price", "weight", "weight_units",
+    "length", "width", "height", "length_units", "pkg_qty", "default_qty",
+    "unique_hash", "ordering", "shippable",
+  );
+}
+
+/**
  * Implementation of hook_access().
  */
 function uc_product_access($op, $node, $account) {

=== modified file 'uc_roles/uc_roles.module'
--- uc_roles/uc_roles.module	2009-05-19 20:19:33 +0000
+++ uc_roles/uc_roles.module	2009-06-11 06:01:57 +0000
@@ -143,6 +143,21 @@ function uc_roles_theme() {
  */
 function uc_roles_nodeapi(&$node, $op, $arg3 = NULL, $arg4 = NULL) {
   switch ($op) {
+    case 'insert':
+      // Handle translation copying.
+      if (isset($node->translation_source)) {
+        // Copy over all roles product features.
+        $result = db_query("SELECT * FROM {uc_product_features} upf INNER JOIN {uc_roles_products} urp ON upf.pfid = urp.pfid WHERE upf.nid = %d AND upf.fid = 'role'", $node->translation_source->nid);
+        while ($feature = db_fetch_array($result)) {
+          $feature['nid'] = $node->nid;
+
+          drupal_write_record('uc_product_features', $feature);
+          drupal_write_record('uc_roles_products', $feature);
+        }
+      }
+      
+    break;
+      
     case 'delete':
       // Deleted node was a product; remove all role associations.
       if (uc_product_is_product($node->type)) {

=== modified file 'uc_stock/uc_stock.module'
--- uc_stock/uc_stock.module	2009-05-07 20:12:13 +0000
+++ uc_stock/uc_stock.module	2009-06-11 05:02:37 +0000
@@ -69,6 +69,26 @@ function uc_stock_menu() {
   return $items;
 }
 
+/**
+ * Implementation of hook_nodeapi().
+ */
+function uc_stock_nodeapi(&$node, $op, $arg3 = NULL, $arg4 = NULL) {
+  
+  switch ($op) {
+    case 'insert':
+      // Handle translation copying.
+      if (isset($node->translation_source)) {
+        // Copy over all stock.
+        $result = db_query("SELECT * FROM {uc_product_stock} WHERE nid = %d", $node->translation_source->nid);
+        while ($stock = db_fetch_array($result)) {
+          drupal_write_record('uc_product_stock', $stock);
+        }
+      }
+      
+    break;
+  }
+}
+
 function uc_stock_product_access($node) {
   if ($node->type == 'product_kit') {
     return FALSE;


