Commit adab4ab8 authored by Dries's avatar Dries

- Patch #142869 by Gabor: import interface translation files at install time.

parent 26da0ada
......@@ -37,6 +37,7 @@ Drupal 6.0, xxxx-xx-xx (development version)
* Themed the installer with the Garland theme.
* Added form to provide initial site information during installation.
* Added ability to provide extra installation steps programmatically.
* Made it possible to import interface translations at install time.
Drupal 5.0, 2007-01-15
----------------------
......
......@@ -578,7 +578,7 @@ function st($string, $args = array()) {
if (!isset($locale_strings)) {
$locale_strings = array();
$filename = './profiles/'. $profile .'/'. $install_locale .'.po';
$filename = './profiles/'. $profile .'/po/'. $install_locale .'.po';
if (file_exists($filename)) {
require_once './includes/locale.inc';
$file = (object) array('filepath' => $filename);
......
......@@ -281,12 +281,13 @@ function locale_languages_predefined_form_submit($form_values, $form, &$form_sta
if (isset($form_values['name'])) {
// Custom language form.
locale_add_language($langcode, $form_values['name'], $form_values['native'], $form_values['direction'], $form_values['domain'], $form_values['prefix']);
drupal_set_message(t('The language %language has been created and can now be used. More information is available on the <a href="@locale-help">help screen</a>.', array('%language' => t($form_values['name']), '@locale-help' => url('admin/help/locale'))));
}
else {
// Predefined language selection.
$predefined = _locale_get_predefined_list();
$lang = &$predefined[$langcode];
locale_add_language($langcode, $lang[0], isset($lang[1]) ? $lang[1] : $lang[0], isset($lang[2]) ? $lang[2] : 0, '', $langcode);
locale_add_language($langcode);
drupal_set_message(t('The language %language has been created and can now be used. More information is available on the <a href="@locale-help">help screen</a>.', array('%language' => t($predefined[$langcode][0]), '@locale-help' => url('admin/help/locale'))));
}
$form_state['redirect'] = 'admin/settings/language';
......@@ -602,8 +603,8 @@ function locale_translate_import_form_submit($form_values, $form, &$form_state)
$langcode = $form_values['langcode'];
if (!isset($languages[$langcode])) {
$predefined = _locale_get_predefined_list();
$lang = &$predefined[$langcode];
locale_add_language($langcode, $lang[0], isset($lang[1]) ? $lang[1] : '', isset($lang[2]) ? $lang[2] : 0, '', '', FALSE);
locale_add_language($langcode);
drupal_set_message(t('The language %language has been created.', array('%language' => t($predefined[$langcode][0]))));
}
// Now import strings into the language
......@@ -831,17 +832,26 @@ function locale_translate_delete($lid) {
* @param $prefix
* Optional path prefix for the language. Defaults to the
* language code if omitted.
* @param $verbose
* Switch to omit the verbose message to the user when used
* only as a utility function.
* @param $enabled
* Optionally TRUE to enable the language when created or FALSE to disable.
* @param $default
* Optionall set this language to be the default.
*/
function locale_add_language($langcode, $name, $native, $direction = LANGUAGE_RTL, $domain = '', $prefix = '', $verbose = TRUE) {
function locale_add_language($langcode, $name = NULL, $native = NULL, $direction = LANGUAGE_RTL, $domain = '', $prefix = '', $enabled = FALSE, $default = FALSE) {
// Default prefix on language code.
if (empty($prefix)) {
$prefix = $langcode;
}
db_query("INSERT INTO {languages} (language, name, native, direction, domain, prefix) VALUES ('%s', '%s', '%s', %d, '%s', '%s')", $langcode, $name, $native, $direction, $domain, $prefix);
// If name was not set, we add a predefined language.
if (!isset($name)) {
$predefined = _locale_get_predefined_list();
$name = $predefined[$langcode][0];
$native = isset($predefined[$langcode][1]) ? $predefined[$langcode][1] : $predefined[$langcode][0];
$direction = isset($predefined[$langcode][2]) ? $predefined[$langcode][2] : LANGUAGE_RTL;
}
db_query("INSERT INTO {languages} (language, name, native, direction, domain, prefix, enabled) VALUES ('%s', '%s', '%s', %d, '%s', '%s', %d)", $langcode, $name, $native, $direction, $domain, $prefix, $enabled);
// Add empty translations for strings (to optimize locale())
$result = db_query("SELECT lid FROM {locales_source}");
......@@ -849,14 +859,14 @@ function locale_add_language($langcode, $name, $native, $direction = LANGUAGE_RT
db_query("INSERT INTO {locales_target} (lid, language, translation) VALUES (%d,'%s', '')", $string->lid, $langcode);
}
// Set message depending on the verbosity required.
if ($verbose) {
drupal_set_message(t('The language %language has been created and can now be used. More information is available on the <a href="@locale-help">help screen</a>.', array('%language' => t($name), '@locale-help' => url('admin/help/locale'))));
}
else {
drupal_set_message(t('The language %language has been created.', array('%language' => t($name))));
// Only set it as default if enabled.
if ($enabled && $default) {
variable_set('language_default', (object) array('language' => $langcode, 'name' => $name, 'native' => $native, 'direction' => $direction, 'enabled' => (int) $enabled, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => $prefix, 'weight' => 0));
}
// Increment count of enabled languages.
variable_set('language_count', variable_get('language_count', 1) + 1);
watchdog('locale', 'The %language language (%code) has been created.', array('%language' => $name, '%code' => $langcode));
}
/**
......@@ -1906,7 +1916,7 @@ function _locale_get_predefined_list() {
"af" => array("Afrikaans"),
"ak" => array("Akan"),
"am" => array("Amharic", "አማርኛ"),
"ar" => array("Arabic", /* Left-to-right marker "‭" */ "العربية", 1),
"ar" => array("Arabic", /* Left-to-right marker "‭" */ "العربية", LANGUAGE_RTL),
"as" => array("Assamese"),
"av" => array("Avar"),
"ay" => array("Aymara"),
......@@ -1941,7 +1951,7 @@ function _locale_get_predefined_list() {
"es" => array("Spanish", "Español"),
"et" => array("Estonian", "Eesti"),
"eu" => array("Basque", "Euskera"),
"fa" => array("Persian", /* Left-to-right marker "‭" */ "فارسی", 1),
"fa" => array("Persian", /* Left-to-right marker "‭" */ "فارسی", LANGUAGE_RTL),
"ff" => array("Fulah", "Fulfulde"),
"fi" => array("Finnish", "Suomi"),
"fj" => array("Fiji"),
......@@ -1955,7 +1965,7 @@ function _locale_get_predefined_list() {
"gu" => array("Gujarati"),
"gv" => array("Manx"),
"ha" => array("Hausa"),
"he" => array("Hebrew", /* Left-to-right marker "‭" */ "עברית", 1),
"he" => array("Hebrew", /* Left-to-right marker "‭" */ "עברית", LANGUAGE_RTL),
"hi" => array("Hindi", "हिन्दी"),
"ho" => array("Hiri Motu"),
"hr" => array("Croatian", "Hrvatski"),
......@@ -2022,7 +2032,7 @@ function _locale_get_predefined_list() {
"pa" => array("Punjabi"),
"pi" => array("Pali"),
"pl" => array("Polish", "Polski"),
"ps" => array("Pashto", /* Left-to-right marker "‭" */ "پښتو", 1),
"ps" => array("Pashto", /* Left-to-right marker "‭" */ "پښتو", LANGUAGE_RTL),
"pt" => array("Portuguese, Portugal", "Português"),
"pt-br" => array("Portuguese, Brazil", "Português"),
"qu" => array("Quechua"),
......@@ -2066,7 +2076,7 @@ function _locale_get_predefined_list() {
"ty" => array("Tahitian"),
"ug" => array("Uighur"),
"uk" => array("Ukrainian", "Українська"),
"ur" => array("Urdu", /* Left-to-right marker "‭" */ "اردو", 1),
"ur" => array("Urdu", /* Left-to-right marker "‭" */ "اردو", LANGUAGE_RTL),
"uz" => array("Uzbek", "o'zbek"),
"ve" => array("Venda"),
"vi" => array("Vietnamese", "Tiếng Việt"),
......@@ -2083,3 +2093,95 @@ function _locale_get_predefined_list() {
/**
* @} End of "locale-api-languages-predefined"
*/
/**
* @defgroup locale-autoimport Automatic interface translation import
* @{
*/
/**
* Prepare a batch to use to import translations.
*
* @param $langcode
* Language code to import translations for.
* @return
* A batch structure or FALSE if no files found.
*/
function locale_batch_installer($langcode) {
// Collect all files to import for all enabled modules and themes.
$files = array();
$result = db_query("SELECT name, filename FROM {system} WHERE status = 1");
while ($component = db_fetch_object($result)) {
// 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) .'/po/', '(^|\.)'. $langcode .'\.po$', array('.', '..', 'CVS'), 0, FALSE));
}
if (count($files)) {
$$operations = array();
foreach($files as $file) {
// We call _locale_batch_import for every batch operation
// with the file name and language code.
$operations[] = array('_locale_batch_import', array($file->filename, $langcode));
}
return _locale_batch_build($operations, '_locale_batch_installer_finished');
}
// Found nothing to import.
return FALSE;
}
/**
* Build a locale batch from an array of files.
*
* @param $operations
* Array of operations to perform
* @param $finished
* A finished callback to use for the batch
* @return
* A batch structure
*/
function _locale_batch_build($operations, $finished) {
$t = get_t();
if (count($operations)) {
$batch = array(
'operations' => $operations,
'title' => $t('Importing interface translations'),
'init_message' => $t('Starting import'),
'error_message' => $t('Error importing interface translations'),
'finished' => $finished,
);
return $batch;
}
return FALSE;
}
/**
* Perform interface translation import as a batch step.
*
* @param $filepath
* Path to a file to import.
* @param $langcode
* Language to import file into.
* @param $results
* Contains a list of files imported.
*/
function _locale_batch_import($filepath, $langcode, &$context) {
$file = (object) array('filename' => basename($filepath), 'filepath' => $filepath);
_locale_import_read_po('db-store', $file, 'keep', $langcode);
$context['results'][] = $filepath;
}
/**
* Batch callback invoked when installer import processing finishes.
* Advance installer task to the finished screen.
*/
function _locale_batch_installer_finished($success, $results) {
variable_set('install_task', 'finished');
}
/**
* @} End of "locale-autoimport"
*/
......@@ -436,7 +436,7 @@ function install_select_profile() {
}
drupal_maintenance_theme();
install_task_list('profile');
install_task_list('profile-select');
drupal_set_title(st('Select an installation profile'));
print theme('install_page', drupal_get_form('install_select_profile_form', $profiles));
......@@ -474,7 +474,7 @@ function install_select_profile_form($profiles) {
* Find all .po files for the current profile.
*/
function install_find_locales($profilename) {
$locales = file_scan_directory('./profiles/'. $profilename, '\.po$', array('.', '..', 'CVS'), 0, FALSE);
$locales = file_scan_directory('./profiles/'. $profilename .'/po', '\.po$', array('.', '..', 'CVS'), 0, FALSE);
array_unshift($locales, (object) array('name' => 'en'));
return $locales;
}
......@@ -492,8 +492,26 @@ function install_select_locale($profilename) {
// Find all available locales.
$locales = install_find_locales($profilename);
// Don't need to choose locale if only one (English) is available.
if (sizeof($locales) == 1) {
// If only the built-in (English) language is available,
// and we are using the default profile, inform the user
// that the installer can be localized. Otherwise we assume
// the user know what he is doing.
if (count($locales) == 1) {
if ($profilename == 'default') {
drupal_maintenance_theme();
install_task_list('profile-select');
drupal_set_title(st('Localization of the Drupal installer'));
$output = '<p>'. st('Drupal is capable of being installed in any language from the start, not only English. A language pack might be available in your language already. To be able to install Drupal and use it in your language from the start, follow these steps:') . '</p>';
$output .= '<ul><li>'. st('Check whether <a href="@translations" target="_blank">a translation of this Drupal version</a> is available in your language.', array('@translations' => 'http://drupal.org/project/Translations')) .'</li>';
$output .= '<li>'. st('If available, download the translation pack and extract it to your Drupal root directory. Translation files will get placed into different directories.') .'</li>';
$output .= '<li>'. st('Continue the installation by reloading this page and select from the listed languages.') .'</li>';
$output .= '</ul><p>' . st('How should the installation continue?') .'</p>';
$output .= '<ul><li><a href="install.php?profile='. $profilename . '&amp;locale=en">'. st('Continue installation in English') .'</a></li><li><a href="install.php?profile='. $profilename . '">'. st('Reload this page to select a language') .'</a></li></ul>';
print theme('install_page', $output);
exit;
}
// One language, but not the default profile, assume
// the user knows what he is doing.
return FALSE;
}
else {
......@@ -504,7 +522,7 @@ function install_select_locale($profilename) {
}
drupal_maintenance_theme();
install_task_list('locale');
install_task_list('locale-select');
drupal_set_title(st('Choose your preferred language'));
print theme('install_page', drupal_get_form('install_select_locale_form', $locales));
......@@ -531,7 +549,7 @@ function install_select_locale_form($locales) {
}
$form['submit'] = array(
'#type' => 'submit',
'#value' => st('Save configuration'),
'#value' => st('Select language'),
);
return $form;
}
......@@ -541,7 +559,7 @@ function install_select_locale_form($locales) {
*/
function install_no_profile_error() {
drupal_maintenance_theme();
install_task_list('profile');
install_task_list('profile-select');
drupal_set_title(st('No profiles available'));
print theme('install_page', '<p>'. st('We were unable to find any installer profiles. Installer profiles tell us what modules to enable and what schema to install in the database. A profile is necessary to continue with the installation process.') .'</p>');
exit;
......@@ -577,7 +595,7 @@ function install_missing_modules_error($profile) {
* Tasks performed after the database is initialized. Called from install.php.
*/
function install_tasks($profile, $task) {
global $base_url;
global $base_url, $install_locale;
$output = '';
// Bootstrap newly installed Drupal, while preserving existing messages.
......@@ -592,6 +610,9 @@ function install_tasks($profile, $task) {
$task = 'configure';
}
// We are using a list of if constructs here to allow for
// passing from one task to the other in the same request.
if ($task == 'configure') {
drupal_set_title(st('Configure site'));
// Build menu to allow clean URL check.
......@@ -621,55 +642,95 @@ function install_tasks($profile, $task) {
drupal_process_form('install_configure_form', $form, $form_state);
if (empty($form_state['redirect'])) {
$output = drupal_render_form('install_configure_form', $form);
install_task_list('configure');
}
else {
$task = 'profile';
}
}
// If we have no output, then install.php is done and now we turn to
// our profile to run it's own tasks.
if (empty($output)) {
// Profile might define more tasks.
$function = $profile .'_profile_final';
// If found an unknown task or the 'profile-custom' task, which is
// reserved for profiles, hand over the control to the profile,
// so it can run any number of custom tasks it defines.
if (!in_array($task, install_reserved_tasks())) {
$function = $profile .'_profile_tasks';
if (function_exists($function)) {
// More tasks are required by this profile.
// The profile needs to run more code, maybe even more tasks.
// $task is sent through as a reference and may be changed!
$output = $function($task);
}
// Safety: if the profile doesn't do anything, catch it.
if ($task == 'configure') {
$task = 'finished';
// If the profile doesn't move on to a new task we assume
// that it is done: we let the installer regain control and
// proceed with the locale import.
if ($task == 'profile') {
$task = 'locale-import';
}
}
// Display default 'finished' page to user. A custom finished page
// can be displayed by skipping this step and going to 'done' directly.
if ($task == 'finished') {
drupal_set_title(st('@drupal installation complete', array('@drupal' => drupal_install_profile_name())));
$page = '<p>'. st('Congratulations, @drupal has been successfully installed.', array('@drupal' => drupal_install_profile_name())) .'</p>';
$page .= $output;
$messages = drupal_set_message();
$page .= '<p>'. (isset($messages['error']) ? st('Please review the messages above before continuing on to <a href="@url">your new site</a>.', array('@url' => url(''))) : st('You may now visit <a href="@url">your new site</a>.', array('@url' => url('')))) .'</p>';
$output = $page;
$task = 'done';
// Import interface translations for the enabled modules, after
// any changes made by the profile through the profile forms.
if ($task == 'locale-import') {
if (!empty($install_locale) && ($install_locale != 'en')) {
include_once 'includes/locale.inc';
// Enable installation language as default site language.
locale_add_language($install_locale, NULL, NULL, NULL, NULL, NULL, 1, TRUE);
// Collect files to import for this language.
$batch = locale_batch_installer($install_locale);
if (!empty($batch)) {
// Start a batch, switch to 'locale-batch' task. We need to
// set the variable here, because batch_process() redirects.
variable_set('install_task', 'locale-batch');
batch_set($batch);
$path = $base_url .'/install.php?locale='. $install_locale .'&profile='. $profile;
batch_process($path, $path);
}
}
// Found nothing to import or not foreign language, go to next task.
$task = 'finished';
}
// The end of the install process. Remember profile used.
if ($task == 'done') {
// Rebuild menu to get content type links registered by the profile,
// and possibly any other menu items created through the tasks.
menu_rebuild();
variable_set('install_profile', $profile);
}
// We are running a batch import of interface translation files.
// This might run in multiple HTTP requests, constantly redirecting
// to the same address, until the batch finished callback is invoked
// and the task advances to 'finished'.
if ($task == 'locale-batch') {
include_once 'includes/batch.inc';
include_once 'includes/locale.inc';
$output .= _batch_page();
}
// Set task for user, and remember the task in the database.
install_task_list($task);
variable_set('install_task', $task);
// Display a 'finished' page to user.
if ($task == 'finished') {
drupal_set_title(st('@drupal installation complete', array('@drupal' => drupal_install_profile_name())));
$output = '<p>'. st('Congratulations, @drupal has been successfully installed.', array('@drupal' => drupal_install_profile_name())) .'</p>';
$messages = drupal_set_message();
$output .= '<p>'. (isset($messages['error']) ? st('Please review the messages above before continuing on to <a href="@url">your new site</a>.', array('@url' => url(''))) : st('You may now visit <a href="@url">your new site</a>.', array('@url' => url('')))) .'</p>';
$task = 'done';
}
// The end of the install process. Remember profile used.
if ($task == 'done') {
// Rebuild menu to get content type links registered by the profile,
// and possibly any other menu items created through the tasks.
menu_rebuild();
variable_set('install_profile', $profile);
}
// Set task for user, and remember the task in the database.
install_task_list($task);
variable_set('install_task', $task);
// Output page.
print theme('maintenance_page', $output);
}
/**
* The list of reserved tasks to run in the installer.
*/
function install_reserved_tasks() {
return array('configure', 'locale-import', 'locale-batch', 'finished', 'done');
}
/**
* Page to check installation requirements and report any errors.
*/
......@@ -700,25 +761,28 @@ function install_check_requirements($profile) {
function install_task_list($active = NULL) {
// Default list of tasks.
$tasks = array(
'profile' => st('Choose profile'),
'locale' => st('Choose language'),
'requirements' => st('Verify requirements'),
'database' => st('Setup database'),
'configure' => st('Configure site'),
'profile-select' => st('Choose profile'),
'locale-select' => st('Choose language'),
'requirements' => st('Verify requirements'),
'database' => st('Setup database'),
'configure' => st('Configure site'),
);
$profiles = install_find_profiles();
// Remove profiles if only one profile exists.
if (count($profiles) == 1) {
unset($tasks['profile']);
}
// Remove locale if no install profiles use them.
$profile = isset($_GET['profile']) && isset($profiles[$_GET['profile']]) ? $_GET['profile'] : '.';
if (count(install_find_locales($profile)) == 1) {
unset($tasks['locale']);
$locales = install_find_locales($profile);
// Keep the profile selection task if we have more profiles or only the
// default profile is available and with only the built-in language, in
// which case we use this screen to present information about translations.
if (count($profiles) == 1) {
$first_profile = array_shift($profiles);
if ($first_profile->name != 'default' || count($locales) > 1) {
unset($tasks['profile-select']);
}
}
// Add tasks defined by the profile.
if ($profile) {
$function = $profile .'_profile_task_list';
if (function_exists($function)) {
......@@ -730,7 +794,16 @@ function install_task_list($active = NULL) {
}
// Add finished step as the last task.
$tasks += array('finished' => st('Finished'));
$tasks += array(
'locale-batch' => st('Import translations'),
'finished' => st('Finished')
);
// Remove locale related tasks if the install profile does not use them.
if (count($locales) == 1) {
unset($tasks['locale-select']);
unset($tasks['locale-batch']);
}
// Let the theming function know that 'finished' and 'done'
// include everything, so every step is completed.
......@@ -865,6 +938,8 @@ function install_configure_form_validate($form_values, $form, &$form_state) {
}
function install_configure_form_submit($form_values, $form, &$form_state) {
global $user;
variable_set('site_name', $form_values['site_name']);
variable_set('site_mail', $form_values['site_mail']);
variable_set('date_default_timezone', $form_values['date_default_timezone']);
......@@ -875,6 +950,9 @@ function install_configure_form_submit($form_values, $form, &$form_state) {
if (isset($form_values['clean_url'])) {
variable_set('clean_url', $form_values['clean_url']);
}
// The user is now logged in, but has no session ID yet, which
// would be required later in the request, so remember it.
$user->sid = session_id();
return 'finished';
}
......
......@@ -28,7 +28,10 @@ function default_profile_details() {
* Return a list of tasks that this profile supports.
*
* @return
* A keyed array of tasks the profile will perform during the _final stage.
* A keyed array of tasks the profile will perform during
* the final stage. The keys of the array will be used internally,
* while the values will be displayed to the user in the installer
* task list.
*/
function default_profile_task_list() {
}
......@@ -36,19 +39,23 @@ function default_profile_task_list() {
/**
* Perform any final installation tasks for this profile.
*
* You can implement a state machine here to walk the user through
* more tasks, by setting $task to something other then the reserved
* 'configure', 'finished' and 'done' values. The installer goes
* through the configure-finished-done tasks in this order, if you
* don't modify $task. If you implement your custom tasks, this
* function will get called in every HTTP request (for form
* processing, printing your information screens and so on) until
* you advance to the 'finished' or 'done' tasks. Once ready with
* your profile's tasks, set $task to 'finished' and optionally
* return a final message to be included on the default final
* install page. Alternatively you can set $task to 'done' and
* return a completely custom finished page. In both cases, you
* hand the control back to the installer.
* The installer goes through the configure -> locale-import ->
* locale-batch -> finished -> done tasks in this order, if you
* don't implement this function in your profile.
*
* If this function is implemented, you can have any number of
* custom tasks to perform, implementing a state machine here to
* walk the user through those tasks, by setting $task to something
* other then the reserved tasks listed in install_reserved_tasks()
* and the 'profile' task this function gets called with for first
* time. If you implement your custom tasks, this function will get called
* in every HTTP request (for form processing, printing your
* information screens and so on) until you advance to the
* 'locale-import' task, with which you hand control back to the
* installer.
*
* You should define the list of custom tasks you implement by
* returning an array of them in hook_profile_task_list().
*
* Should a profile want to display a form here, it can; it should set
* the task using variable_set('install_task', 'new_task') and use
......@@ -56,16 +63,14 @@ function default_profile_task_list() {
* drupal_get_form().
*
* @param $task
* The current $task of the install system. When hook_profile_final()
* is first called, this is 'configure' (the last built-in task of
* the Drupal installer).
* The current $task of the install system. When hook_profile_tasks()
* is first called, this is 'profile'.
*
* @return
* An optional HTML string to display to the user. Used as part of the
* completed page if $task is set to 'finished', or used to display a
* complete page in all other cases.
* An optional HTML string to display to the user. Only used if you
* modify the $task, otherwise discarded.
*/
function default_profile_final(&$task) {
function default_profile_tasks(&$task) {
// Insert default user-defined node types into the database. For a complete
// list of available node type attributes, refer to the node type API
......@@ -108,7 +113,4 @@ function default_profile_final(&$task) {
$theme_settings = variable_get('theme_settings', array());
$theme_settings['toggle_node_info_page'] = FALSE;
variable_set('theme_settings', $theme_settings);
// Let the installer know we're finished:
$task = 'finished';
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment