Index: modules/field/field.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.install,v
retrieving revision 1.4
diff -u -r1.4 field.install
--- modules/field/field.install	10 Mar 2009 09:45:31 -0000	1.4
+++ modules/field/field.install	13 Apr 2009 21:42:05 -0000
@@ -58,6 +58,12 @@
         'not null' => TRUE,
           'default' => 0,
       ),
+      'translatable' => array(
+        'type' => 'int',
+        'size' => 'tiny',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
       'active' => array(
         'type' => 'int',
         'size' => 'tiny',
Index: modules/field/field.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.module,v
retrieving revision 1.7
diff -u -r1.7 field.module
--- modules/field/field.module	26 Mar 2009 13:31:24 -0000	1.7
+++ modules/field/field.module	13 Apr 2009 21:42:06 -0000
@@ -57,6 +57,11 @@
 /**
  * TODO
  */
+define('FIELD_LANGUAGE_NEUTRAL', '0');
+
+/**
+ * TODO
+ */
 define('FIELD_BEHAVIOR_NONE', 0x0001);
 /**
  * TODO
Index: modules/field/field.attach.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.attach.inc,v
retrieving revision 1.10
diff -u -r1.10 field.attach.inc
--- modules/field/field.attach.inc	13 Apr 2009 05:18:17 -0000	1.10
+++ modules/field/field.attach.inc	13 Apr 2009 21:42:04 -0000
@@ -171,24 +171,39 @@
   foreach ($instances as $instance) {
     $field_name = $instance['field_name'];
     $field = field_info_field($field_name);
-    $items = isset($object->$field_name) ? $object->$field_name : array();
-
+    
+    if (empty($object->$field_name)) {
+      // Initialize field translations according to the available languages.
+      foreach(_field_available_languages($field, $instance) as $language) {
+        $field_translations[$language] = array();
+      }
+    }
+    else {
+      $field_translations = $object->$field_name;
+    }
+    
     $function = $default ? 'field_default_' . $op : $field['module'] . '_field_' . $op;
     if (drupal_function_exists($function)) {
-      $result = $function($obj_type, $object, $field, $instance, $items, $a, $b);
-      if (is_array($result)) {
-        $return = array_merge($return, $result);
-      }
-      else if (isset($result)) {
-        $return[] = $result;
+      // Iterate over all the field translations.
+      foreach ($field_translations as $language => $items) {
+        // TODO: Should we introduce a $language parameter?
+        $instance['language'] = $language;
+        $result = $function($obj_type, $object, $field, $instance, $items, $a, $b);
+        // Store the result into per-language separate fields.
+        if (is_array($result)) {
+          $return[$language] = isset($return[$language]) ? array_merge($return[$language], $result) : $result;
+        }
+        else if (isset($result)) {
+          $return[$language][] = $result;
+        }
+        // Put back the altered items in the object, if the field was present to
+        // begin with (avoid replacing missing field with empty array(), those are
+        // not semantically equivalent on update).
+        if (isset($object->{$field_name}[$language])) {
+          $object->{$field_name}[$language] = $items;
+        }
       }
     }
-    // Put back the altered items in the object, if the field was present to
-    // begin with (avoid replacing missing field with empty array(), those are
-    // not semantically equivalent on update).
-    if (isset($object->$field_name)) {
-      $object->$field_name = $items;
-    }
   }
 
   return $return;
@@ -202,6 +217,33 @@
 }
 
 /**
+ * TODO
+ */
+function _field_available_languages($field, $instance) {
+  static $field_languages;
+  
+  $field_name = $instance['field_name'];
+  if (!isset($field_languages[$field_name])) {
+    if ($field['translatable']) {
+      $available_languages = array_keys(language_list());
+      $languages = isset($instance['languages']) ? $instance['languages'] : $available_languages;
+      // TODO: Do we need this hook invocation?
+      foreach (module_implements('field_languages') as $module) {
+        $function = $module . '_field_languages';
+        $function($field, $instance, &$languages);
+      }
+      // Accept only available languages.
+      $field_languages[$field_name] = array_intersect($available_languages, $languages);
+    }
+    else {
+      $field_languages[$field_name] = array(FIELD_LANGUAGE_NEUTRAL);
+    }
+  }
+  
+  return $field_languages[$field_name];
+}
+
+/**
  * @} End of "defgroup field_attach"
  *
  * The rest of the functions in this file are not in a group, but
@@ -226,8 +268,24 @@
  */
 function _field_attach_form($obj_type, $object, &$form, $form_state) {
   // TODO : something's not right here : do we alter the form or return a value ?
-  $form += (array) _field_invoke_default('form', $obj_type, $object, $form, $form_state);
+  $result = _field_invoke_default('form', $obj_type, $object, $form, $form_state);
 
+  // TODO: is this the right way?
+  $languages = language_list();
+  $form['field_translations'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Field translations'),
+    '#tree' => TRUE
+  );
+  foreach ($result as $language => $field_form) {
+    $form['field_translations'][$language] = array(
+      '#type' => 'fieldset',
+      '#title' => empty($language) ? t('Language neutral') : $languages[$language]->native,
+      '#tree' => TRUE,
+    );
+    $form['field_translations'][$language] += (array) $field_form;
+  } 
+  
   // Let other modules make changes to the form.
   foreach (module_implements('field_attach_form') as $module) {
     $function = $module . '_field_attach_form';
@@ -296,7 +354,7 @@
     // Invoke the storage engine's hook_field_storage_load(): the field storage
     // engine loads the rest.
     $additions = module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_load', $obj_type, $queried_objects, $age, $skip_fields);
-
+    
     // First, merge the additions from the storage engine.
     foreach ($additions as $id => $obj_additions) {
       foreach ($obj_additions as $key => $value) {
@@ -310,7 +368,7 @@
         $additions[$id][$key] = $value;
       }
     }
-
+    
     // TODO D7 : to be consistent we might want to make hook_field_load() accept
     // multiple objects too. Which forbids going through _field_invoke(), but
     // requires manually iterating the instances instead.
@@ -636,7 +694,6 @@
   }
 
   return $output;
-
 }
 
 /**
Index: modules/field/field.default.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.default.inc,v
retrieving revision 1.5
diff -u -r1.5 field.default.inc
--- modules/field/field.default.inc	26 Mar 2009 13:31:24 -0000	1.5
+++ modules/field/field.default.inc	13 Apr 2009 21:42:05 -0000
@@ -12,21 +12,22 @@
 
 function field_default_extract_form_values($obj_type, $object, $field, $instance, &$items, $form, &$form_state) {
   $field_name = $field['field_name'];
-
-  if (isset($form_state['values'][$field_name])) {
-    $items = $form_state['values'][$field_name];
+  $language = $instance['language'];
+  
+  if (isset($form_state['values']['field_translations'][$language][$field_name])) {
+    $items = $form_state['values']['field_translations'][$language][$field_name];
 
     // Remove the 'value' of the 'add more' button.
     unset($items[$field_name . '_add_more']);
 
     // _field_invoke() does not add back items for fields not present in the
     // original $object, so we add them manually.
-    $object->{$field_name} = $items;
+    $object->{$field_name}[$language] = $items;
   }
   else {
     // The form did not include this field, for instance because of access
     // rules: make sure any existing value for the field stays unchanged.
-    unset($object->{$field_name});
+    unset($object->{$field_name}[$language]);
   }
 }
 
Index: modules/field/field.crud.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.crud.inc,v
retrieving revision 1.9
diff -u -r1.9 field.crud.inc
--- modules/field/field.crud.inc	30 Mar 2009 05:24:38 -0000	1.9
+++ modules/field/field.crud.inc	13 Apr 2009 21:42:05 -0000
@@ -46,6 +46,8 @@
  * - cardinality (integer)
  *     The number of values the field can hold. Legal values are any
  *     positive integer or FIELD_CARDINALITY_UNLIMITED.
+ * - translatable (integer)
+ *     Whether the field is translatable
  * - locked (integer)
  *     TODO: undefined.
  * - module (string, read-only)
@@ -204,6 +206,7 @@
 
   $field += array(
     'cardinality' => 1,
+    'translatable' => 0,
     'locked' => FALSE,
     'settings' => array(),
   );
Index: modules/field/modules/field_sql_storage/field_sql_storage.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/field_sql_storage/field_sql_storage.module,v
retrieving revision 1.6
diff -u -r1.6 field_sql_storage.module
--- modules/field/modules/field_sql_storage/field_sql_storage.module	30 Mar 2009 03:44:55 -0000	1.6
+++ modules/field/modules/field_sql_storage/field_sql_storage.module	13 Apr 2009 21:42:07 -0000
@@ -128,9 +128,17 @@
         'not null' => TRUE,
         'description' => 'The sequence number for this data item, used for multi-value fields',
       ),
+      // @todo Consider an integer field for 'language'.
+      'language' => array(
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'The language for this data item.',
+      ),
     ),
-    'primary key' => array('etid', 'entity_id', 'deleted', 'delta'),
-    // TODO : index on 'bundle'
+    'primary key' => array('etid', 'entity_id', 'deleted', 'delta', 'language'),
+    // @todo Index on 'bundle'.
   );
 
   // Add field columns.
@@ -144,7 +152,7 @@
   $revision = $current;
   $revision['description'] = 'Revision archive storage for field ' . $field['field_name'];
   $revision['revision_id']['description'] = 'The entity revision id this data is attached to';
-  $revision['primary key'] = array('etid', 'revision_id', 'deleted', 'delta');
+  $revision['primary key'] = array('etid', 'revision_id', 'deleted', 'delta', 'language');
 
   return array(
     _field_sql_storage_tablename($field['field_name']) => $current,
@@ -179,7 +187,7 @@
 function field_sql_storage_field_storage_load($obj_type, $objects, $age, $skip_fields = array()) {
   $etid = _field_sql_storage_etid($obj_type);
   $load_current = $age == FIELD_LOAD_CURRENT;
-
+  
   // Gather ids needed for each field.
   $field_ids = array();
   $delta_count = array();
@@ -199,7 +207,7 @@
 
     $field = field_info_field($field_name);
     $table = $load_current ? _field_sql_storage_tablename($field_name) : _field_sql_storage_revision_tablename($field_name);
-
+    
     $results = db_select($table, 't')
       ->fields('t')
       ->condition('etid', $etid)
@@ -209,16 +217,16 @@
       ->execute();
 
     foreach ($results as $row) {
-      if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$field_name] < $field['cardinality']) {
+      if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || !empty($field['translatable']) || $delta_count[$row->entity_id][$field_name] < $field['cardinality']) {
         $item = array();
         // For each column declared by the field, populate the item
         // from the prefixed database column.
         foreach ($field['columns'] as $column => $attributes) {
           $item[$column] = $row->{_field_sql_storage_columnname($field_name, $column)};
         }
-
-        // Add the item to the field values for the entity.
-        $additions[$row->entity_id][$field_name][] = $item;
+        
+        // Add the item to the field values for the entity according to its language.
+        $additions[$row->entity_id][$field_name][$row->language][$row->delta] = $item;
         $delta_count[$row->entity_id][$field_name]++;
       }
     }
@@ -230,19 +238,21 @@
  * Implementation of hook_field_storage_write().
  */
 function field_sql_storage_field_storage_write($obj_type, $object, $op, $skip_fields) {
-  list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
-  $etid = _field_sql_storage_etid($obj_type);
+  $info = new stdClass();
+  list($info->id, $info->vid, $info->bundle) = field_attach_extract_ids($obj_type, $object);
+  $info->etid = _field_sql_storage_etid($obj_type);
+  //$fieldable_info = field_info_fieldable_types($obj_type);
 
-  $instances = field_info_instances($bundle);
+  $instances = field_info_instances($info->bundle);
   foreach ($instances as $instance) {
     $field_name = $instance['field_name'];
     if (isset($skip_fields[$field_name])) {
       continue;
     }
-
-    $table_name = _field_sql_storage_tablename($field_name);
-    $revision_name = _field_sql_storage_revision_tablename($field_name);
-    $field = field_read_field($field_name);
+    
+    $info->table_name = _field_sql_storage_tablename($field_name);
+    $info->revision_name = _field_sql_storage_revision_tablename($field_name);
+    $info->field = field_read_field($field_name);
 
     // Leave the field untouched if $object comes with no $field_name property.
     // Empty the field if $object->$field_name is NULL or an empty array.
@@ -252,52 +262,62 @@
     if (isset($object->$field_name) || property_exists($object, $field_name)) {
       // Delete and insert, rather than update, in case a value was added.
       if ($op == FIELD_STORAGE_UPDATE) {
-        db_delete($table_name)->condition('etid', $etid)->condition('entity_id', $id)->execute();
-        if (isset($vid)) {
-          db_delete($revision_name)->condition('etid', $etid)->condition('entity_id', $id)->condition('revision_id', $vid)->execute();
+        db_delete($info->table_name)->condition('etid', $info->etid)->condition('entity_id', $info->id)->execute();
+        if (isset($info->vid)) {
+          db_delete($info->revision_name)->condition('etid', $info->etid)->condition('entity_id', $info->id)->condition('revision_id', $info->vid)->execute();
         }
       }
+      // Store the items.
+      foreach ($object->$field_name as $language => $items) {
+        _field_sql_storage_field_storage_write_items($info, $items, $language);
+      }
+    }
+  }
+}
 
-      if ($object->$field_name) {
-        // Prepare the multi-insert query.
-        $columns = array('etid', 'entity_id', 'revision_id', 'bundle', 'delta');
-        foreach ($field['columns'] as $column => $attributes) {
-          $columns[] = _field_sql_storage_columnname($field_name, $column);
-        }
-        $query = db_insert($table_name)->fields($columns);
-        if (isset($vid)) {
-          $revision_query = db_insert($revision_name)->fields($columns);
-        }
+/**
+ * Actually store the given field values with respect to the given language.
+ */
+function _field_sql_storage_field_storage_write_items($info, $items, $language = FIELD_LANGUAGE_NEUTRAL) {
+  if ($items) {
+    // Prepare the multi-insert query.
+    $columns = array('etid', 'entity_id', 'revision_id', 'bundle', 'delta', 'language');
+    foreach ($info->field['columns'] as $column => $attributes) {
+      $columns[] = _field_sql_storage_columnname($info->field['field_name'], $column);
+    }
+    $query = db_insert($info->table_name)->fields($columns);
+    if (isset($info->vid)) {
+      $revision_query = db_insert($info->revision_name)->fields($columns);
+    }
 
-        $delta_count = 0;
-        foreach ($object->$field_name as $delta => $item) {
-          $record = array(
-            'etid' => $etid,
-            'entity_id' => $id,
-            'revision_id' => $vid,
-            'bundle' => $bundle,
-            'delta' => $delta,
-          );
-          foreach ($field['columns'] as $column => $attributes) {
-            $record[_field_sql_storage_columnname($field_name, $column)] = isset($item[$column]) ? $item[$column] : NULL;
-          }
-          $query->values($record);
-          if (isset($vid)) {
-            $revision_query->values($record);
-          }
-
-          if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && ++$delta_count == $field['cardinality']) {
-            break;
-          }
-        }
+    $delta_count = 0;
+    foreach ($items as $delta => $item) {
+      $record = array(
+        'etid' => $info->etid,
+        'entity_id' => $info->id,
+        'revision_id' => $info->vid,
+        'bundle' => $info->bundle,
+        'delta' => $delta,
+        'language' => !empty($language) ? $language : FIELD_LANGUAGE_NEUTRAL,
+      );
+      foreach ($info->field['columns'] as $column => $attributes) {
+        $record[_field_sql_storage_columnname($info->field['field_name'], $column)] = isset($item[$column]) ? $item[$column] : NULL;
+      }
+      $query->values($record);
+      if (isset($info->vid)) {
+        $revision_query->values($record);
+      }
 
-        // Execute the insert.
-        $query->execute();
-        if (isset($vid)) {
-          $revision_query->execute();
-        }
+      if ($info->field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && empty($info->field['translatable']) && ++$delta_count == $info->field['cardinality']) {
+        break;
       }
     }
+
+    // Execute the insert.
+    $query->execute();
+    if (isset($info->vid)) {
+      $revision_query->execute();
+    }
   }
 }
 
@@ -384,4 +404,4 @@
       ->condition('bundle', $bundle_old)
       ->execute();
   }
-}
\ No newline at end of file
+}
