Index: includes/locale.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/locale.inc,v
retrieving revision 1.202
diff -u -p -r1.202 locale.inc
--- includes/locale.inc	5 Feb 2009 00:32:46 -0000	1.202
+++ includes/locale.inc	12 Feb 2009 12:31:13 -0000
@@ -88,7 +88,7 @@ function theme_locale_languages_overview
           array('data' => drupal_render($form['enabled'][$key]), 'align' => 'center'),
           drupal_render($form['site_default'][$key]),
           drupal_render($form['weight'][$key]),
-          l(t('edit'), 'admin/settings/language/edit/' . $key) . (($key != 'en' && $key != $default->language) ? ' ' . l(t('delete'), 'admin/settings/language/delete/' . $key) : '')
+          l(t('edit'), 'admin/international/language/edit/' . $key) . (($key != 'en' && $key != $default->language) ? ' ' . l(t('delete'), 'admin/international/language/delete/' . $key) : '')
         ),
         'class' => 'draggable'
       );
@@ -135,7 +135,7 @@ function locale_languages_overview_form_
   // Changing the language settings impacts the interface.
   cache_clear_all('*', 'cache_page', TRUE);
 
-  $form_state['redirect'] = 'admin/settings/language';
+  $form_state['redirect'] = 'admin/international/language';
   return;
 }
 /**
@@ -336,7 +336,7 @@ function locale_languages_predefined_for
     batch_set($batch);
   }
 
-  $form_state['redirect'] = 'admin/settings/language';
+  $form_state['redirect'] = 'admin/international/language';
   return;
 }
 
@@ -373,7 +373,7 @@ function locale_languages_edit_form_subm
     }
     variable_set('language_default', $default);
   }
-  $form_state['redirect'] = 'admin/settings/language';
+  $form_state['redirect'] = 'admin/international/language';
   return;
 }
 /**
@@ -393,12 +393,12 @@ function locale_languages_delete_form(&$
   // Do not allow deletion of English locale.
   if ($langcode == 'en') {
     drupal_set_message(t('The English language cannot be deleted.'));
-    drupal_goto('admin/settings/language');
+    drupal_goto('admin/international/language');
   }
 
   if (language_default('language') == $langcode) {
     drupal_set_message(t('The default language cannot be deleted.'));
-    drupal_goto('admin/settings/language');
+    drupal_goto('admin/international/language');
   }
 
   // For other languages, warn user that data loss is ahead.
@@ -409,7 +409,7 @@ function locale_languages_delete_form(&$
   }
   else {
     $form['langcode'] = array('#type' => 'value', '#value' => $langcode);
-    return confirm_form($form, t('Are you sure you want to delete the language %name?', array('%name' => t($languages[$langcode]->name))), 'admin/settings/language', t('Deleting a language will remove all interface translations associated with it, and posts in this language will be set to be language neutral. This action cannot be undone.'), t('Delete'), t('Cancel'));
+    return confirm_form($form, t('Are you sure you want to delete the language %name?', array('%name' => t($languages[$langcode]->name))), 'admin/international/language', t('Deleting a language will remove all interface translations associated with it, and posts in this language will be set to be language neutral. This action cannot be undone.'), t('Delete'), t('Cancel'));
   }
 }
 
@@ -435,7 +435,7 @@ function locale_languages_delete_form_su
   // Changing the language settings impacts the interface:
   cache_clear_all('*', 'cache_page', TRUE);
 
-  $form_state['redirect'] = 'admin/settings/language';
+  $form_state['redirect'] = 'admin/international/language';
   return;
 }
 /**
@@ -475,7 +475,7 @@ function locale_languages_configure_form
 function locale_languages_configure_form_submit($form, &$form_state) {
   variable_set('language_negotiation', $form_state['values']['language_negotiation']);
   drupal_set_message(t('Language negotiation configuration saved.'));
-  $form_state['redirect'] = 'admin/settings/language';
+  $form_state['redirect'] = 'admin/international/language';
   return;
 }
 /**
@@ -662,7 +662,7 @@ function locale_translation_filter_form_
       break;
   }
   
-  $form_state['redirect'] = 'admin/build/translate/translate';
+  $form_state['redirect'] = 'admin/international/translate/translate';
 }
 
 /**
@@ -750,11 +750,11 @@ function locale_translate_import_form_su
   }
   else {
     drupal_set_message(t('File to import not found.'), 'error');
-    $form_state['redirect'] = 'admin/build/translate/import';
+    $form_state['redirect'] = 'admin/international/translate/import';
     return;
   }
 
-  $form_state['redirect'] = 'admin/build/translate';
+  $form_state['redirect'] = 'admin/international/translate';
   return;
 }
 /**
@@ -858,7 +858,7 @@ function locale_translate_edit_form(&$fo
   $source = db_fetch_object(db_query('SELECT source, textgroup, location FROM {locales_source} WHERE lid = %d', $lid));
   if (!$source) {
     drupal_set_message(t('String not found.'), 'error');
-    drupal_goto('admin/build/translate/translate');
+    drupal_goto('admin/international/translate/translate');
   }
 
   // Add original text to the top and some values for form altering.
@@ -976,7 +976,7 @@ function locale_translate_edit_form_subm
   _locale_invalidate_js();
   cache_clear_all('locale:', 'cache', TRUE);
 
-  $form_state['redirect'] = 'admin/build/translate/translate';
+  $form_state['redirect'] = 'admin/international/translate/translate';
   return;
 }
 /**
@@ -1005,7 +1005,7 @@ function locale_translate_delete_page($l
  */
 function locale_translate_delete_form(&$form_state, $source) {
   $form['lid'] = array('#type' => 'value', '#value' => $source->lid);
-  return confirm_form($form, t('Are you sure you want to delete the string "%source"?', array('%source' => $source->source)), 'admin/build/translate/translate', t('Deleting the string will remove all translations of this string in all languages. This action cannot be undone.'), t('Delete'), t('Cancel'));
+  return confirm_form($form, t('Are you sure you want to delete the string "%source"?', array('%source' => $source->source)), 'admin/international/translate/translate', t('Deleting the string will remove all translations of this string in all languages. This action cannot be undone.'), t('Delete'), t('Cancel'));
 }
 
 /**
@@ -1018,7 +1018,7 @@ function locale_translate_delete_form_su
   _locale_invalidate_js();
   cache_clear_all('locale:', 'cache', TRUE);
   drupal_set_message(t('The string has been removed.'));
-  $form_state['redirect'] = 'admin/build/translate/translate';
+  $form_state['redirect'] = 'admin/international/translate/translate';
 }
 /**
  * @} End of "locale-translate-delete"
@@ -2142,8 +2142,8 @@ function _locale_translate_seek() {
       $value['group'],
       array('data' => check_plain(truncate_utf8($value['source'], 150, FALSE, TRUE)) . '<br /><small>' . $value['location'] . '</small>'),
       array('data' => _locale_translate_language_list($value['languages'], $limit_language), 'align' => 'center'),
-      array('data' => l(t('edit'), "admin/build/translate/edit/$lid", array('query' => drupal_get_destination())), 'class' => 'nowrap'),
-      array('data' => l(t('delete'), "admin/build/translate/delete/$lid", array('query' => drupal_get_destination())), 'class' => 'nowrap'),
+      array('data' => l(t('edit'), "admin/international/translate/edit/$lid", array('query' => drupal_get_destination())), 'class' => 'nowrap'),
+      array('data' => l(t('delete'), "admin/international/translate/delete/$lid", array('query' => drupal_get_destination())), 'class' => 'nowrap'),
     );
   }
 
Index: modules/field/field.crud.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.crud.inc,v
retrieving revision 1.4
diff -u -p -r1.4 field.crud.inc
--- modules/field/field.crud.inc	10 Feb 2009 03:16:14 -0000	1.4
+++ modules/field/field.crud.inc	12 Feb 2009 12:31:13 -0000
@@ -48,6 +48,8 @@ class FieldException extends Exception {
  * - 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)
@@ -195,6 +197,7 @@ function field_create_field($field) {
 
   $field += array(
     'cardinality' => 1,
+    'translatable' => 0,
     'locked' => FALSE,
     'settings' => array(),
   );
Index: modules/field/field.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.install,v
retrieving revision 1.3
diff -u -p -r1.3 field.install
--- modules/field/field.install	11 Feb 2009 04:45:57 -0000	1.3
+++ modules/field/field.install	12 Feb 2009 12:31:13 -0000
@@ -53,6 +53,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.4
diff -u -p -r1.4 field.module
--- modules/field/field.module	10 Feb 2009 03:16:14 -0000	1.4
+++ modules/field/field.module	12 Feb 2009 12:31:14 -0000
@@ -597,4 +597,4 @@ function template_preprocess_field(&$var
 
 /**
  * @} End of "defgroup field"
- */
\ No newline at end of file
+ */
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.4
diff -u -p -r1.4 field_sql_storage.module
--- modules/field/modules/field_sql_storage/field_sql_storage.module	10 Feb 2009 03:16:14 -0000	1.4
+++ modules/field/modules/field_sql_storage/field_sql_storage.module	12 Feb 2009 12:31:14 -0000
@@ -128,9 +128,17 @@ function _field_sql_storage_schema($fiel
         'not null' => TRUE,
         'description' => 'The sequence number for this data item, used for multi-value fields',
       ),
