Index: modules/aggregator/aggregator.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.test,v
retrieving revision 1.26
diff -u -p -r1.26 aggregator.test
--- modules/aggregator/aggregator.test	12 Jun 2009 08:39:35 -0000	1.26
+++ modules/aggregator/aggregator.test	27 Jun 2009 07:45:26 -0000
@@ -252,7 +252,7 @@ EOF;
     for ($i = 0; $i < 5; $i++) {
       $edit = array();
       $edit['title'] = $this->randomName();
-      $edit['body[0][value]'] = $this->randomName();
+      $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $this->randomName();
       $this->drupalPost('node/add/article', $edit, t('Save'));
     }
   }
Index: modules/blog/blog.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/blog/blog.test,v
retrieving revision 1.13
diff -u -p -r1.13 blog.test
--- modules/blog/blog.test	12 Jun 2009 08:39:35 -0000	1.13
+++ modules/blog/blog.test	27 Jun 2009 07:45:26 -0000
@@ -154,7 +154,7 @@ class BlogTestCase extends DrupalWebTest
       // Edit blog node.
       $edit = array();
       $edit['title'] = 'node/' . $node->nid;
-      $edit['body[0][value]'] = $this->randomName(256);
+      $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $this->randomName(256);
       $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
       $this->assertRaw(t('Blog entry %title has been updated.', array('%title' => $edit['title'])), t('Blog node was edited'));
 
