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 24 Feb 2009 01:26:56 -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.18 diff -u -p -r1.18 locale.test --- modules/locale/locale.test 22 Feb 2009 20:55:18 -0000 1.18 +++ modules/locale/locale.test 24 Feb 2009 01:27:01 -0000 @@ -55,13 +55,13 @@ class LocaleConfigurationTest extends Dr // Add custom language. // Code for the language. - $langcode = $this->randomName(6, 'si-'); + $langcode = 'xx'; // The English name for the language. $name = $this->randomName(16); // The native name for the language. $native = $this->randomName(16); // The domain prefix. - $prefix = strtolower(str_replace('si-', '', $langcode)); + $prefix = $langcode; $edit = array( 'langcode' => $langcode, 'name' => $name, @@ -188,13 +188,13 @@ class LocaleTranslationFunctionalTest ex // User to translate and delete string. $translate_user = $this->drupalCreateUser(array('translate interface', 'access administration pages')); // Code for the language. - $langcode = $this->randomName(6, 'si-'); + $langcode = 'xx'; // The English name for the language. This will be translated. $name = $this->randomName(16); // The native name for the language. $native = $this->randomName(16); // The domain prefix. - $prefix = strtolower(str_replace('si-', '', $langcode)); + $prefix = $langcode; // This is the language indicator on the translation search screen for // untranslated strings. Copied straight from locale.inc. $language_indicator = "$langcode "; @@ -314,13 +314,13 @@ class LocaleTranslationFunctionalTest ex // User to add language and strings. $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'translate interface')); $this->drupalLogin($admin_user); - $langcode = $this->randomName(6, 'si-'); + $langcode = 'xx'; // The English name for the language. This will be translated. $name = $this->randomName(16); // The native name for the language. $native = $this->randomName(16); // The domain prefix. - $prefix = strtolower(str_replace('si-', '', $langcode)); + $prefix = $langcode; // This is the language indicator on the translation search screen for // untranslated strings. Copied straight from locale.inc. $language_indicator = "$langcode "; @@ -381,13 +381,13 @@ class LocaleTranslationFunctionalTest ex $translate_user = $this->drupalCreateUser(array('translate interface', 'access administration pages')); // Code for the language. - $langcode = $this->randomName(6, 'si-'); + $langcode = 'xx'; // The English name for the language. This will be translated. $name = $this->randomName(16); // The native name for the language. $native = $this->randomName(16); // The domain prefix. - $prefix = strtolower(str_replace('si-', '', $langcode)); + $prefix = $langcode; // This is the language indicator on the translation search screen for // untranslated strings. Copied straight from locale.inc. $language_indicator = "$langcode "; @@ -655,22 +655,14 @@ class LocaleImportFunctionalTest extends * is enabled. */ function testAutomaticModuleTranslationImportLanguageEnable() { - // Code for the language. - $langcode = $this->randomName(6, 'si-'); + // Code for the language - manually set to match the test translation file. + $langcode = 'xx'; // The English name for the language. $name = $this->randomName(16); // The native name for the language. $native = $this->randomName(16); // The domain prefix. - $prefix = strtolower(str_replace('si-', '', $langcode)); - - // Create a .po file. - $translations_dir = drupal_get_path('module', 'locale_test') . '/translations/'; - if (!file_exists($translations_dir)) { - mkdir($translations_dir); - } - $filename = $translations_dir . $langcode . '.po'; - file_put_contents($filename, $this->getPoFile()); + $prefix = $langcode; // Create a custom language. $edit = array( @@ -696,8 +688,53 @@ 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.')); - // Remove our temporary .po file. - unlink($filename); + // 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.')); } /** @@ -1102,13 +1139,13 @@ class LocaleUserLanguageFunctionalTest e // Add custom language. $this->drupalLogin($admin_user); // Code for the language. - $langcode = $this->randomName(6, 'si-'); + $langcode = 'xx'; // The English name for the language. $name = $this->randomName(16); // The native name for the language. $native = $this->randomName(16); // The domain prefix. - $prefix = strtolower(str_replace('si-', '', $langcode)); + $prefix = 'xx'; $edit = array( 'langcode' => $langcode, 'name' => $name, @@ -1120,13 +1157,13 @@ class LocaleUserLanguageFunctionalTest e // Add custom language and disable it. // Code for the language. - $langcode_disabled = $this->randomName(6, 'si-'); + $langcode_disabled = 'xx-yy'; // The English name for the language. This will be translated. $name_disabled = $this->randomName(16); // The native name for the language. $native_disabled = $this->randomName(16); // The domain prefix. - $prefix_disabled = strtolower(str_replace('si-', '', $langcode_disabled)); + $prefix_disabled = $langcode_disabled; $edit = array( 'langcode' => $langcode_disabled, 'name' => $name_disabled, @@ -1195,13 +1232,13 @@ class LocalePathFunctionalTest extends D // Add custom language. $this->drupalLogin($admin_user); // Code for the language. - $langcode = $this->randomName(6, 'si-'); + $langcode = 'xx'; // The English name for the language. $name = $this->randomName(16); // The native name for the language. $native = $this->randomName(16); // The domain prefix. - $prefix = strtolower(str_replace('si-', '', $langcode)); + $prefix = $langcode; $edit = array( 'langcode' => $langcode, 'name' => $name, @@ -1281,13 +1318,13 @@ class LocaleContentFunctionalTest extend // Add custom language. $this->drupalLogin($admin_user); // Code for the language. - $langcode = $this->randomName(6, 'si-'); + $langcode = 'xx'; // The English name for the language. $name = $this->randomName(16); // The native name for the language. $native = $this->randomName(16); // The domain prefix. - $prefix = strtolower(str_replace('si-', '', $langcode)); + $prefix = $langcode; $edit = array( 'langcode' => $langcode, 'name' => $name, @@ -1299,13 +1336,13 @@ class LocaleContentFunctionalTest extend // Add disabled custom language. // Code for the language. - $langcode_disabled = $this->randomName(6, 'si-'); + $langcode_disabled = 'xx-yy'; // The English name for the language. $name_disabled = $this->randomName(16); // The native name for the language. $native_disabled = $this->randomName(16); // The domain prefix. - $prefix_disabled = strtolower(str_replace('si-', '', $langcode_disabled)); + $prefix_disabled = $langcode_disabled; $edit = array( 'langcode' => $langcode_disabled, 'name' => $name_disabled, 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 24 Feb 2009 01:27:01 -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" + Index: modules/locale/tests/translations/test.xx.po =================================================================== RCS file: modules/locale/tests/translations/test.xx.po diff -N modules/locale/tests/translations/test.xx.po --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/locale/tests/translations/test.xx.po 24 Feb 2009 01:27:01 -0000 @@ -0,0 +1,28 @@ +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 "lundi" + +msgid "Tuesday" +msgstr "mardi" + +msgid "Wednesday" +msgstr "mercredi" + +msgid "Thursday" +msgstr "jeudi" + +msgid "Friday" +msgstr "vendredi" + +msgid "Saturday" +msgstr "samedi" + +msgid "Sunday" +msgstr "dimanche"