+      'language' => array(
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'The language for this data item.',
+      ),
     ),
-    'primary key' => array('etid', 'entity_id', 'deleted', 'delta'),
-    // TODO : index on 'bundle'
+    'primary key' => array('etid', 'entity_id', 'deleted', 'delta', 'language'),
+    // TODO : index on 'bundle'.
+    // TODO : consider an integer field for 'language'.
   );
 
   // Add field columns.
@@ -144,7 +152,7 @@ function _field_sql_storage_schema($fiel
   $revision = $current;
   $revision['description'] = 'Revision archive storage for field ' . $field['field_name'];
   $revision['revision_id']['description'] = 'The entity revision id this data is attached to';
-  $revision['primary key'] = array('etid', 'revision_id', 'deleted', 'delta');
+  $revision['primary key'] = array('etid', 'revision_id', 'deleted', 'delta', 'language');
 
   return array(
     _field_sql_storage_tablename($field['field_name']) => $current,
@@ -212,7 +220,7 @@ 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 ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || !empty($field['translatable']) || $delta_count[$row->entity_id][$field_name] < $field['cardinality']) {
         $item = array();
         // For each column declared by the field, populate the item
         // from the prefixed database column.
@@ -220,8 +228,17 @@ function field_sql_storage_field_storage
           $item[$column] = $row->{_field_sql_storage_columnname($field_name, $column)};
         }
 
-        // Add the item to the field values for the entity.
-        $additions[$row->entity_id][$field_name][] = $item;
+        // For translatable fields, put the multilingual values for each delta
+        // into a '#languages' property. This information can be safely cached
+        // in the field and object caches.
+        if ($field['translatable']) {
+          $item['language'] = empty($row->language) ? '' : $row->language;
+          $additions[$row->entity_id][$field_name][$row->delta]['#languages'][$item['language']] = $item;
+        }
+        else {
+          // Add the item to the field values for the entity.
+          $additions[$row->entity_id][$field_name][] = $item;
+        }
         $delta_count[$row->entity_id][$field_name]++;
       }
     }