Index: modules/blogapi/blogapi.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/blogapi/blogapi.module,v
retrieving revision 1.155
diff -u -p -r1.155 blogapi.module
--- modules/blogapi/blogapi.module	12 Jun 2009 08:39:35 -0000	1.155
+++ modules/blogapi/blogapi.module	27 Jun 2009 07:45:26 -0000
@@ -213,7 +213,7 @@ function blogapi_blogger_new_post($appke
   }
   else {
     $edit['title'] = blogapi_blogger_title($content);
-    $edit['body'][0]['value'] = $content;
+    $edit['body'][FIELD_LANGUAGE_NEUTRAL][0]['value'] = $content;
   }
 
   if (!node_access('create', $edit['type'])) {
@@ -274,12 +274,12 @@ function blogapi_blogger_edit_post($appk
   // Check for bloggerAPI vs. metaWeblogAPI.
   if (is_array($content)) {
     $node->title = $content['title'];
-    $node->body[0]['value'] = $content['description'];
+    $node->body[FIELD_LANGUAGE_NEUTRAL][0]['value'] = $content['description'];
     _blogapi_mt_extra($node, $content);
   }
   else {
     $node->title = blogapi_blogger_title($content);
-    $node->body[0]['value'] = $content;
+    $node->body[FIELD_LANGUAGE_NEUTRAL][0]['value'] = $content;
   }
 
   module_invoke_all('node_blogapi_edit', $node);
@@ -892,14 +892,14 @@ function _blogapi_mt_extra($node, $struc
 
   // Merge the 3 body sections (description, mt_excerpt, mt_text_more) into one body.
   if ($struct['mt_excerpt']) {
-    $node->body[0]['value'] = $struct['mt_excerpt'] . '<!--break-->' . $node->body[0]['value'];
+    $node->body[FIELD_LANGUAGE_NEUTRAL][0]['value'] = $struct['mt_excerpt'] . '<!--break-->' . $node->body[FIELD_LANGUAGE_NEUTRAL][0]['value'];
   }
   if ($struct['mt_text_more']) {
-    $node->body[0]['value'] = $node->body[0]['value'] . '<!--extended-->' . $struct['mt_text_more'];
+    $node->body[FIELD_LANGUAGE_NEUTRAL][0]['value'] = $node->body[FIELD_LANGUAGE_NEUTRAL][0]['value'] . '<!--extended-->' . $struct['mt_text_more'];
   }
 
   if ($struct['mt_convert_breaks']) {
-    $node->body[0]['format'] = $struct['mt_convert_breaks'];
+    $node->body[FIELD_LANGUAGE_NEUTRAL][0]['format'] = $struct['mt_convert_breaks'];
   }
 
   if ($struct['dateCreated']) {
@@ -922,8 +922,8 @@ function _blogapi_get_post($node, $bodie
   );
 
   if ($bodies) {
-    $body = $node->body[0]['value'];
-    $format = $node->body[0]['format'];
+    $body = $node->body[FIELD_LANGUAGE_NEUTRAL][0]['value'];
+    $format = $node->body[FIELD_LANGUAGE_NEUTRAL][0]['format'];
     if ($node->comment == 1) {
       $comment = 2;
     }
Index: modules/book/book.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/book/book.test,v
retrieving revision 1.11
diff -u -p -r1.11 book.test
--- modules/book/book.test	12 Jun 2009 08:39:36 -0000	1.11
+++ modules/book/book.test	27 Jun 2009 07:45:26 -0000
@@ -141,7 +141,7 @@ class BookTestCase extends DrupalWebTest
     // Check printer friendly version.
     $this->drupalGet('book/export/html/' . $node->nid);
     $this->assertText($node->title, t('Printer friendly title found.'));
-    $this->assertRaw(check_markup($node->body[0]['value'], $node->body[0]['format']), t('Printer friendly body found.'));
+    $this->assertRaw(check_markup($node->body[FIELD_LANGUAGE_NEUTRAL][0]['value'], $node->body[FIELD_LANGUAGE_NEUTRAL][0]['format']), t('Printer friendly body found.'));
 
     $number++;
   }
@@ -173,7 +173,7 @@ class BookTestCase extends DrupalWebTest
 
     $edit = array();
     $edit['title'] = $number . ' - SimpleTest test node ' . $this->randomName(10);
-    $edit['body[0][value]'] = 'SimpleTest test body ' . $this->randomName(32) . ' ' . $this->randomName(32);
+    $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = 'SimpleTest test body ' . $this->randomName(32) . ' ' . $this->randomName(32);
     $edit['book[bid]'] = $book_nid;
 
     if ($parent !== NULL) {
Index: modules/dblog/dblog.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/dblog/dblog.test,v
retrieving revision 1.21
diff -u -p -r1.21 dblog.test
--- modules/dblog/dblog.test	12 Jun 2009 08:39:36 -0000	1.21
+++ modules/dblog/dblog.test	27 Jun 2009 07:45:26 -0000
@@ -327,7 +327,7 @@ class DBLogTestCase extends DrupalWebTes
       default:
         $content = array(
           'title' => $this->randomName(8),
-          'body[0][value]' => $this->randomName(32),
+          'body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]' => $this->randomName(32),
         );
       break;
     }
@@ -351,7 +351,7 @@ class DBLogTestCase extends DrupalWebTes
 
       default:
         $content = array(
-          'body[0][value]' => $this->randomName(32),
+          'body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]' => $this->randomName(32),
         );
       break;
     }
Index: modules/field/field.attach.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.attach.inc,v
retrieving revision 1.26
diff -u -p -r1.26 field.attach.inc
--- modules/field/field.attach.inc	25 Jun 2009 06:23:37 -0000	1.26
+++ modules/field/field.attach.inc	27 Jun 2009 07:45:26 -0000
@@ -172,6 +172,7 @@ function _field_invoke($op, $obj_type, $
   // Merge default options.
   $default_options = array(
     'default' => FALSE,
+    'languages' => NULL
   );
   $options += $default_options;
 
@@ -184,32 +185,37 @@ function _field_invoke($op, $obj_type, $
     // When in 'single field' mode, only act on the specified field.
     if (empty($options['field_name']) || $options['field_name'] == $field_name) {
       $field = field_info_field($field_name);
+      $field_translations = array();
 
-      // Extract the field values into a separate variable, easily accessed by
-      // hook implementations.
-      $items = isset($object->$field_name) ? $object->$field_name : array();
+      // Initialize field translations according to the available languages.
+      foreach (_field_available_languages($field, $instance, $options['languages']) as $language) {
+        $field_translations[$language] = isset($object->{$field_name}[$language]) ? $object->{$field_name}[$language] : array();
+      }
 
       // Invoke the field hook and collect results.
       $function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op;
       if (drupal_function_exists($function)) {
-        $result = $function($obj_type, $object, $field, $instance, $items, $a, $b);
-        if (isset($result)) {
-          // For hooks with array results, we merge results together.
-          // For hooks with scalar results, we collect results in an array.
-          if (is_array($result)) {
-            $return = array_merge($return, $result);
+        // Iterate over all the field translations.
+        foreach ($field_translations as $language => $items) {
+          $result = $function($obj_type, $object, $field, $instance, $language, $items, $a, $b);
+          if (isset($result)) {
+            // For hooks with array results, we merge results together.
+            // For hooks with scalar results, we collect results in an array.
+            if (is_array($result)) {
+              $return = array_merge($return, $result);
+            }
+            else {
+              $return[] = $result;
+            }
           }
-          else {
-            $return[] = $result;
+
+          // Populate $items back in the field values, but avoid replacing missing
+          // fields with an empty array (those are not equivalent on update).
+          if ($items !== array() || isset($object->{$field_name}[$language])) {
+            $object->{$field_name}[$language] = $items;
           }
         }
       }
-
-      // Populate field values back in the object, but avoid replacing missing
-      // fields with an empty array (those are not equivalent on update).
-      if ($items !== array() || property_exists($object, $field_name)) {
-        $object->$field_name = $items;
-      }
     }
   }
 
@@ -254,6 +260,7 @@ function _field_invoke_multiple($op, $ob
   // Merge default options.
   $default_options = array(
     'default' => FALSE,
+    'languages' => NULL
   );
   $options += $default_options;
 
@@ -277,10 +284,10 @@ function _field_invoke_multiple($op, $ob
         }
         // Group the corresponding instances and objects.
         $grouped_instances[$field_name][$id] = $instance;
-        $grouped_objects[$field_name][$id] = $objects[$id];
-        // Extract the field values into a separate variable, easily accessed
-        // by hook implementations.
-        $grouped_items[$field_name][$id] = isset($object->$field_name) ? $object->$field_name : array();
+        $grouped_objects[$field_name][$id] = &$objects[$id];
+        foreach (_field_available_languages($fields[$field_name], $instance, $options['languages']) as $language) {
+          $grouped_items[$field_name][$language][$id] = isset($object->{$field_name}[$language]) ? $object->{$field_name}[$language] : array();
+        }
       }
     }
     // Initialize the return value for each object.
@@ -291,17 +298,20 @@ function _field_invoke_multiple($op, $ob
   foreach ($fields as $field_name => $field) {
     $function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op;
     if (drupal_function_exists($function)) {
-      $results = $function($obj_type, $grouped_objects[$field_name], $field, $grouped_instances[$field_name], $grouped_items[$field_name], $a, $b);
-      if (isset($results)) {
-        // Collect results by object.
-        // For hooks with array results, we merge results together.
-        // For hooks with scalar results, we collect results in an array.
-        foreach ($results as $id => $result) {
-          if (is_array($result)) {
-            $return[$id] = array_merge($return[$id], $result);
-          }
-          else {
-            $return[$id][] = $result;
+      // Iterate over all the field translations.
+      foreach ($grouped_items[$field_name] as $language => $items) {
+        $results = $function($obj_type, $grouped_objects[$field_name], $field, $grouped_instances[$field_name], $language, $grouped_items[$field_name][$language], $a, $b);
+        if (isset($results)) {
+          // Collect results by object.
+          foreach ($results as $id => $result) {
+            // For hooks with array results, we merge results together.
+            // For hooks with scalar results, we collect results in an array.
+            if (is_array($result)) {
+              $return[$id] = array_merge($return[$id], $result);
+            }
+            else {
+              $return[$id][] = $result;
+            }
           }
         }
       }
@@ -310,8 +320,10 @@ function _field_invoke_multiple($op, $ob
     // Populate field values back in the objects, but avoid replacing missing
     // fields with an empty array (those are not equivalent on update).
     foreach ($grouped_objects[$field_name] as $id => $object) {
-      if ($grouped_items[$field_name][$id] !== array() || property_exists($object, $field_name)) {
-        $object->$field_name = $grouped_items[$field_name][$id];
+      foreach ($grouped_items[$field_name] as $language => $items) {
+        if ($grouped_items[$field_name][$language][$id] !== array() || isset($object->{$field_name}[$language])) {
+          $object->{$field_name}[$language] = $grouped_items[$field_name][$language][$id];
+        }
       }
     }
   }
@@ -348,6 +360,53 @@ function _field_invoke_multiple_default(
 }
 
 /**
+ * TODO
+ */
+function _field_available_languages($field, $instance, $suggested_languages = NULL) {
+  static $field_languages;
+
+  $field_name = $field['field_name'];
+  if (!isset($field_languages[$field_name])) {
+    if ($field['translatable']) {
+      $available_languages = array_keys(language_list());
+      // The returned languages are a subset of the intersection of enabled ones and suggested ones.
+      $languages = is_array($suggested_languages) ? $available_languages = array_intersect($available_languages, $suggested_languages) : $available_languages;
+      // TODO: $instance['languages'] might be populated somehow to let users/modules
+      // decide which languages are available for the current instance.
+      // - Should this exist or the following hook invocation should be enough?
+      // - Should this be an instance or a field setting?
+      // $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);
+      }
+
+      // TODO: Consider distinguishing between content language and UI language:
+      // probably they should not be tied in any way.
+      // Accept only available languages.
+      $field_languages[$field_name] = array_values(array_intersect($available_languages, $languages));
+    }
+    else {
+      $field_languages[$field_name] = array(FIELD_LANGUAGE_NEUTRAL);
+    }
+  }
+
+  return $field_languages[$field_name];
+}
+
+/**
+ * TODO
+ * @param $requested_language
+ */
+function _field_valid_language($requested_language) {
+  // TODO: This should use a content language default instead of an UI one.
+  global $language;
+  return empty($requested_language) ? $language->language : $requested_language;
+}
+
+
+/**
  * Add form elements for all fields for an object to a form structure.
  *
  * @param $obj_type
@@ -359,13 +418,19 @@ function _field_invoke_multiple_default(
  *   The form structure to fill in.
  * @param $form_state
  *   An associative array containing the current state of the form.
+ * @param $language
+ *   TODO: The are two ways to enter field values: predefined language and
+ *   user provided language [...]
  *
  * TODO : document the resulting $form structure, like we do for
  * field_attach_view().
  */
-function field_attach_form($obj_type, $object, &$form, &$form_state) {
+function field_attach_form($obj_type, $object, &$form, &$form_state, $language = NULL) {
+  // Ensure that a valid language is used.
+  $options = array('languages' => array(_field_valid_language($language)));
+
   // 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);
+  $form += (array) _field_invoke_default('form', $obj_type, $object, $form, $form_state, $options);
 
   // Let other modules make changes to the form.
   foreach (module_implements('field_attach_form') as $module) {
@@ -887,17 +952,19 @@ function field_attach_query_revisions($f
  * @return
  *   A structured content array tree for drupal_render().
  */
-function field_attach_view($obj_type, $object, $build_mode = 'full') {
+function field_attach_view($obj_type, $object, $build_mode = 'full', $language = NULL) {
+  // Ensure that a valid language is used.
+  $options = array('languages' => array(_field_valid_language($language)));
+
   // Let field modules sanitize their data for output.
-  _field_invoke('sanitize', $obj_type, $object);
+  _field_invoke('sanitize', $obj_type, $object, $a, $b, $options);
 
-  $output = _field_invoke_default('view', $obj_type, $object, $build_mode);
+  $output = _field_invoke_default('view', $obj_type, $object, $build_mode, $b, $options);
 
   // Let other modules make changes after rendering the view.
   drupal_alter('field_attach_view', $output, $obj_type, $object, $build_mode);
 
   return $output;
-
 }
 
 /**
Index: modules/field/field.crud.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.crud.inc,v
retrieving revision 1.16
diff -u -p -r1.16 field.crud.inc
--- modules/field/field.crud.inc	24 Jun 2009 18:16:38 -0000	1.16
+++ modules/field/field.crud.inc	27 Jun 2009 07:45:26 -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)
@@ -232,6 +234,7 @@ function field_create_field($field) {
 
   $field += array(
     'cardinality' => 1,
+    'translatable' => FALSE,
     'locked' => FALSE,
     'settings' => array(),
   );
Index: modules/field/field.default.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.default.inc,v
retrieving revision 1.9
diff -u -p -r1.9 field.default.inc
--- modules/field/field.default.inc	24 Jun 2009 18:16:38 -0000	1.9
+++ modules/field/field.default.inc	27 Jun 2009 07:45:26 -0000
@@ -11,21 +11,21 @@
  * the corresponding field_attach_[operation]() function.
  */
 
-function field_default_extract_form_values($obj_type, $object, $field, $instance, &$items, $form, &$form_state) {
+function field_default_extract_form_values($obj_type, $object, $field, $instance, $language, &$items, $form, &$form_state) {
   $field_name = $field['field_name'];
 
-  if (isset($form_state['values'][$field_name])) {
-    $items = $form_state['values'][$field_name];
+  if (isset($form_state['values'][$field_name][$language])) {
+    $items = $form_state['values'][$field_name][$language];
     // Remove the 'value' of the 'add more' button.
     unset($items[$field_name . '_add_more']);
   }
 }
 
-function field_default_validate($obj_type, $object, $field, $instance, $items) {
+function field_default_validate($obj_type, $object, $field, $instance, $language, $items) {
   // TODO: here we could validate that required fields are filled in (for programmatic save)
 }
 
-function field_default_submit($obj_type, $object, $field, $instance, &$items, $form, &$form_state) {
+function field_default_submit($obj_type, $object, $field, $instance, $language, &$items, $form, &$form_state) {
   $field_name = $field['field_name'];
 
   // TODO: should me move what's below to __extract_form_values ?
@@ -46,7 +46,7 @@ function field_default_submit($obj_type,
  * This can happen with programmatic saves, or on form-based creation where
  * the current user doesn't have 'edit' permission for the field.
  */
-function field_default_insert($obj_type, $object, $field, $instance, &$items) {
+function field_default_insert($obj_type, $object, $field, $instance, $language, &$items) {
   // _field_invoke() populates $items with an empty array if the $object has no
   // entry for the field, so we check on the $object itself.
   if (!property_exists($object, $field['field_name']) && !empty($instance['default_value_function'])) {
@@ -112,7 +112,7 @@ function field_default_insert($obj_type,
  *     ),
  *   );
  */
-function field_default_view($obj_type, $object, $field, $instance, $items, $build_mode) {
+function field_default_view($obj_type, $object, $field, $instance, $language, $items, $build_mode) {
   list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
 
   $addition = array();
@@ -181,7 +181,7 @@ function field_default_view($obj_type, $
   return $addition;
 }
 
-function field_default_prepare_translation($obj_type, $object, $field, $instance, &$items) {
+function field_default_prepare_translation($obj_type, $object, $field, $instance, $language, &$items) {
   $addition = array();
   if (isset($object->translation_source->$field['field_name'])) {
     $addition[$field['field_name']] = $object->translation_source->$field['field_name'];
Index: modules/field/field.form.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.form.inc,v
retrieving revision 1.10
diff -u -p -r1.10 field.form.inc
--- modules/field/field.form.inc	2 Jun 2009 13:47:25 -0000	1.10
+++ modules/field/field.form.inc	27 Jun 2009 07:45:26 -0000
@@ -11,7 +11,7 @@
 /**
  * Create a separate form element for each field.
  */
-function field_default_form($obj_type, $object, $field, $instance, $items, &$form, &$form_state, $get_delta = NULL) {
+function field_default_form($obj_type, $object, $field, $instance, $language, $items, &$form, &$form_state, $get_delta = NULL) {
   // This could be called with no object, as when a UI module creates a
   // dummy form to set default values.
   if ($object) {
@@ -97,7 +97,14 @@ function field_default_form($obj_type, $
       '#weight' => $instance['weight'],
     );
 
-    $addition[$field['field_name']] = array_merge($form_element, $defaults);
+    $form_element = array_merge($form_element, $defaults);
+
+    $addition[$field['field_name']] = array(
+      '#tree' => TRUE,
+      '#weight' => $form_element['#weight'],
+      $language => $form_element,
+    );
+
     $form['#fields'][$field['field_name']]['form_path'] = array($field['field_name']);
   }
 
@@ -287,9 +294,9 @@ function theme_field_multiple_value_form
 /**
  * Transfer field-level validation errors to widgets.
  */
-function field_default_form_errors($obj_type, $object, $field, $instance, $items, $form, $errors) {
+function field_default_form_errors($obj_type, $object, $field, $instance, $language, $items, $form, $errors) {
   $field_name = $field['field_name'];
-  if (!empty($errors[$field_name])) {
+  if (!empty($errors[$field_name][$language])) {
     $function = $instance['widget']['module'] . '_field_widget_error';
     $function_exists = drupal_function_exists($function);
 
@@ -301,10 +308,10 @@ function field_default_form_errors($obj_
     }
 
     $multiple_widget = field_behaviors_widget('multiple values', $instance) != FIELD_BEHAVIOR_DEFAULT;
-    foreach ($errors[$field_name] as $delta => $delta_errors) {
+    foreach ($errors[$field_name][$language] as $delta => $delta_errors) {
       // For multiple single-value widgets, pass errors by delta.
       // For a multiple-value widget, all errors are passed to the main widget.
-      $error_element = $multiple_widget ? $element : $element[$delta];
+      $error_element = $multiple_widget ? $element[$language] : $element[$language][$delta];
       foreach ($delta_errors as $error) {
         if ($function_exists) {
           $function($error_element, $error);
@@ -332,7 +339,7 @@ function field_add_more_submit($form, &$
     // Make the changes we want to the form state.
     $field_name = $form_state['clicked_button']['#field_name'];
     if ($form_state['values'][$field_name . '_add_more']) {
-      $form_state['field_item_count'][$field_name] = count($form_state['values'][$field_name]);
+      $form_state['field_item_count'][$field_name] = count(current($form_state['values'][$field_name]));
     }
   }
 }
@@ -364,7 +371,7 @@ function field_add_more_js($bundle_name,
   $instance = $form['#fields'][$field_name]['instance'];
   $form_path = $form['#fields'][$field_name]['form_path'];
   if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED) {
-    // Ivnalid
+    // Invalid
     $invalid = TRUE;
   }
 
@@ -393,20 +400,21 @@ function field_add_more_js($bundle_name,
   $form_state['values'] = $form_state_copy['values'];
   // Reset cached ids, so that they don't affect the actual form we output.
   drupal_static_reset('form_clean_id');
+  $language = key($_POST[$field_name]);
 
   // Sort the $form_state['values'] we just built *and* the incoming $_POST data
   // according to d-n-d reordering.
-  unset($form_state['values'][$field_name][$field['field_name'] . '_add_more']);
-  foreach ($_POST[$field_name] as $delta => $item) {
-    $form_state['values'][$field_name][$delta]['_weight'] = $item['_weight'];
+  unset($form_state['values'][$field_name][$language][$field['field_name'] . '_add_more']);
+  foreach ($_POST[$field_name][$language] as $delta => $item) {
+    $form_state['values'][$field_name][$language][$delta]['_weight'] = $item['_weight'];
   }
-  $form_state['values'][$field_name] = _field_sort_items($field, $form_state['values'][$field_name]);
-  $_POST[$field_name]                = _field_sort_items($field, $_POST[$field_name]);
+  $form_state['values'][$field_name][$language] = _field_sort_items($field, $form_state['values'][$field_name][$language]);
+  $_POST[$field_name][$language]                = _field_sort_items($field, $_POST[$field_name][$language]);
 
   // Build our new form element for the whole field, asking for one more element.
-  $form_state['field_item_count'] = array($field_name => count($_POST[$field_name]) + 1);
-  $items = $form_state['values'][$field_name];
-  $form_element = field_default_form(NULL, NULL, $field, $instance, $items, $form, $form_state);
+  $form_state['field_item_count'] = array($field_name => count($_POST[$field_name][$language]) + 1);
+  $items = $form_state['values'][$field_name][$language];
+  $form_element = field_default_form(NULL, NULL, $field, $instance, $language, $items, $form, $form_state);
   // Let other modules alter it.
   drupal_alter('form', $form_element, array(), 'field_add_more_js');
 
@@ -423,8 +431,8 @@ function field_add_more_js($bundle_name,
 
   // Build the new form against the incoming $_POST values so that we can
   // render the new element.
-  $delta = max(array_keys($_POST[$field_name])) + 1;
-  $_POST[$field_name][$delta]['_weight'] = $delta;
+  $delta = max(array_keys($_POST[$field_name][$language])) + 1;
+  $_POST[$field_name][$language][$delta]['_weight'] = $delta;
   $form_state = form_state_defaults();
   $form_state['input'] = $_POST;
   $form = form_builder($_POST['form_id'], $form, $form_state);
Index: modules/field/field.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.install,v
retrieving revision 1.9
diff -u -p -r1.9 field.install
--- modules/field/field.install	28 May 2009 10:05:32 -0000	1.9
+++ modules/field/field.install	27 Jun 2009 07:45:26 -0000
@@ -63,6 +63,12 @@ function field_schema() {
         '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.15
diff -u -p -r1.15 field.module
--- modules/field/field.module	24 Jun 2009 18:16:38 -0000	1.15
+++ modules/field/field.module	27 Jun 2009 07:45:26 -0000
@@ -66,6 +66,11 @@ define('FIELD_CARDINALITY_UNLIMITED', -1
 /**
  * TODO
  */
+define('FIELD_LANGUAGE_NEUTRAL', '_0');
+
+/**
+ * TODO
+ */
 define('FIELD_BEHAVIOR_NONE', 0x0001);
 /**
  * TODO
Index: modules/field/field.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.test,v
retrieving revision 1.28
diff -u -p -r1.28 field.test
--- modules/field/field.test	24 Jun 2009 18:16:38 -0000	1.28
+++ modules/field/field.test	27 Jun 2009 07:45:26 -0000
@@ -58,6 +58,7 @@ class FieldAttachTestCase extends Drupal
     // field_test_field_load() in field_test.module).
     $this->instance['settings']['test_hook_field_load'] = TRUE;
     field_update_instance($this->instance);
+    $language = FIELD_LANGUAGE_NEUTRAL;
 
     $entity_type = 'test_entity';
     $values = array();
@@ -72,12 +73,12 @@ class FieldAttachTestCase extends Drupal
       $current_revision = $revision_id;
       // If this is the first revision do an insert.
       if (!$revision_id) {
-        $revision[$revision_id]->{$this->field_name} = $values[$revision_id];
+        $revision[$revision_id]->{$this->field_name}[$language] = $values[$revision_id];
         field_attach_insert($entity_type, $revision[$revision_id]);
       }
       else {
         // Otherwise do an update.
-        $revision[$revision_id]->{$this->field_name} = $values[$revision_id];
+        $revision[$revision_id]->{$this->field_name}[$language] = $values[$revision_id];
         field_attach_update($entity_type, $revision[$revision_id]);
       }
     }
@@ -86,12 +87,12 @@ class FieldAttachTestCase extends Drupal
     $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
     field_attach_load($entity_type, array(0 => $entity));
     // Number of values per field loaded equals the field cardinality.
-    $this->assertEqual(count($entity->{$this->field_name}), $this->field['cardinality'], t('Currrent revision: expected number of values'));
+    $this->assertEqual(count($entity->{$this->field_name}[$language]), $this->field['cardinality'], t('Currrent revision: expected number of values'));
     for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
       // The field value loaded matches the one inserted or updated.
-      $this->assertEqual($entity->{$this->field_name}[$delta]['value'] , $values[$current_revision][$delta]['value'], t('Currrent revision: expected value %delta was found.', array('%delta' => $delta)));
+      $this->assertEqual($entity->{$this->field_name}[$language][$delta]['value'] , $values[$current_revision][$delta]['value'], t('Currrent revision: expected value %delta was found.', array('%delta' => $delta)));
       // The value added in hook_field_load() is found.
-      $this->assertEqual($entity->{$this->field_name}[$delta]['additional_key'], 'additional_value', t('Currrent revision: extra information for value %delta was found', array('%delta' => $delta)));
+      $this->assertEqual($entity->{$this->field_name}[$language][$delta]['additional_key'], 'additional_value', t('Currrent revision: extra information for value %delta was found', array('%delta' => $delta)));
     }
 
     // Confirm each revision loads the correct data.
@@ -99,12 +100,12 @@ class FieldAttachTestCase extends Drupal
       $entity = field_test_create_stub_entity(0, $revision_id, $this->instance['bundle']);
       field_attach_load_revision($entity_type, array(0 => $entity));
       // Number of values per field loaded equals the field cardinality.
-      $this->assertEqual(count($entity->{$this->field_name}), $this->field['cardinality'], t('Revision %revision_id: expected number of values.', array('%revision_id' => $revision_id)));
+      $this->assertEqual(count($entity->{$this->field_name}[$language]), $this->field['cardinality'], t('Revision %revision_id: expected number of values.', array('%revision_id' => $revision_id)));
       for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
         // The field value loaded matches the one inserted or updated.
-        $this->assertEqual($entity->{$this->field_name}[$delta]['value'], $values[$revision_id][$delta]['value'], t('Revision %revision_id: expected value %delta was found.', array('%revision_id' => $revision_id, '%delta' => $delta)));
+        $this->assertEqual($entity->{$this->field_name}[$language][$delta]['value'], $values[$revision_id][$delta]['value'], t('Revision %revision_id: expected value %delta was found.', array('%revision_id' => $revision_id, '%delta' => $delta)));
         // The value added in hook_field_load() is found.
-        $this->assertEqual($entity->{$this->field_name}[$delta]['additional_key'], 'additional_value', t('Revision %revision_id: extra information for value %delta was found', array('%revision_id' => $revision_id, '%delta' => $delta)));
+        $this->assertEqual($entity->{$this->field_name}[$language][$delta]['additional_key'], 'additional_value', t('Revision %revision_id: extra information for value %delta was found', array('%revision_id' => $revision_id, '%delta' => $delta)));
       }
     }
   }
@@ -114,6 +115,7 @@ class FieldAttachTestCase extends Drupal
    */
   function testFieldAttachLoadMultiple() {
     $entity_type = 'test_entity';
+    $language = FIELD_LANGUAGE_NEUTRAL;
 
     // Define 2 bundles.
     $bundles = array(
@@ -156,7 +158,7 @@ class FieldAttachTestCase extends Drupal
       $instances = field_info_instances($bundle);
       foreach ($instances as $field_name => $instance) {
         $values[$index][$field_name] = mt_rand(1, 127);
-        $entity->$field_name = array(array('value' => $values[$index][$field_name]));
+        $entity->$field_name = array($language => array(array('value' => $values[$index][$field_name])));
       }
       field_attach_insert($entity_type, $entity);
     }
@@ -167,9 +169,9 @@ class FieldAttachTestCase extends Drupal
       $instances = field_info_instances($bundles[$index]);
       foreach ($instances as $field_name => $instance) {
         // The field value loaded matches the one inserted.
-        $this->assertEqual($entity->{$field_name}[0]['value'], $values[$index][$field_name], t('Entity %index: expected value was found.', array('%index' => $index)));
+        $this->assertEqual($entity->{$field_name}[$language][0]['value'], $values[$index][$field_name], t('Entity %index: expected value was found.', array('%index' => $index)));
         // The value added in hook_field_load() is found.
-        $this->assertEqual($entity->{$field_name}[0]['additional_key'], 'additional_value', t('Entity %index: extra information was found', array('%index' => $index)));
+        $this->assertEqual($entity->{$field_name}[$language][0]['additional_key'], 'additional_value', t('Entity %index: extra information was found', array('%index' => $index)));
       }
     }
   }
@@ -180,6 +182,7 @@ class FieldAttachTestCase extends Drupal
   function testFieldAttachSaveMissingData() {
     $entity_type = 'test_entity';
     $entity_init = field_test_create_stub_entity();
+    $language = FIELD_LANGUAGE_NEUTRAL;
 
     // Insert: Field is missing.
     $entity = clone($entity_init);
@@ -187,28 +190,28 @@ class FieldAttachTestCase extends Drupal
 
     $entity = clone($entity_init);
     field_attach_load($entity_type, array($entity->ftid => $entity));
-    $this->assertTrue(empty($entity->{$this->field_name}), t('Insert: missing field results in no value saved'));
+    $this->assertTrue(empty($entity->{$this->field_name}[$language]), t('Insert: missing field results in no value saved'));
 
     // Insert: Field is NULL.
     field_cache_clear();
     $entity = clone($entity_init);
-    $entity->{$this->field_name} = NULL;
+    $entity->{$this->field_name}[$language] = NULL;
     field_attach_insert($entity_type, $entity);
 
     $entity = clone($entity_init);
     field_attach_load($entity_type, array($entity->ftid => $entity));
-    $this->assertTrue(empty($entity->{$this->field_name}), t('Insert: NULL field results in no value saved'));
+    $this->assertTrue(empty($entity->{$this->field_name}[$language]), t('Insert: NULL field results in no value saved'));
 
     // Add some real data.
     field_cache_clear();
     $entity = clone($entity_init);
     $values = $this->_generateTestFieldValues(1);
-    $entity->{$this->field_name} = $values;
+    $entity->{$this->field_name}[$language] = $values;
     field_attach_insert($entity_type, $entity);
 
     $entity = clone($entity_init);
     field_attach_load($entity_type, array($entity->ftid => $entity));
-    $this->assertEqual($entity->{$this->field_name}, $values, t('Field data saved'));
+    $this->assertEqual($entity->{$this->field_name}[$language], $values, t('Field data saved'));
 
     // Update: Field is missing. Data should survive.
     field_cache_clear();
@@ -217,17 +220,17 @@ class FieldAttachTestCase extends Drupal
 
     $entity = clone($entity_init);
     field_attach_load($entity_type, array($entity->ftid => $entity));
-    $this->assertEqual($entity->{$this->field_name}, $values, t('Update: missing field leaves existing values in place'));
+    $this->assertEqual($entity->{$this->field_name}[$language], $values, t('Update: missing field leaves existing values in place'));
 
     // Update: Field is NULL. Data should be wiped.
     field_cache_clear();
     $entity = clone($entity_init);
-    $entity->{$this->field_name} = NULL;
+    $entity->{$this->field_name}[$language] = NULL;
     field_attach_update($entity_type, $entity);
 
     $entity = clone($entity_init);
     field_attach_load($entity_type, array($entity->ftid => $entity));
-    $this->assertTrue(empty($entity->{$this->field_name}), t('Update: NULL field removes existing values'));
+    $this->assertTrue(empty($entity->{$this->field_name}[$language]), t('Update: NULL field removes existing values'));
   }
 
   /**
@@ -240,15 +243,16 @@ class FieldAttachTestCase extends Drupal
 
     $entity_type = 'test_entity';
     $entity_init = field_test_create_stub_entity();
+    $language = FIELD_LANGUAGE_NEUTRAL;
 
     // Insert: Field is NULL.
     $entity = clone($entity_init);
-    $entity->{$this->field_name} = NULL;
+    $entity->{$this->field_name}[$language] = NULL;
     field_attach_insert($entity_type, $entity);
 
     $entity = clone($entity_init);
     field_attach_load($entity_type, array($entity->ftid => $entity));
-    $this->assertTrue(empty($entity->{$this->field_name}), t('Insert: NULL field results in no value saved'));
+    $this->assertTrue(empty($entity->{$this->field_name}[$language]), t('Insert: NULL field results in no value saved'));
 
     // Insert: Field is missing.
     field_cache_clear();
@@ -258,7 +262,7 @@ class FieldAttachTestCase extends Drupal
     $entity = clone($entity_init);
     field_attach_load($entity_type, array($entity->ftid => $entity));
     $values = field_test_default_value($entity_type, $entity, $this->field, $this->instance);
-    $this->assertEqual($entity->{$this->field_name}, $values, t('Insert: missing field results in default value saved'));
+    $this->assertEqual($entity->{$this->field_name}[$language], $values, t('Insert: missing field results in default value saved'));
   }
 
   /**
@@ -266,6 +270,7 @@ class FieldAttachTestCase extends Drupal
    */
   function testFieldAttachQuery() {
     $cardinality = $this->field['cardinality'];
+    $language = FIELD_LANGUAGE_NEUTRAL;
 
     // Create an additional bundle with an instance of the field.
     field_test_create_bundle('test_bundle_1', 'Test Bundle 1');
@@ -284,13 +289,13 @@ class FieldAttachTestCase extends Drupal
         $value = mt_rand(1, 127);
       } while (in_array($value, $values));
       $values[$delta] = $value;
-      $entities[1]->{$this->field_name}[$delta] = array('value' => $values[$delta]);
+      $entities[1]->{$this->field_name}[$language][$delta] = array('value' => $values[$delta]);
     }
     field_attach_insert($entity_types[1], $entities[1]);
 
     // Create second test object, sharing a value with the first one.
     $common_value = $values[$cardinality - 1];
-    $entities[2]->{$this->field_name} = array(array('value' => $common_value));
+    $entities[2]->{$this->field_name} = array($language => array(array('value' => $common_value)));
     field_attach_insert($entity_types[2], $entities[2]);
 
     // Query on the object's values.
@@ -356,8 +361,10 @@ class FieldAttachTestCase extends Drupal
           'ftvid' => $entities[1]->ftvid,
           'fttype' => $entities[1]->fttype,
           $this->field_name => array(
-            array('value' => $values[0], 'additional_key' => 'additional_value'),
-            array('value' => $common_value, 'additional_key' => 'additional_value'),
+            $language => array(
+              array('value' => $values[0], 'additional_key' => 'additional_value'),
+              array('value' => $common_value, 'additional_key' => 'additional_value'),
+            ),
           ),
         ),
       ),
@@ -367,7 +374,9 @@ class FieldAttachTestCase extends Drupal
           'ftvid' => $entities[2]->ftvid,
           'fttype' => $entities[2]->fttype,
           $this->field_name => array(
-            array('value' => $common_value, 'additional_key' => 'additional_value'),
+            $language => array(
+              array('value' => $common_value, 'additional_key' => 'additional_value'),
+            ),
           ),
         ),
       ),
@@ -384,19 +393,20 @@ class FieldAttachTestCase extends Drupal
     // Create first object revision with random (distinct) values.
     $entity_type = 'test_entity';
     $entities = array(1 => field_test_create_stub_entity(1, 1), 2 => field_test_create_stub_entity(1, 2));
+    $language = FIELD_LANGUAGE_NEUTRAL;
     $values = array();
     for ($delta = 0; $delta < $cardinality; $delta++) {
       do {
         $value = mt_rand(1, 127);
       } while (in_array($value, $values));
       $values[$delta] = $value;
-      $entities[1]->{$this->field_name}[$delta] = array('value' => $values[$delta]);
+      $entities[1]->{$this->field_name}[$language][$delta] = array('value' => $values[$delta]);
     }
     field_attach_insert($entity_type, $entities[1]);
 
     // Create second object revision, sharing a value with the first one.
     $common_value = $values[$cardinality - 1];
-    $entities[2]->{$this->field_name}[0] = array('value' => $common_value);
+    $entities[2]->{$this->field_name}[$language][0] = array('value' => $common_value);
     field_attach_update($entity_type, $entities[2]);
 
     // Query on the object's values.
@@ -452,8 +462,10 @@ class FieldAttachTestCase extends Drupal
           'ftvid' => $entities[1]->ftvid,
           'fttype' => $entities[1]->fttype,
           $this->field_name => array(
-            array('value' => $values[0], 'additional_key' => 'additional_value'),
-            array('value' => $common_value, 'additional_key' => 'additional_value'),
+            $language => array(
+              array('value' => $values[0], 'additional_key' => 'additional_value'),
+              array('value' => $common_value, 'additional_key' => 'additional_value'),
+            ),
           ),
         ),
         $entities[2]->ftvid => (object) array(
@@ -461,7 +473,9 @@ class FieldAttachTestCase extends Drupal
           'ftvid' => $entities[2]->ftvid,
           'fttype' => $entities[2]->fttype,
           $this->field_name => array(
-            array('value' => $common_value, 'additional_key' => 'additional_value'),
+            $language => array(
+              array('value' => $common_value, 'additional_key' => 'additional_value'),
+            ),
           ),
         ),
       ),
@@ -472,10 +486,11 @@ class FieldAttachTestCase extends Drupal
   function testFieldAttachViewAndPreprocess() {
     $entity_type = 'test_entity';
     $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+    $language = FIELD_LANGUAGE_NEUTRAL;
 
     // Populate values to be displayed.
     $values = $this->_generateTestFieldValues($this->field['cardinality']);
-    $entity->{$this->field_name} = $values;
+    $entity->{$this->field_name}[$language] = $values;
 
     // Simple formatter, label displayed.
     $formatter_setting = $this->randomName();
@@ -549,28 +564,29 @@ class FieldAttachTestCase extends Drupal
 
   function testFieldAttachDelete() {
     $entity_type = 'test_entity';
+    $language = FIELD_LANGUAGE_NEUTRAL;
     $rev[0] = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
 
     // Create revision 0
     $values = $this->_generateTestFieldValues($this->field['cardinality']);
-    $rev[0]->{$this->field_name} = $values;
+    $rev[0]->{$this->field_name}[$language] = $values;
     field_attach_insert($entity_type, $rev[0]);
 
     // Create revision 1
     $rev[1] = field_test_create_stub_entity(0, 1, $this->instance['bundle']);
-    $rev[1]->{$this->field_name} = $values;
+    $rev[1]->{$this->field_name}[$language] = $values;
     field_attach_update($entity_type, $rev[1]);
 
     // Create revision 2
     $rev[2] = field_test_create_stub_entity(0, 2, $this->instance['bundle']);
-    $rev[2]->{$this->field_name} = $values;
+    $rev[2]->{$this->field_name}[$language] = $values;
     field_attach_update($entity_type, $rev[2]);
 
     // Confirm each revision loads
     foreach (array_keys($rev) as $vid) {
       $read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']);
       field_attach_load_revision($entity_type, array(0 => $read));
-      $this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test object revision $vid has {$this->field['cardinality']} values.");
+      $this->assertEqual(count($read->{$this->field_name}[$language]), $this->field['cardinality'], "The test object revision $vid has {$this->field['cardinality']} values.");
     }
 
     // Delete revision 1, confirm the other two still load.
@@ -578,13 +594,13 @@ class FieldAttachTestCase extends Drupal
     foreach (array(0, 2) as $vid) {
       $read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']);
       field_attach_load_revision($entity_type, array(0 => $read));
-      $this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test object revision $vid has {$this->field['cardinality']} values.");
+      $this->assertEqual(count($read->{$this->field_name}[$language]), $this->field['cardinality'], "The test object revision $vid has {$this->field['cardinality']} values.");
     }
 
     // Confirm the current revision still loads
     $read = field_test_create_stub_entity(0, 2, $this->instance['bundle']);
     field_attach_load($entity_type, array(0 => $read));
-    $this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test object current revision has {$this->field['cardinality']} values.");
+    $this->assertEqual(count($read->{$this->field_name}[$language]), $this->field['cardinality'], "The test object current revision has {$this->field['cardinality']} values.");
 
     // Delete all field data, confirm nothing loads
     field_attach_delete($entity_type, $rev[2]);
@@ -610,15 +626,16 @@ class FieldAttachTestCase extends Drupal
 
     // Save an object with data in the field.
     $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+    $language = FIELD_LANGUAGE_NEUTRAL;
     $values = $this->_generateTestFieldValues($this->field['cardinality']);
-    $entity->{$this->field_name} = $values;
+    $entity->{$this->field_name}[$language] = $values;
     $entity_type = 'test_entity';
     field_attach_insert($entity_type, $entity);
 
     // Verify the field data is present on load.
     $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
     field_attach_load($entity_type, array(0 => $entity));
-    $this->assertEqual(count($entity->{$this->field_name}), $this->field['cardinality'], "Data are retrieved for the new bundle");
+    $this->assertEqual(count($entity->{$this->field_name}[$language]), $this->field['cardinality'], "Data are retrieved for the new bundle");
 
     // Rename the bundle. This has to be initiated by the module so that its
     // hook_fieldable_info() is consistent.
@@ -632,7 +649,7 @@ class FieldAttachTestCase extends Drupal
     // Verify the field data is present on load.
     $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
     field_attach_load($entity_type, array(0 => $entity));
-    $this->assertEqual(count($entity->{$this->field_name}), $this->field['cardinality'], "Bundle name has been updated in the field storage");
+    $this->assertEqual(count($entity->{$this->field_name}[$language]), $this->field['cardinality'], "Bundle name has been updated in the field storage");
   }
 
   function testFieldAttachDeleteBundle() {
@@ -664,17 +681,18 @@ class FieldAttachTestCase extends Drupal
 
     // Save an object with data for both fields
     $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+    $language = FIELD_LANGUAGE_NEUTRAL;
     $values = $this->_generateTestFieldValues($this->field['cardinality']);
-    $entity->{$this->field_name} = $values;
-    $entity->{$field_name} = $this->_generateTestFieldValues(1);
+    $entity->{$this->field_name}[$language] = $values;
+    $entity->{$field_name}[$language] = $this->_generateTestFieldValues(1);
     $entity_type = 'test_entity';
     field_attach_insert($entity_type, $entity);
 
     // Verify the fields are present on load
     $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
     field_attach_load($entity_type, array(0 => $entity));
-    $this->assertEqual(count($entity->{$this->field_name}), 4, "First field got loaded");
-    $this->assertEqual(count($entity->{$field_name}), 1, "Second field got loaded");
+    $this->assertEqual(count($entity->{$this->field_name}[$language]), 4, "First field got loaded");
+    $this->assertEqual(count($entity->{$field_name}[$language]), 1, "Second field got loaded");
 
     // Delete the bundle. This has to be initiated by the module so that its
     // hook_fieldable_info() is consistent.
@@ -683,8 +701,8 @@ class FieldAttachTestCase extends Drupal
     // Verify no data gets loaded
     $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
     field_attach_load($entity_type, array(0 => $entity));
-    $this->assertFalse(isset($entity->{$this->field_name}), "No data for first field");
-    $this->assertFalse(isset($entity->{$field_name}), "No data for second field");
+    $this->assertFalse(isset($entity->{$this->field_name}[$language]), "No data for first field");
+    $this->assertFalse(isset($entity->{$field_name}[$language]), "No data for second field");
 
     // Verify that the instances are gone
     $this->assertFalse(field_read_instance($this->field_name, $this->instance['bundle']), "First field is deleted");
@@ -697,6 +715,7 @@ class FieldAttachTestCase extends Drupal
   function testFieldAttachCache() {
     // Initialize random values and a test entity.
     $entity_init = field_test_create_stub_entity(1, 1, $this->instance['bundle']);
+    $language = FIELD_LANGUAGE_NEUTRAL;
     $values = $this->_generateTestFieldValues($this->field['cardinality']);
 
     $noncached_type = 'test_entity';
@@ -711,7 +730,7 @@ class FieldAttachTestCase extends Drupal
 
     // Save, and check that no cache entry is present.
     $entity = clone($entity_init);
-    $entity->{$this->field_name} = $values;
+    $entity->{$this->field_name}[$language] = $values;
     field_attach_insert($noncached_type, $entity);
     $this->assertFalse(cache_get($cid, 'cache_field'), t('Non-cached: no cache entry on insert'));
 
@@ -729,7 +748,7 @@ class FieldAttachTestCase extends Drupal
 
     // Save, and check that no cache entry is present.
     $entity = clone($entity_init);
-    $entity->{$this->field_name} = $values;
+    $entity->{$this->field_name}[$language] = $values;
     field_attach_insert($cached_type, $entity);
     $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on insert'));
 
@@ -737,12 +756,12 @@ class FieldAttachTestCase extends Drupal
     $entity = clone($entity_init);
     field_attach_load($cached_type, array($entity->ftid => $entity));
     $cache = cache_get($cid, 'cache_field');
-    $this->assertEqual($cache->data[$this->field_name], $values, t('Cached: correct cache entry on load'));
+    $this->assertEqual($cache->data[$this->field_name][$language], $values, t('Cached: correct cache entry on load'));
 
     // Update with different values, and check that the cache entry is wiped.
     $values = $this->_generateTestFieldValues($this->field['cardinality']);
     $entity = clone($entity_init);
-    $entity->{$this->field_name} = $values;
+    $entity->{$this->field_name}[$language] = $values;
     field_attach_update($cached_type, $entity);
     $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on update'));
 
@@ -750,13 +769,13 @@ class FieldAttachTestCase extends Drupal
     $entity = clone($entity_init);
     field_attach_load($cached_type, array($entity->ftid => $entity));
     $cache = cache_get($cid, 'cache_field');
-    $this->assertEqual($cache->data[$this->field_name], $values, t('Cached: correct cache entry on load'));
+    $this->assertEqual($cache->data[$this->field_name][$language], $values, t('Cached: correct cache entry on load'));
 
     // Create a new revision, and check that the cache entry is wiped.
     $entity_init = field_test_create_stub_entity(1, 2, $this->instance['bundle']);
     $values = $this->_generateTestFieldValues($this->field['cardinality']);
     $entity = clone($entity_init);
-    $entity->{$this->field_name} = $values;
+    $entity->{$this->field_name}[$language] = $values;
     field_attach_update($cached_type, $entity);
     $cache = cache_get($cid, 'cache_field');
     $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on new revision creation'));
@@ -765,7 +784,7 @@ class FieldAttachTestCase extends Drupal
     $entity = clone($entity_init);
     field_attach_load($cached_type, array($entity->ftid => $entity));
     $cache = cache_get($cid, 'cache_field');
-    $this->assertEqual($cache->data[$this->field_name], $values, t('Cached: correct cache entry on load'));
+    $this->assertEqual($cache->data[$this->field_name][$language], $values, t('Cached: correct cache entry on load'));
 
     // Delete, and check that the cache entry is wiped.
     field_attach_delete($cached_type, $entity);
@@ -777,6 +796,7 @@ class FieldAttachTestCase extends Drupal
   function testFieldAttachValidate() {
     $entity_type = 'test_entity';
     $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+    $language = FIELD_LANGUAGE_NEUTRAL;
 
     // Set up values to generate errors
     $values = array();
@@ -786,7 +806,7 @@ class FieldAttachTestCase extends Drupal
     }
     // Arrange for item 1 not to generate an error
     $values[1]['value'] = 1;
-    $entity->{$this->field_name} = $values;
+    $entity->{$this->field_name}[$language] = $values;
 
     try {
       field_attach_validate($entity_type, $entity);
@@ -797,15 +817,15 @@ class FieldAttachTestCase extends Drupal
 
     foreach ($values as $delta => $value) {
       if ($value['value'] != 1) {
-        $this->assertIdentical($errors[$this->field_name][$delta][0]['error'], 'field_test_invalid', "Error set on value $delta");
-        $this->assertEqual(count($errors[$this->field_name][$delta]), 1, "Only one error set on value $delta");
-        unset($errors[$this->field_name][$delta]);
+        $this->assertIdentical($errors[$this->field_name][$language][$delta][0]['error'], 'field_test_invalid', "Error set on value $delta");
+        $this->assertEqual(count($errors[$this->field_name][$language][$delta]), 1, "Only one error set on value $delta");
+        unset($errors[$this->field_name][$language][$delta]);
       }
       else {
-        $this->assertFalse(isset($errors[$this->field_name][$delta]), "No error set on value $delta");
+        $this->assertFalse(isset($errors[$this->field_name][$language][$delta]), "No error set on value $delta");
       }
     }
-    $this->assertEqual(count($errors[$this->field_name]), 0, 'No extraneous errors set');
+    $this->assertEqual(count($errors[$this->field_name][$language]), 0, 'No extraneous errors set');
   }
 
   // Validate that FAPI elements are generated. This could be much
@@ -817,10 +837,11 @@ class FieldAttachTestCase extends Drupal
     $form = $form_state = array();
     field_attach_form($entity_type, $entity, $form, $form_state);
 
-    $this->assertEqual($form[$this->field_name]['#title'], $this->instance['label'], "Form title is {$this->instance['label']}");
+    $language = FIELD_LANGUAGE_NEUTRAL;
+    $this->assertEqual($form[$this->field_name][$language]['#title'], $this->instance['label'], "Form title is {$this->instance['label']}");
     for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
       // field_test_widget uses 'textfield'
-      $this->assertEqual($form[$this->field_name][$delta]['value']['#type'], 'textfield', "Form delta $delta widget is textfield");
+      $this->assertEqual($form[$this->field_name][$language][$delta]['value']['#type'], 'textfield', "Form delta $delta widget is textfield");
     }
   }
 
@@ -847,7 +868,8 @@ class FieldAttachTestCase extends Drupal
     // Leave an empty value. 'field_test' fields are empty if empty().
     $values[1]['value'] = 0;
 
-    $form_state['values'] = array($this->field_name => $values);
+    $language = FIELD_LANGUAGE_NEUTRAL;
+    $form_state['values'] = array($this->field_name => array($language => $values));
     field_attach_submit($entity_type, $entity, $form, $form_state);
 
     asort($weights);
@@ -857,7 +879,7 @@ class FieldAttachTestCase extends Drupal
         $expected_values[] = array('value' => $values[$key]['value']);
       }
     }
-    $this->assertIdentical($entity->{$this->field_name}, $expected_values, 'Submit filters empty values');
+    $this->assertIdentical($entity->{$this->field_name}[$language], $expected_values, 'Submit filters empty values');
   }
 
   /**
@@ -1027,45 +1049,46 @@ class FieldFormTestCase extends DrupalWe
     $this->instance['field_name'] = $this->field_name;
     field_create_field($this->field);
     field_create_instance($this->instance);
+    $language = FIELD_LANGUAGE_NEUTRAL;
 
     // Display creation form.
     $this->drupalGet('test-entity/add/test-bundle');
-    $this->assertFieldByName($this->field_name . '[0][value]', '', 'Widget is displayed');
-    $this->assertNoField($this->field_name . '[1][value]', 'No extraneous widget is displayed');
+    $this->assertFieldByName($this->field_name . '[' . $language . '][0][value]', '', 'Widget is displayed');
+    $this->assertNoField($this->field_name . '[' . $language . '][1][value]', 'No extraneous widget is displayed');
     // TODO : check that the widget is populated with default value ?
 
     // Submit with invalid value (field-level validation).
-    $edit = array($this->field_name . '[0][value]' => -1);
+    $edit = array($this->field_name . '[' . $language . '][0][value]' => -1);
     $this->drupalPost(NULL, $edit, t('Save'));
     $this->assertRaw(t('%name does not accept the value -1.', array('%name' => $this->instance['label'])), 'Field validation fails with invalid input.');
     // TODO : check that the correct field is flagged for error.
 
     // Create an entity
     $value = mt_rand(1, 127);
-    $edit = array($this->field_name . '[0][value]' => $value);
+    $edit = array($this->field_name . '[' . $language . '][0][value]' => $value);
     $this->drupalPost(NULL, $edit, t('Save'));
     preg_match('|test-entity/(\d+)/edit|', $this->url, $match);
     $id = $match[1];
     $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created');
     $entity = field_test_entity_load($id);
-    $this->assertEqual($entity->{$this->field_name}[0]['value'], $value, 'Field value was saved');
+    $this->assertEqual($entity->{$this->field_name}[$language][0]['value'], $value, 'Field value was saved');
 
     // Display edit form.
     $this->drupalGet('test-entity/' . $id . '/edit');
-    $this->assertFieldByName($this->field_name . '[0][value]', $value, 'Widget is displayed with the correct default value');
-    $this->assertNoField($this->field_name . '[1][value]', 'No extraneous widget is displayed');
+    $this->assertFieldByName($this->field_name . '[' . $language . '][0][value]', $value, 'Widget is displayed with the correct default value');
+    $this->assertNoField($this->field_name . '[' . $language . '][1][value]', 'No extraneous widget is displayed');
 
     // Update the entity.
     $value = mt_rand(1, 127);
-    $edit = array($this->field_name . '[0][value]' => $value);
+    $edit = array($this->field_name . '[' . $language . '][0][value]' => $value);
     $this->drupalPost(NULL, $edit, t('Save'));
     $this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), 'Entity was updated');
     $entity = field_test_entity_load($id);
-    $this->assertEqual($entity->{$this->field_name}[0]['value'], $value, 'Field value was updated');
+    $this->assertEqual($entity->{$this->field_name}[$language][0]['value'], $value, 'Field value was updated');
 
     // Empty the field.
     $value = '';
-    $edit = array($this->field_name . '[0][value]' => $value);
+    $edit = array($this->field_name . '[' . $language . '][0][value]' => $value);
     $this->drupalPost('test-entity/' . $id . '/edit', $edit, t('Save'));
     $this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), 'Entity was updated');
     $entity = field_test_entity_load($id);
@@ -1080,6 +1103,7 @@ class FieldFormTestCase extends DrupalWe
     $this->instance['required'] = TRUE;
     field_create_field($this->field);
     field_create_instance($this->instance);
+    $language = FIELD_LANGUAGE_NEUTRAL;
 
     // Submit with missing required value.
     $edit = array();
@@ -1088,17 +1112,17 @@ class FieldFormTestCase extends DrupalWe
 
     // Create an entity
     $value = mt_rand(1, 127);
-    $edit = array($this->field_name . '[0][value]' => $value);
+    $edit = array($this->field_name . '[' . $language . '][0][value]' => $value);
     $this->drupalPost(NULL, $edit, t('Save'));
     preg_match('|test-entity/(\d+)/edit|', $this->url, $match);
     $id = $match[1];
     $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created');
     $entity = field_test_entity_load($id);
-    $this->assertEqual($entity->{$this->field_name}[0]['value'], $value, 'Field value was saved');
+    $this->assertEqual($entity->{$this->field_name}[$language][0]['value'], $value, 'Field value was saved');
 
     // Edit with missing required value.
     $value = '';
-    $edit = array($this->field_name . '[0][value]' => $value);
+    $edit = array($this->field_name . '[' . $language . '][0][value]' => $value);
     $this->drupalPost('test-entity/' . $id . '/edit', $edit, t('Save'));
     $this->assertRaw(t('!name field is required.', array('!name' => $this->instance['label'])), 'Required field with no value fails validation');
   }
@@ -1117,17 +1141,18 @@ class FieldFormTestCase extends DrupalWe
     $this->instance['field_name'] = $this->field_name;
     field_create_field($this->field);
     field_create_instance($this->instance);
+    $language = FIELD_LANGUAGE_NEUTRAL;
 
     // Display creation form -> 1 widget.
     $this->drupalGet('test-entity/add/test-bundle');
-    $this->assertFieldByName($this->field_name . '[0][value]', '', 'Widget 1 is displayed');
-    $this->assertNoField($this->field_name . '[1][value]', 'No extraneous widget is displayed');
+    $this->assertFieldByName($this->field_name . '[' . $language . '][0][value]', '', 'Widget 1 is displayed');
+    $this->assertNoField($this->field_name . '[' . $language . '][1][value]', 'No extraneous widget is displayed');
 
     // Press 'add more' button -> 2 widgets.
     $this->drupalPost(NULL, array(), t('Add another item'));
-    $this->assertFieldByName($this->field_name . '[0][value]', '', 'Widget 1 is displayed');
-    $this->assertFieldByName($this->field_name . '[1][value]', '', 'New widget is displayed');
-    $this->assertNoField($this->field_name . '[2][value]', 'No extraneous widget is displayed');
+    $this->assertFieldByName($this->field_name . '[' . $language . '][0][value]', '', 'Widget 1 is displayed');
+    $this->assertFieldByName($this->field_name . '[' . $language . '][1][value]', '', 'New widget is displayed');
+    $this->assertNoField($this->field_name . '[' . $language . '][2][value]', 'No extraneous widget is displayed');
     // TODO : check that non-field inpurs are preserved ('title')...
 
     // Yet another time so that we can play with more values -> 3 widgets.
@@ -1144,8 +1169,8 @@ class FieldFormTestCase extends DrupalWe
       } while (in_array($weight, $weights));
       $weights[] = $weight;
       $value = mt_rand(1, 127);
-      $edit["$this->field_name[$delta][value]"] = $value;
-      $edit["$this->field_name[$delta][_weight]"] = $weight;
+      $edit["$this->field_name[$language][$delta][value]"] = $value;
+      $edit["$this->field_name[$language][$delta][_weight]"] = $weight;
       // We'll need three slightly different formats to check the values.
       $values[$weight] = $value;
       $field_values[$weight]['value'] = (string)$value;
@@ -1157,15 +1182,15 @@ class FieldFormTestCase extends DrupalWe
     ksort($values);
     $values = array_values($values);
     for ($delta = 0; $delta <= $delta_range; $delta++) {
-      $this->assertFieldByName("$this->field_name[$delta][value]", $values[$delta], "Widget $delta is displayed and has the right value");
-      $this->assertFieldByName("$this->field_name[$delta][_weight]", $delta, "Widget $delta has the right weight");
+      $this->assertFieldByName("$this->field_name[$language][$delta][value]", $values[$delta], "Widget $delta is displayed and has the right value");
+      $this->assertFieldByName("$this->field_name[$language][$delta][_weight]", $delta, "Widget $delta has the right weight");
     }
     ksort($pattern);
     $pattern = implode('.*', array_values($pattern));
     $this->assertPattern("|$pattern|s", 'Widgets are displayed in the correct order');
-    $this->assertFieldByName("$this->field_name[$delta][value]", '', "New widget is displayed");
-    $this->assertFieldByName("$this->field_name[$delta][_weight]", $delta, "New widget has the right weight");
-    $this->assertNoField("$this->field_name[" . ($delta + 1) . '][value]', 'No extraneous widget is displayed');
+    $this->assertFieldByName("$this->field_name[$language][$delta][value]", '', "New widget is displayed");
+    $this->assertFieldByName("$this->field_name[$language][$delta][_weight]", $delta, "New widget has the right weight");
+    $this->assertNoField("$this->field_name[$language][" . ($delta + 1) . '][value]', 'No extraneous widget is displayed');
 
     // Submit the form and create the entity.
     $this->drupalPost(NULL, $edit, t('Save'));
@@ -1175,7 +1200,7 @@ class FieldFormTestCase extends DrupalWe
     $entity = field_test_entity_load($id);
     ksort($field_values);
     $field_values = array_values($field_values);
-    $this->assertIdentical($entity->{$this->field_name}, $field_values, 'Field values were saved in the correct order');
+    $this->assertIdentical($entity->{$this->field_name}[$language], $field_values, 'Field values were saved in the correct order');
 
     // Display edit form: check that the expected number of widgets is
     // displayed, with correct values change values, reorder, leave an empty
@@ -1197,6 +1222,7 @@ class FieldFormTestCase extends DrupalWe
     $this->instance['field_name'] = $this->field_name;
     field_create_field($this->field);
     field_create_instance($this->instance);
+    $language = FIELD_LANGUAGE_NEUTRAL;
 
     // Display creation form -> 1 widget.
     $this->drupalGet('test-entity/add/test-bundle');
@@ -1218,8 +1244,8 @@ class FieldFormTestCase extends DrupalWe
       } while (in_array($weight, $weights));
       $weights[] = $weight;
       $value = mt_rand(1, 127);
-      $edit["$this->field_name[$delta][value]"] = $value;
-      $edit["$this->field_name[$delta][_weight]"] = $weight;
+      $edit["$this->field_name[$language][$delta][value]"] = $value;
+      $edit["$this->field_name[$language][$delta][_weight]"] = $weight;
       // We'll need three slightly different formats to check the values.
       $values[$weight] = $value;
       $field_values[$weight]['value'] = (string)$value;
@@ -1232,15 +1258,15 @@ class FieldFormTestCase extends DrupalWe
     ksort($values);
     $values = array_values($values);
     for ($delta = 0; $delta <= $delta_range; $delta++) {
-      $this->assertFieldByName("$this->field_name[$delta][value]", $values[$delta], "Widget $delta is displayed and has the right value");
-      $this->assertFieldByName("$this->field_name[$delta][_weight]", $delta, "Widget $delta has the right weight");
+      $this->assertFieldByName("$this->field_name[$language][$delta][value]", $values[$delta], "Widget $delta is displayed and has the right value");
+      $this->assertFieldByName("$this->field_name[$language][$delta][_weight]", $delta, "Widget $delta has the right weight");
     }
     ksort($pattern);
     $pattern = implode('.*', array_values($pattern));
     $this->assertPattern("|$pattern|s", 'Widgets are displayed in the correct order');
-    $this->assertFieldByName("$this->field_name[$delta][value]", '', "New widget is displayed");
-    $this->assertFieldByName("$this->field_name[$delta][_weight]", $delta, "New widget has the right weight");
-    $this->assertNoField("$this->field_name[" . ($delta + 1) . '][value]', 'No extraneous widget is displayed');
+    $this->assertFieldByName("$this->field_name[$language][$delta][value]", '', "New widget is displayed");
+    $this->assertFieldByName("$this->field_name[$language][$delta][_weight]", $delta, "New widget has the right weight");
+    $this->assertNoField("$this->field_name[$language][" . ($delta + 1) . '][value]', 'No extraneous widget is displayed');
   }
 
   /**
@@ -1473,17 +1499,18 @@ class FieldCrudTestCase extends DrupalWe
 
     // Save an object with data for the field
     $entity = field_test_create_stub_entity(0, 0, $instance['bundle']);
+    $language = FIELD_LANGUAGE_NEUTRAL;
     $values[0]['value'] = mt_rand(1, 127);
-    $entity->{$field['field_name']} = $values;
+    $entity->{$field['field_name']}[$language] = $values;
     $entity_type = 'test_entity';
     field_attach_insert($entity_type, $entity);
 
     // Verify the field is present on load
     $entity = field_test_create_stub_entity(0, 0, $this->instance_definition['bundle']);
     field_attach_load($entity_type, array(0 => $entity));
-    $this->assertIdentical(count($entity->{$field['field_name']}), count($values), "Data in previously deleted field saves and loads correctly");
+    $this->assertIdentical(count($entity->{$field['field_name']}[$language]), count($values), "Data in previously deleted field saves and loads correctly");
     foreach ($values as $delta => $value) {
-      $this->assertEqual($entity->{$field['field_name']}[$delta]['value'], $values[$delta]['value'], "Data in previously deleted field saves and loads correctly");
+      $this->assertEqual($entity->{$field['field_name']}[$language][$delta]['value'], $values[$delta]['value'], "Data in previously deleted field saves and loads correctly");
     }
   }
 }
@@ -1652,3 +1679,180 @@ class FieldInstanceTestCase extends Drup
     $this->assertTrue(!empty($another_instance) && empty($another_instance['deleted']), t('A non-deleted field instance is not marked for deletion.'));
   }
 }
+
+/**
+ * Unit test class for the multilanguage fields logic.
+ *
+ * The following tests will check the multilanguage logic of 
+ * _field_invoke and that only the correct values are returned 
+ * by _field_available_languages.
+ */
+class FieldTranslationsTestCase extends DrupalWebTestCase {
+  public static function getInfo() {
+    return array(
+      'name'  => t('Field translations tests'),
+      'description'  => t("Test multilanguage fields logic."),
+      'group' => t('Field')
+    );
+  }
+
+  function setUp() {
+    parent::setUp('locale', 'field_test');
+
+    $this->field_name = drupal_strtolower($this->randomName() . '_field_name');
+
+    $this->field = array(
+      'field_name' => $this->field_name,
+      'type' => 'test_field',
+      'cardinality' => 4,
+      'translatable' => TRUE,
+      'settings' => array(
+        'test_hook_in' => FALSE,
+      ),
+    );
+    field_create_field($this->field);
+
+    $this->instance = array(
+      'field_name' => $this->field_name,
+      'bundle' => 'test_bundle',
+      'label' => $this->randomName() . '_label',
+      'description' => $this->randomName() . '_description',
+      'weight' => mt_rand(0, 127),
+      'settings' => array(
+        'test_instance_setting' => $this->randomName(),
+      ),
+      'widget' => array(
+        'type' => 'test_field_widget',
+        'label' => 'Test Field',
+        'settings' => array(
+          'test_widget_setting' => $this->randomName(),
+        )
+      )
+    );
+    field_create_instance($this->instance);
+
+    require_once DRUPAL_ROOT . '/includes/locale.inc';
+    for ($i = 0; $i < 3; ++$i) {
+      locale_add_language('l'.$i, $this->randomString(), $this->randomString());
+    }
+  }
+
+  /**
+   * Check that that only the correct values are returned by _field_available_languages.
+   */
+  function testFieldAvailableLanguages() {
+    // Test hook_field_languages invocation on a translatable field.
+    $this->field['settings']['test_hook_in'] = TRUE;
+    $enabled_languages = array_keys(language_list());
+    $available_languages = _field_available_languages($this->field, $this->instance); 
+    foreach ($available_languages as $language) {
+      $this->assertTrue(in_array($language, $enabled_languages), t('%language is an enabled language', array('%language' => $language)));
+    }
+    $this->assertFalse(in_array('xx', $available_languages), t('No invalid language was made available'));
+    $this->assertTrue(count($available_languages) == count($enabled_languages) - 1, t('An enabled language was successfully made unavailable'));
+
+    // Test _field_available_languages behavior for untranslatable fields.
+    $this->field['translatable'] = FALSE;
+    $this->field_name = $this->field['field_name'] = $this->instance['field_name'] = drupal_strtolower($this->randomName() . '_field_name');
+    $available_languages = _field_available_languages($this->field, $this->instance);
+    $this->assertTrue(count($available_languages) == 1 && $available_languages[0] === FIELD_LANGUAGE_NEUTRAL, t('For untranslatable fields only neutral language is available'));
+
+    // Test language suggestions.
+    $this->field['settings']['test_hook_in'] = FALSE;
+    $this->field['translatable'] = TRUE;
+    $this->field_name = $this->field['field_name'] = $this->instance['field_name'] = drupal_strtolower($this->randomName() . '_field_name');
+    $suggested_languages = array();
+    $lang_count = mt_rand(1, count($enabled_languages) - 1);
+    for ($i = 0; $i < $lang_count; ++$i) {
+      do {
+        $language = $enabled_languages[mt_rand(0, $lang_count)];
+      }
+      while (in_array($language, $suggested_languages));
+      $suggested_languages[] = $language;
+    }
+    $available_languages = _field_available_languages($this->field, $this->instance, $suggested_languages);
+    $this->assertEqual(count($available_languages), count($suggested_languages), t('Suggested languages were succesully made available'));
+    foreach ($available_languages as $language) {
+      $this->assertTrue(in_array($language, $available_languages), t('Suggested language %language is available', array('%language' => $language)));
+    }
+    $this->field_name = $this->field['field_name'] = $this->instance['field_name'] = drupal_strtolower($this->randomName() . '_field_name');
+    $suggested_languages = array('xx');
+    $available_languages = _field_available_languages($this->field, $this->instance, $suggested_languages);
+    $this->assertTrue(empty($available_languages), t('An invalid suggested language was not made available'));
+  }
+
+  /**
+   * Test the multilanguage logic of _field_invoke.
+   */
+  function testFieldInvoke() {
+    $entity_type = 'test_entity';
+    $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+
+    // Populate some extra languages to check if _field_invoke correctly uses
+    // the result of _field_available_languages.
+    $values = array();
+    $extra_languages = mt_rand(1, 4);
+    $languages = $available_languages = _field_available_languages($this->field, $this->instance);
+    for ($i = 0; $i < $extra_languages; ++$i) {
+      $languages[] = $this->randomString(2);
+    }
+
+    // For each given language provide some random values.
+    foreach ($languages as $language) {
+      for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
+        $values[$language][$delta]['value'] = mt_rand(1, 127);
+      }
+    }
+    $entity->{$this->field_name} = $values;
+
+    $results = _field_invoke('test_op', $entity_type, $entity);
+    foreach ($results as $language => $result) {
+      $hash = md5(serialize(array($entity_type, $entity, $this->field_name, $language, $values[$language])));
+      // Check if the parameters passed to _field_invoke were correctly forwarded to the callback function.
+      $this->assertEqual($hash, $result, t('The result for %language is correctly stored', array('%language' => $language)));
+    }
+    $this->assertEqual(count($results), count($available_languages), t('No unavailable language has been processed'));
+  }
+
+  /**
+   * Test the multilanguage logic of _field_invoke_multiple.
+   */
+  function testFieldInvokeMultiple() {
+    $values = array();
+    $entities = array();
+    $entity_type = 'test_entity';
+    $entity_count = mt_rand(1, 5);
+    $available_languages = _field_available_languages($this->field, $this->instance);
+
+    for ($id = 1; $id <= $entity_count; ++$id) {
+      $entity = field_test_create_stub_entity($id, $id, $this->instance['bundle']);
+      $languages = $available_languages;
+
+      // Populate some extra languages to check if _field_invoke correctly uses
+      // the result of _field_available_languages.
+      $extra_languages = mt_rand(1, 4);
+      for ($i = 0; $i < $extra_languages; ++$i) {
+        $languages[] = $this->randomString(2);
+      }
+
+      // For each given language provide some random values.
+      foreach ($languages as $language) {
+        for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
+          $values[$id][$language][$delta]['value'] = mt_rand(1, 127);
+        }
+      }
+      $entity->{$this->field_name} = $values[$id];
+      $entities[$id] = $entity;
+    }
+
+    $grouped_results = _field_invoke_multiple('test_op_multiple', $entity_type, $entities);
+    foreach ($grouped_results as $id => $results) {
+      foreach ($results as $language => $result) {
+        $hash = md5(serialize(array($entity_type, $entities[$id], $this->field_name, $language, $values[$id][$language])));
+        // Check if the parameters passed to _field_invoke were correctly forwarded to the callback function.
+        $this->assertEqual($hash, $result, t('The result for object %id/%language is correctly stored', array('%id' => $id, '%language' => $language)));
+      }
+      $this->assertEqual(count($results), count($available_languages), t('No unavailable language has been processed for object %id', array('%id' => $id)));
+    }
+  }
+}
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.14
diff -u -p -r1.14 field_sql_storage.module
--- modules/field/modules/field_sql_storage/field_sql_storage.module	15 Jun 2009 10:10:46 -0000	1.14
+++ modules/field/modules/field_sql_storage/field_sql_storage.module	27 Jun 2009 07:45:26 -0000
@@ -144,8 +144,16 @@ function _field_sql_storage_schema($fiel
         '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'),
+    'primary key' => array('etid', 'entity_id', 'deleted', 'delta', 'language'),
     // TODO : index on 'bundle'
   );
 
@@ -169,7 +177,7 @@ function _field_sql_storage_schema($fiel
   $revision = $current;
   $revision['description'] = "Revision archive storage for {$deleted}field {$field['id']} ({$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) => $current,
@@ -215,7 +223,7 @@ function field_sql_storage_field_storage
       if (!isset($skip_fields[$field_name])) {
         $objects[$id]->{$field_name} = array();
         $field_ids[$field_name][] = $load_current ? $id : $vid;
-        $delta_count[$id][$field_name] = 0;
+        $delta_count[$id][$field_name] = array();
       }
     }
   }
@@ -233,7 +241,11 @@ function field_sql_storage_field_storage
       ->execute();
 
     foreach ($results as $row) {
-      if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$field_name] < $field['cardinality']) {
+      if (!isset($delta_count[$row->entity_id][$field_name][$row->language])) {
+        $delta_count[$row->entity_id][$field_name][$row->language] = 0;
+      }
+
+      if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$field_name][$row->language] < $field['cardinality']) {
         $item = array();
         // For each column declared by the field, populate the item
         // from the prefixed database column.
@@ -243,8 +255,8 @@ function field_sql_storage_field_storage
         }
 
         // Add the item to the field values for the entity.
-        $objects[$row->entity_id]->{$field_name}[] = $item;
-        $delta_count[$row->entity_id][$field_name]++;
+        $objects[$row->entity_id]->{$field_name}[$row->language][] = $item;
+        $delta_count[$row->entity_id][$field_name][$row->language]++;
       }
     }
   }
@@ -268,23 +280,32 @@ function field_sql_storage_field_storage
     $table_name = _field_sql_storage_tablename($field);
     $revision_name = _field_sql_storage_revision_tablename($field);
 
-    // 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.
-
-    // Function property_exists() is slower, so we catch the more frequent cases
-    // where it's an empty array with the faster isset().
-    if (isset($object->$field_name) || property_exists($object, $field_name)) {
+    // Leave the field untouched if $object comes with an empty $field_name property.
+    // Empty the field translations if $object->$field_name[$language] is NULL or an empty array.
+    if (isset($object->$field_name) && is_array($object->$field_name) && count($object->$field_name) > 0) {
       // 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();
+        $languages = array_keys($object->$field_name);
+
+        db_delete($table_name)
+          ->condition('etid', $etid)
+          ->condition('entity_id', $id)
+          ->condition('language', $languages)
+          ->execute();
+
         if (isset($vid)) {
-          db_delete($revision_name)->condition('etid', $etid)->condition('entity_id', $id)->condition('revision_id', $vid)->execute();
+          db_delete($revision_name)
+            ->condition('etid', $etid)
+            ->condition('entity_id', $id)
+            ->condition('revision_id', $vid)
+            ->condition('language', $languages)
+            ->execute();
         }
       }
 
       if ($object->$field_name) {
-        // Prepare the multi-insert query.
-        $columns = array('etid', 'entity_id', 'revision_id', 'bundle', 'delta');
+      	// Prepare the multi-insert query.
+        $columns = array('etid', 'entity_id', 'revision_id', 'bundle', 'delta', 'language');
         foreach ($field['columns'] as $column => $attributes) {
           $columns[] = _field_sql_storage_columnname($field_name, $column);
         }
@@ -293,25 +314,30 @@ function field_sql_storage_field_storage
           $revision_query = db_insert($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;
+        foreach ($object->$field_name as $language => $items) {
+          if ($items) {
+            $delta_count = 0;
+            foreach ($items as $delta => $item) {
+              $record = array(
+                'etid' => $etid,
+                'entity_id' => $id,
+                'revision_id' => $vid,
+                'bundle' => $bundle,
+                'delta' => $delta,
+                'language' => $language,
+              );
+              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;
+              }
+            }
           }
         }
 
@@ -419,10 +445,10 @@ function field_sql_storage_field_storage
 
     if ($load_values) {
       // Populate actual field values.
-      if (!isset($delta_count[$row->type][$id])) {
-        $delta_count[$row->type][$id] = 0;
+      if (!isset($delta_count[$row->type][$id][$row->language])) {
+        $delta_count[$row->type][$id][$row->language] = 0;
       }
-      if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->type][$id] < $field['cardinality']) {
+      if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->type][$id][$row->language] < $field['cardinality']) {
         $item = array();
         // For each column declared by the field, populate the item
         // from the prefixed database column.
@@ -436,8 +462,8 @@ function field_sql_storage_field_storage
           $return[$row->type][$id] = field_attach_create_stub_object($row->type, array($row->entity_id, $row->revision_id, $row->bundle));
         }
         // Add the item to the field values for the entity.
-        $return[$row->type][$id]->{$field_name}[] = $item;
-        $delta_count[$row->type][$id]++;
+        $return[$row->type][$id]->{$field_name}[$row->language][] = $item;
+        $delta_count[$row->type][$id][$row->language]++;
       }
     }
     else {
Index: modules/field/modules/field_sql_storage/field_sql_storage.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/field_sql_storage/field_sql_storage.test,v
retrieving revision 1.4
diff -u -p -r1.4 field_sql_storage.test
--- modules/field/modules/field_sql_storage/field_sql_storage.test	28 May 2009 10:05:32 -0000	1.4
+++ modules/field/modules/field_sql_storage/field_sql_storage.test	27 Jun 2009 07:45:26 -0000
@@ -56,9 +56,10 @@ class FieldSqlStorageTestCase extends Dr
   function testFieldAttachLoad() {
     $entity_type = 'test_entity';
     $eid = 0;
+    $language = FIELD_LANGUAGE_NEUTRAL;
 
     $etid = _field_sql_storage_etid($entity_type);
-    $columns = array('etid', 'entity_id', 'revision_id', 'delta', $this->field_name . '_value');
+    $columns = array('etid', 'entity_id', 'revision_id', 'delta', 'language', $this->field_name . '_value');
 
     // Insert data for four revisions to the field revisions table
     $query = db_insert($this->revision_table)->fields($columns);
@@ -68,7 +69,7 @@ class FieldSqlStorageTestCase extends Dr
       for ($delta = 0; $delta <= $this->field['cardinality']; $delta++) {
         $value = mt_rand(1, 127);
         $values[$evid][] = $value;
-        $query->values(array($etid, $eid, $evid, $delta, $value));
+        $query->values(array($etid, $eid, $evid, $delta, $language, $value));
       }
     }
     $query->execute();
@@ -76,7 +77,7 @@ class FieldSqlStorageTestCase extends Dr
     // Insert data for the "most current revision" into the field table
     $query = db_insert($this->table)->fields($columns);
     foreach ($values[0] as $delta => $value) {
-      $query->values(array($etid, $eid, 0, $delta, $value));
+      $query->values(array($etid, $eid, 0, $delta, $language, $value));
     }
     $query->execute();
 
@@ -85,10 +86,10 @@ class FieldSqlStorageTestCase extends Dr
     field_attach_load($entity_type, array($eid => $entity));
     foreach ($values[0] as $delta => $value) {
       if ($delta < $this->field['cardinality']) {
-        $this->assertEqual($entity->{$this->field_name}[$delta]['value'], $value, "Value $delta is loaded correctly for current revision");
+        $this->assertEqual($entity->{$this->field_name}[$language][$delta]['value'], $value, "Value $delta is loaded correctly for current revision");
       }
       else {
-        $this->assertFalse(array_key_exists($delta, $entity->{$this->field_name}), "No extraneous value gets loaded for current revision.");
+        $this->assertFalse(array_key_exists($delta, $entity->{$this->field_name}[$language]), "No extraneous value gets loaded for current revision.");
       }
     }
 
@@ -98,10 +99,10 @@ class FieldSqlStorageTestCase extends Dr
       field_attach_load_revision($entity_type, array($eid => $entity));
       foreach ($values[$evid] as $delta => $value) {
         if ($delta < $this->field['cardinality']) {
-          $this->assertEqual($entity->{$this->field_name}[$delta]['value'], $value, "Value $delta for revision $evid is loaded correctly");
+          $this->assertEqual($entity->{$this->field_name}[$language][$delta]['value'], $value, "Value $delta for revision $evid is loaded correctly");
         }
         else {
-          $this->assertFalse(array_key_exists($delta, $entity->{$this->field_name}), "No extraneous value gets loaded for revision $evid.");
+          $this->assertFalse(array_key_exists($delta, $entity->{$this->field_name}[$language]), "No extraneous value gets loaded for revision $evid.");
         }
       }
     }
@@ -114,6 +115,7 @@ class FieldSqlStorageTestCase extends Dr
   function testFieldAttachInsertAndUpdate() {
     $entity_type = 'test_entity';
     $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+    $language = FIELD_LANGUAGE_NEUTRAL;
 
     // Test insert.
     $values = array();
@@ -122,7 +124,7 @@ class FieldSqlStorageTestCase extends Dr
     for ($delta = 0; $delta <= $this->field['cardinality']; $delta++) {
       $values[$delta]['value'] = mt_rand(1, 127);
     }
-    $entity->{$this->field_name} = $rev_values[0] = $values;
+    $entity->{$this->field_name}[$language] = $rev_values[0] = $values;
     field_attach_insert($entity_type, $entity);
 
     $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC);
@@ -142,7 +144,7 @@ class FieldSqlStorageTestCase extends Dr
     for ($delta = 0; $delta <= $this->field['cardinality']; $delta++) {
       $values[$delta]['value'] = mt_rand(1, 127);
     }
-    $entity->{$this->field_name} = $rev_values[1] = $values;
+    $entity->{$this->field_name}[$language] = $rev_values[1] = $values;
     field_attach_update($entity_type, $entity);
     $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC);
     foreach ($values as $delta => $value) {
@@ -170,9 +172,9 @@ class FieldSqlStorageTestCase extends Dr
     }
     $this->assertTrue(empty($rev_values), "All values for all revisions are stored in revision table {$this->revision_table}");
 
-    // Check that update leaves the field data untouched if $object has no
-    // $field_name key.
-    unset($entity->{$this->field_name});
+    // Check that update leaves the field data untouched if $object->{$field_name} has no
+    // language key.
+    unset($entity->{$this->field_name}[$language]);
     field_attach_update($entity_type, $entity);
     $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC);
     foreach ($values as $delta => $value) {
@@ -182,7 +184,7 @@ class FieldSqlStorageTestCase extends Dr
     }
 
     // Check that update with an empty $object->$field_name empties the field.
-    $entity->{$this->field_name} = NULL;
+    $entity->{$this->field_name}[$language] = NULL;
     field_attach_update($entity_type, $entity);
     $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC);
     $this->assertEqual(count($rows), 0, t("Update with an empty field_name entry empties the field."));
@@ -194,6 +196,7 @@ class FieldSqlStorageTestCase extends Dr
   function testFieldAttachSaveMissingData() {
     $entity_type = 'test_entity';
     $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+    $language = FIELD_LANGUAGE_NEUTRAL;
 
     // Insert: Field is missing
     field_attach_insert($entity_type, $entity);
@@ -201,25 +204,25 @@ class FieldSqlStorageTestCase extends Dr
     $this->assertEqual($count, 0, 'Missing field results in no inserts');
 
     // Insert: Field is NULL
-    $entity->{$this->field_name} = NULL;
+    $entity->{$this->field_name}[$language] = NULL;
     field_attach_insert($entity_type, $entity);
     $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}"));
     $this->assertEqual($count, 0, 'NULL field results in no inserts');
 
     // Add some real data
-    $entity->{$this->field_name} = array(0 => array('value' => 1));
+    $entity->{$this->field_name}[$language] = array(0 => array('value' => 1));
     field_attach_insert($entity_type, $entity);
     $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}"));
     $this->assertEqual($count, 1, 'Field data saved');
 
-    // Update: Field is missing. Data should survive.
-    unset($entity->{$this->field_name});
+    // Update: Field translation is missing. Data should survive.
+    unset($entity->{$this->field_name}[$language]);
     field_attach_update($entity_type, $entity);
     $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}"));
     $this->assertEqual($count, 1, 'Missing field leaves data in table');
 
     // Update: Field is NULL. Data should be wiped.
-    $entity->{$this->field_name} = NULL;
+    $entity->{$this->field_name}[$language] = NULL;
     field_attach_update($entity_type, $entity);
     $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}"));
     $this->assertEqual($count, 0, 'NULL field leaves no data in table');
Index: modules/field/modules/list/list.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/list/list.module,v
retrieving revision 1.6
diff -u -p -r1.6 list.module
--- modules/field/modules/list/list.module	27 May 2009 18:33:56 -0000	1.6
+++ modules/field/modules/list/list.module	27 Jun 2009 07:45:26 -0000
@@ -103,12 +103,12 @@ function list_field_schema($field) {
  * Possible error codes:
  * - 'list_illegal_value': The value is not part of the list of allowed values.
  */
-function list_field_validate($obj_type, $object, $field, $instance, $items, &$errors) {
+function list_field_validate($obj_type, $object, $field, $instance, $language, $items, &$errors) {
   $allowed_values = list_allowed_values($field);
   foreach ($items as $delta => $item) {
     if (!empty($item['value'])) {
       if (count($allowed_values) && !array_key_exists($item['value'], $allowed_values)) {
-        $errors[$field['field_name']][$delta][] = array(
+        $errors[$field['field_name']][$language][$delta][] = array(
           'error' => 'list_illegal_value',
           'message' => t('%name: illegal value.', array('%name' => t($instance['label']))),
         );
Index: modules/field/modules/number/number.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/number/number.module,v
retrieving revision 1.10
diff -u -p -r1.10 number.module
--- modules/field/modules/number/number.module	28 May 2009 16:44:06 -0000	1.10
+++ modules/field/modules/number/number.module	27 Jun 2009 07:45:26 -0000
@@ -94,17 +94,17 @@ function number_field_schema($field) {
  * - 'number_min': The value is smaller than the allowed minimum value.
  * - 'number_max': The value is larger than the allowed maximum value.
  */
-function number_field_validate($obj_type, $node, $field, $instance, $items, &$errors) {
+function number_field_validate($obj_type, $node, $field, $instance, $language, $items, &$errors) {
   foreach ($items as $delta => $item) {
     if ($item['value'] != '') {
       if (is_numeric($instance['settings']['min']) && $item['value'] < $instance['settings']['min']) {
-        $errors[$field['field_name']][$delta][] = array(
+        $errors[$field['field_name']][$language][$delta][] = array(
           'error' => 'number_min',
           'message' => t('%name: the value may be no smaller than %min.', array('%name' => t($instance['label']), '%min' => $instance['settings']['min'])),
         );
       }
       if (is_numeric($instance['settings']['max']) && $item['value'] > $instance['settings']['max']) {
-        $errors[$field['field_name']][$delta][] = array(
+        $errors[$field['field_name']][$language][$delta][] = array(
           'error' => 'number_max',
           'message' => t('%name: the value may be no larger than %max.', array('%name' => t($instance['label']), '%max' => $instance['settings']['max'])),
         );
Index: modules/field/modules/text/text.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/text/text.module,v
retrieving revision 1.13
diff -u -p -r1.13 text.module
--- modules/field/modules/text/text.module	22 Jun 2009 09:10:04 -0000	1.13
+++ modules/field/modules/text/text.module	27 Jun 2009 07:45:26 -0000
@@ -135,7 +135,7 @@ function text_field_schema($field) {
  * - 'text_value_max_length': The value exceeds the maximum length.
  * - 'text_summary_max_length': The summary exceeds the maximum length.
  */
-function text_field_validate($obj_type, $object, $field, $instance, $items, &$errors) {
+function text_field_validate($obj_type, $object, $field, $instance, $language, $items, &$errors) {
   foreach ($items as $delta => $item) {
     foreach (array('value' => t('full text'), 'summary' => t('summary')) as $column => $desc) {
       if (!empty($item[$column])) {
@@ -148,7 +148,7 @@ function text_field_validate($obj_type, 
               $message = t('%name: the summary may not be longer than %max characters.', array('%name' => $instance['label'], '%max' => $field['settings']['max_length']));
               break;
           }
-          $errors[$field['field_name']][$delta][] = array(
+          $errors[$field['field_name']][$language][$delta][] = array(
             'error' => "text_{$column}_length",
             'message' => $message,
           );
@@ -166,9 +166,7 @@ function text_field_validate($obj_type, 
  * separately.
  * @see text_field_sanitize().
  */
-function text_field_load($obj_type, $objects, $field, $instances, &$items) {
-  global $language;
-
+function text_field_load($obj_type, $objects, $field, $instances, $language, &$items) {
   foreach ($objects as $id => $object) {
     foreach ($items[$id] as $delta => $item) {
       if (!empty($instances[$id]['settings']['text_processing'])) {
@@ -176,10 +174,9 @@ function text_field_load($obj_type, $obj
         // handled by text_field_sanitize().
         $format = $item['format'];
         if (filter_format_allowcache($format)) {
-          $lang = isset($object->language) ? $object->language : $language->language;
-          $items[$id][$delta]['safe'] = isset($item['value']) ? check_markup($item['value'], $format, $lang, FALSE, FALSE) : '';
+          $items[$id][$delta]['safe'] = isset($item['value']) ? check_markup($item['value'], $format, $language, FALSE, FALSE) : '';
           if ($field['type'] == 'text_with_summary') {
-            $items[$id][$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format, $lang, FALSE, FALSE) : '';
+            $items[$id][$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format, $language, FALSE, FALSE) : '';
           }
         }
       }
@@ -198,7 +195,7 @@ function text_field_load($obj_type, $obj
  *
  * @see text_field_load()
  */
-function text_field_sanitize($obj_type, $object, $field, $instance, &$items) {
+function text_field_sanitize($obj_type, $object, $field, $instance, $language, &$items) {
   global $language;
   foreach ($items as $delta => $item) {
     // Only sanitize items which were not already processed inside
Index: modules/field/modules/text/text.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/text/text.test,v
retrieving revision 1.7
diff -u -p -r1.7 text.test
--- modules/field/modules/text/text.test	12 Jun 2009 08:39:37 -0000	1.7
+++ modules/field/modules/text/text.test	27 Jun 2009 07:45:26 -0000
@@ -50,8 +50,9 @@ class TextFieldTestCase extends DrupalWe
     field_create_instance($this->instance);
     // Test valid and invalid values with field_attach_validate().
     $entity = field_test_create_stub_entity(0, 0, FIELD_TEST_BUNDLE);
+    $language = FIELD_LANGUAGE_NEUTRAL;
     for ($i = 0; $i <= $max_length + 2; $i++) {
-      $entity->{$this->field['field_name']}[0]['value'] = str_repeat('x', $i);
+      $entity->{$this->field['field_name']}[$language][0]['value'] = str_repeat('x', $i);
       try {
         field_attach_validate('test_entity', $entity);
         $this->assertTrue($i <= $max_length, "Length $i does not cause validation error when max_length is $max_length");
@@ -91,16 +92,17 @@ class TextFieldTestCase extends DrupalWe
       )
     );
     field_create_instance($this->instance);
+    $language = FIELD_LANGUAGE_NEUTRAL;
 
     // Display creation form.
     $this->drupalGet('test-entity/add/test-bundle');
-    $this->assertFieldByName($this->field_name . '[0][value]', '', t('Widget is displayed'));
-    $this->assertNoFieldByName($this->field_name . '[0][format]', '1', t('Format selector is not displayed'));
+    $this->assertFieldByName($this->field_name . '[' . $language . '][0][value]', '', t('Widget is displayed'));
+    $this->assertNoFieldByName($this->field_name . '[' . $language . '][0][format]', '1', t('Format selector is not displayed'));
 
     // Submit with some value.
     $value = $this->randomName();
     $edit = array(
-      $this->field_name . '[0][value]' => $value,
+      $this->field_name . '[' . $language . '][0][value]' => $value,
     );
     $this->drupalPost(NULL, $edit, t('Save'));
     preg_match('|test-entity/(\d+)/edit|', $this->url, $match);
@@ -143,18 +145,19 @@ class TextFieldTestCase extends DrupalWe
       )
     );
     field_create_instance($this->instance);
+    $language = FIELD_LANGUAGE_NEUTRAL;
 
     // Display creation form.
     // By default, the user only has access to 'Filtered HTML', and no format
     // selector is displayed
     $this->drupalGet('test-entity/add/test-bundle');
-    $this->assertFieldByName($this->field_name . '[0][value]', '', t('Widget is displayed'));
-    $this->assertNoFieldByName($this->field_name . '[0][value_format]', '1', t('Format selector is not displayed'));
+    $this->assertFieldByName($this->field_name . '[' . $language . '][0][value]', '', t('Widget is displayed'));
+    $this->assertNoFieldByName($this->field_name . '[' . $language . '][0][value_format]', '1', t('Format selector is not displayed'));
 
     // Submit with data that should be filtered.
     $value = $this->randomName() . '<br />' . $this->randomName();
     $edit = array(
-      $this->field_name . '[0][value]' => $value,
+      $this->field_name . '[' . $language . '][0][value]' => $value,
     );
     $this->drupalPost(NULL, $edit, t('Save'));
     preg_match('|test-entity/(\d+)/edit|', $this->url, $match);
@@ -174,12 +177,12 @@ class TextFieldTestCase extends DrupalWe
     // Display edition form.
     // We should now have a 'text format' selector.
     $this->drupalGet('test-entity/' . $id . '/edit');
-    $this->assertFieldByName($this->field_name . '[0][value]', '', t('Widget is displayed'));
-    $this->assertFieldByName($this->field_name . '[0][value_format]', '1', t('Format selector is displayed'));
+    $this->assertFieldByName($this->field_name . '[' . $language . '][0][value]', '', t('Widget is displayed'));
+    $this->assertFieldByName($this->field_name . '[' . $language . '][0][value_format]', '1', t('Format selector is displayed'));
 
     // Edit and change the format to 'Full HTML'.
     $edit = array(
-      $this->field_name . '[0][value_format]' => 2,
+      $this->field_name . '[' . $language . '][0][value_format]' => 2,
     );
     $this->drupalPost(NULL, $edit, t('Save'));
     $this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), t('Entity was updated'));
Index: modules/filter/filter.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.test,v
retrieving revision 1.23
diff -u -p -r1.23 filter.test
--- modules/filter/filter.test	12 Jun 2009 08:39:37 -0000	1.23
+++ modules/filter/filter.test	27 Jun 2009 07:45:26 -0000
@@ -109,8 +109,8 @@ class FilterAdminTestCase extends Drupal
 
     $edit = array();
     $edit['title'] = $this->randomName();
-    $edit['body[0][value]'] = $body . '<random>' . $extra_text . '</random>';
-    $edit['body[0][value_format]'] = $filtered;
+    $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $body . '<random>' . $extra_text . '</random>';
+    $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value_format]'] = $filtered;
     $this->drupalPost('node/add/page', $edit, t('Save'));
     $this->assertRaw(t('Page %title has been created.', array('%title' => $edit['title'])), t('Filtered node created.'));
 
Index: modules/forum/forum.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/forum/forum.test,v
retrieving revision 1.22
diff -u -p -r1.22 forum.test
--- modules/forum/forum.test	19 Jun 2009 06:28:45 -0000	1.22
+++ modules/forum/forum.test	27 Jun 2009 07:45:26 -0000
@@ -234,7 +234,7 @@ class ForumTestCase extends DrupalWebTes
 
     $edit = array(
       'title' => $title,
-      'body[0][value]' => $body,
+      'body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]' => $body,
       'taxonomy[1]' => $tid
     );
 
@@ -323,7 +323,7 @@ class ForumTestCase extends DrupalWebTes
       // Edit forum node (including moving it to another forum).
       $edit = array();
       $edit['title'] = 'node/' . $node->nid;
-      $edit['body[0][value]'] = $this->randomName(256);
+      $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $this->randomName(256);
       $edit['taxonomy[1]'] = $this->root_forum['tid']; // Assumes the topic is initially associated with $forum.
       $edit['shadow'] = TRUE;
       $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
Index: modules/locale/locale.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.test,v
retrieving revision 1.27
diff -u -p -r1.27 locale.test
--- modules/locale/locale.test	20 Jun 2009 15:17:38 -0000	1.27
+++ modules/locale/locale.test	27 Jun 2009 07:45:26 -0000
@@ -1395,7 +1395,7 @@ class LocaleContentFunctionalTest extend
     $edit = array(
       'type' => 'page',
       'title' => $node_title,
-      'body' => array(array('value' => $node_body)),
+      'body' => array(FIELD_LANGUAGE_NEUTRAL => array(array('value' => $node_body))),
       'language' => $langcode,
     );
     $node = $this->drupalCreateNode($edit);
Index: modules/node/node.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.install,v
retrieving revision 1.24
diff -u -p -r1.24 node.install
--- modules/node/node.install	18 Jun 2009 15:46:30 -0000	1.24
+++ modules/node/node.install	27 Jun 2009 07:45:26 -0000
@@ -481,15 +481,15 @@ function node_update_7005(&$context) {
             'type' => $revision->type,
           );
           if (!empty($revision->teaser) && $revision->teaser != text_summary($revision->body)) {
-            $node->body[0]['summary'] = $revision->teaser;
+            $node->body[FIELD_LANGUAGE_NEUTRAL][0]['summary'] = $revision->teaser;
           }
           // Do this after text_summary() above.
           $break = '<!--break-->';
           if (substr($revision->body, 0, strlen($break)) == $break) {
             $revision->body = substr($revision->body, strlen($break));
           }
-          $node->body[0]['value'] = $revision->body;
-          $node->body[0]['format'] = $revision->format;
+          $node->body[FIELD_LANGUAGE_NEUTRAL][0]['value'] = $revision->body;
+          $node->body[FIELD_LANGUAGE_NEUTRAL][0]['format'] = $revision->format;
           // This is a core update and no contrib modules are enabled yet, so
           // we can assume default field storage for a faster update.
           field_sql_storage_field_storage_write('node', $node, FIELD_STORAGE_INSERT, array());
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1073
diff -u -p -r1.1073 node.module
--- modules/node/node.module	24 Jun 2009 18:16:38 -0000	1.1073
+++ modules/node/node.module	27 Jun 2009 07:45:26 -0000
@@ -855,7 +855,7 @@ function node_validate($node, $form = ar
 
   // Make sure the body has the minimum number of words.
   // TODO : use a better word counting algorithm that will work in other languages
-  if (!empty($type->min_word_count) && isset($node->body[0]['value']) && count(explode(' ', $node->body[0]['value'])) < $type->min_word_count) {
+  if (!empty($type->min_word_count) && isset($node->body[FIELD_LANGUAGE_NEUTRAL][0]['value']) && count(explode(' ', $node->body[FIELD_LANGUAGE_NEUTRAL][0]['value'])) < $type->min_word_count) {
     // TODO: Use Field API to set this error.
     form_set_error('body', t('The body of your @type is too short. You need at least %words words.', array('%words' => $type->min_word_count, '@type' => $type->name)));
   }
Index: modules/node/node.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.test,v
retrieving revision 1.33
diff -u -p -r1.33 node.test
--- modules/node/node.test	22 Jun 2009 09:10:05 -0000	1.33
+++ modules/node/node.test	27 Jun 2009 07:45:26 -0000
@@ -141,7 +141,7 @@ class NodeRevisionsTestCase extends Drup
 
     // Confirm the correct revision text appears on "view revisions" page.
     $this->drupalGet("node/$node->nid/revisions/$node->vid/view");
-    $this->assertText($node->body[0]['value'], t('Correct text displays for version.'));
+    $this->assertText($node->body[FIELD_LANGUAGE_NEUTRAL][0]['value'], t('Correct text displays for version.'));
 
     // Confirm the correct log message appears on "revisions overview" page.
     $this->drupalGet("node/$node->nid/revisions");
@@ -155,7 +155,7 @@ class NodeRevisionsTestCase extends Drup
                         array('@type' => 'Page', '%title' => $nodes[1]->title,
                               '%revision-date' => format_date($nodes[1]->revision_timestamp))), t('Revision reverted.'));
     $reverted_node = node_load($node->nid);
-    $this->assertTrue(($nodes[1]->body[0]['value'] == $reverted_node->body[0]['value']), t('Node reverted correctly.'));
+    $this->assertTrue(($nodes[1]->body[FIELD_LANGUAGE_NEUTRAL][0]['value'] == $reverted_node->body[FIELD_LANGUAGE_NEUTRAL][0]['value']), t('Node reverted correctly.'));
 
     // Confirm revisions delete properly.
     $this->drupalPost("node/$node->nid/revisions/{$nodes[1]->vid}/delete", array(), t('Delete'));
@@ -186,7 +186,7 @@ class PageEditTestCase extends DrupalWeb
    * Check node edit functionality.
    */
   function testPageEdit() {
-    $body_key = 'body[0][value]';
+    $body_key = 'body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]';
     // Create node to edit.
     $edit = array();
     $edit['title'] = $this->randomName(8);
@@ -241,7 +241,7 @@ class PagePreviewTestCase extends Drupal
    * Check the node preview functionality.
    */
   function testPagePreview() {
-    $body_key = 'body[0][value]';
+    $body_key = 'body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]';
 
     // Fill in node creation form and preview node.
     $edit = array();
@@ -263,7 +263,7 @@ class PagePreviewTestCase extends Drupal
    * Check the node preview functionality, when using revisions.
    */
   function testPagePreviewWithRevisions() {
-    $body_key = 'body[0][value]';
+    $body_key = 'body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]';
     // Force revision on page content.
     variable_set('node_options_page', array('status', 'revision'));
 
@@ -311,7 +311,7 @@ class PageCreationTestCase extends Drupa
     // Create a node.
     $edit = array();
     $edit['title'] = $this->randomName(8);
-    $edit['body[0][value]'] = $this->randomName(16);
+    $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $this->randomName(16);
     $this->drupalPost('node/add/page', $edit, t('Save'));
 
     // Check that the page has been created.
@@ -458,7 +458,7 @@ class NodePostSettingsTestCase extends D
     // Create a node.
     $edit = array();
     $edit['title'] = $this->randomName(8);
-    $edit['body[0][value]'] = $this->randomName(16);
+    $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $this->randomName(16);
     $this->drupalPost('node/add/page', $edit, t('Save'));
 
     // Check that the post information is displayed.
@@ -479,7 +479,7 @@ class NodePostSettingsTestCase extends D
     // Create a node.
     $edit = array();
     $edit['title'] = $this->randomName(8);
-    $edit['body[0][value]'] = $this->randomName(16);
+    $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $this->randomName(16);
     $this->drupalPost('node/add/page', $edit, t('Save'));
 
     // Check that the post information is displayed.
@@ -645,7 +645,7 @@ class NodeSaveTestCase extends DrupalWeb
     $title = $this->randomName(8);
     $node = array(
       'title' => $title,
-      'body' => array(array('value' => $this->randomName(32))),
+      'body' => array(FIELD_LANGUAGE_NEUTRAL => array(array('value' => $this->randomName(32)))),
       'uid' => $this->web_user->uid,
       'type' => 'article',
       'nid' => $test_nid,
Index: modules/path/path.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/path/path.test,v
retrieving revision 1.13
diff -u -p -r1.13 path.test
--- modules/path/path.test	12 Jun 2009 08:39:38 -0000	1.13
+++ modules/path/path.test	27 Jun 2009 07:45:27 -0000
@@ -212,7 +212,7 @@ class PathLanguageTestCase extends Drupa
     $this->clickLink(t('add translation'));
     $edit = array();
     $edit['title'] = $this->randomName();
-    $edit['body[0][value]'] = $this->randomName();
+    $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $this->randomName();
     $edit['path'] = $this->randomName();
     $this->drupalPost(NULL, $edit, t('Save'));
 
Index: modules/php/php.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/php/php.test,v
retrieving revision 1.13
diff -u -p -r1.13 php.test
--- modules/php/php.test	27 Jun 2009 02:05:55 -0000	1.13
+++ modules/php/php.test	27 Jun 2009 07:45:27 -0000
@@ -23,7 +23,7 @@ class PHPTestCase extends DrupalWebTestC
    * @return stdObject Node object.
    */
   function createNodeWithCode() {
-    return $this->drupalCreateNode(array('body' => array(array('value' => '<?php print "SimpleTest PHP was executed!"; ?>'))));
+    return $this->drupalCreateNode(array('body' => array(FIELD_LANGUAGE_NEUTRAL => array(array('value' => '<?php print "SimpleTest PHP was executed!"; ?>')))));
   }
 }
 
@@ -60,7 +60,7 @@ class PHPFilterTestCase extends PHPTestC
 
     // Change filter to PHP filter and see that PHP code is evaluated.
     $edit = array();
-    $edit['body[0][value_format]'] = 3;
+    $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value_format]'] = 3;
     $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
     $this->assertRaw(t('Page %title has been updated.', array('%title' => $node->title)), t('PHP code filter turned on.'));
 
Index: modules/search/search.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/search/search.test,v
retrieving revision 1.23
diff -u -p -r1.23 search.test
--- modules/search/search.test	12 Jun 2009 08:39:38 -0000	1.23
+++ modules/search/search.test	27 Jun 2009 07:45:27 -0000
@@ -312,7 +312,7 @@ class SearchRankingTestCase extends Drup
 
     // Create nodes for testing.
     foreach ($node_ranks as $node_rank) {
-      $settings = array('type' => 'page', 'title' => 'Drupal rocks', 'body' => array(array('value' => "Drupal's search rocks")));
+      $settings = array('type' => 'page', 'title' => 'Drupal rocks', 'body' => array(FIELD_LANGUAGE_NEUTRAL => array(array('value' => "Drupal's search rocks"))));
       foreach (array(0, 1) as $num) {
         if ($num == 1) {
           switch ($node_rank) {
@@ -321,7 +321,7 @@ class SearchRankingTestCase extends Drup
               $settings[$node_rank] = 1;
               break;
             case 'relevance':
-              $settings['body'][0]['value'] .= " really rocks";
+              $settings['body'][FIELD_LANGUAGE_NEUTRAL][0]['value'] .= " really rocks";
               break;
             case 'recent':
               $settings['created'] = REQUEST_TIME + 3600;
Index: modules/simpletest/drupal_web_test_case.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v
retrieving revision 1.117
diff -u -p -r1.117 drupal_web_test_case.php
--- modules/simpletest/drupal_web_test_case.php	16 Jun 2009 04:43:47 -0000	1.117
+++ modules/simpletest/drupal_web_test_case.php	27 Jun 2009 07:45:27 -0000
@@ -639,7 +639,7 @@ class DrupalWebTestCase extends DrupalTe
   protected function drupalCreateNode($settings = array()) {
     // Populate defaults array.
     $settings += array(
-      'body'      => array(array()),
+      'body'      => array(FIELD_LANGUAGE_NEUTRAL => array(array())),
       'title'     => $this->randomName(8),
       'comment'   => 2,
       'changed'   => REQUEST_TIME,
@@ -676,7 +676,7 @@ class DrupalWebTestCase extends DrupalTe
       'value' => $this->randomName(32),
       'format' => FILTER_FORMAT_DEFAULT
     );
-    $settings['body'][0] += $body;
+    $settings['body'][FIELD_LANGUAGE_NEUTRAL][0] += $body;
 
     $node = (object) $settings;
     node_save($node);
Index: modules/simpletest/tests/common.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/common.test,v
retrieving revision 1.46
diff -u -p -r1.46 common.test
--- modules/simpletest/tests/common.test	12 Jun 2009 08:39:39 -0000	1.46
+++ modules/simpletest/tests/common.test	27 Jun 2009 07:45:27 -0000
@@ -261,7 +261,7 @@ class CascadingStylesheetsTestCase exten
     // Create a node, using the PHP filter that tests drupal_add_css().
     $settings = array(
       'type' => 'page',
-      'body' => array(array('value' => t('This tests the inline CSS!') . "<?php drupal_add_css('$css', 'inline'); ?>", 'format' => 3)), // PHP filter.
+      'body' => array(FIELD_LANGUAGE_NEUTRAL => array(array('value' => t('This tests the inline CSS!') . "<?php drupal_add_css('$css', 'inline'); ?>", 'format' => 3))), // PHP filter.
       'promote' => 1,
     );
     $node = $this->drupalCreateNode($settings);
Index: modules/simpletest/tests/field_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/field_test.module,v
retrieving revision 1.9
diff -u -p -r1.9 field_test.module
--- modules/simpletest/tests/field_test.module	27 May 2009 18:34:00 -0000	1.9
+++ modules/simpletest/tests/field_test.module	27 Jun 2009 07:45:27 -0000
@@ -371,10 +371,10 @@ function field_test_field_schema($field)
  * Possible error codes:
  * - 'field_test_invalid': The value is invalid.
  */
-function field_test_field_validate($obj_type, $object, $field, $instance, $items, &$errors) {
+function field_test_field_validate($obj_type, $object, $field, $instance, $language, $items, &$errors) {
   foreach ($items as $delta => $item) {
     if ($item['value'] == -1) {
-      $errors[$field['field_name']][$delta][] = array(
+      $errors[$field['field_name']][$language][$delta][] = array(
         'error' => 'field_test_invalid',
         'message' => t('%name does not accept the value -1.', array('%name' => $instance['label'])),
       );
@@ -385,7 +385,7 @@ function field_test_field_validate($obj_
 /**
  * Implement hook_field_sanitize().
  */
-function field_test_field_sanitize($obj_type, $object, $field, $instance, &$items) {
+function field_test_field_sanitize($obj_type, $object, $field, $instance, $language, &$items) {
   foreach ($items as $delta => $item) {
     $value = check_plain($item['value']);
     $items[$delta]['safe'] = $value;
@@ -456,8 +456,8 @@ function field_test_field_widget_info() 
  *   holds the field's form values.
  * @param $field
  *   The field structure.
- * @param $insatnce
- *   the insatnce array
+ * @param $instance
+ *   the instance array
  * @param $items
  *   array of default values for this field
  * @param $delta
@@ -516,7 +516,7 @@ function field_test_field_formatter_info
 /**
  * Implement hook_field_load().
  */
-function field_test_field_load($obj_type, $objects, $field, $instances, &$items, $age) {
+function field_test_field_load($obj_type, $objects, $field, $instances, $language, &$items, $age) {
   foreach ($items as $id => $item) {
     // To keep the test non-intrusive, only act for instances with the
     // test_hook_field_load setting explicitly set to TRUE.
@@ -575,3 +575,33 @@ function theme_field_formatter_field_tes
 function field_test_default_value($obj_type, $object, $field, $instance) {
   return array(array('value' => 99));
 }
+
+/**
+ * Generic op to test _field_invoke behavior.
+ */
+function field_test_field_test_op($obj_type, $object, $field, $instance, $language, &$items) {
+  return array($language => md5(serialize(array($obj_type, $object, $field['field_name'], $language, $items))));
+}
+
+/**
+ * Generic op to test _field_invoke_multiple behavior.
+ */
+function field_test_field_test_op_multiple($obj_type, $objects, $field, $instances, $language, &$items) {
+  $result = array();
+  foreach ($objects as $id => $object) {
+    $result[$id] = array($language => md5(serialize(array($obj_type, $object, $field['field_name'], $language, $items[$id]))));
+  }
+  return $result;
+}
+
+/**
+ * Implementation of hook_field_languages
+ */
+function field_test_field_languages($field, $instance, &$languages) {
+  if ($field['settings']['test_hook_in']) {
+    // Add an unavailable language.
+    $languages[] = 'xx';
+    // Remove an available language.
+    unset($languages[0]);
+  }
+}
Index: modules/system/system.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.test,v
retrieving revision 1.50
diff -u -p -r1.50 system.test
--- modules/system/system.test	13 Jun 2009 21:08:31 -0000	1.50
+++ modules/system/system.test	27 Jun 2009 07:45:27 -0000
@@ -518,7 +518,7 @@ class AccessDeniedTestCase extends Drupa
 
     $edit = array(
       'title' => $this->randomName(10),
-      'body' => array(array('value' => $this->randomName(100))),
+      'body' => array(FIELD_LANGUAGE_NEUTRAL => array(array('value' => $this->randomName(100)))),
     );
     $node = $this->drupalCreateNode($edit);
 
@@ -579,7 +579,7 @@ class PageNotFoundTestCase extends Drupa
 
     $edit = array(
       'title' => $this->randomName(10),
-      'body' => array(array('value' => $this->randomName(100))),
+      'body' => array(FIELD_LANGUAGE_NEUTRAL => array(array('value' => $this->randomName(100)))),
     );
     $node = $this->drupalCreateNode($edit);
 
@@ -705,7 +705,7 @@ class PageTitleFiltering extends DrupalW
     // Generate node content.
     $edit = array(
      'title' => '!SimpleTest! ' . $title . $this->randomName(20),
-     'body[0][value]' => '!SimpleTest! test body' . $this->randomName(200),
+     'body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]' => '!SimpleTest! test body' . $this->randomName(200),
     );
     // Create the node with HTML in the title.
     $this->drupalPost('node/add/page', $edit, t('Save'));
Index: modules/taxonomy/taxonomy.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.test,v
retrieving revision 1.37
diff -u -p -r1.37 taxonomy.test
--- modules/taxonomy/taxonomy.test	21 Jun 2009 14:13:59 -0000	1.37
+++ modules/taxonomy/taxonomy.test	27 Jun 2009 07:45:27 -0000
@@ -477,7 +477,7 @@ class TaxonomyTermTestCase extends Taxon
     // Post an article.
     $edit = array();
     $edit['title'] = $this->randomName();
-    $edit['body[0][value]'] = $this->randomName();
+    $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $this->randomName();
     $edit['taxonomy[' . $this->vocabulary->vid . ']'] = $term1->tid;
     $this->drupalPost('node/add/article', $edit, t('Save'));
 
@@ -519,7 +519,7 @@ class TaxonomyTermTestCase extends Taxon
     // Insert the terms in a comma separated list. Vocabulary 1 is a
     // free-tagging field created by the default profile.
     $edit['taxonomy[tags][' . $this->vocabulary->vid . ']'] =  implode(', ', $terms);
-    $edit['body[0][value]'] = $this->randomName();
+    $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $this->randomName();
     $this->drupalPost('node/add/article', $edit, t('Save'));
     $this->assertRaw(t('@type %title has been created.', array('@type' => t('Article'), '%title' => $edit['title'])), t('The node was created successfully'));
     foreach ($terms as $term) {
Index: modules/translation/translation.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/translation/translation.test,v
retrieving revision 1.12
diff -u -p -r1.12 translation.test
--- modules/translation/translation.test	12 Jun 2009 08:39:40 -0000	1.12
+++ modules/translation/translation.test	27 Jun 2009 07:45:27 -0000
@@ -60,14 +60,14 @@ class TranslationTestCase extends Drupal
     // to return to the page then resubmitting the form without a refresh.
     $edit = array();
     $edit['title'] = $this->randomName();
-    $edit['body[0][value]'] = $this->randomName();
+    $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $this->randomName();
     $this->drupalPost('node/add/page', $edit, t('Save'), array('query' => array('translation' => $node->nid, 'language' => 'es')));
     $duplicate = $this->drupalGetNodeByTitle($edit['title']);
     $this->assertEqual($duplicate->tnid, 0, t('The node does not have a tnid.'));
 
     // Update original and mark translation as outdated.
     $edit = array();
-    $edit['body[0][value]'] = $this->randomName();
+    $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $this->randomName();
     $edit['translation[retranslate]'] = TRUE;
     $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
     $this->assertRaw(t('Page %title has been updated.', array('%title' => $node_title)), t('Original node updated.'));
@@ -78,7 +78,7 @@ class TranslationTestCase extends Drupal
 
     // Update translation and mark as updated.
     $edit = array();
-    $edit['body[0][value]'] = $this->randomName();
+    $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $this->randomName();
     $edit['translation[status]'] = FALSE;
     $this->drupalPost('node/' . $node_translation->nid . '/edit', $edit, t('Save'));
     $this->assertRaw(t('Page %title has been updated.', array('%title' => $node_translation_title)), t('Translated node updated.'));
@@ -128,7 +128,7 @@ class TranslationTestCase extends Drupal
   function createPage($title, $body, $language) {
     $edit = array();
     $edit['title'] = $title;
-    $edit['body[0][value]'] = $body;
+    $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $body;
     $edit['language'] = $language;
     $this->drupalPost('node/add/page', $edit, t('Save'));
     $this->assertRaw(t('Page %title has been created.', array('%title' => $edit['title'])), t('Page created.'));
@@ -153,7 +153,7 @@ class TranslationTestCase extends Drupal
 
     $edit = array();
     $edit['title'] = $title;
-    $edit['body[0][value]'] = $body;
+    $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $body;
     $this->drupalPost(NULL, $edit, t('Save'));
     $this->assertRaw(t('Page %title has been created.', array('%title' => $edit['title'])), t('Translation created.'));
 
Index: modules/trigger/trigger.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/trigger/trigger.test,v
retrieving revision 1.12
diff -u -p -r1.12 trigger.test
--- modules/trigger/trigger.test	12 Jun 2009 08:39:40 -0000	1.12
+++ modules/trigger/trigger.test	27 Jun 2009 07:45:27 -0000
@@ -38,7 +38,7 @@ class TriggerContentTestCase extends Dru
       $this->drupalLogin($web_user);
       $edit = array();
       $edit['title'] = '!SimpleTest test node! ' . $this->randomName(10);
-      $edit['body[0][value]'] = '!SimpleTest test body! ' . $this->randomName(32) . ' ' . $this->randomName(32);
+      $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = '!SimpleTest test body! ' . $this->randomName(32) . ' ' . $this->randomName(32);
       $edit[$info['property']] = !$info['expected'];
       $this->drupalPost('node/add/page', $edit, t('Save'));
       // Make sure the text we want appears.
