Index: includes/locale.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/locale.inc,v
retrieving revision 1.205
diff -u -p -r1.205 locale.inc
--- includes/locale.inc	22 Feb 2009 17:55:29 -0000	1.205
+++ includes/locale.inc	26 Feb 2009 21:50:45 -0000
@@ -2605,6 +2605,8 @@ function locale_batch_by_language($langc
   // Collect all files to import for all enabled modules and themes.
   $files = array();
   $components = array();
+
+  $language_with_fallbacks = _locale_get_language_with_fallbacks($langcode);
   $query = db_select('system', 's');
   $query->fields('s', array('name', 'filename'));
   $query->condition('s.status', 1);
@@ -2613,11 +2615,22 @@ function locale_batch_by_language($langc
   }
   $result = $query->execute();
   foreach ($result as $component) {
-    // Collect all files for all components, names as $langcode.po or
-    // with names ending with $langcode.po. This allows for filenames
-    // like node-module.de.po to let translators use small files and
-    // be able to import in smaller chunks.
-    $files = array_merge($files, file_scan_directory(dirname($component->filename) . '/translations', '/(^|\.)' . $langcode . '\.po$/', array('recurse' => FALSE)));
+    // Iterate through a language and its potential fallbacks.
+    // We need to do this in order to ensure that the language takes
+    // precedence over its fallback.
+    foreach ($language_with_fallbacks as $language) {
+      // Collect all files for all components, names as $language.po or
+      // with names ending with $language.po. This allows for filenames
+      // like node-module.de.po to let translators use small files and
+      // be able to import in smaller chunks.
+      $new_files = file_scan_directory(dirname($component->filename) . '/translations', '/(^|\.)' . $language . '\.po$/', array('recurse' => FALSE));
+      // Whether for the original or a fallback language, the imports
+      // need to be assigned to the original language.
+      foreach (array_keys($new_files) as $key) {
+        $new_files[$key]->langcode = $langcode;
+      }
+      $files = array_merge($files, $new_files);
+    }
     $components[] = $component->name;
   }
 
@@ -2640,16 +2653,32 @@ function locale_batch_by_component($comp
   $languages = language_list('enabled');
   unset($languages[1]['en']);
   if (count($languages[1])) {
-    $language_list = join('|', array_keys($languages[1]));
-    // Collect all files to import for all $components.
-    $result = db_query("SELECT name, filename FROM {system} WHERE status = 1");
-    while ($component = db_fetch_object($result)) {
-      if (in_array($component->name, $components)) {
-        // Collect all files for this component in all enabled languages, named
-        // as $langcode.po or with names ending with $langcode.po. This allows
-        // for filenames like node-module.de.po to let translators use small
-        // files and be able to import in smaller chunks.
-        $files = array_merge($files, file_scan_directory(dirname($component->filename) . '/translations', '/(^|\.)(' . $language_list . ')\.po$/', array('recurse' => FALSE)));
+    foreach (array_keys($languages[1]) as $langcode) {
+      $language_with_fallbacks = _locale_get_language_with_fallbacks($langcode);
+      // Collect all files to import for all $components.
+      $query = db_select('system', 's');
+      $query->fields('s', array('name', 'filename'));
+      $query->condition('s.status', 1);
+      $result = $query->execute();
+      foreach ($result as $component) {
+        if (in_array($component->name, $components)) {
+          // Iterate through a language and its potential fallbacks.
+          // We need to do this in order to ensure that the language takes
+          // precedence over its fallback.
+          foreach ($language_with_fallbacks as $language) {
+            // Collect all files for this component in all enabled languages, named
+            // as $language.po or with names ending with $language.po. This allows
+            // for filenames like node-module.de.po to let translators use small
+            // files and be able to import in smaller chunks.
+            $new_files = file_scan_directory(dirname($component->filename) . '/translations', '/(^|\.)' . $language . '\.po$/', array('recurse' => FALSE));
+            // Whether for the original or a fallback language, the imports
+            // need to be assigned to the original language.
+            foreach (array_keys($new_files) as $key) {
+              $new_files[$key]->langcode = $langcode;
+            }
+            $files = array_merge($files, $new_files);
+          }
+        }
       }
     }
     return _locale_batch_build($files, $finished);
@@ -2675,7 +2704,7 @@ function _locale_batch_build($files, $fi
     $operations = array();
     foreach ($files as $file) {
       // We call _locale_batch_import for every batch operation.
-      $operations[] = array('_locale_batch_import', array($file->filepath));
+      $operations[] = array('_locale_batch_import', array($file->filepath, $file->langcode));
     }
     $batch = array(
       'operations'    => $operations,
@@ -2700,15 +2729,18 @@ function _locale_batch_build($files, $fi
  *
  * @param $filepath
  *   Path to a file to import.
- * @param $results
+ * @param $langcode_override
+ *   A language code to be set to the imported strings. Enables the use
+ *   of language import fallbacks.
+ * @param $context
  *   Contains a list of files imported.
  */
-function _locale_batch_import($filepath, &$context) {
+function _locale_batch_import($filepath, $langcode_override, &$context) {
   // The filename is either {langcode}.po or {prefix}.{langcode}.po, so
   // we can extract the language code to use for the import from the end.
   if (preg_match('!(/|\.)([^\./]+)\.po$!', $filepath, $langcode)) {
     $file = (object) array('filename' => basename($filepath), 'filepath' => $filepath);
-    _locale_import_read_po('db-store', $file, LOCALE_IMPORT_KEEP, $langcode[2]);
+    _locale_import_read_po('db-store', $file, LOCALE_IMPORT_KEEP, $langcode_override ? $langcode_override : $langcode[2]);
     $context['results'][] = $filepath;
   }
 }
@@ -2734,5 +2766,21 @@ function _locale_batch_language_finished
 }
 
 /**
+ * Return an array of a language and its fallbacks, if any.
+ *
+ * By default, if a language code has more than two digits, the first
+ * two digits are used as a fallback.
+ */
+function _locale_get_language_with_fallbacks($langcode) {
+  $langcodes = array($langcode);
+  if (strlen($langcode) > 2) {
+    $langcodes[] = substr($langcode, 0, 2);
+  }
+  // Allow other modules to alter the language fallbacks.
+  drupal_alter('locale_language_fallbacks', $langcodes, $langcode);
+  return $langcodes;
+}
+
+/**
  * @} End of "locale-autoimport"
  */
Index: modules/locale/locale.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.test,v
retrieving revision 1.19
diff -u -p -r1.19 locale.test
--- modules/locale/locale.test	24 Feb 2009 16:46:52 -0000	1.19
+++ modules/locale/locale.test	26 Feb 2009 21:50:47 -0000
@@ -655,7 +655,7 @@ class LocaleImportFunctionalTest extends
    * is enabled.
    */
   function testAutomaticModuleTranslationImportLanguageEnable() {
-    // Code for the language - manually set to match the test translation file.
+    // Code for the language.
     $langcode = 'xx';
     // The English name for the language.
     $name = $this->randomName(16);
@@ -687,6 +687,54 @@ class LocaleImportFunctionalTest extends
     );
     $this->drupalPost('admin/build/translate/translate', $search, t('Filter'));
     $this->assertNoText(t('No strings found for your search.'), t('String successfully imported.'));
+
+    // Now repeat with a language including an extended code to test language
+    // fallback imports.
+    // Code for the language.
+    $langcode = 'xx-yy';
+    // The English name for the language.
+    $name = $this->randomName(16);
+    // The native name for the language.
+    $native = $this->randomName(16);
+    // The domain prefix.
+    $prefix = $langcode;
+
+    // Create a custom language.
+    $edit = array(
+      'langcode' => $langcode,
+      'name' => $name,
+      'native' => $native,
+      'prefix' => $prefix,
+      'direction' => '0',
+    );
+    $this->drupalPost('admin/settings/language/add', $edit, t('Add custom language'));
+
+    // Ensure the translation files were automatically imported when language was
+    // added.
+    $this->assertText(t('2 translation files imported for the enabled modules.'), t('Language file and fallback language file automatically imported.'));
+
+    // Ensure primary language strings were successfully imported.
+    $search = array(
+      'string' => 'vacances',
+      'language' => $langcode,
+      'translation' => 'translated',
+      'group' => 'all',
+    );
+    $this->drupalPost('admin/build/translate/translate', $search, t('Filter'));
+    $this->assertNoText(t('No strings found for your search.'), t('String in primary language successfully saved.'));
+
+    // Ensure fallback language string was not imported if string existed in
+    // primary language.
+    $search['string'] = 'lundi';
+    $this->drupalPost('admin/build/translate/translate', $search, t('Filter'));
+    $this->assertText(t('No strings found for your search.'), t('String not imported in fallback language when found in primary language.'));
+
+    // Ensure fallback language string was imported if string did not exist in
+    // primary language.
+    $search['string'] = 'mardi';
+    $this->drupalPost('admin/build/translate/translate', $search, t('Filter'));
+    $this->assertNoText(t('No strings found for your search.'), t('String successfully imported in fallback language when not found in primary language.'));
+
   }
 
   /**
Index: modules/locale/tests/translations/test.xx-yy.po
===================================================================
RCS file: modules/locale/tests/translations/test.xx-yy.po
diff -N modules/locale/tests/translations/test.xx-yy.po
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/locale/tests/translations/test.xx-yy.po	26 Feb 2009 21:50:47 -0000
@@ -0,0 +1,11 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: Drupal 7\\n"
+"MIME-Version: 1.0\\n"
+"Content-Type: text/plain; charset=UTF-8\\n"
+"Content-Transfer-Encoding: 8bit\\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
+
+msgid "Monday"
+msgstr "vacances"
+