@@ -256,7 +273,7 @@ function field_sql_storage_field_storage
 
       if ($object->$field_name) {
         // Prepare the multi-insert query.
-        $columns = array('etid', 'entity_id', 'revision_id', 'bundle', 'delta');
+        $columns = array('etid', 'entity_id', 'revision_id', 'bundle', 'delta', 'language');
         foreach ($field['columns'] as $column => $attributes) {
           $columns[] = _field_sql_storage_columnname($field_name, $column);
         }
@@ -273,6 +290,7 @@ function field_sql_storage_field_storage
             'revision_id' => $vid,
             'bundle' => $bundle,
             'delta' => $delta,
+            'language' => !empty($item->language) ? $item->language : '',
           );
           foreach ($field['columns'] as $column => $attributes) {
             $record[_field_sql_storage_columnname($field_name, $column)] = isset($item[$column]) ? $item[$column] : NULL;
@@ -282,7 +300,7 @@ function field_sql_storage_field_storage
             $revision_query->values($record);
           }
 
-          if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && ++$delta_count == $field['cardinality']) {
+          if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && empty($field['translatable']) && ++$delta_count == $field['cardinality']) {
             break;
           }
         }
@@ -385,4 +403,4 @@ function field_sql_storage_field_storage
       ->condition('bundle', $bundle_old)
       ->execute();
   }
-}
\ No newline at end of file
+}
Index: modules/locale/locale.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.module,v
retrieving revision 1.237
diff -u -p -r1.237 locale.module
--- modules/locale/locale.module	5 Feb 2009 00:32:46 -0000	1.237
+++ modules/locale/locale.module	12 Feb 2009 12:31:15 -0000
@@ -26,40 +26,40 @@ function locale_help($path, $arg) {
       $output .= '<p>' . t('Translations may be provided by:') . '</p>';
       $output .= '<ul><li>' . t("translating the original text via the locale module's integrated web interface, or") . '</li>';
       $output .= '<li>' . t('importing files from a set of existing translations, known as a translation package. A translation package enables the display of a specific version of Drupal in a specific language, and contain files in the Gettext Portable Object (<em>.po</em>) format. Although not all languages are available for every version of Drupal, translation packages for many languages are available for download from the <a href="@translations">Drupal translation page</a>.', array('@translations' => 'http://drupal.org/project/translations')) . '</li></ul>';
-      $output .= '<p>' . t('If an existing translation package does not meet your needs, the Gettext Portable Object (<em>.po</em>) files within a package may be modified, or new <em>.po</em> files may be created, using a desktop Gettext editor. The locale module\'s <a href="@import">import</a> feature allows the translated strings from a new or modified <em>.po</em> file to be added to your site. The locale module\'s <a href="@export">export</a> feature generates files from your site\'s translated strings, that can either be shared with others or edited offline by a Gettext translation editor.', array('@import' => url('admin/build/translate/import'), '@export' => url('admin/build/translate/export'))) . '</p>';
+      $output .= '<p>' . t('If an existing translation package does not meet your needs, the Gettext Portable Object (<em>.po</em>) files within a package may be modified, or new <em>.po</em> files may be created, using a desktop Gettext editor. The locale module\'s <a href="@import">import</a> feature allows the translated strings from a new or modified <em>.po</em> file to be added to your site. The locale module\'s <a href="@export">export</a> feature generates files from your site\'s translated strings, that can either be shared with others or edited offline by a Gettext translation editor.', array('@import' => url('admin/international/translate/import'), '@export' => url('admin/international/translate/export'))) . '</p>';
       $output .= '<p>' . t('For more information, see the online handbook entry for <a href="@locale">Locale module</a>.', array('@locale' => 'http://drupal.org/handbook/modules/locale/')) . '</p>';
       return $output;
-    case 'admin/settings/language':
+    case 'admin/international/language':
       $output = '<p>' . t("This page provides an overview of your site's enabled languages. If multiple languages are available and enabled, the text on your site interface may be translated, registered users may select their preferred language on the <em>My account</em> page, and site authors may indicate a specific language when creating posts. Languages will be displayed in the order you specify in places such as the language switcher block, or the language dropdown when creating or editing posts. The site's default language is used for anonymous visitors and for users who have not selected a preferred language.") . '</p>';
       $output .= '<p>' . t('For each language available on the site, use the <em>edit</em> link to configure language details, including name, an optional language-specific path or domain, and whether the language is natively presented either left-to-right or right-to-left. These languages also appear in the <em>Language</em> selection when creating a post of a content type with multilingual support.') . '</p>';
-      $output .= '<p>' . t('Use the <a href="@add-language">add language page</a> to enable additional languages (and automatically import files from a translation package, if available), the <a href="@search">translate interface page</a> to locate strings for manual translation, or the <a href="@import">import page</a> to add translations from individual <em>.po</em> files. A number of contributed translation packages containing <em>.po</em> files are available on the <a href="@translations">Drupal.org translations page</a>.', array('@add-language' => url('admin/settings/language/add'), '@search' => url('admin/build/translate/translate'), '@import' => url('admin/build/translate/import'), '@translations' => 'http://drupal.org/project/translations')) . '</p>';
+      $output .= '<p>' . t('Use the <a href="@add-language">add language page</a> to enable additional languages (and automatically import files from a translation package, if available), the <a href="@search">translate interface page</a> to locate strings for manual translation, or the <a href="@import">import page</a> to add translations from individual <em>.po</em> files. A number of contributed translation packages containing <em>.po</em> files are available on the <a href="@translations">Drupal.org translations page</a>.', array('@add-language' => url('admin/international/language/add'), '@search' => url('admin/international/translate/translate'), '@import' => url('admin/international/translate/import'), '@translations' => 'http://drupal.org/project/translations')) . '</p>';
       $output .= '<p>' . t('To rearrange languages, grab a drag-and-drop handle under the <em>English name</em> column and drag the item to a new location in the list. (Grab a handle by clicking and holding the mouse while hovering over a handle icon.) Remember that your changes will not be saved until you click the <em>Save configuration</em> button at the bottom of the page.') . '</p>';
       return $output;
-    case 'admin/settings/language/add':
+    case 'admin/international/language/add':
       return '<p>' . t('Add all languages to be supported by your site. If your desired language is not available in the <em>Language name</em> drop-down, click <em>Custom language</em> and provide a language code and other details manually. When providing a language code manually, be sure to enter a standardized language code, since this code may be used by browsers to determine an appropriate display language.') . '</p>';
-    case 'admin/settings/language/configure':
+    case 'admin/international/language/configure':
       $output = '<p>' . t("Language negotiation settings determine the site's presentation language. Available options include:") . '</p>';
       $output .= '<ul><li>' . t('<strong>None.</strong> The default language is used for site presentation, though users may (optionally) select a preferred language on the <em>My Account</em> page. (User language preferences will be used for site e-mails, if available.)') . '</li>';
       $output .= '<li>' . t('<strong>Path prefix only.</strong> The presentation language is determined by examining the path for a language code or other custom string that matches the path prefix (if any) specified for each language. If a suitable prefix is not identified, the default language is used. <em>Example: "example.com/de/contact" sets presentation language to German based on the use of "de" within the path.</em>') . '</li>';
       $output .= '<li>' . t("<strong>Path prefix with language fallback.</strong> The presentation language is determined by examining the path for a language code or other custom string that matches the path prefix (if any) specified for each language. If a suitable prefix is not identified, the display language is determined by the user's language preferences from the <em>My Account</em> page, or by the browser's language settings. If a presentation language cannot be determined, the default language is used.") . '</li>';
       $output .= '<li>' . t('<strong>Domain name only.</strong> The presentation language is determined by examining the domain used to access the site, and comparing it to the language domain (if any) specified for each language. If a match is not identified, the default language is used. <em>Example: "http://de.example.com/contact" sets presentation language to German based on the use of "http://de.example.com" in the domain.</em>') . '</li></ul>';
-      $output .= '<p>' . t('The path prefix or domain name for a language may be set by editing the <a href="@languages">available languages</a>. In the absence of an appropriate match, the site is displayed in the <a href="@languages">default language</a>.', array('@languages' => url('admin/settings/language'))) . '</p>';
+      $output .= '<p>' . t('The path prefix or domain name for a language may be set by editing the <a href="@languages">available languages</a>. In the absence of an appropriate match, the site is displayed in the <a href="@languages">default language</a>.', array('@languages' => url('admin/international/language'))) . '</p>';
       return $output;
-    case 'admin/build/translate':
+    case 'admin/international/translate':
       $output = '<p>' . t('This page provides an overview of available translatable strings. Drupal displays translatable strings in text groups; modules may define additional text groups containing other translatable strings. Because text groups provide a method of grouping related strings, they are often used to focus translation efforts on specific areas of the Drupal interface.') . '</p>';
-      $output .= '<p>' . t('Review the <a href="@languages">languages page</a> for more information on adding support for additional languages.', array('@languages' => url('admin/settings/language'))) . '</p>';
+      $output .= '<p>' . t('Review the <a href="@languages">languages page</a> for more information on adding support for additional languages.', array('@languages' => url('admin/international/language'))) . '</p>';
       return $output;
-    case 'admin/build/translate/import':
+    case 'admin/international/translate/import':
       $output = '<p>' . t('This page imports the translated strings contained in an individual Gettext Portable Object (<em>.po</em>) file. Normally distributed as part of a translation package (each translation package may contain several <em>.po</em> files), a <em>.po</em> file may need to be imported after offline editing in a Gettext translation editor. Importing an individual <em>.po</em> file may be a lengthy process.') . '</p>';
-      $output .= '<p>' . t('Note that the <em>.po</em> files within a translation package are imported automatically (if available) when new modules or themes are enabled, or as new languages are added. Since this page only allows the import of one <em>.po</em> file at a time, it may be simpler to download and extract a translation package into your Drupal installation directory and <a href="@language-add">add the language</a> (which automatically imports all <em>.po</em> files within the package). Translation packages are available for download on the <a href="@translations">Drupal translation page</a>.', array('@language-add' => url('admin/settings/language/add'), '@translations' => 'http://drupal.org/project/translations')) . '</p>';
+      $output .= '<p>' . t('Note that the <em>.po</em> files within a translation package are imported automatically (if available) when new modules or themes are enabled, or as new languages are added. Since this page only allows the import of one <em>.po</em> file at a time, it may be simpler to download and extract a translation package into your Drupal installation directory and <a href="@language-add">add the language</a> (which automatically imports all <em>.po</em> files within the package). Translation packages are available for download on the <a href="@translations">Drupal translation page</a>.', array('@language-add' => url('admin/international/language/add'), '@translations' => 'http://drupal.org/project/translations')) . '</p>';
       return $output;
-    case 'admin/build/translate/export':
+    case 'admin/international/translate/export':
       return '<p>' . t('This page exports the translated strings used by your site. An export file may be in Gettext Portable Object (<em>.po</em>) form, which includes both the original string and the translation (used to share translations with others), or in Gettext Portable Object Template (<em>.pot</em>) form, which includes the original strings only (used to create new translations with a Gettext translation editor).') . '</p>';
-    case 'admin/build/translate/translate':
-      return '<p>' . t('This page allows a translator to search for specific translated and untranslated strings, and is used when creating or editing translations. (Note: For translation tasks involving many strings, it may be more convenient to <a href="@export">export</a> strings for offline editing in a desktop Gettext translation editor.) Searches may be limited to strings found within a specific text group or in a specific language.', array('@export' => url('admin/build/translate/export'))) . '</p>';
+    case 'admin/international/translate/translate':
+      return '<p>' . t('This page allows a translator to search for specific translated and untranslated strings, and is used when creating or editing translations. (Note: For translation tasks involving many strings, it may be more convenient to <a href="@export">export</a> strings for offline editing in a desktop Gettext translation editor.) Searches may be limited to strings found within a specific text group or in a specific language.', array('@export' => url('admin/international/translate/export'))) . '</p>';
     case 'admin/build/block/configure':
       if ($arg[4] == 'locale' && $arg[5] == 0) {
-        return '<p>' . t('This block is only shown if <a href="@languages">at least two languages are enabled</a> and <a href="@configuration">language negotiation</a> is set to something other than <em>None</em>.', array('@languages' => url('admin/settings/language'), '@configuration' => url('admin/settings/language/configure'))) . '</p>';
+        return '<p>' . t('This block is only shown if <a href="@languages">at least two languages are enabled</a> and <a href="@configuration">language negotiation</a> is set to something other than <em>None</em>.', array('@languages' => url('admin/international/language'), '@configuration' => url('admin/international/language/configure'))) . '</p>';
       }
       break;
   }
