Commit 04d0ef5c authored by Dries's avatar Dries

- Patch #11623 by sun, stella, Gábor Hojtsy, Pancho: add ability to localize date formats.

parent 24c259cd
<?php
// $Id$
/**
* @file
* Initialize the list of date formats and their locales.
*/
/**
* Implements hook_date_formats().
*/
function system_default_date_formats() {
$formats = array();
// Short date formats.
$formats[] = array(
'type' => 'short',
'format' => 'Y-m-d H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'm/d/Y - H:i',
'locales' => array('en-us'),
);
$formats[] = array(
'type' => 'short',
'format' => 'd/m/Y - H:i',
'locales' => array('en-gb', 'en-hk', 'en-ie', 'el-gr', 'es-es', 'fr-be', 'fr-fr', 'fr-lu', 'it-it', 'nl-be', 'pt-pt'),
);
$formats[] = array(
'type' => 'short',
'format' => 'Y/m/d - H:i',
'locales' => array('en-ca', 'fr-ca', 'no-no', 'sv-se'),
);
$formats[] = array(
'type' => 'short',
'format' => 'd.m.Y - H:i',
'locales' => array('de-ch', 'de-de', 'de-lu', 'fi-fi', 'fr-ch', 'is-is', 'pl-pl', 'ro-ro', 'ru-ru'),
);
$formats[] = array(
'type' => 'short',
'format' => 'm/d/Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'd/m/Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'Y/m/d - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'M j Y - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'j M Y - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'Y M j - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'M j Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'j M Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'Y M j - g:ia',
'locales' => array(),
);
// Medium date formats.
$formats[] = array(
'type' => 'medium',
'format' => 'D, Y-m-d H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'D, m/d/Y - H:i',
'locales' => array('en-us'),
);
$formats[] = array(
'type' => 'medium',
'format' => 'D, d/m/Y - H:i',
'locales' => array('en-gb', 'en-hk', 'en-ie', 'el-gr', 'es-es', 'fr-be', 'fr-fr', 'fr-lu', 'it-it', 'nl-be', 'pt-pt'),
);
$formats[] = array(
'type' => 'medium',
'format' => 'D, Y/m/d - H:i',
'locales' => array('en-ca', 'fr-ca', 'no-no', 'sv-se'),
);
$formats[] = array(
'type' => 'medium',
'format' => 'F j, Y - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'j F, Y - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'Y, F j - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'D, m/d/Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'D, d/m/Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'D, Y/m/d - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'F j, Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'j F Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'Y, F j - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'j. F Y - G:i',
'locales' => array(),
);
// Long date formats.
$formats[] = array(
'type' => 'long',
'format' => 'l, F j, Y - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'long',
'format' => 'l, j F, Y - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'long',
'format' => 'l, Y, F j - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'long',
'format' => 'l, F j, Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'long',
'format' => 'l, j F Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'long',
'format' => 'l, Y, F j - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'long',
'format' => 'l, j. F Y - G:i',
'locales' => array(),
);
return $formats;
}
......@@ -3216,3 +3216,87 @@ function country_get_list() {
return $countries;
}
/**
* Save locale specific date formats to the database.
*
* @param $langcode
* Language code, can be 2 characters, e.g. 'en' or 5 characters, e.g.
* 'en-CA'.
* @param $type
* Date format type, e.g. 'short', 'medium'.
* @param $format
* The date format string.
*/
function locale_date_format_save($langcode, $type, $format) {
$locale_format = array();
$locale_format['language'] = $langcode;
$locale_format['type'] = $type;
$locale_format['format'] = $format;
$is_existing = (bool) db_query_range('SELECT 1 FROM {date_format_locale} WHERE language = :langcode AND type = :type', 0, 1, array(':langcode' => $langcode, ':type' => $type))->fetchField();
if ($is_existing) {
$keys = array('type', 'language');
drupal_write_record('date_format_locale', $locale_format, $keys);
}
else {
drupal_write_record('date_format_locale', $locale_format);
}
}
/**
* Select locale date format details from database.
*
* @param $languages
* An array of language codes.
* @return
* An array of date formats.
*/
function locale_get_localized_date_format($languages) {
$formats = array();
// Get list of different format types.
$format_types = system_get_date_types();
$short_default = variable_get('date_format_short', 'm/d/Y - H:i');
// Loop through each language until we find one with some date formats
// configured.
foreach ($languages as $language) {
$date_formats = system_date_format_locale($language);
if (!empty($date_formats)) {
// We have locale-specific date formats, so check for their types. If
// we're missing a type, use the default setting instead.
foreach ($format_types as $type => $type_info) {
// If format exists for this language, use it.
if (!empty($date_formats[$type])) {
$formats['date_format_' . $type] = $date_formats[$type];
}
// Otherwise get default variable setting. If this is not set, default
// to the short format.
else {
$formats['date_format_' . $type] = variable_get('date_format_' . $type, $short_default);
}
}
// Return on the first match.
return $formats;
}
}
// No locale specific formats found, so use defaults.
$system_types = array('short', 'medium', 'long');
// Handle system types separately as they have defaults if no variable exists.
$formats['date_format_short'] = $short_default;
$formats['date_format_medium'] = variable_get('date_format_medium', 'D, m/d/Y - H:i');
$formats['date_format_long'] = variable_get('date_format_long', 'l, F j, Y - H:i');
// For non-system types, get the default setting, otherwise use the short
// format.
foreach ($format_types as $type => $type_info) {
if (!in_array($type, $system_types)) {
$formats['date_format_' . $type] = variable_get('date_format_' . $type, $short_default);
}
}
return $formats;
}
......@@ -224,9 +224,66 @@ function locale_menu() {
'file path' => 'includes',
);
// Localize date formats.
$items['admin/config/regional/date-time/locale'] = array(
'title' => 'Localize',
'description' => 'Configure date formats for each locale',
'page callback' => 'locale_date_format_language_overview_page',
'access arguments' => array('administer site configuration'),
'type' => MENU_LOCAL_TASK,
'weight' => -8,
'file' => 'locale.inc',
'file path' => 'includes',
);
$items['admin/config/regional/date-time/locale/%/edit'] = array(
'title' => 'Localize date formats',
'description' => 'Configure date formats for each locale',
'page callback' => 'drupal_get_form',
'page arguments' => array('locale_date_format_form', 5),
'access arguments' => array('administer site configuration'),
'type' => MENU_CALLBACK,
'file' => 'locale.inc',
'file path' => 'includes',
);
$items['admin/config/regional/date-time/locale/%/reset'] = array(
'title' => 'Reset date formats',
'description' => 'Reset localized date formats to global defaults',
'page callback' => 'drupal_get_form',
'page arguments' => array('locale_date_format_reset_form', 5),
'access arguments' => array('administer site configuration'),
'type' => MENU_CALLBACK,
'file' => 'locale.inc',
'file path' => 'includes',
);
return $items;
}
/**
* Implements hook_init().
*
* Initialize date formats according to the user's current locale.
*/
function locale_init() {
global $conf, $language;
include_once DRUPAL_ROOT . '/includes/locale.inc';
// For each date type (e.g. long, short), get the localized date format
// for the user's current language and override the default setting for it
// in $conf. This should happen on all pages except the date and time formats
// settings page, where we want to display the site default and not the
// localized version.
if (strpos($_GET['q'], 'admin/config/regional/date-time/formats') !== 0) {
$languages = array($language->language);
// Setup appropriate date formats for this locale.
$formats = locale_get_localized_date_format($languages);
foreach ($formats as $format_type => $format) {
$conf[$format_type] = $format;
}
}
}
/**
* Wrapper function to be able to set callbacks in locale.inc
*/
......@@ -376,6 +433,9 @@ function locale_theme() {
'locale_translation_filters' => array(
'arguments' => array('form' => array()),
),
'locale_date_format_form' => array(
'arguments' => array('form' => array()),
),
);
}
......@@ -798,3 +858,160 @@ function theme_locale_translation_filters($variables) {
$output .= '<div id="locale-translation-buttons">' . drupal_render($form['buttons']) . '</div>';
return $output;
}
/**
* Theme locale date format form.
*
* @ingroup themeable
*/
function theme_locale_date_format_form($variables) {
$form = $variables['form'];
$header = array(
t('Date type'),
t('Format'),
);
foreach (element_children($form['date_formats']) as $key) {
$row = array();
$row[] = $form['date_formats'][$key]['#title'];
unset($form['date_formats'][$key]['#title']);
$row[] = array('data' => drupal_render($form['date_formats'][$key]));
$rows[] = $row;
}
$output = drupal_render($form['language']);
$output .= theme('table', array('header' => $header, 'rows' => $rows));
$output .= drupal_render_children($form);
return $output;
}
/**
* Display edit date format links for each language.
*/
function locale_date_format_language_overview_page() {
$header = array(
t('Language'),
array('data' => t('Operations'), 'colspan' => '2'),
);
// Get list of languages.
$languages = locale_language_list('native');
foreach ($languages as $langcode => $info) {
$row = array();
$row[] = $languages[$langcode];
$row[] = l(t('edit'), 'admin/config/regional/date-time/locale/' . $langcode . '/edit');
$row[] = l(t('reset'), 'admin/config/regional/date-time/locale/' . $langcode . '/reset');
$rows[] = $row;
}
return theme('table', array('header' => $header, 'rows' => $rows));
}
/**
* Provide date localization configuration options to users.
*/
function locale_date_format_form($form, &$form_state, $langcode) {
$languages = locale_language_list('native');
$language_name = $languages[$langcode];
// Display the current language name.
$form['language'] = array(
'#type' => 'item',
'#title' => t('Language'),
'#markup' => check_plain($language_name),
'#weight' => -10,
);
$form['langcode'] = array(
'#type' => 'value',
'#value' => $langcode,
);
// Get list of date format types.
$types = system_get_date_types();
// Get list of available formats.
$formats = system_get_date_formats();
$choices = array();
foreach ($formats as $type => $list) {
foreach ($list as $f => $format) {
$choices[$f] = format_date(REQUEST_TIME, 'custom', $f);
}
}
// Get configured formats for each language.
$locale_formats = system_date_format_locale($langcode);
// Display a form field for each format type.
foreach ($types as $type => $type_info) {
if (!empty($locale_formats) && in_array($type, array_keys($locale_formats))) {
$default = $locale_formats[$type];
}
else {
$default = variable_get('date_format_' . $type, array_shift(array_keys($formats)));
}
// Show date format select list.
$form['date_formats']['date_format_' . $type] = array(
'#type' => 'select',
'#title' => check_plain($type_info['title']),
'#attributes' => array('class' => array('date-format')),
'#default_value' => (isset($choices[$default]) ? $default : 'custom'),
'#options' => $choices,
);
}
$form['buttons']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save configuration'),
);
return $form;
}
/**
* Submit handler for configuring localized date formats on the locale_date_format_form.
*/
function locale_date_format_form_submit($form, &$form_state) {
include_once DRUPAL_ROOT . '/includes/locale.inc';
$langcode = $form_state['values']['langcode'];
// Get list of date format types.
$types = system_get_date_types();
foreach ($types as $type => $type_info) {
$format = $form_state['values']['date_format_' . $type];
if ($format == 'custom') {
$format = $form_state['values']['date_format_' . $type . '_custom'];
}
locale_date_format_save($langcode, $type, $format);
}
drupal_set_message(t('Configuration saved.'));
$form_state['redirect'] = 'admin/config/regional/date-time/locale';
}
/**
* Reset locale specific date formats to the global defaults.
*
* @param $langcode
* Language code, e.g. 'en'.
*/
function locale_date_format_reset_form($form, &$form_state, $langcode) {
$form['langcode'] = array('#type' => 'value', '#value' => $langcode);
$languages = language_list();
return confirm_form($form,
t('Are you sure you want to reset the date formats for %language to the global defaults?', array('%language' => $languages[$langcode]->name)),
'admin/config/regional/date-time/locale',
t('Resetting will remove all localized date formats for this language. This action cannot be undone.'),
t('Reset'), t('Cancel'));
}
/**
* Reset date formats for a specific language to global defaults.
*/
function locale_date_format_reset_form_submit($form, &$form_state) {
db_delete('date_format_locale')
->condition('language', $form_state['values']['langcode'])
->execute();
$form_state['redirect'] = 'admin/config/regional/date-time/locale';
}
......@@ -1618,3 +1618,74 @@ class UILanguageNegotiationTest extends DrupalWebTestCase {
$this->assertText($test['expect'], $test['message']);
}
}
/**
* Functional tests for localizing date formats.
*/
class LocalizeDateFormatsFunctionalTest extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Localize date formats',
'description' => 'Tests for the localization of date formats.',
'group' => 'Locale',
);
}
function setUp() {
parent::setUp('locale');
// Create and login user.
$admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'create article content'));
$this->drupalLogin($admin_user);
}
/**
* Functional tests for localizing date formats.
*/
function testLocalizeDateFormats() {
// Add language.
$edit = array(
'langcode' => 'fr',
);
$this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
// Set language negotiation.
$edit = array(
'language[enabled][locale-url]' => TRUE,
'language_interface[enabled][locale-url]' => TRUE,
);
$this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
// Configure date formats.
$this->drupalGet('admin/config/regional/date-time/locale');
$this->assertText('Français', 'Configured languages appear.');
$edit = array(
'date_format_long' => 'd.m.Y - H:i',
'date_format_medium' => 'd.m.Y - H:i',
'date_format_short' => 'd.m.Y - H:i',
);
$this->drupalPost('admin/config/regional/date-time/locale/fr/edit', $edit, t('Save configuration'));
$this->assertText(t('Configuration saved.'), 'French date formats updated.');
$edit = array(
'date_format_long' => 'j M Y - g:ia',
'date_format_medium' => 'j M Y - g:ia',
'date_format_short' => 'j M Y - g:ia',
);
$this->drupalPost('admin/config/regional/date-time/locale/en/edit', $edit, t('Save configuration'));
$this->assertText(t('Configuration saved.'), 'English date formats updated.');
// Create node content.
$node = $this->drupalCreateNode(array('type' => 'article'));
// Configure format for the node posted date changes with the language.
$this->drupalGet('node/' . $node->nid);
$english_date = format_date($node->created, 'custom', 'j M Y');
$this->assertText($english_date, t('English date format appears'));
$this->drupalGet('fr/node/' . $node->nid);
$french_date = format_date($node->created, 'custom', 'd.m.Y');
$this->assertText($french_date, t('French date format appears'));
}
}
This diff is collapsed.
......@@ -2376,6 +2376,110 @@ function hook_action_info_alter(&$actions) {
$actions['node_unpublish_action']['label'] = t('Unpublish and remove from public view.');
}
/**
* Defines additional date types.
*
* Next to the 'long', 'medium' and 'short' date types defined in core, any
* module can define additional types that can be used when displaying dates. A
* date type is a key which can be passed to format_date() to return a date in
* the configured display format.
*
* To avoid namespace collisions with date types defined by other modules, it is
* recommended that each date type starts with the module name. A date type
* can consist of letters, numbers and underscores.
*
* @see hook_date_formats()
* @see format_date()
*
* @return
* A list of date types in 'key' => 'label' format.
*/
function hook_date_format_types() {
return array(
'long' => t('Long'),
'medium' => t('Medium'),
'short' => t('Short'),
);
}
/**
* Defines additional date formats.
*
* Next to the 'long', 'medium' and 'short' date types defined in core, any
* module can define additional types that can be used when displaying dates. A
* date type is a key which can be passed to format_date() to return a date in
* the configured displayed format. A date format is a string defining the date
* and time elements to use. For example, a date type could be
* 'mymodule_extra_long', while a date format is like 'Y-m-d'.
*
* New date types must first be declared using hook_date_format_types(). It is
* then possible to define one or more date formats for each.
*
* A module may also extend the list date formats defined for a date type
* provided by another module.
*
* There may be more than one format for the same locale. For example d/m/Y and
* Y/m/d work equally well in some locales. It may also be necessary to define
* multiple versions of the same date format, for example, one using AM, one
* with PM and one without the time at all.
*
* However at the same time you may wish to define some additional date formats
* that aren't specific to any one locale, for example, "Y m". For these cases
* the locales field should be omitted.
*
* @see hook_date_format_types()
*
* @return
* A list of date formats. Each date format is a keyed array
* consisting of three elements:
* - 'type': the date type is a key used to identify which date format to
* display. It consists of letters, numbers and underscores, e.g. 'long',
* 'short', 'mymodule_extra_long'. It must first be declared in
* hook_date_format_types() unless extending a type provided by another
* module.
* - 'format': a string defining the date and time elements to use. It
* can contain any of the formatting options described at
* http://php.net/manual/en/function.date.php
* - 'locales': (optional) an array of 2 and 5 character language codes, for
* example, 'en', 'en-us'. The language codes are used to determine which
* date format to display for the user's current language. If more than one
* date format is suggested for the same date type and locale, then the
* first one will be used unless overridden via
* admin/config/regional/date-time/locale. If your date format is not
* language specific, leave this field empty.
*/
function hook_date_formats() {
return array(
array(
'type' => 'mymodule_extra_long',
'format' => 'l jS F Y H:i:s e',
'locales' => array('en-ie'),
),
array(
'type' => 'mymodule_extra_long',
'format' => 'l jS F Y h:i:sa',