@@ -70,26 +70,34 @@ function locale_help($path, $arg) {
  */
 function locale_menu() {
   // Manage languages
-  $items['admin/settings/language'] = array(
+  $items['admin/international'] = array(
+   'title' => 'International',
+   'description' => 'Localization and translation.',
+   'position' => 'left',
+   'weight' => -7,
+   'page callback' => 'system_admin_menu_block_page',
+   'access arguments' => array('access administration pages'),
+  );
+  $items['admin/international/language'] = array(
     'title' => 'Languages',
     'description' => 'Configure languages for content and the user interface.',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('locale_languages_overview_form'),
     'access arguments' => array('administer languages'),
   );
-  $items['admin/settings/language/overview'] = array(
+  $items['admin/international/language/overview'] = array(
     'title' => 'List',
     'weight' => 0,
     'type' => MENU_DEFAULT_LOCAL_TASK,
   );
-  $items['admin/settings/language/add'] = array(
+  $items['admin/international/language/add'] = array(
     'title' => 'Add language',
     'page callback' => 'locale_languages_add_screen', // two forms concatenated
     'access arguments' => array('administer languages'),
     'weight' => 5,
     'type' => MENU_LOCAL_TASK,
   );
-  $items['admin/settings/language/configure'] = array(
+  $items['admin/international/language/configure'] = array(
     'title' => 'Configure',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('locale_languages_configure_form'),
@@ -97,14 +105,14 @@ function locale_menu() {
     'weight' => 10,
     'type' => MENU_LOCAL_TASK,
   );
-  $items['admin/settings/language/edit/%'] = array(
+  $items['admin/international/language/edit/%'] = array(
     'title' => 'Edit language',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('locale_languages_edit_form', 4),
     'access arguments' => array('administer languages'),
     'type' => MENU_CALLBACK,
   );
-  $items['admin/settings/language/delete/%'] = array(
+  $items['admin/international/language/delete/%'] = array(
     'title' => 'Confirm',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('locale_languages_delete_form', 4),
@@ -113,25 +121,25 @@ function locale_menu() {
   );
 
   // Translation functionality
-  $items['admin/build/translate'] = array(
+  $items['admin/international/translate'] = array(
     'title' => 'Translate interface',
     'description' => 'Translate the built in interface and optionally other text.',
     'page callback' => 'locale_translate_overview_screen', // not a form, just a table
     'access arguments' => array('translate interface'),
   );
-  $items['admin/build/translate/overview'] = array(
+  $items['admin/international/translate/overview'] = array(
     'title' => 'Overview',
     'weight' => 0,
     'type' => MENU_DEFAULT_LOCAL_TASK,
   );
-  $items['admin/build/translate/translate'] = array(
+  $items['admin/international/translate/translate'] = array(
     'title' => 'Translate',
     'weight' => 10,
     'type' => MENU_LOCAL_TASK,
     'page callback' => 'locale_translate_seek_screen', // search results and form concatenated
     'access arguments' => array('translate interface'),
   );
-  $items['admin/build/translate/import'] = array(
+  $items['admin/international/translate/import'] = array(
     'title' => 'Import',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('locale_translate_import_form'),
@@ -139,21 +147,21 @@ function locale_menu() {
     'weight' => 20,
     'type' => MENU_LOCAL_TASK,
   );
-  $items['admin/build/translate/export'] = array(
+  $items['admin/international/translate/export'] = array(
     'title' => 'Export',
     'page callback' => 'locale_translate_export_screen',  // possibly multiple forms concatenated
     'access arguments' => array('translate interface'),
     'weight' => 30,
     'type' => MENU_LOCAL_TASK,
   );
-  $items['admin/build/translate/edit/%'] = array(
+  $items['admin/international/translate/edit/%'] = array(
     'title' => 'Edit string',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('locale_translate_edit_form', 4),
     'access arguments' => array('translate interface'),
     'type' => MENU_CALLBACK,
   );
-  $items['admin/build/translate/delete/%'] = array(
+  $items['admin/international/translate/delete/%'] = array(
     'title' => 'Delete string',
     'page callback' => 'locale_translate_delete_page',
     'page arguments' => array(4),
@@ -277,7 +285,7 @@ function locale_form_node_type_form_alte
       '#title' => t('Multilingual support'),
       '#default_value' => variable_get('language_content_type_' . $form['#node_type']->type, 0),
       '#options' => array(t('Disabled'), t('Enabled')),
-      '#description' => t('Enable multilingual support for this content type. If enabled, a language selection field will be added to the editing form, allowing you to select from one of the <a href="!languages">enabled languages</a>. If disabled, new posts are saved with the default language. Existing content will not be affected by changing this option.', array('!languages' => url('admin/settings/language'))),
+      '#description' => t('Enable multilingual support for this content type. If enabled, a language selection field will be added to the editing form, allowing you to select from one of the <a href="!languages">enabled languages</a>. If disabled, new posts are saved with the default language. Existing content will not be affected by changing this option.', array('!languages' => url('admin/international/language'))),
     );
   }
 }
@@ -601,6 +609,56 @@ function locale_block_view($delta = '') 
 }
 
 /**
+ * Implementation of hook_field_attach_load().
+ *
+ * For multilingual fields, populate the value for each
+ * $object->field_name[$delta] with the best match for the current language
+ * or the site default. Since hook_field_attach_load() is not persistently
+ * cached, we can add language specific information here in the knowledge
+ * it will only affect the curent request. Field rendering and formatting is
+ * agnostic to whether a field is multilingual or not, however we leave the
+ * #languages array in situ to allow other modules to access it if necessary.
+ */
+function locale_field_attach_load($obj_type, $object) {
+  global $language;
+
+  // Only operate on translatable fields.
+  $fields = array();
+  list(, , $bundle) = field_attach_extract_ids($obj_type, $object);
+  $instances = field_info_instances($bundle);
+  foreach ($instances as $instance) {
+    $field = field_info_field($instance['field_name']);
+    if (!empty($field['translatable'])) {
+      $fields[] = $instance['field_name'];
+    }
+  }
+  if (empty($fields)) {
+    return;
+  }
+
+  $default = language_default();
+  foreach ($fields as $field_name) {
+    foreach ($object->$field_name as $delta => $item) {
+      $best_fit = array();
+      // Use current language, if available.
+      if (isset($item['#languages'][$language->language])) {
+        $best_fit = $item['#languages'][$language->language];
+      }
+      // Use default language, if available.
+      elseif (isset($item['#languages'][$default->language])) {
+        $best_fit = $item['#languages'][$default->language];
+      }
+      // Use language neutral values, if available.
+      elseif (isset($item['#languages'][''])) {
+        $best_fit = $item['#languages'][''];
+      }
+            
+      $object->{$field_name}[$delta] = array_merge($item, $best_fit);
+    }
+  }
+}
+
+/**
  * Theme locale translation filter selector.
  *
  * @ingroup themeable
Index: modules/locale/locale.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.test,v
retrieving revision 1.16
diff -u -p -r1.16 locale.test
--- modules/locale/locale.test	5 Feb 2009 00:32:47 -0000	1.16
+++ modules/locale/locale.test	12 Feb 2009 12:31:15 -0000
@@ -44,7 +44,7 @@ class LocaleTestCase extends DrupalWebTe
       'prefix' => $prefix,
       'direction' => '0',
     );
-    $this->drupalPost('admin/settings/language/add', $edit, t('Add custom language'));
+    $this->drupalPost('admin/international/language/add', $edit, t('Add custom language'));
     // Add string.
     t($name, array(), $langcode);
     // Reset locale cache.
@@ -65,7 +65,7 @@ class LocaleTestCase extends DrupalWebTe
       'translation' => 'all',
       'group' => 'all',
     );
-    $this->drupalPost('admin/build/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/international/translate/translate', $search, t('Filter'));
     // assertText seems to remove the input field where $name always could be
     // found, so this is not a false assert. See how assertNoText succeeds
     // later.
@@ -76,7 +76,7 @@ class LocaleTestCase extends DrupalWebTe
     $this->clickLink(t('edit'));
     // We save the lid from the path.
     $matches = array();
-    preg_match('!admin/build/translate/edit/(\d)+!', $this->getUrl(), $matches);
+    preg_match('!admin/international/translate/edit/(\d)+!', $this->getUrl(), $matches);
     $lid = $matches[1];
     // No t() here, it's surely not translated yet.
     $this->assertText($name, 'name found on edit screen');
@@ -86,14 +86,14 @@ class LocaleTestCase extends DrupalWebTe
     $this->drupalPost(NULL, $edit, t('Save translations'));
     $this->assertText(t('The string has been saved.'), 'The string has been saved.');
     $this->assertTrue($name != $translation && t($name, array(), $langcode) == $translation, 't() works');
-    $this->drupalPost('admin/build/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/international/translate/translate', $search, t('Filter'));
     // The indicator should not be here.
     $this->assertNoRaw($language_indicator, 'String is translated');
     $this->drupalLogout();
 
     // Delete the language.
     $this->drupalLogin($admin_user);
-    $path = 'admin/settings/language/delete/' . $langcode;
+    $path = 'admin/international/language/delete/' . $langcode;
     // This a confirm form, we do not need any fields changed.
     $this->drupalPost($path, array(), t('Delete'));
     // We need raw here because %locale will add HTML.
@@ -107,9 +107,9 @@ class LocaleTestCase extends DrupalWebTe
 
     // Delete the name string.
     $this->drupalLogin($translate_user);
-    $this->drupalPost('admin/build/translate/delete/' . $lid, array(), t('Delete'));
+    $this->drupalPost('admin/international/translate/delete/' . $lid, array(), t('Delete'));
     $this->assertText(t('The string has been removed.'), 'The string has been removed message.');
-    $this->drupalPost('admin/build/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/international/translate/translate', $search, t('Filter'));
     $this->assertNoText($name, 'Search now can not find the name');
   }
 
@@ -147,7 +147,7 @@ class LocaleTestCase extends DrupalWebTe
       'prefix' => $prefix,
       'direction' => '0',
     );
-    $this->drupalPost('admin/settings/language/add', $edit, t('Add custom language'));
+    $this->drupalPost('admin/international/language/add', $edit, t('Add custom language'));
     // Add string.
     t($name, array(), $langcode);
     // Reset locale cache.
@@ -157,10 +157,10 @@ class LocaleTestCase extends DrupalWebTe
       'translation' => 'all',
       'group' => 'all',
     );
-    $this->drupalPost('admin/build/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/international/translate/translate', $search, t('Filter'));
     // Find the edit path
     $content = $this->drupalGetContent();
-    $this->assertTrue(preg_match('@(admin/build/translate/edit/[0-9]+)@', $content, $matches), t('Found the edit path'));
+    $this->assertTrue(preg_match('@(admin/international/translate/edit/[0-9]+)@', $content, $matches), t('Found the edit path'));
     $path = $matches[0];
     foreach ($bad_translations as $key => $translation) {
       $edit = array (
@@ -206,7 +206,7 @@ class LocaleImportFunctionalTest extends
     // Try importing a .po file.
     $name = tempnam(file_directory_temp(), "po_");
     file_put_contents($name, $this->getPoFile());
-    $this->drupalPost('admin/build/translate/import', array(
+    $this->drupalPost('admin/international/translate/import', array(
       'langcode' => 'fr',
       'files[file]' => $name,
     ), t('Import'));
@@ -221,7 +221,7 @@ class LocaleImportFunctionalTest extends
     // Try importing a .po file with invalid tags in the default text group.
     $name = tempnam(file_directory_temp(), "po_");
     file_put_contents($name, $this->getBadPoFile());
-    $this->drupalPost('admin/build/translate/import', array(
+    $this->drupalPost('admin/international/translate/import', array(
       'langcode' => 'fr',
       'files[file]' => $name,
     ), t('Import'));
@@ -234,7 +234,7 @@ class LocaleImportFunctionalTest extends
     // Try importing a .po file with invalid tags in a non default text group.
     $name = tempnam(file_directory_temp(), "po_");
     file_put_contents($name, $this->getBadPoFile());
-    $this->drupalPost('admin/build/translate/import', array(
+    $this->drupalPost('admin/international/translate/import', array(
       'langcode' => 'fr',
       'files[file]' => $name,
       'group' => 'custom',
@@ -338,13 +338,13 @@ class LanguageSwitchingFunctionalTest ex
     $edit = array(
       'langcode' => 'fr',
     );
-    $this->drupalPost('admin/settings/language/add', $edit, t('Add language'));
+    $this->drupalPost('admin/international/language/add', $edit, t('Add language'));
 
     // Set language negotiation.
     $edit = array(
       'language_negotiation' => LANGUAGE_NEGOTIATION_PATH_DEFAULT,
     );
-    $this->drupalPost('admin/settings/language/configure', $edit, t('Save settings'));
+    $this->drupalPost('admin/international/language/configure', $edit, t('Save settings'));
 
     // Assert that the language switching block is displayed on the frontpage.
     $this->drupalGet('');
Index: modules/path/path.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/path/path.test,v
retrieving revision 1.6
diff -u -p -r1.6 path.test
--- modules/path/path.test	9 Jan 2009 07:44:00 -0000	1.6
+++ modules/path/path.test	12 Feb 2009 12:31:15 -0000
@@ -148,7 +148,7 @@ class PathLanguageTestCase extends Drupa
     $edit = array();
     $edit['langcode'] = 'fr';
 
-    $this->drupalPost('admin/settings/language/add', $edit, t('Add language'));
+    $this->drupalPost('admin/international/language/add', $edit, t('Add language'));
 
     // Set language negotiation to "Path prefix with fallback".
     variable_set('language_negotiation', LANGUAGE_NEGOTIATION_PATH);
Index: modules/translation/translation.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/translation/translation.test,v
retrieving revision 1.7
diff -u -p -r1.7 translation.test
--- modules/translation/translation.test	30 Dec 2008 16:43:19 -0000	1.7
+++ modules/translation/translation.test	12 Feb 2009 12:31:17 -0000
@@ -77,13 +77,13 @@ class TranslationTestCase extends Drupal
    */
   function addLanguage($language_code) {
     // Check to make sure that language has not already been installed.
-    $this->drupalGet('admin/settings/language');
+    $this->drupalGet('admin/international/language');
 
     if (strpos($this->drupalGetContent(), 'enabled[' . $language_code . ']') === FALSE) {
       // Doesn't have language installed so add it.
       $edit = array();
       $edit['langcode'] = $language_code;
-      $this->drupalPost('admin/settings/language/add', $edit, t('Add language'));
+      $this->drupalPost('admin/international/language/add', $edit, t('Add language'));
 
       $languages = language_list('language', TRUE); // Make sure we're not using a stale list.
       $this->assertTrue(array_key_exists($language_code, $languages), t('Language was installed successfully.'));
? sites/all/modules/devel/devel_generate.patch.txt
